iOS-认识@property

前言

关于@property基础的一次总结学习


属性与实例变量

当我们写下@property NSObject *foo时,编译器帮我们做了以下几件事(这个过程也被称为“自动合成(autoSynthesize)“)

  • 创建实例变量_foo
  • 声明foo属性的settergetter方法
  • 实现foo属性的settergetter方法

但是很久之前的GCC编译器时代,声明一个属性,需要分三步书写

.h
{
    NSObject *foo;
}
@property NSObject *foo
.m
@synthesize foo;

创建foo成员变量,@property负责声明setter/getter方法,而@synthesize则负责实现这两个方法

后来苹果将编译器改为LLVM,我们就不必再为属性对应声明实例变量,编译器自动创建一个下划线开头的_foo实例变量,并且为我们实现@synthesize foo = _foo的属性与实例变量的对应,这就解释了为什么我们重写setter/getter方法时,都是操作的_foo实例变量,而不是foo。当然,如果你愿意,可以不使用系统自动生成的_foo的命名,自己用@synthesize foo = differentFoo去指定一下这个局部变量名字。

与自动合成相对应的是@dynamic,作用是告诉编译器取消自动执行的上述三个步骤,对应操作与开发者自己实现。最为常见的是程序中实现对NSUserDefault读写的管理类,这个类的属性就不需要编译器帮我们自动合成


@property中属性的作用

// 无数次不厌其烦的书写是因为烦也得写
@property (nonatomic, assign) NSInteger testTag;
@property (nonatomic, strong) UIButton *btn;

原子性:atomic&nonatomic

  • atomic:原子性,这个属性是默认的,通过在setter中加@synchronized(self)保证数据数据的读写安全,需要注意的是,但它不是线程安全
  • nonatomic:非原子性,就是不加锁

atomic和nonatomic的区别在于,系统自动生成的getter/setter方法不一样。如果自己写getter/setter,那atomic/nonatomic/retain/assign/copy这些关键字只起提示作用,写不写都一样

nonatomic

//@property(nonatomic, retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    return userName;
}

- (void) setUserName:(UITextField *)userName_ {
    [userName_ retain];
    [userName release];
    userName = userName_;
}

atomic

//@property(retain) UITextField *userName;
//系统生成的代码如下:

- (UITextField *) userName {
    UITextField *retval = nil;
    @synchronized(self) {
        retval = [[userName retain] autorelease];
    }
    return retval;
}

- (void) setUserName:(UITextField *)userName_ {
    @synchronized(self) {
      [userName release];
      userName = [userName_ retain];
    }
}

atomic通过加锁@synchronized(self)来保障读写安全,并且在getter中引用计数同样会 +1,来向调用者保证这个对象会一直存在。假如不这样做,如有另一个线程调setter可能会出现线程竞态,导致引用计数降到0,原来那个对象就释放掉了

参考文章:《爆栈热门iOS问题 atomic和nonatomic有什么区别?》


读写权限:readwrite&readonly

  • readwrite:可读写属性,默认属性,允许编译器@synthesize自动合成
  • readonly:只读属性,不生成setter

方法名定义:getter=/setter=

看一下UIButton中的应用,一般都是在Bool类型的属性时使用,方便阅读

@property(nonatomic,getter=isEnabled) BOOL enabled;                                  // default is YES. if NO, ignores touch events and subclasses may draw differently
@property(nonatomic,getter=isSelected) BOOL selected;                                // default is NO may be used by some subclasses or by application
@property(nonatomic,getter=isHighlighted) BOOL highlighted;  

内存管理

  • assign
  • unsafe_unretained
  • strong
  • retain
  • weak
  • copy
assign

assign用于值类型,如int、float、double和NSInteger,CGFloat等表示单纯的赋值,简单覆盖原值,没有多余的操作

unsafe_unretained

语义同assign一样,区别是适用于对象类型。但是对象销毁后,属性值并不等于nil而是指向了一块野地址,形成野指针。如果访问,则会出现BAD_ACCESS

strong/retain

id类型及对象类型的所有权修饰符。strong是在iOS引入ARC的时候引入的关键字,是retain的一个可选的替代。表示实例变量对传入的对象要有所有权关系,即强引用。strong跟retain的意思相同并产生相同的代码,但是语意上strong更能体现对象的关系。作用是在setter中,对新值retain,对旧值release,并返回新值

weak

使用范围同strong。区别在于在setter方法中,对传入的对象不进行引用计数加1的操作,即对传入的对象没有所有权。属性所指的对象销毁时,属性也会清空(nil out)

copy

