iOS常见的几个修饰词深入讲解
前言:
最近公司在扩招,做为公司仅有的唯一一个首席ios开发工程师(手动滑稽),我不得不硬着头皮上阵。
然后却发现很多人的水平和年限严重不符,公司招的人都是3年+以上经验的人,然而这些人中有一半连修饰词的作用也说的模棱两可,加上自己水平也不高,对以后的职业生涯产生了严重的危机感,遂决定以后每周希望能写一篇有价值的文章,与君共勉,今天就说说ios常见的几个修饰词。
一、readonly,readwrite
readonly:
根据字面意思,大家都很容易知道是“只读”的意思,意味着只生成了getter方法,而没有生成setter方法,如果这时候调用setter方法,会报一个assignment to readonly property错误
ps:这里顺便说一下self.和_的区别
self.就是调用property自动生成的getter和setter方法,而_则是直接去调用实例变量(property会自动生成一个实例变量,如果你重写了getter与setter方法,property自动生成的实例变量就无效了,需要手动去申明一个实例变量或者用@@synthesize).
回到正题,那么这意味着我们就完全没办法去修改readonly的属性了吗?不然,如果你尝试一下setvalue:forkey:,你就会发现竟然改变成功了,amazing,让我们来看看代码:
@interface myfirstclass : nsobject @property (nonatomic, copy, readonly) nsstring * string; @end #import "myfirstclass.h" @implementation myfirstclass - (instancetype) init{ self = [super init]; if (self) { _string = @"来改变我啊"; } return self; } @end - (void)viewdidload { [super viewdidload]; myfirstclass * class = [myfirstclass new]; nslog(@"string === %@", class.string); [class setvalue:@"改变了" forkey:nsstringfromselector(@selector(string))]; nslog(@"string === %@", class.string); } log如下: 2018-03-16 11:08:58.932303+0800 propertydesc[5681:445705] string === 来改变我啊 2018-03-16 11:08:58.932454+0800 propertydesc[5681:445705] string === 改变了
而如果这个时候在myfirstclass里加入
@implementation myfirstclass - (instancetype) init{ self = [super init]; if (self) { _string = @"来改变我啊"; } return self; } + (bool) accessinstancevariablesdirectly{ return no; } @end
在运行,boom,系统会报以下错误
2018-03-16 11:19:21.619747+0800 propertydesc[6016:478446] *** terminating app due to uncaught exception 'nsunknownkeyexception', reason: '[<myfirstclass 0x6040000088f0> setvalue:forundefinedkey:]: this class is not key value coding-compliant for the key string.'
没有找到当前要赋值的属性,那么accessinstancevariablesdirectly究竟是什么呢,我们打开苹果的官方文档找到key-value coding programming guide
在这里可以看到,如果这个方法设为yes,访问器就会去寻找名称为_<key>, _is<key>, <key>, or is<key>的成员变量,如果为no,就会跳到第6步,第6步就会报[valueforundefinedkey:]错误。
总结:
readonly并不能完全保证只读,我们可以通过kvc尝试去修改其值。
ps:有兴趣的小伙伴可以尝试去修改别人的sdk,包括苹果爸爸的
readwrite:
这个实在没什么可说的,默认的修饰词就是readwrite,代表可读可写
二、nonatomic、atomic
我们先看一下官方文档
苹果官网对两者的说法
atomic:
默认的属性修饰词,按官方文档上说即使从不同的线程通过getter或setter方法去访问属性也能完全的获取到或设置值,从这里也可以看出,atomic并不是线程安全的,因为atomic只能保证通过setter和getter方法能获取到一个完整的value,如果a线程在getter,b、c线程在setter,可能a获取到的值是bc执行之后的值,也可能是bc线程执行完之前的值,也可能是b c线程任何一个线程执行完的值。
因此atomic的伪代码大概如下:
- (void)setstring:(nsstring *)string{ @synchronized(self) { if (_string != string) { [_string release];//mrc _string = [string copy]; } } } - (nsstring *) string{ @synchronized(self) { return _ string; } }
nonatomic:
相对而言,通过nonatomic修饰的属性,并没有做锁的操作,多线程同时进行setter/getter操作,并不能保证得到一个完整的value,所以相对atomic来说nonatomic修饰的属性访问速度更快,而且平时对线程安全我们更倾向于使用信号量、nslock和synchronized去控制线程安全,他们都能保证代码块的原子性,所以几乎所有的属性都用nonatomic去修饰。
三、assign、weak与strong
assign:
一般来说,我们都用assign去修饰oc的基本数据类型,but why?
因为assign并不会使对象的引用计数加1,也就是说如果用assign去修饰一个对象,这个对象会立即被释放,重要的是assgin在被释放的时候是不会自动置为nil,还是保留对象的指针地址,会形成野指针,这个时候向其发送消息就会崩溃,简单实验的代码如下:
@interface mysecondclass : nsobject @property (nonatomic, assign) nsmutablearray * array; @end - (void) testmethodtwo{ mysecondclass * class = [[mysecondclass alloc] init]; self.secondclass = class; self.secondclass.array = [nsmutablearray array]; [self.secondclass.array addobject:@(0)]; }
在运行到最后一步的时候程序会崩溃报exc_bad_access的错误,如果打断点的话会发现在执行到这步的时候array还是有地址的。
weak:
如果把上面的代码
@property (nonatomic, assign) nsmutablearray * array;换成
@property (nonatomic, weak) nsmutablearray * array;
.这个时候程序并不会崩溃,如果你打个断点的话会发现array被自动置为nil,而oc的特性使得像nil发送消息并不会崩溃,这就是weak和assgin最大的区别,此外weak必须用于修饰对象,这和他自动置为nil相关,如果强行使用weak修饰基本数据类型,编译器会报一个大大的红色错误!
strong:
strong的作用和assign和weak恰恰相反,strong也是属性默认的修饰词,代表着被修饰的对象引用计数+1
如果把上面的代码
@property (nonatomic, assign) nsmutablearray * array;换成
@property (nonatomic, strong) nsmutablearray * array;self.secondclass.array = [nsmutablearray array];
最后一句代码可以解释为[nsmutablearray array]创造了一个对象a,此时a的引用计数为1,self.secondclass.array做为对象b,把a赋值给b的时候,a的引用计数加1,此时a的引用计数为2,b指向了a,然后编译器会自动对a进行释放操作(因为是局部变量),a的引用计数-1。在拥有b的对象不释放的时候,a的引用计数永远不可能为0,除非你手动释放或者把b指向一个新的对象,这样a永远不会被释放,这就是所谓的强引用。
weak和strong的区别:weak和strong不同的是 当一个对象不再有strong类型的指针指向它的时候 它会被释放 ,即使还有weak型指针指向它。一旦最后一个strong型指针离去 ,这个对象将被释放,所有剩余的weak型指针都将被清除。
copy与retain:
- copy其实是建立了一个相同的对象,而retain不是.
- copy是内容拷贝,retain是指针拷贝.
- copy是内容的拷贝 ,对于像nsstring,的确是这样,如果拷贝的是nsarray这时只是copy了指向array中相对应元素的指针.这便是所谓的"浅复制".
atomic是objc使用的一种线程保护技术,基本上来讲,是防止在写未完成的时候被另外一个线程读取,造成数据错误。而这种机制是耗费系统资源的,所以在iphone这种小型设备上,如果没有使用多线程间的通讯编程,那么nonatomic是一个非常好的选择。
四、copy与mutablecopy
苹果官网对对象拷贝的说法
在说copy与mutablecopy之前我们先看看官方文档对深拷贝与浅拷贝的阐释,如下
深拷贝:
对象拷贝 - 重新申请一片内存保留这个对象,与原对象之间没有半点关系。
浅拷贝:
指针拷贝 - 实际上相当于引用计数+1,被拷贝的和拷贝的引用同一个对象。
接下来我们分两个方面做测试:
1.对非集合类对象的copy操作,以nsstring为例
对immutableobject做copy操作
nsstring * string = [nsstring stringwithformat:@"1"]; nsstring * copystring = [string copy]; nsstring * mutablecopystring = [string mutablecopy]; nslog(@"string:%p", string); nslog(@"copystring:%p", copystring); nslog(@"mutablecopystring:%p", mutablecopystring);
log如下:
2018-03-19 15:51:38.785253+0800 propertydesc[10283:759804] string:0xa000000000000311
2018-03-19 15:51:38.785435+0800 propertydesc[10283:759804] copystring:0xa000000000000311
2018-03-19 15:51:38.785518+0800 propertydesc[10283:759804] mutablecopystring:0x608000055150
可以看出对string和copystring的地址是一样的,而mutablecopystring则不同。
对mutableobject做copy操作
nsmutablestring * string = [nsmutablestring stringwithformat:@"1"]; nsstring * copystring = [string copy]; nsstring * mutablecopystring = [string mutablecopy]; nslog(@"string:%p - %@", string, string); nslog(@"copystring:%p - %@", copystring, copystring); nslog(@"mutablecopstring:%p - %@", mutablecopystring, mutablecopystring); [string appendstring:@",2"]; nslog(@"copystring:%p - %@", copystring, copystring); nslog(@"mutablecopstring:%p - %@", mutablecopystring, mutablecopystring);
log如下:
2018-03-19 15:51:38.785670+0800 propertydesc[10283:759804] string:0x60400005a940 - 1
2018-03-19 15:51:38.785784+0800 propertydesc[10283:759804] copystring:0xa000000000000311 - 1
2018-03-19 15:51:38.785834+0800 propertydesc[10283:759804] copystring:0xa000000000000311 - 1
2018-03-19 15:51:38.785891+0800 propertydesc[10283:759804] mutablecopystring:0x60400005a910 - 1
2018-03-19 15:51:38.786037+0800 propertydesc[10283:759804] mutablecopystring:0x60400005a910 - 1
可以看出对string与copystring、mutablecopystring三者的地址都是不同的。
即使改变了原string的value,copystring与mutablecopystring也没有改变,这与下文对集合类对象得出的结论正好相反。
结论:
对 immutableobject进行 copy 操作是指针拷贝,mutablecopy 操作时对象拷贝。
对 mutable object进行 copy 和 mutablecopy 都是对象拷贝。简单的表格图如下:
object | handle | result |
---|---|---|
immutableobject | copy | 指针拷贝 |
immutableobject | mutablecopy | 深拷贝 |
mutableobject | copy | 深拷贝 |
mutableobject | mutablecopy | 深拷贝 |
2.对集合类对象的copy操作
对immutableobject做copy操作
nsarray * array = [nsarray arraywithobject:@"1"]; nsarray * copyarry = [array copy]; nsmutablearray * mutablecopyarray = [array mutablecopy]; nslog(@"array:%p", array); nslog(@"copyarry:%p", copyarry); nslog(@"mutablecopyarray:%p", mutablecopyarray);
log如下
2018-03-19 15:51:38.786167+0800 propertydesc[10283:759804] array:0x6000000094c0
2018-03-19 15:51:38.786278+0800 propertydesc[10283:759804] copyarray:0x6000000094c0
2018-03-19 15:51:38.786385+0800 propertydesc[10283:759804] mutablecopyarray:0x600000240030
可以看出array与copyarray的地址是一样的,而mutablecopyarray则不同。
对mutableobject做copy操作
nsmutablestring * string = [nsmutablestring stringwithformat:@"1"]; nsmutablearray * array = [nsmutablearray arraywithobject:string]; nsarray * copyarry = [array copy]; nsmutablearray * mutablecopyarray = [array mutablecopy]; nslog(@"array:%p", array); nslog(@"copyarry:%p", copyarry); nslog(@"mutablecopyarray:%p", mutablecopyarray); [array addobject:@"2"]; [string appendstring:@"1"]; nslog(@"array:%p - %@", array, array); nslog(@"copyarry:%p - %@", copyarry, copyarry); nslog(@"mutablecoparray:%p - %@", mutablecopyarray, mutablecopyarray);
log如下
2018-03-26 13:36:38.786499+0800 propertydesc[10283:759804] array:0x600000240150
2018-03-26 13:36:38.786600+0800 propertydesc[10283:759804] copyarry:0x6000000095f0
2018-03-26 13:36:38.786698+0800 propertydesc[10283:759804] mutablecopyarray:0x6000002400f0
2018-03-26 13:36:38.786865+0800 propertydesc[10283:759804] array:0x600000240150 - (
11,
2
)
2018-03-26 13:36:38.787018+0800 propertydesc[10283:759804] copyarry:0x6000000095f0 - (
11
)
2018-03-26 13:36:38.787142+0800 propertydesc[10283:759804] mutablecopyarray:0x6000002400f0 - (
11
)
what??不管是copy还是mutablecopy我们可以看到当我们修改了string的值后,数组中的值都变了,但是在 [array addobject:@"2"];的时候两个复制出来的数组的对象并没有变化?
这里我们要提到一个新概念 不完全深拷贝(也有人说是单层深拷贝 )------ 即虽然新开辟了内存地址,但是存放在内存上的值(也就是数组里的元素仍然指向原数组元素值,并没有另外复制一份),所以说上文中的array和mutablecoparray类似于有一个或多个相交点的相交链表。
而且我们也可以看到不管是copy还是mutablecopy都是不完全深拷贝,三者的地址都是不一样的。
结论:
对immutableobject做copy是指针拷贝,做mutablecopy是不完全深拷贝。
对mutableobject做copy或mutablecopy都是不完全深拷贝。
有趣的是,这与上文的结论有类似之处。简单的表格图如下:
object | handle | result |
---|---|---|
immutableobject | copy | 指针拷贝 |
immutableobject | mutablecopy | 不完全深拷贝 |
mutableobject | copy | 不完全深拷贝 |
mutableobject | mutablecopy | 不完全深拷贝 |
并且如果打个断点可以发现对任何对象做copy操作返回的是一个不可变的对象,对任何对象做mutablecopy返回的是一个可变的对象(有兴趣的请自行验证)。
五、是用copy还是strong?
通过上述对copy与strong的描述,copy和strong最终都会返回一个引用计数为1的对象,所以单单从内存管理上说copy和strong并无任何区别,但是copy始终会返回一个不可变对象,他的值是不会被修改的。
而strong不同,被strong修饰的对象,可能会被可变对象赋值,从而在外部导致不可预料的被更改的情况。
总而言之,是否使用copy或strong还是根据具体场景来定,这里还有个性能优化的小技巧,如果copy的是可变的对象,会对它做一次完全深拷贝/不完全深拷贝,性能上是肯定不如strong直接引用计数+1来的快。
六、结尾
这次的修饰词讲解到此就告一段落了,然后想一下这周写什么样的文章。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。