從源碼看iOS中的深拷貝和淺拷貝

關(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ì)象的值的改變而改變跑杭。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咆耿,隨后出現(xiàn)的幾起案子德谅,更是在濱河造成了極大的恐慌,老刑警劉巖萨螺,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窄做,死亡現(xiàn)場(chǎng)離奇詭異愧驱,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)椭盏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)组砚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人掏颊,你說(shuō)我怎么就攤上這事糟红。” “怎么了乌叶?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵盆偿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我枉昏,道長(zhǎng)陈肛,這世上最難降的妖魔是什么揍鸟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任兄裂,我火速辦了婚禮,結(jié)果婚禮上阳藻,老公的妹妹穿的比我還像新娘晰奖。我一直安慰自己,他們只是感情好腥泥,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布匾南。 她就那樣靜靜地躺著,像睡著了一般蛔外。 火紅的嫁衣襯著肌膚如雪蛆楞。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天夹厌,我揣著相機(jī)與錄音豹爹,去河邊找鬼。 笑死矛纹,一個(gè)胖子當(dāng)著我的面吹牛臂聋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播或南,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孩等,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了采够?” 一聲冷哼從身側(cè)響起肄方,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蹬癌,沒(méi)想到半個(gè)月后扒秸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體播演,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年伴奥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了写烤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拾徙,死狀恐怖洲炊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尼啡,我是刑警寧澤暂衡,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布,位于F島的核電站崖瞭,受9級(jí)特大地震影響狂巢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜书聚,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一唧领、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧雌续,春花似錦斩个、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至鸽心,卻和暖如春滚局,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顽频。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工藤肢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冲九。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓谤草,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親莺奸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丑孩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文為轉(zhuǎn)載: 作者:zyydeveloper 鏈接:http://www.reibang.com/p/5f776a...
    Buddha_like閱讀 877評(píng)論 0 2
  • 前言 不敢說(shuō)覆蓋OC中所有copy的知識(shí)點(diǎn),但最起碼是目前最全的最新的一篇關(guān)于 copy的技術(shù)文檔了灭贷。后續(xù)發(fā)現(xiàn)有新...
    zyydeveloper閱讀 3,362評(píng)論 4 35
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,405評(píng)論 8 265
  • 深拷貝和淺拷貝這個(gè)問(wèn)題在面試中常常被問(wèn)到温学,而在實(shí)際開(kāi)發(fā)中,只要稍有不慎甚疟,就會(huì)在這里出現(xiàn)問(wèn)題仗岖。尤其對(duì)于初學(xué)者來(lái)說(shuō)逃延,我...
    西門(mén)淋雨閱讀 1,790評(píng)論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,109評(píng)論 1 32