理解本质,mutableCopy和copy的一切问题,引刃而解
内容均为原创, 如有任何疑问或者错误,请在文章下留言或者直接与我联系,一定及时回复: )
对于copy
和mutableCopy
这两个方法,相信大家在日常开发中并不陌生,但是有一部分程序员对于这两者的认知,仅仅停留在copy是浅拷贝,mutableCopy
是深拷贝,NSString
需要用copy
修饰,不然会出问题,具体出什么问题,也说不上来。本文将从苹果的API文档入手,对copy
和mutablecopy
做由浅入深的剖析。
主要说明以下问题:
---- 可变拷贝&不可变拷贝,深拷贝&浅拷贝。
---- copy一定是浅拷贝,mutableCopy一定是深拷贝吗?
---- NSString作为属性时,一定要用copy修饰吗?如果不用copy,会发生什么状况?
0.什么是拷贝?
在我们平时的电脑使用中,当需要对一个文件做修改,但是没有100%的把握可以按照需求修改好,或者修改后还是需要留一份修改前的文件,这个时候我们会用到拷贝操作。如下图。
简单来说就是副本文件和原文件之间的数据修改,互不影响。这就是拷贝这个操作真正的意义。
1.可变拷贝&不可变拷贝,深拷贝&浅拷贝。
我们都知道,foundation框架中提供给我们的部分类比如NSString,NSArray有可变和不可变之分;对于这些实现了NSCopy协议的类,执行不可变拷贝操作,会调用其协议方法- (id)mutableCopyWithZone:(NSZone *)zone;
,返回一个不可变的对象。可变拷贝,会调用其协议方法- (id)copyWithZone:(NSZone *)zone;
,返回一个可变的对象。如果拷贝操作返回的是一个新的对象,那么我们就称其为深拷贝;返回结果没有产生新对象,我们称其为浅拷贝。
所以,深浅拷贝是对拷贝结果的一个简称,copy不等于浅拷贝,mutableCopy不等于深拷贝。
2.copy一定是浅拷贝,mutableCopy一定是深拷贝吗?
首先,我们来看下对不可变对象进行copy操作会发生什么情况:
NSString *test = @"test";
NSString *test2 = [test copy];
NSLog(@"%p,%p",test,test2);
2018-10-04 00:38:29.360235+0800 gcdtestr[32886:1486529] 0x104768260,0x104768260
通过内存地址,我们可以发现,对一个不可变的常量字符串进行copy操作,并没有返回一个全新的对象,而是简单的指针复制(浅拷贝)。
对可变对象进行copy:
NSMutableString *test = [NSMutableString stringWithFormat:@"test"];
NSString *test2 = [test copy];
NSLog(@"%p,%p",test,test2);
2018-10-04 00:41:58.144299+0800 gcdtestr[32934:1490919] 0x6000015642d0,0xfad0108fd523415a
通过内存地址,我们可以发现,对一个可变字符串进行copy操作,得到了一个全新的对象(深拷贝)。
通过打印结果,我们知道了copy和mutableCopy的区别是前者不会产生新的对象,后者会产生新的对象
这句话是错误的。在对不可变对象进行copy
的操作,会返回给我们一个全新的、不可变的对象。
2.1为什么对可变对象copy会返回一个不可变的对象,而不是简单地复制指针呢?
有两个原因,第一:copy本来就是不可变拷贝,所以对不可变对象进行拷贝的时候,只能返回一个新的不可变对象。第二:要从拷贝这个操作发明的初衷去理解了,我们之所以需要拷贝,是希望副本和原文件两者都是独立存在且互不影响的,如果对不可变对象仅仅做一个指针复制的操作,由于两者都指向同一块内存,对副本进行修改的时候,会影响到到原对象,那么这就违背了我们拷贝的初衷了。
2.2 为什么对不可变对象copy,仅仅复制指针,而不是创建一个新的不可变对象?
原因很简单,因为不可变对象它被创建出来后就再也无法被修改了,连修改都不能了,更不存在修改副本对象影响到原对象了,所以副本和原对象指向同一块内存不会产生问题,不需要浪费内存创建一个新的对象。
2.3 mutableCopy一定是深拷贝吗?
mutableCopy一定是深拷贝。这个问题其实不用代码演示,理解了拷贝操作的原理,我们就可以推测到当我们对可变对象进行mutableCopy
的时候,由于是可变拷贝,需要返回一个可变的对象,副本对象和原对象都是可变的,那不可能两个指针都指向同一块内存(2.1中已经说明)。对不可变对象进行可变拷贝那就更需要返回一个新对象了。
3.NSString作为属性时,一定要用copy修饰吗?如果不用copy,会发生什么状况?
@property (nonatomic, copy) NSString *testStr;
当我们用copy修饰符号修饰属性时,runtime会在赋值前调用一次copy方法,set方法大概长这样:
-(void)setTestStr:(NSString *)testStr{
_testStr = [testStr copy];
}
通过set方法的代码,我们可以发现:如果给testStr属性赋值一个不可变的字符串,那么无论用copy和strong修饰,都不会产生任何问题。原因上面已经说了,不可变的字符串调用copy方法,仅仅是指针复制而已,和strong的效果一模一样。
但是如果我给testStr属性赋值一个可变的字符串,strong和copy修饰的差别就体现出来了。用copy修饰,赋值时会进行一个copy操作,产生一个全新的不可变对象,副本和原对象互不影响。也就是说赋值完毕后,testStr属性已经给自己保留了一份副本,不管原字符串如何修改,都不会影响到testStr属性的值。strong修饰,则不会产生副本,当传入的可变字符串在其他地方被修改了,testStr属性的值也会一起被修改!试想一下如果UITextField控件苹果用strong对其text属性修饰,那么当你给其赋值一个NSMutableString类型的对象后,将来这个MutableString的值一旦被修改,UITextField的值也会一起被修改,用户会懵逼的,明明什么都没做,怎么刚刚输入的内容,就被修改了呢!?