会将修饰的结果copy一份,重新保存。所以通常用于修饰NSString/NSArray/NSDictionary的属性保护封装性。因为多态性,父类指针可以指向子类,如果给一个上述不可变属性赋值了他们的子类,那么在子类发生增删时,同样会影响了当前属性的值,违背了不可变的初衷,使程序变的不可控。而mutable类型copy操作后,就会保存当前的值储存,成为不可变的类型(immutable)

我们简单写个例子,检验一下copy对属性的影响:

  1. 声明两个分别用strongcopy修饰类型为可变数组的属性
  2. 声明两个分别用strongcopy修饰类型为不可变数组的属性
@property (nonatomic, strong) NSMutableArray *smArr;
@property (nonatomic, copy) NSMutableArray *cmArr;
@property (nonatomic, strong) NSArray *strongImArr;
@property (nonatomic, copy) NSArray *CopyImArr;

接下来分别给他们赋值一个可变数组的局部变量,并在改变这个局部变量前后打印几个属性的信息

// 初始化一个局部可变数组
    NSArray *arr = @[@"1"];
    NSLog(@"original arr address:%p class:%@",arr,[arr class]);
    NSArray *carr = [arr copy];
    NSLog(@"copy arr address:%p class:%@",carr,[carr class]);
    NSMutableArray *marr = [arr mutableCopy];
    NSLog(@"mutable arr address:%p class:%@",marr,[marr class]);

// 将这个可变数组分别赋值给声明的属性并打印信息
    self.cmArr = marr;
    NSLog(@"copy mutable property address:%p class:%@ property:%@",self.cmArr,[self.cmArr class],self.cmArr);
    self.smArr = marr;
    NSLog(@"strong mutable property address:%p class:%@ property:%@",self.smArr,[self.smArr class],self.smArr);
    
    self.strongImArr = marr;
    NSLog(@"strong immutable property address:%p class:%@ property:%@",self.strongImArr,[self.strongImArr class],self.strongImArr);
    self.CopyImArr = marr;
    NSLog(@"copy immutable property address:%p class:%@ property:%@",self.CopyImArr,[self.CopyImArr class],self.CopyImArr);
 
// 改变可变数组并打印信息
    [marr addObject:@"2"];
    NSLog(@"copy mutable property address:%p class:%@ property:%@",self.cmArr,[self.cmArr class],self.cmArr);
    NSLog(@"strong mutable property address:%p class:%@ property:%@",self.smArr,[self.smArr class],self.smArr);
    NSLog(@"strong immutable property address:%p class:%@ property:%@",self.strongImArr,[self.strongImArr class],self.strongImArr);
    NSLog(@"copy immutable property address:%p class:%@ property:%@",self.CopyImArr,[self.CopyImArr class],self.CopyImArr);

打印结果

original arr address:0x600000017f50 class:__NSSingleObjectArrayI
copy arr address:0x600000017f50 class:__NSSingleObjectArrayI
mutable arr address:0x60c000250680 class:__NSArrayM

copy mutable property address:0x608000201de0 class:__NSSingleObjectArrayI property:(
    1
)
strong mutable property address:0x60c000250680 class:__NSArrayM property:(
    1
)
strong immutable property address:0x60c000250680 class:__NSArrayM property:(
    1
)
copy immutable property address:0x608000201dd0 class:__NSSingleObjectArrayI property:(
    1
)

// 改变局部变量后的打印结果
copy mutable property address:0x608000201de0 class:__NSSingleObjectArrayI property:(
    1
)
strong mutable property address:0x60c000250680 class:__NSArrayM property:(
    1,
    2
)
strong immutable property address:0x60c000250680 class:__NSArrayM property:(
    1,
    2
)
copy immutable property address:0x608000201dd0 class:__NSSingleObjectArrayI property:(
    1
)

可以看到,copy将原本是可变数组的的属性self.cmArr的类型变成了__NSSingleObjectArrayI不可变,同时对比内存地址可以发现,编译器开辟了一块新的内存空间用来存储copy的值。所以当我们如果写下类似@property (nonatomic, copy) NSMutableArray *foo时,需要考虑一下这样声明是否有意义。

同样有趣的是,我们本来声明为不可变数组self.strongImArr却因为多态(父类指针指向子类),指向了一个可变数组,并在marr改变时,同样也发生了变化。这与开发者声明它为不可变时的用意可能会出现出入,对比最后一行打印的结果,可能copy会更贴近开发者的真正想法。所以这就是很多文章提倡用copy修饰NSString/NSArray/NSDictionary属性的用意所在。


在认识@property后,接下来,我们会继续iOS-探究@property,学习更多@property的底层知识

博客地址:iOS-认识@property

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 我是个很容易对事物着迷的人,偏偏好奇心也很旺盛,所以人生很多时候都是在入迷中度过的,并常常会达到心流的状态。...
    午后瞌睡猫阅读 725评论 0 0