Objective-C中的深拷貝和淺拷貝

集合類型的拷貝

深拷貝是深度拷貝,是拷貝一個(gè)實(shí)例對象到一個(gè)新的內(nèi)存地址攘残,而淺拷貝只是簡單拷貝一個(gè)實(shí)例對象的指針插佛。蘋果官方文檔提供了如下圖由于理解深拷貝和淺拷貝

CopyingCollections_2x.png

由上圖可知产弹,集合的淺拷貝(shallow copy)后的數(shù)組Array2與之前的數(shù)組Array1指向同一段內(nèi)存區(qū)域两疚,而深拷貝(deep copy)下Array2和Array1指向不同的內(nèi)存區(qū)域床估。

淺拷貝

集合淺拷貝的方式有很多膀跌。當(dāng)創(chuàng)建一個(gè)淺拷貝時(shí)嘲驾,之前集合里面的每個(gè)對象都會(huì)收到retain消息样漆,使其引用計(jì)數(shù)加1将宪,并且將其指針拷貝到新集合中。

NSArray *array = @[@"1111"];
NSArray *shallowCopyArray = [array copyWithZone:nil];
NSLog(@"array address: %p", array);
NSLog(@"shallowCopyArray address: %p", shallowCopyArray);

NSDictionary *dic = @{@"key": @"value"};
NSDictionary *shallowCopyDic = [[NSDictionary alloc]initWithDictionary:dic copyItems:NO];
NSLog(@"dic address: %p", dic);
NSLog(@"shallowCopyDic address: %p", shallowCopyDic);

運(yùn)行輸出

array address: 0x60800001f700
shallowCopyArray address: 0x60800001f700
dic address: 0x6080002212c0
shallowCopyDic address: 0x6080002215c0

可以發(fā)現(xiàn)淺拷貝前后的數(shù)組所指向的內(nèi)存地址一樣知态,而字典所指向的內(nèi)存地址發(fā)生了變化识腿,為何同樣是淺拷貝,拷貝前后地址卻發(fā)生了改變呢励烦?

這是因?yàn)閷τ跀?shù)組,我們只是調(diào)用它的copyWithZone方法泼诱,由于是不可變數(shù)組坛掠,返回自身,所以淺拷貝前后數(shù)組的內(nèi)存地址不變治筒。

而對于字典來說屉栓,shallowCopyDic是通過allocinit創(chuàng)建的耸袜,因此在內(nèi)存中開辟了一段新的內(nèi)存友多,但對于之前字典中的對象,只是拷貝其內(nèi)存地址堤框,所以淺拷貝前后字典的內(nèi)存地址發(fā)生了變化域滥,其實(shí)內(nèi)部元素的地址是不變的纵柿。

因此,在集合對象的淺拷貝中启绰,并非是對于自身的淺拷貝昂儒,而是對內(nèi)部元素的拷貝

深拷貝

在深拷貝中,系統(tǒng)會(huì)向集合中的每一個(gè)元素發(fā)生一個(gè)copyWithZone消息委可,該消息是來自NSCoping協(xié)議渊跋,如果有對象沒有實(shí)現(xiàn)該協(xié)議方法,那么就會(huì)奔潰着倾,如果實(shí)現(xiàn)了該方法拾酝,那么會(huì)根據(jù)該方法的具體實(shí)現(xiàn),實(shí)現(xiàn)具體的深拷貝卡者。

NSString *str = @"222";
NSArray * array = @[str];
NSArray *shalldowCopyArray = [array copyWithZone:nil];
NSArray *deepCopyArray = [[NSArray alloc]initWithArray:array copyItems:YES];

NSLog(@"array address: %p", array);
NSLog(@"shalldowCopyArray address: %p" , shalldowCopyArray);
NSLog(@"deepCopyArray address: %p", deepCopyArray);

