iOS底层 - property

@property修饰符

  • atomic nonatomic
  • readwrite readonly
  • retain
  • assign
  • copy
  • strong
  • weak
  • unsafe_unretained
  • autoreleasing
  • setter getter

atomic nonatomic
atomic(默认参数):原子性,性能低(一般开发OC中的APP不推荐使用,做金融等要求高安全的时候使用)

(原子性操作),会被加锁,就是一个操作执行过程不能被中断,要不就执行完,要不就不执行(一个操作不可以在中途被CPU暂停然后调度)。如果一个操作是原子性的,那么在多线程环境下,就不会出现变量被修改等奇怪的问题(保证数据同步)。原子操作就是不可再分的操作,在多线程程序中原子操作是一个非常重要的概念,它常常用来实现一些同步机制,同时也是一些常见的多线程bug的源头。

nonatomic:非原子性,性能高(推荐使用,性能高)

(非原子性操作)操作是直接从内存中取数值(不考虑其是否被占用)。在多线程环境下可能提高性能,但无法保证数据同步。

readwrite readonly
readwrite是编译器的默认选项,便是自动生成gettersetter,如果需要gettersetter不写即可。
readonly表示只会合成getter而不合成setter

assign weak unsafe_unretained
assign表示对属性只进行简单的赋值操作,不更改所赋值的引用计数,也不改变旧的引用计数,常用于标量类型,如NSInteger NSUInteger CGFloat NSTimerInterval等。

assign也可以修饰对象如NSString等类型对象,上面说过使用assign修饰不会更改所赋的新值的引用计数,也不改变旧值的引用计数,如果当所赋的新值引用计数为0对象被销毁时属性并不知道,编译器不会将该属性置为nil,指针仍旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,因此使用assign修饰的类型一定要为标量类型。

使用weak修饰的时候同样不会增加所赋新值的引用计数,也不会减少旧值的引用计数,但当该值被销毁时,weak修饰的属性会被自动赋值为nil,这样就可以避免野指针错误。

使用unsafe_unretained修饰时效果与assign相同,不会增加引用计数,当所赋的值被销毁时不会被置为nil可能会发生野指针错误。unsafe_unretainedassign的区别在于,unsafe_unretained只能修饰对象,不能修饰标量类型,而assign两者均可修饰。

strong weak
strong表示属性对所赋的值持有强引用表示一种“拥有关系”(owing relationship),会先保留新值即增加新值的引用计数,然后再释放旧值即减少旧值的引用计数。只能修饰对象。如果对一些对象需要保持强引用则使用strong

weak表示对所赋的值对象持有弱引用表示一种“非拥有关系”(nonowning relationship),对新值不会增加引用计数,也不会减少旧值的引用计数。所赋的值在引用计数为0被销毁后,weak修饰的属性会被自动置为nil能够有效防止野指针错误。
weak常用在修饰delegate等防止循环引用的场景。

copy
copy修饰的属性会在内存里拷贝一份对象,两个指针指向不同的内存地址。
一般用来修饰有对应可变类型子类的对象。
如:NSString/NSMutableString NSArray/NSMutableArray NSDictionary/NSMutableDictionary等。
为确保这些不可变对象因为可变子类对象影响,需要copy一份备份,如果不使用copy修饰,使用strong或者assign等修饰则会因为多态导致属性值被修改。
这里的copy还牵扯到NSCopyingNSMutableCopying协议。

copy还被用来修饰block,在ARC环境下编译器默认会用copy修饰,一般情况下在block需要捕获外界数据时该block就会被分配在堆区,但在MRC环境下由于手动管理引用计数,block一般被分配在栈区,需要copy到堆区来防止野指针错误。有一个栗子:

@interface Person : NSObject

//使用copy修饰NSMutableString
@property (nonatomic, copy) NSMutableString* name;
@property (nonatomic, assign) NSUInteger age;

@end

@implementation Person

@synthesize name = _name;
@synthesize age = _age;

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        NSMutableString *s = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
        //将可变字符串赋值给p.name
        p.name = s;
        //输出的地址不一致,内容一致
        NSLog(@"%p %p %@ %@", p.name, s, p.name, s);
        //修改p.name,此时抛出异常
        [p.name appendString:@" is a good guy."];
    }
    return 0;
}

