關(guān)于iOS中對(duì)象的深拷貝和淺拷貝的文章有很多吨些,但是大部分都是基于打印內(nèi)存地址來(lái)推導(dǎo)結(jié)果,這篇文章是從源碼的角度來(lái)分析深拷貝和淺拷貝聋伦。
深拷貝和淺拷貝的概念
拷貝的方式有兩種:深拷貝和淺拷貝绪爸。
- 淺拷貝又叫指針拷貝,比如說(shuō)有一個(gè)指針厕宗,這個(gè)指針指向一個(gè)字符串画舌,也就是說(shuō)這個(gè)指針變量的值是這個(gè)字符串的地址,那么此時(shí)對(duì)這個(gè)字符串進(jìn)行指針拷貝的意思就是又創(chuàng)建了一個(gè)指針變量已慢,這個(gè)指針變量的值是這個(gè)字符串的地址曲聂,也就是這個(gè)字符串的引用計(jì)數(shù)+1。
- 深拷貝又叫內(nèi)容拷貝佑惠,比如有一個(gè)指針朋腋,這個(gè)指針指向一個(gè)字符串,也就是說(shuō)這個(gè)指針變量的值是這個(gè)字符串的地址值膜楷,那么此時(shí)對(duì)這個(gè)字符串進(jìn)行內(nèi)容拷貝乍丈,就會(huì)創(chuàng)建一個(gè)新的指針,在一個(gè)新的地址區(qū)域創(chuàng)建一個(gè)字符串把将,這個(gè)字符串的值和原字符串的值相同轻专,新的指針指向這個(gè)新創(chuàng)建的字符串。這時(shí)原字符串的引用計(jì)數(shù)沒(méi)有+1察蹲。
對(duì)象的copy和mutableCopy方法
不管是集合對(duì)象還是非集合對(duì)象请垛,接收到copy和mutableCopy消息時(shí),都遵循以下準(zhǔn)則:
- copy返回immutable對(duì)象
- mutableCopy返回mutable對(duì)象
下面對(duì)非集合對(duì)象和集合對(duì)象的copy和mutableCopy方法進(jìn)行具體的闡述洽议。
1.非集合類(lèi)對(duì)象的copy和mutableCopy方法
非集合類(lèi)對(duì)象指的是NSString宗收,NSNumber...這些類(lèi)。下面的例子以NSString類(lèi)為例亚兄。
- 首先來(lái)看immutable對(duì)象拷貝的例子:
NSString *string = @"test";
NSString *copyString = [string copy];
NSMutableString *mutableCopyString = [string mutableCopy];
NSLog(@"%p \n %p \n %p \n", string, copyString, mutableCopyString);
打印結(jié)果:
0x101545068
0x101545068
0x60000024e940
通過(guò)打印結(jié)果我們可以看出來(lái)混稽,copyString和string的地址值一樣,而mutableCopyString和string的地址值不一樣,這就說(shuō)明imutable對(duì)象的copy方法進(jìn)行了淺拷貝匈勋,mutableCopy方法進(jìn)行了深拷貝礼旅。
- 再來(lái)看看mutable對(duì)象拷貝的例子:
NSMutableString *string = [[NSMutableString alloc] initWithString:@"test"];
NSString *copyString = [string copy];
NSMutableString *mutableCopyString = [string mutableCopy];
NSLog(@"%p \n%p \n%p \n", string, copyString, mutableCopyString);
打印結(jié)果:
0x600000240e40
0xa000000747365744
0x6000002411a0
通過(guò)打印結(jié)果可以看出來(lái),copyString和string的內(nèi)存地址不同洽洁,mutableCopyString和string的內(nèi)存地址也不同痘系。這說(shuō)明mutable對(duì)象的copy方法和mutableCopy方法都進(jìn)行了深拷貝。
總結(jié)起來(lái)就是:
immutable對(duì)象的copy方法進(jìn)行了淺拷貝
immutable對(duì)象的mutableCopy方法進(jìn)行了深拷貝
mutable對(duì)象的copy方法進(jìn)行了深拷貝
mutable對(duì)象的mutableCopy方法進(jìn)行了深拷貝饿自。
用代碼表示就是:
[immutableObject copy];//淺拷貝
[immutableObject mutableCopy];//深拷貝
[mutableObject copy];//深拷貝
[mutableObject mutableCopy];//深拷貝
以上是通過(guò)打印內(nèi)存地址得出的結(jié)論汰翠,下面我們通過(guò)查看源碼來(lái)證實(shí)一下我們的結(jié)論。
在opensource.apple.com的git倉(cāng)庫(kù)中的runtime源碼中有NSObject.mm這個(gè)文件昭雌,在這個(gè)文件中有copy和mutableCopy方法的實(shí)現(xiàn):
- (id)copy {
return [(id)self copyWithZone:nil];
}
- (id)mutableCopy {
return [(id)self mutableCopyWithZone:nil];
}
我們發(fā)現(xiàn)copy和mutableCopy方法只是簡(jiǎn)單的調(diào)用了copyWithZone:
和mutableCopyWithZone:
兩個(gè)方法复唤。然后我在searchcode.com中找到了NSString和NSMutableString的Source Code。
在NSString.m中烛卧,找到了關(guān)于copy的方法:
- (id)copyWithZone:(NSZone *)zone {
if (NSStringClass == Nil)
NSStringClass = [NSString class];
return RETAIN(self);
}
- (id)mutableCopyWithZone:(NSZone*)zone {
return [[NSMutableString allocWithZone:zone] initWithString:self];
}
通過(guò)這個(gè)源碼我們知道了苟穆,對(duì)于NSString對(duì)象,調(diào)用copy方法就是調(diào)用了copyWithZone:
方法唱星。而copyWithZone:
方法并沒(méi)有創(chuàng)建新的對(duì)象雳旅,而是使指針持有了原來(lái)的NSString對(duì)象,所以NSString的copy方法是淺拷貝间聊。
而調(diào)用mutableCopy方法就是調(diào)用了mutableCopyWithZone:
方法攒盈。從mutableCopyWithZone:
的實(shí)現(xiàn)我們可以看到,這個(gè)方法是創(chuàng)建了一個(gè)新的可變的字符串對(duì)象哎榴。因此NSString的mutableCopy方法是深拷貝型豁。
在NSMutableString.m中,只找到了copy和copyWithZone:方法尚蝌,并沒(méi)有找到mutableCopyWithZone:方法:
-(id)copy {
return [[NSString alloc] initWithString:self];
}
-(id)copyWithZone:(NSZone*)zone {
return [[NSString allocWithZone:zone] initWithString:self];
}
對(duì)NSMutableString對(duì)象調(diào)用copy方法會(huì)調(diào)用這里的copyWithZone:方法的實(shí)現(xiàn)迎变,我們可以看到這里創(chuàng)建了一個(gè)新的不可變的字符串。所以對(duì)NSMutableString對(duì)象執(zhí)行copy方法是深拷貝飘言。
由于在NSMutableString中沒(méi)有實(shí)現(xiàn)mutableCopyWithZone:方法衣形,所以會(huì)調(diào)用父類(lèi)的mutableCopyWithZone:方法,也就是NSString類(lèi)的mutableCopyWithZone:方法姿鸿,而我們知道谆吴,NSString類(lèi)的mutableCopyWithZone:方法會(huì)創(chuàng)建一個(gè)新的可變字符串。所以對(duì)NSMutableString對(duì)象執(zhí)行mutableCopy方法是深拷貝苛预。
2.集合對(duì)象的copy和mutableCopy
集合對(duì)象指的是NSArray句狼,NSDictionary,NSSet等之類(lèi)的對(duì)象热某。下面以NSArray為例看看immutable對(duì)象使用copy和mutableCopy的例子:
NSArray *array = @[@"1", @"2", @"3"];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [array mutableCopy];
NSLog(@"%p\n%p\n%p", array, copyArray, mutableCopyArray);
打印結(jié)果:
0x60400025bed0
0x60400025bed0
0x60400025c2f0
通過(guò)打印結(jié)果可以看出來(lái)腻菇,copyArray的地址和array的地址是一樣的胳螟,說(shuō)明對(duì)array進(jìn)行copy是進(jìn)行淺拷貝。而mutableCopyArray的地址和array的地址是不一樣的筹吐,說(shuō)明對(duì)array進(jìn)行mutableCopy是進(jìn)行了深拷貝糖耸。
再來(lái)看mutable對(duì)象執(zhí)行copy和mutableCopy的例子:
NSMutableArray *array = [[NSMutableArray alloc] initWithArray:@[@"1", @"2", @"3"]];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [array mutableCopy];
NSLog(@"%p\n%p\n%p", array, copyArray, mutableCopyArray);
打印結(jié)果:
0x604000447440
0x604000447050
0x604000447080
通過(guò)打印結(jié)果可以看出,copyArray和mutableCopyArray的地址都和array的地址不同骏令,這說(shuō)明對(duì)可變數(shù)組進(jìn)行copy和mutableCopy操作都進(jìn)行了深拷貝。
因此得出結(jié)論:
在集合類(lèi)對(duì)象中垄提,對(duì)immutable對(duì)象進(jìn)行copy操作是淺拷貝榔袋,進(jìn)行mutableCopy操作是深拷貝。對(duì)mutable對(duì)象進(jìn)行copy操作是深拷貝铡俐,進(jìn)行mutableCopy操作是深拷貝凰兑。
用代碼表示就是:
[immutableObject copy];//淺拷貝
[immutableObject mutableCopy];//深拷貝
[mutableObject copy];//深拷貝
[mutableObject mutableCopy];//深拷貝
以上是通過(guò)打印內(nèi)存地址得到的結(jié)論,下面我們通過(guò)源碼來(lái)驗(yàn)證一下我們的結(jié)論审丘。
在NSArray.m中吏够,我找到了copyWithZone:
和mutableCopyWithZone:
方法。
- (id)copyWithZone:(NSZone *)zone
{
return RETAIN(self);
}
- (id)mutableCopyWithZone:(NSZone*)zone
{
if (NSMutableArrayClass == Nil)
NSMutableArrayClass = [NSMutableArray class];
return [[NSMutableArrayClass alloc] initWithArray:self];
}
當(dāng)調(diào)用copy方法時(shí)滩报,實(shí)際上是執(zhí)行了這里的copyWithZone:
方法锅知,在這個(gè)方法里面并沒(méi)有創(chuàng)建新的對(duì)象,而只是持有了舊的對(duì)象脓钾,因此售睹,對(duì)于不可變的數(shù)組對(duì)象,執(zhí)行copy操作是淺拷貝可训。
當(dāng)調(diào)用mutableCopy方法時(shí)昌妹,實(shí)際上是執(zhí)行了這里的mutableCopyWithZone:
方法,在這個(gè)方法里面握截,利用原來(lái)的數(shù)組對(duì)象飞崖,創(chuàng)建了一個(gè)新的可變數(shù)組對(duì)象,因此對(duì)于不可變的數(shù)組對(duì)象谨胞,執(zhí)行mutableCopy操作是深拷貝固歪。
在NSArray.m這個(gè)文件的第825行是NSMutableArray的實(shí)現(xiàn)。在第875行找到了copyWithZone:
的實(shí)現(xiàn)胯努,沒(méi)有找到mutableCopyWithZone:
的實(shí)現(xiàn):
- (id)copyWithZone:(NSZone*)zone
{
if (NSArrayClass == Nil)
NSArrayClass = [NSArray class];
return [[NSArrayClass alloc] initWithArray:self copyItems:YES];
}
當(dāng)調(diào)用copy方法時(shí)昼牛,實(shí)際是調(diào)用了這里的copyWithZone:
方法,在這個(gè)方法的實(shí)現(xiàn)里康聂,是利用原來(lái)的可變數(shù)組創(chuàng)建了一個(gè)新的不可變數(shù)組贰健,因此對(duì)可變數(shù)組執(zhí)行copy操作是深拷貝。
當(dāng)調(diào)用mutableCopy時(shí)恬汁,由于NSMutableArray本身沒(méi)有實(shí)現(xiàn)mutableCopyWithZone:
方法伶椿,所以會(huì)調(diào)用父類(lèi)也就是NSArray類(lèi)的實(shí)現(xiàn)辜伟,而通過(guò)上面我們也能看到NSArray的實(shí)現(xiàn):利用原數(shù)組創(chuàng)建了一個(gè)新的可變數(shù)組,因此脊另,對(duì)可變數(shù)組進(jìn)行mutableCopy操作是深拷貝导狡。
回答經(jīng)典面試題
面試題:為什么NSString類(lèi)型的成員變量的修飾屬性用copy而不是strong呢?
首先要搞清楚的就是對(duì)NSString類(lèi)型的成員變量用copy修飾和用strong修飾的區(qū)別偎痛。如果使用了copy修飾符旱捧,那么在給成員變量賦值的時(shí)候就會(huì)對(duì)被賦值的對(duì)象進(jìn)行copy操作,然后再賦值給成員變量踩麦。如果使用的是strong修飾符枚赡,則不會(huì)執(zhí)行copy操作,直接將被賦值的變量賦值給成員變量谓谦。
假設(shè)有一個(gè)NSString類(lèi)型的成員變量string贫橙,對(duì)其進(jìn)行賦值:
NSString *testString = @"test";
self.string = testString;
如果該成員變量是用copy修飾的,則等價(jià)于:
self.string = [testString copy];
如果是用strong修飾的反粥,則沒(méi)有copy操作:
self.string = testString;
知道了使用copy和strong的區(qū)別后卢肃,我們?cè)賮?lái)分析為什么要使用copy修飾符。先看一段代碼:
NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"test"];
self.string = mutableString;
NSLog(@"%@", self.string);
[mutableString appendString:@"addstring"];
NSLog(@"%@", self.string);
如果這里成員變量string是用strong修飾的話才顿,打印結(jié)果就是:
2018-09-04 10:50:16.909998+0800 copytest[2856:78171] test
2018-09-04 10:50:16.910128+0800 copytest[2856:78171] testaddstring
很顯然莫湘,當(dāng)mutableString的值發(fā)生了改變后,string的值也隨之發(fā)生改變郑气,因?yàn)?code>self.string = mutableString;這行代碼實(shí)際上是執(zhí)行了一次指針拷貝逊脯。string的值隨mutableString的值的發(fā)生改變這顯然不是我們想要的結(jié)果。
如果成員變量string是用copy修飾竣贪,打印結(jié)果就是:
2018-09-04 10:58:07.705373+0800 copytest[3024:84066] test
2018-09-04 10:58:07.705496+0800 copytest[3024:84066] test
這是因?yàn)槭褂胏opy修飾符后军洼,self.string = mutableString;
就等價(jià)于self.string = [mutableString copy];
,也就是進(jìn)行了一次深拷貝演怎,所以mutableString的值再發(fā)生變化就不會(huì)影響到string的值匕争。
回答面試題:
NSString類(lèi)型的成員變量使用copy修飾而不是strong修飾是因?yàn)橛袝r(shí)候賦給該成員變量的值是NSMutableString類(lèi)型的,這時(shí)候如果修飾符是strong爷耀,那成員變量的值就會(huì)隨著被賦值對(duì)象的值的變化而變化甘桑。若是用copy修飾,則對(duì)NSMutableString類(lèi)型的值進(jìn)行了一次深拷貝歹叮,成員變量的值就不會(huì)隨著被賦值對(duì)象的值的改變而改變跑杭。