NSLog(@"array[0] address: %p", array[0]);
NSLog(@"shalldowCopyArray[0] address: %p", shalldowCopyArray[0]);
NSLog(@"deepCopyArray[0] address: %p", deepCopyArray[0]);

運(yùn)行輸出

array address: 0x6000000158a0
shalldowCopyArray address: 0x6000000158a0
deepCopyArray address: 0x6000000158b0

array[0] address: 0x10ece8158
shalldowCopyArray[0] address: 0x10ece8158
deepCopyArray[0] address: 0x10ece8158

打印前3行與我們的猜測一致微宝,但是后面3行卻打印這相同的地址。這有些意外虎眨,明明采用了深拷貝和淺拷貝蟋软,結(jié)果卻是相同的內(nèi)存地址,為什么會(huì)這樣呢嗽桩?

是因?yàn)榧项愋蜕羁截悤?huì)對每一個(gè)元素調(diào)用copyWithZone方法岳守,這意味著后面3行最終打印輸出什么取決于該方法。在深拷貝時(shí)碌冶,對于第一個(gè)元素調(diào)用了copyWithZone方法湿痢,但是由于NSString是不可變的,對于其深拷貝創(chuàng)建一個(gè)新內(nèi)存是無意義的扑庞,所以我們可以猜測NSString的copyWithZone方法也是直接返回self譬重,所以淺拷貝時(shí)是直接拷貝元素地址,而深拷貝是通過copyWithZone方法來獲取元素地址罐氨,兩個(gè)結(jié)果是一樣的臀规。

如果將str的類型改動(dòng)一下,將其改為NSMutableString類型:

NSMutableString *str = [[NSMutableString alloc]initWithString:@"222"];

就可以看到打印的元素地址發(fā)生了改變

array[0] address: 0x6080002641c0
shalldowCopyArray[0] address: 0x6080002641c0
deepCopyArray[0] address: 0xa000000003232323

對于深拷貝來說栅隐,自身如何拷貝取決于實(shí)例方法中copyWithZone如何實(shí)現(xiàn)塔嬉,對于下一級還是采用淺拷貝方式,這稱為集合類型的單層深拷貝

完全拷貝

除了淺拷貝和深拷貝租悄,還有一個(gè)完全拷貝的概念谨究。那么什么是完全拷貝呢?就是對對象的每一層都是重新創(chuàng)建實(shí)例變量泣棋,不存在指針拷貝胶哲。舉個(gè)例子,在對數(shù)組進(jìn)行歸檔和解檔時(shí)潭辈,其實(shí)就是完全拷貝鸯屿。

 NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

完全拷貝不僅是在歸檔俩檬、解檔中存在,在其他場景也存在碾盟。

對于以上三種拷貝類型總結(jié):

淺拷貝:在淺拷貝操作時(shí)棚辽,對于被拷貝對象的每一層進(jìn)行指針拷貝

深拷貝:在深拷貝操作時(shí),對于被拷貝對象冰肴,至少有一層是深拷貝

完全拷貝:在完成拷貝操作時(shí)屈藐,對于被拷貝對象的每一層都是深拷貝

非集合類型拷貝

不可變對象調(diào)用copymutableCopy

NSString *string = @"1234";
NSString *stringCopy = [string copy];
NSMutableString *stringMCopy = [string mutableCopy];

NSLog(@"string address: %p", string);
NSLog(@"stringCopy: %p", stringCopy);
NSLog(@"stringMCopy: %p", stringMCopy);

運(yùn)行輸出

string address: 0x10f83a218
stringCopy: 0x10f83a218
stringMCopy: 0x600000267540

可以看到,對NSString進(jìn)行copy只是對其指針進(jìn)行的拷貝熙尉,而進(jìn)行mutableCopy是真正重新創(chuàng)建一個(gè)新的NSString對象联逻。注意:寫定的字符串是存放在內(nèi)存的常量區(qū),因此可以看到兩處的地址位置相差甚遠(yuǎn)检痰。