上面的栗子使用copy修饰可变对象,在进行赋值的时候会通过copy方法获取一个不可变对象,因此p.name的地址和s的地址不同,而p.name运行时类型为NSString,调用appendString:方法会抛出异常。

所以,针对不可变对象使用copy修饰,针对可变对象使用strong修饰。

retain
在ARC环境下使用较少,在MRC下使用效果与strong一致。

copy题外话

有时候我们需要copy一个对象,或是mutableCopy一个对象,这时需要遵守NSCopyingNSMutableCopying协议,来实现copyWithZone:mutableCopyWithZone:两个方法,而不是重写copymutableCopy两个方法。

Foundation框架中的很多数据类型已经帮我们实现了上述两个方法,因此我们可以使用copy方法和mutableCopy方法来复制一个对象,两者的区别在于copy的返回值仍为不可变对象,mutableCopy的返回值为可变对象。

type copy mutableCopy
NS* 浅拷贝,只拷贝指针,地址相同 单层深拷贝,拷贝内容,地址不同
NSMutable* 单层深拷贝,拷贝内容,地址不同 单层深拷贝,拷贝内容,地址不同

对于不可变类型,使用copy方法时是浅拷贝,只拷贝指针,因为内容是不会变化的。使用mutableCopy时由于返回可变对象因此需要一份拷贝,供其他对象使用。对于可变类型,不管是copy还是mutableCopy均会进行深拷贝,所指向指针不同。

前文介绍copy修饰符的时候讲过,在修饰NSString这样的不可变对象的时候使用copy修饰,但其实当给对象赋一个NSString时仍旧只复制了指针而不是拷贝内容,原因同上。

与@property相关的问题

1. @property的本质是什么?ivar、getter、setter是如何生成并添加到这个类中的?

@property本质是什么?

@property = ivar + getter + setter

下面解释下:

"属性"(property)有两大概念:ivar(实例变量)、存取方法(access method = getter + setter)

“属性” (property)作为 Objective-C 的一项特性,主要的作用就在于封装对象中的数据。 Objective-C 对象通常会把其所需要的数据保存为各种实例变量。实例变量一般通过“存取方法”(access method)来访问。其中,“获取方法” (getter)用于读取变量值,而“设置方法” (setter)用于写入变量值。这个概念已经定型,并且经由“属性”这一特性而成为 Objective-C 2.0 的一部分。

而在正规的 Objective-C 编码风格中,存取方法有着严格的命名规范。
正因为有了这种严格的命名规范,所以 Objective-C 这门语言才能根据名称自动创建出存取方法。其实也可以把属性当做一种关键字,其表示:

编译器会自动写出一套存取方法,用以访问给定类型中具有给定名称的变量。
所以你也可以这么说:
@property = getter + setter;

例如下面这个类:

@interface Person : NSObject
@property NSString *firstNamel;
@property NSString *lastNamel;
@end

上述代码写出来的类与下面这种写法等效:

@interface Person : NSObject
- (NSString *)firstName;
- (void)setFirstName:(NSString *)firstName;
- (NSString *)lastName;
- (void)setLastName:(NSString *)lastName;
@end

ivar、getter、setter是如何生成并添加到这个类中的?

“自动合成”(autosynthesis)

完成属性定义后,编译器会自动编写访问这些属性所需的方法,此过程叫做“自动合成”(autosynthesis)。需要强调的是,这个过程由编译器在编译期执行,所以编辑器里看不到这些“合成方法”(synthesized method)的源代码。除了生成方法代码 getter、setter 之外,编译器还要自动向类中添加适当类型的实例变量,并且在属性名前面加下划线,以此作为实例变量的名字。在前例中,会生成两个实例变量,其名称分别为_firstName_lastName。也可以在类的实现代码里通过@synthesize 语法来指定实例变量的名字.

@implementation Person
@synthesize firstName = _myFirstName;
@synthesize lastName = _myLastName;
@end

