共计 3302 个字符,预计需要花费 9 分钟才能阅读完成。
虽然写了很多年的 iOS 代码,但是很多东西没有深入理解,或者当时理解了,后来不用又慢慢又忘了。所以抽空整理一份资料,以备自己以后查找。也希望看到的小伙伴批评指正。
这篇文章主要写 @property 后面的修饰符。
1、assign、unsafe_unretained 和 weak
assign:既可以修饰对象,也可以修饰基本类型。unsafe_unretained 与 assign 同义。assign 修饰对象会产生野指针的问题,修饰的对象释放后,指针不会自动置成 nil,此时再向对象发消息程序会崩溃。
weak:只能修饰对象,如果修饰基本数据类型会报错:Property with ‘weak’ attribute must be of object type. weak 修饰的对象释放后(引用计数变为 0),指针会被自动置为 nil,之后再向该对象发消息也不会崩溃(向 nil 发送任何消息都不会崩溃)。weak 适用于 delegate 和 block 等引用类型,不会导致野指针问题,也不会循环引用,非常安全。
总结:
assign、weak 和 unsafe_unretained,修饰的属性,再调用 set 方法时是不会增加引用计数的。可以理解为 set 方法只是简单的赋值。也就是栈上的变量的赋值。验证代码如下:
@property(nonatomic,assign) NSObject *property;
// 在代码实现中
NSObject *object = [[NSObject alloc] init];
NSLog(@"\n 引用计数 = %lu \n", (unsigned long)[object retainCount]);
self.property = object;
NSLog(@"\n 引用计数 = %lu \n", (unsigned long)[object retainCount]);
输出结果
2019-08-05 02:02:08.475192+0800 test4[89384:3597709]
引用计数 = 1
2019-08-05 02:02:08.475320+0800 test4[89384:3597709]
引用计数 = 1
所以 assign、weak 和 unsafe_unretained 修饰的对象,默认生成的 set 方法,应该如下方法一所示:
-(void)setProperty:(NSObject *)property{
_property = property;
//weak 可能还有其他一些操作
}
而 retain 和 strong 修饰的对象,大概生成的 set 方法如下方法二所示:
-(void)setProperty:(NSObject *)property{
[property retain];
[_property release];
_property = property;
}
注意:
如果自己实现了 set 方法,则这些关于存取方法的修饰符就不再起作用了。比如,你虽然用 assign 修饰了对象,但是自己实现了 set 方法二,则 assign 会被忽略。
2、retain 和 strong
在苹果的文档里有这样一句话:
// The following declaration is a synonym for: @property(retain) MyClass *myObject;
@property(strong) MyClass *myObject;
所以在 ARC 下他们是等价的。
通常在 ARC 环境里,不会用 assign 修饰对象,因为有了 weak,再用 assign 就是自找麻烦。总之在 ARC 环境里用 strong 代替 retain 强引用对象,用 weak 弱引用对象,用 assign 修饰基本数据类型。
3、atomic 和 nonatomic
atomic 和 nonatomic 的区别在于:系统自动生成的 getter/setter 方法不一样。(如果你自己写 getter/setter,那 atomic/nonatomic/retain/assign/copy 这些关键字只起提示作用,写不写都一样)
对于 atomic 的属性,系统生成的 getter/setter 会保证 get、set 操作的完整性,不受其他线程影响。比如,线程 A 的 getter 方法运行到一半,线程 B 调用了 setter:那么线程 A 的 getter 还是能得到一个完好无损的对象。
而 nonatomic就没有这个保证了。所以,nonatomic 的速度要比 atomic 快。
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 的属性 “userName”,如果线程 A 调 [self setUserName:@”A”],线程 B 调[self setUserName:@”B”],线程 C 调[self userName],那么所有这些不同线程上的操作都将依次顺序执行——也就是说,如果一个线程正在执行 getter/setter,其他线程就得等待。因此,属性 setUserName 是读 / 写安全的。
但是,如果有另一个线程 D 同时在调 [userName release],那可能就会 crash,因为 release 不受 getter/setter 操作的限制。也就是说,这个属性只能说是读 / 写安全的,但并不是线程安全的,因为别的线程还能进行读写之外的其他操作。线程安全需要开发者自己来保证。
如果 userName 属性是 nonatomic 的,那么上面例子里的所有线程 A、B、C、D 都可以同时执行,可能导致无法预料的结果。如果是 atomic 的,那么 A、B、C 会串行,而 D 还是并行的。
4、readwrite 和 readonly
readwrite:默认的属性修饰符。会合成 get 和 set 方法。
readonly:只会生成 get 方法。所以如果 obj 用 readonly 修饰,则 self.obj = xxx; 编译时会报错提示:“Assignment to readonly property”。
5、copy 属性修饰符
copy:只能修饰对象类型,不能修饰基础数据类型(用了会报编译时错误)。用 copy 修饰的对象,必须实现
NSCopying 协议也就是实现方法 -(id)copyWithZone:(nullable NSZone *)zone。合成的 set 方法中会调用这个方法。这里也能看出 copy,和 retain 不一样的地方。
至于 copy 是深拷贝还是浅拷贝完全是看 copyWithZone 的实现方式。也就是说 copy 和深拷贝和浅拷贝没有关系!
另外对象的 mutableCopy 方法,也是要对象满足 NSMutableCopying 协议,实现 - (id)mutableCopyWithZone:(nullable NSZone *)zone 方法。如果不实现次方法会运行时报错!
总结
修饰符其实就是告诉编译器怎么来合成存取方法的。有些修饰符需要自己另外实现一些方法,比如 copy,这里就能定制一些自己的东西,比如实现真正的多层深拷贝等等。
参考:
https://www.jianshu.com/p/728…