另外copy方法是與NSCoping協(xié)議相關(guān)的包归,mutableCopy是與NSMutableCoping協(xié)議相關(guān)的。對于NSString這樣的不可變系統(tǒng)類來說铅歼,copy后返回自身是比較好理解的公壤,所以copy后仍然是相同的內(nèi)存地址。而對NSString調(diào)用mutableCopy表明你需要一個(gè)新的可變對象椎椰,所以會(huì)返回一個(gè)NSMutableString對象厦幅。

可變對象的copymutableCopy

NSMutableString *mString = [[NSMutableString alloc]initWithString:@"123"];
NSString *copyString = [mString copy];
NSString * mCopyString = [mString mutableCopy];

NSLog(@"mString address: %p", mString);
NSLog(@"copyString address: %p", copyString);
NSLog(@"mCopyString address: %p", mCopyString);

NSLog(@"copyString is mutable: %@", [copyString isKindOfClass:[NSMutableString class]] ? @"YES": @"No");
NSLog(@"mCopyString is mutable: %@", [mCopyString isKindOfClass:[NSMutableString class]] ? @"YES": @"No");

運(yùn)行輸出

mString address: 0x600000073e00
copyString address: 0xa000000003332313
mCopyString address: 0x600000074100
copyString is mutable: No
mCopyString is mutable: YES

從打印結(jié)果來看,對于NSMutableString來說慨飘,其copy的返回值是一個(gè)不可變的字符串确憨,而mutableCopy返回的是可變字符串,即使三者是不一樣的地址瓤的,即三個(gè)對象休弃。

FoundationUIkit框架中,類似于NSStringNSMutableString這樣的非集合對象分為可變和不可變對象并不多圈膏,但是對于copymutableCopy的實(shí)現(xiàn)原理都是一樣的塔猾,即:對可變對象的copy是不可變對象,使用mutableCopy都為可變

[immutableObject copy]; //淺復(fù)制
[immutableObject mutableCopy]; //深復(fù)制
[mutableObject copy]; //深復(fù)制
[mutableObject mutableCopy]; //深復(fù)制

自定義拷貝

如果自定義的類需要實(shí)現(xiàn)淺拷貝本辐,則在實(shí)現(xiàn)copyWithZone方法是返回自身即可,而如果需要實(shí)現(xiàn)深拷貝慎皱,則在copyWithZone方法中創(chuàng)建一個(gè)新實(shí)例對象返回即可

對于所謂的深拷貝叶骨,其實(shí)應(yīng)當(dāng)取決于每一層對象本身,如果需要達(dá)到完全深拷貝忽刽,則每一層對象都應(yīng)當(dāng)在copyWithZone方法中創(chuàng)建好新的實(shí)例對象天揖,如果每一層都為深拷貝夺欲,那么最外層拷貝就是完全拷貝了今膊。

copy屬性

在類中聲明屬性時(shí)有個(gè)copy修飾符,一般用于修飾字符串和block以及一些數(shù)組和字典斑唬。那么為什么要聲明為copy市埋,而不是聲明為strong呢?

@property(nonatomic, copy) NSMutableString *string;

self.string = [[NSMutableString alloc]initWithString:@"1234"];
NSString *copyString = self.string;
NSLog(@"copyString is mutable: %@", [copyString isKindOfClass:[NSMutableString class]] ? @"YES": @"NO");
NSLog(@"string is mutable: %@", [self.string isKindOfClass:[NSMutableString class]] ? @"YES": @"NO");

運(yùn)行輸出

copyString is mutable: NO
string is mutable: NO

但是當(dāng)我們把copy改為strong恕刘,則會(huì)打印YES.

這其實(shí)并不復(fù)雜,在使用copy的時(shí)候褐着,會(huì)對屬性的setter方法進(jìn)行判斷,對屬性進(jìn)行copy频敛,根據(jù)屬性是否可變馅扣,則與前面說到的邏輯相同,如果屬性可變岂嗓,則返回一個(gè)新的不可變對象,即為不可變字符串厌殉,而對于不可變則直接返回self,即為不可變字符串