属性是怎么生成的?增加一个属性,底层大致生成五个东西:

  1. OBJC_IVAR_$类名$属性名:该属性的“偏移量“(offset),这个偏移量是”硬编码“(hardcode),表示该变量距离存放对象的内存区域的起始地址有多远。
  2. gettersetter方法对应的实现函数
  3. ivar_list:成员变量列表
  4. method_list:方法列表
  5. prop_list:属性列表

也就是说我们每次在增加一个属性,系统都会在ivar_list中添加一个成员变量的描述,在method_list中增加setter和getter方法的描述,在属性列表中增加一个属性的描述,然后计算该属性在对象中的偏移量,然后给出setter与getter方法对应的实现,在setter方法中从偏移量的位置开始赋值,在getter方法中从偏移量的开始取值,为了能够读取正确字节数,系统对象偏移量的指针类型进行了强转。

2. @property中有哪些属性关键字?(或@property后面可以有哪些修饰符?)

属性可以拥有的特质分为四类:
1、原子性 --- nonatomic特质
2、读写权限 --- readwrite readonly
3、内存管理语义 --- assign strong weak unsafe_unretained copy
4、方法名 --- getter=<name> setter=<name>

getter=<name>的样子:

@property (nonatomic, getter=isOn) BOOL on;

setter=<name>这种不常用,也不推荐使用)
5、不常用的:nonnullnull_resettablenullable

3. 什么情况下使用weak关键字,相比assign有什么不同?

什么情况下使用weak关键字?

  1. 在ARC中,在有可能出现循环引用的时候,往往要通过让其中一端使用weak来解决,比如:delegate代理属性
  2. 自身已经对它进行一次强引用,没必要再强引用一次,此时也会使用weak,自定义IBOutlet控件属性一般也使用weak;当然,也可以使用strong

不同点:

  1. weak此特质表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,设置方法既不保留新值,也不释放旧值。此特质同assign类似,然而在属性所指的对象遭到销毁时,属性值也会清空(nil out)。
    assign的“设置方法”只会执行针对“纯量类型” (scalar type,例如 CGFloat 或
    NSlnteger 等)的简单赋值操作。属性所指的对象销毁时,编译器不会将该属性置为nil,指针仍 旧指向之前被销毁的内存,这时访问该属性会产生野指针错误并崩溃,
  2. assigin 可以用非 OC 对象,而 weak 必须用于 OC 对象

4. 怎么用copy关键字?

  1. NSString/NSArray/NSDictiony等等经常使用copy关键字,是因为它们有对应的可变类型:NSMutableString/NSMutableArray/NSMutableDictionary;
  2. block也经常使用copy关键字
    block使用copy是从MRC遗留下来的“传统”,在MRC中,方法内部的block是在栈区的,使用copy可以把它放到堆区。在ARC中写不写都行:对于block使用copy还是strong效果是一样的,但写上copy也无伤大雅,还能时刻提醒我们:编译器自动对block进行了copy操作。如果不写copy,该类的调用者有可能会忘记或者根本不知道“编译器会自动对block进行了copy操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。

