iOS深淺拷貝(糾錯)

道歉

之前我的一篇關(guān)于深淺拷貝的文章,里面有諸多錯誤,主要是混淆了混淆copy荠雕、mutableCopy和深淺拷貝,給大家?guī)砹苏`導(dǎo)驶赏,這里我深表歉意炸卑。

經(jīng)過大家的指正和參考前輩的文章:http://www.cocoachina.com/bbs/read.php?tid-323045-page-1.html

以及在網(wǎng)上搜集了各種資料煤傍,我已經(jīng)刪除了原文盖文,在這里做出了更改,但是肯定還是有紕漏之處蚯姆,歡迎大家不吝賜教五续。

理解深淺拷貝

先來看看蘋果文檔的定義:

There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection.

In the case of these objects, a shallow copy means that a new collection object is created, but the contents of the original collection are not duplicated—only the object references are copied to the new container.
A deep copy duplicates the compound object as well as the contents of all of its contained objects.

再來看看stackoverfolow網(wǎng)友的總結(jié):

Shallow copies duplicate as little as possible. A shallow copy of a collection is a copy of the collection structure, not the elements. With a shallow copy, two collections now share the individual elements.

Deep copies duplicate everything. A deep copy of a collection is two collections with all of the elements in the original collection duplicated.

簡單來說:

  • 淺拷貝:只是復(fù)制容器本身,不會復(fù)制容器內(nèi)部的元素龄恋,淺拷貝后生成的新容器對象和原始容器對象共享內(nèi)部元素
  • 深拷貝:不僅復(fù)制容器本身疙驾,容器內(nèi)部的元素也會復(fù)制,深拷貝后生成的新容器對象和原始容器的內(nèi)部元素是獨立的郭毕。

理解copy和mutableCopy

  • copy:不可變拷貝,遵循NSCopying協(xié)議它碎,需要對應(yīng)實現(xiàn)copyWithZone方法
  • mutableCopy:可變拷貝,遵循NSMutableCopying協(xié)議显押,需要對應(yīng)實現(xiàn)mutableCopyWithZone:方法

系統(tǒng)容器類NSArray和NSDictonary都已經(jīng)實現(xiàn)了上述兩個協(xié)議扳肛。


糾錯一、混淆copy乘碑、mutableCopy和深淺拷貝

網(wǎng)上流傳很多的一張圖挖息,我也被誤導(dǎo)過

image

上圖得出了兩個錯誤結(jié)論:

  1. 對于不可變或者可變對象的mutableCopy操作都是深拷貝
  1. 對于可變對象的copy操作是深拷貝

這是錯的!2醭稹!

正確理解

對于不可變或者可變的容器對象的mutableCopy或者copy操作都是淺拷貝!!!

如何驗證

那么如何證明呢殖蚕?

我們先來看看深拷貝的定義:

深拷貝:不僅復(fù)制容器本身轿衔,容器內(nèi)部的元素也會復(fù)制,深拷貝后生成的新容器對象和原始容器的內(nèi)部元素是獨立的睦疫。

那么就可以觀察改變源容器的內(nèi)部元素會不會影響新生成容器的內(nèi)部元素來證明害驹,影響就說明是淺拷貝,不影響就說明是深拷貝蛤育。

1. 代碼:
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"one"],
                                [NSMutableString stringWithString:@"two"],
                                [NSMutableString stringWithString:@"three"],
                                [NSMutableString stringWithString:@"four"],
                                nil
                                ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[dataArray2 mutableCopy];
    
    mStr = dataArray2[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

2.輸出:
2016-07-31 17:40:30.702 test1[2113:169774] dataArray3:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:40:30.703 test1[2113:169774] dataArray2:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
3.結(jié)論:

通過代碼可以看到改變了原始數(shù)組dataArray2的第一個元素宛官,dataArray3的第一個元素也發(fā)生了改變葫松,說明這是淺拷貝。
其他兩種情況我就不驗證了底洗,大家可以自己驗證腋么。你會發(fā)現(xiàn)對于可變和不可變的容器對象的copy和mutablCopy操作都是淺復(fù)制,區(qū)別僅僅是生成的新對象是可變還是不可變的亥揖,copy生成不可變對象珊擂,mutableCopy生成可變對象!7驯洹4萆取!

糾錯二:淺拷貝是指針復(fù)制挚歧,深拷貝是內(nèi)容復(fù)制

同樣是流傳很廣的一張圖


image

這句話前半句是錯的扛稽,后半句是對的。

但是前半句非常迷惑人滑负,而且網(wǎng)上還有證明這句話是對的代碼在张。

如下:

    NSArray *array = @[@1];
    NSLog(@"retainCount:%ld", [array retainCount]);

    NSArray *array2 = [array copy];

    NSLog(@"retainCount:%ld", [array2 retainCount]);
    NSLog(@"內(nèi)存地址:%p---%p", array,array2);

輸出:

2016-08-04 20:25:39.830 test1[27081:776720] retainCount:1
2016-08-04 20:25:39.831 test1[27081:776720] retainCount:2
2016-08-04 20:25:39.831 test1[27081:776720] 內(nèi)存地址:0x7fa0131175c0---0x7fa0131175c0

可以看到引用計數(shù)確實增加了1,而且淺復(fù)制前后的內(nèi)存地址都是一樣的橙困,說明是同一個對象瞧掺,所以得出結(jié)論:copy操作只是一次retain操作,對指針進行了復(fù)制凡傅。

但是真的是這樣嗎辟狈?

真實的情況:

對不可變對象進行copy操作,實際上是把原始對象的的指針的值賦值給生成的新對象的指針的值夏跷,這樣兩個對象的指針的值(對象存放的內(nèi)存地址)就是相同的哼转,也就導(dǎo)致copy操作之后引用計數(shù)增加。

但是怎么證明槽华,我還沒找到辦法壹蔓,望大家賜教。

結(jié)論:

  • copy操作返回的必然是一個不可變對象猫态,無論源對象是可變對象還是不可變對象佣蓉。如果源對象是一個不可變對象,那么它們(源對象和新生成的對象)指向同一個對象亲雪,如果源對象是可變對象勇凭,它們指向不同對象。
  • mutableCopy]返回的必然是一個可變對象义辕,無論源對象是可變對象還是不可變對象虾标,它們(源對象和新生成的對象)仍指向不同地址,是兩個對象灌砖。
  • copy和mutableCopy都生成新對象

那么既然copy和mutableCopy都無法實現(xiàn)深拷貝璧函,那我們只能自己手動實現(xiàn)了傀蚌。但是這里要注意深復(fù)制還分為單層深拷貝和完全深拷貝,下面具體來看看

單層深拷貝

只會復(fù)制容器內(nèi)部的第一層對象蘸吓,對于容器內(nèi)部包含的容器的內(nèi)部對象不進行復(fù)制善炫。

代碼:

    NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:
                                 [NSMutableString stringWithString:@"1"],
                                 [NSMutableString stringWithString:@"2"],
                                 [NSMutableString stringWithString:@"3"],
                                 [NSMutableString stringWithString:@"4"],
                                 nil
                                 
                                 ];
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                 [NSMutableString stringWithString:@"one"],
                                 [NSMutableString stringWithString:@"two"],
                                 [NSMutableString stringWithString:@"three"],
                                 [NSMutableString stringWithString:@"four"],
                                 dataArray1,
                                 nil
                                 ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];
    
    mStr = dataArray2[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

輸出如下:

2016-07-31 17:45:48.472 test1[2151:173221] dataArray3:(
    one,
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:45:48.472 test1[2151:173221] dataArray2:(
    "one--ONE",
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)


可以看到dataArray3并沒有被改變,但是別高興的太早美澳,我們再來改改销部。

代碼如下:

    NSMutableArray * dataArray1=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"1"],
                                [NSMutableString stringWithString:@"2"],
                                [NSMutableString stringWithString:@"3"],
                                [NSMutableString stringWithString:@"4"],
                                nil
                                
                                ];
    NSMutableArray * dataArray2=[NSMutableArray arrayWithObjects:
                                [NSMutableString stringWithString:@"one"],
                                [NSMutableString stringWithString:@"two"],
                                [NSMutableString stringWithString:@"three"],
                                [NSMutableString stringWithString:@"four"],
                                dataArray1,
                                nil
                                ];
    
    NSMutableArray * dataArray3;
    NSMutableString * mStr;
    
    dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];
    
    NSMutableArray *mArr = (NSMutableArray *)dataArray2[4];
    mStr = mArr[0];
    [mStr appendString:@"--ONE"];
    
    NSLog(@"dataArray3:%@",dataArray3);
    NSLog(@"dataArray2:%@",dataArray2);

輸出如下:


2016-07-31 17:47:19.421 test1[2174:174714] dataArray3:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)
2016-07-31 17:47:19.421 test1[2174:174714] dataArray2:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)

可以看到深復(fù)制又失效了,這是因為dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];僅僅能進行一層深復(fù)制制跟,對于第二層容器的內(nèi)部對象或者更多層容器的內(nèi)部對象就無效了舅桩,那怎么辦呢?

這個時候我們需要完全復(fù)制

完全復(fù)制

要想對多層集合對象進行復(fù)制雨膨,我們需要進行完全復(fù)制擂涛,這里可以使用歸檔和接檔。

實現(xiàn)代碼如下:

    dataArray3 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:dataArray2]];

此時輸出如下:

2016-07-31 17:49:55.561 test1[2202:177163] dataArray3:(
    one,
    two,
    three,
    four,
        (
        1,
        2,
        3,
        4
    )
)
2016-07-31 17:49:55.562 test1[2202:177163] dataArray2:(
    one,
    two,
    three,
    four,
        (
        "1--ONE",
        2,
        3,
        4
    )
)

可以看到dataArray3沒有被dataArray2的修改影響聊记。


類復(fù)制

說完了對象的復(fù)制撒妈,我們來看看如何實現(xiàn)類的復(fù)制,因為比較簡單排监,直接放上代碼

定義類復(fù)制
#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCopying>
@property(strong,nonatomic)NSString *age;
@property(strong,nonatomic)NSString *name;
@end
#import "Person.h"
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.age = self.age;
    person.name = self.name;
    return person;
}
@end

調(diào)用
   Person *person = [[Person alloc]init];
        person.age = @"dsdsd";
        person.name = @"dsdsdddww";
        
        Person *copyPerson = [person copy];
        NSLog(@"%@-----%@",copyPerson.age, copyPerson.name);

可以看到copyPerson的兩個屬性和persona一樣狰右。


@property中的copy關(guān)鍵字

在設(shè)置NSString類型的屬性的時候,我們最好設(shè)置為copy類型舆床,這樣別人使用我們定義的屬性的時候棋蚌,他不管怎么改動該屬性的賦值,都不會影響我們給該屬性賦的值挨队,為什么呢谷暮?

下面我們來看看

image

如上圖所示,string2的屬性是copy類型盛垦,可以看到是無法被修改的湿弦。

因為此時string2和copystring的內(nèi)存地址不一樣,修改一個腾夯,不會影響另外一個颊埃。

image

上圖所示,如果string2的屬性是strong類型蝶俱,就可以被修改班利,如下圖所示:

因為此時string2和copystring的內(nèi)存地址都是一樣的,修改一個跷乐,兩個就同時被修改

copy關(guān)鍵字的NSMutableString崩潰

image

原因:

copy關(guān)鍵字的string的setter方法實際上是把參數(shù)copy之后再賦值給變量_string肥败,那么此時變量_string雖然被申明為NSMutableString趾浅,但是copy之后愕提,就把 變量_string變成了不可變的NSString類型馒稍,所以就會出現(xiàn)方法報錯,提示對不可變的NSString使用了NSMutableString的方法appendString浅侨。

參考文章:

  1. https://en.wikipedia.org/wiki/Object_copying
  2. http://www.cocoachina.com/bbs/read.php?tid-323045-page-1.html
  3. http://stackoverflow.com/questions/184710/what-is-the-difference-between-a-deep-copy-and-a-shallow-copy
  4. C++的:http://www.fredosaurus.com/notes-cpp/oop-condestructors/shallowdeepcopy.html
  5. .Net的:http://www.codeproject.com/Articles/28952/Shallow-Copy-vs-Deep-Copy-in-NET
  6. Java的:http://javapapers.com/core-java/java-clone-shallow-copy-and-deep-copy/
  7. Generic:https://secweb.cs.odu.edu/~zeil/cs361/web/website/Lectures/big3/pages/shallowvsdeep.html
  8. Objective-C:https://developer.apple.com/library/mac/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.pdf
    https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Collections/Collections.pdf
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纽谒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子如输,更是在濱河造成了極大的恐慌鼓黔,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件不见,死亡現(xiàn)場離奇詭異澳化,居然都是意外死亡,警方通過查閱死者的電腦和手機稳吮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門缎谷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人灶似,你說我怎么就攤上這事列林。” “怎么了酪惭?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵希痴,是天一觀的道長。 經(jīng)常有香客問我春感,道長砌创,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮谴返,結(jié)果婚禮上蛾扇,老公的妹妹穿的比我還像新娘。我一直安慰自己舶赔,他們只是感情好,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布谦秧。 她就那樣靜靜地躺著竟纳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪疚鲤。 梳的紋絲不亂的頭發(fā)上锥累,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機與錄音集歇,去河邊找鬼桶略。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的际歼。 我是一名探鬼主播惶翻,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鹅心!你這毒婦竟也來了吕粗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤旭愧,失蹤者是張志新(化名)和其女友劉穎颅筋,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體输枯,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡议泵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了桃熄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肢簿。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖蜻拨,靈堂內(nèi)的尸體忽然破棺而出池充,到底是詐尸還是另有隱情,我是刑警寧澤缎讼,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布收夸,位于F島的核電站,受9級特大地震影響血崭,放射性物質(zhì)發(fā)生泄漏卧惜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一夹纫、第九天 我趴在偏房一處隱蔽的房頂上張望咽瓷。 院中可真熱鬧,春花似錦舰讹、人聲如沸茅姜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽钻洒。三九已至,卻和暖如春锄开,著一層夾襖步出監(jiān)牢的瞬間素标,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工萍悴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留头遭,地道東北人寓免。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像计维,于是被迫代替她去往敵國和親再榄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

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