如果是strong器紧,則是直接對其引用楼眷,并沒有執(zhí)行copy方法,這就是區(qū)別罐柳。如果屬性緩存數(shù)組或者字典张吉,原理是一樣的。

對于block稍微有些不同,因?yàn)?code>MRC中创南,block需要顯示地copy到堆上省核,而ARC中如果引用外部變量賦值則會(huì)自動(dòng)拷貝到內(nèi)存中,所以blockARC下使用copystrong無異气忠。

對于NSString來說,作為不可變對象笔刹,修飾符為copy時(shí),執(zhí)行copy方法返回自身舌菜,strong修飾也是返回自身,所以對于NSString這樣的不可變對象來說袱瓮,使用strongcopy也是一樣的爱咬。

參考

Copying Collections

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市燎斩,隨后出現(xiàn)的幾起案子蜂绎,更是在濱河造成了極大的恐慌,老刑警劉巖怪瓶,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件践美,死亡現(xiàn)場離奇詭異,居然都是意外死亡敛滋,警方通過查閱死者的電腦和手機(jī)玫膀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來箕昭,“玉大人解阅,你說我怎么就攤上這事』醭” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵积暖,是天一觀的道長怪与。 經(jīng)常有香客問我,道長遍愿,這世上最難降的妖魔是什么耘斩? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮坞笙,結(jié)果婚禮上荚虚,老公的妹妹穿的比我還像新娘。我一直安慰自己曲管,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布腊徙。 她就那樣靜靜地躺著撬腾,像睡著了一般恢恼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天牵署,我揣著相機(jī)與錄音喧半,去河邊找鬼。 笑死挺据,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的暇检。 我是一名探鬼主播婉称,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼酿矢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瘫筐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肛捍,失蹤者是張志新(化名)和其女友劉穎之众,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缀蹄,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡膘婶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年悬襟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脊岳。...
    茶點(diǎn)故事閱讀 40,013評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垛玻,死狀恐怖奶躯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情朗儒,我是刑警寧澤参淹,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布乏悄,位于F島的核電站,受9級特大地震影響开呐,放射性物質(zhì)發(fā)生泄漏规求。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一瓦戚、第九天 我趴在偏房一處隱蔽的房頂上張望丛塌。 院中可真熱鬧较解,春花似錦赴邻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鲤桥,卻和暖如春渠概,著一層夾襖步出監(jiān)牢的瞬間嫂拴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工筒狠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箱沦,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓灶伊,卻偏偏與公主長得像寒跳,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子米辐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評論 2 355

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

  • 在Objective-C中對象之間的拷貝分為淺拷貝和深拷貝书释。說白了,對非容器類的淺拷貝就是拷貝對象的地址狸页,對象里面...
    zhaihongxia閱讀 230評論 0 0
  • 關(guān)于iOS中對象的深拷貝和淺拷貝的文章有很多检激,但是大部分都是基于打印內(nèi)存地址來推導(dǎo)結(jié)果,這篇文章是從源碼的角度來分...
    雪山飛狐_91ae閱讀 950評論 1 5
  • 詳見 1.retain:始終是淺復(fù)制齿穗。引用計(jì)數(shù)每次加一饺律。返回對象是否可變與被復(fù)制的對象保持一致。2.copy:對于...
    樂樂的熊閱讀 244評論 0 0
  • 1脖卖、對象拷貝有兩種方式:淺復(fù)制和深復(fù)制巧颈。顧名思義,淺復(fù)制砸泛,并不拷貝對象本身蛆封,僅僅是拷貝指向?qū)ο蟮闹羔樄蠢酰簧顝?fù)制是直接...
    滴答大閱讀 772評論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,103評論 1 32