下面做解释:
copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝”(copy)。
当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这是就要拷贝一份“不可变”(immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的“(mutable),就应该在设置新值时拷贝一份。

用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

5. 用@property声明的NSString(或NSArray,NSDictionary)经常使用copy关键字,为什么?如果改用strong关键字,可能造成什么问题?

  1. 因为父类指针可以指向子类对象,使用copy的目的是为了让本对象的属性不受外界影响,使用copy无论给我传入是一个可变对象还是不可变对象,我本身持有的就是一个不可变的副本。
  2. 如果我们使用是strong,那么这个属性就有可能指向一个可变对象,如果这个可变对象在外部被修改了,那么会影响该属性。

copy此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其”拷贝“(copy)。
copy 此特质所表达的所属关系与strong类似。然而设置方法并不保留新值,而是将其“拷贝” (copy)。
当属性类型为NSString时,经常用此特质来保护其封装性,因为传递给设置方法的新值有可能指向一个 NSMutableString类的实例。这个类是NSString的子类,表示一种可修改其值的字符串,此时若是不拷贝字符串,那么设置完属性之后,字符串的值就可能会在对象不知情的情况下遭人更改。所以,这时就要拷贝一份“不可变” (immutable)的字符串,确保对象中的字符串值不会无意间变动。只要实现属性所用的对象是“可变的” (mutable),就应该在设置新属性值时拷贝一份。

举例说明:

定义一个以strong修饰的array:

@property (nonatomic, readwrite, strong) NSArray *marray;

然后进行下面的操作:

NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
    NSArray *array = @[@1, @2, @3, @4];
    self.marray = mutableArray;
    [mutableArray removeAllObjects];
    NSLog(@"%@", self.marray);
    
    [mutableArray addObjectsFromArray:array];
    self.marray = [mutableArray copy];
    [mutableArray removeAllObjects];
    NSLog(@"%@", self.marray);

打印结果如下所示:

2018-06-15 15:35:43.292506+0800 getIP[49185:2440314] (
)
2018-06-15 15:35:43.292805+0800 getIP[49185:2440314] (
    1,
    2,
    3,
    4
)

为了理解这种做法,首先要知道,两种情况:

  1. 非集合类对象copymutableCopy操作
  2. 集合类对象copymutableCopy操作

1. 对非集合类对象的copy与mutableCopy操作

在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时时内容复制;
对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示为:

  • [immutableObject copy] //浅复制
  • [immutableObject mutableCopy]//深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy]//深复制

比如以下代码:

NSMutableString *string = [NSMutableString stringWithString:@"origin"];//copy
NSString *stringCopy = [string copy];

查看内存,会发现stringstringCopy内存地址都不一样,说明此时都是做了内容拷贝、深拷贝。即使你进行如下操作:

[string appendString:@"origion!"];

stringCopy的值也不会因此改变,但是如果不使用copystringCopy的值就会被改变。

集合类对象以此类推。

所以,

用 @property 声明 NSString、NSArray、NSDictionary 经常使用 copy 关键字,是因为他们有对应的可变类型:NSMutableString、NSMutableArray、NSMutableDictionary,他们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性值时拷贝一份。

2. 对集合类对象的copy与mutableCopy操作

集合类对象是指NSArrayNSDictoryNSSet...之类的对象。下面先看集合类immutable对象使用copymutableCopy的一个例子:

NSArray *array = @[@[@"a", @"b"], @[@"c", @"d"]];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内容,可以看到 copyArray 和 array 的地址是一样的,而 mCopyArray 和 array 的地址是不同的。说明 copy 操作进行了指针拷贝,mutableCopy进行了内容拷贝。但需要强调的是:此处的内容拷贝,仅仅是拷贝 array 这个对象,array集合内部的元素仍然是指针拷贝。这和上面的非集合 immutable 对象的拷贝还是挺相似的,那么mutable对象的拷贝会不会类似呢?我们继续往下,看 mutable 对象拷贝的例子:

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArray = [array copy];
NSMutableArray *mCopyArray = [array mutableCopy];

查看内存,如我们所料,copyArray、mCopyArray和 array 的内存地址都不一样,说明 copyArray、mCopyArray 都对 array 进行了内容拷贝。同样,我们可以得出结论:

在集合类对象中,对 immutable 对象进行 copy,是指针复制, mutableCopy 是内容复制;对 mutable 对象进行 copy 和 mutableCopy 都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:

[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制

这个代码结论与非集合类的非常相似。

总结

关于@property的问题,问法比较多,但是万变不离其宗,搞清楚底层原理,迎刃而解!

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • @property介绍 相信做过iOS开发的同学都使用过@property,@property翻译过来是属性。在定...
    子疯zp阅读 828评论 0 6
  • 其实,剽悍一只猫的公众号,我从去年就关注了。当时的自己还是一个安于现状的全职妈妈,虽然有时内心也有一些小波澜,尝试...
    行云2018阅读 446评论 5 7
  • 少顷,教导处的某个女老师一出办公室便看见一个学生站在外面,便问了一句:“这位同学,你站在这干什么,有事吗?” “老...
    智障怡阅读 254评论 0 0
  • 一连几天,王玲玲的状态都不好,话也很少说,终于,我忍不住了,在食堂逮到了姚鹏一个人,就一屁股坐到了他对面。...
    月下柳阅读 229评论 0 0