集合深淺拷貝以及經常遇到的坑

[TOC]

引言

根據(jù)拷貝內容的不同江兢,分為深淺拷貝

  • 深拷貝:內容拷貝是晨,且將指針指向新的內容
  • 淺拷貝:只是簡單的指針賦值

蘋果為什么這么設計呢窍霞?總結起來很簡單:即安全又省內存犁嗅。但是要理解或者避免踩一些坑,還需要看下面的介紹

內存

不得不先說到內存缔赠,又不得不說內存分區(qū):程序底層——程序如何在RAM ROM運行衍锚,內存分配與分區(qū)

看下面圖片:

memoryZone

obj1是定義在函數(shù)外部的全局變量,處于全局區(qū)嗤堰;obj2是定義在函數(shù)內的局部變量构拳,處于棧區(qū)。它們都指向了處于堆區(qū)的對象梁棠。

obj1與obj2是指針,它們指向的對象是內容斗埂,那么現(xiàn)在再看深淺拷貝的現(xiàn)象符糊,或者說執(zhí)行的結果:淺拷貝只是多個指針指向同一對象內容,深拷貝就是每個指針都指向了一個對象內容呛凶,互不影響男娄。

自定義對象需要自己實現(xiàn)NSCoping協(xié)議,一般情況下漾稀,自定義對象都是可變對象模闲,本節(jié)討論的也都是針對系統(tǒng)對象
指針也是會存在堆區(qū)的,比如在block里面我們知道崭捍,如果指針使用了__block修飾尸折,那么指針會存放在堆區(qū)。

返回值的一些基本規(guī)則

無論是集合對象還是非集合對象殷蛇,在收到copy和mutableCopy消息時实夹,都遵守以下規(guī)則:

  • 1 copy返回immutable對象橄浓;
  • 2 mutableCopy返回mutable對象;

那么很簡單亮航,可變與不可變對象的轉變:

  • 不可變對象→可變對象的轉換:不可變對象.mutableCopy荸实。
  • 可變->不可變:可變對象.copy;

集合拷貝

系統(tǒng)提供的集合類型,比如字典缴淋、數(shù)組准给、NSSet等集合類型內存基本都是如下結構:集合內存結構圖

arrMemory

我們可以上面代碼(代碼處于方法內)做個分析,加深對內存的理解重抖。@"123"露氮、@"456"是const屬性,因此處于常量區(qū)仇哆,指針str1沦辙、str2、arr局部變量指針處于棧區(qū),@[]數(shù)組內容存放位置處于堆區(qū)讹剔,數(shù)組里面的內容存放的是指針str1與str2油讯,當然處于堆區(qū)

其實arr = @[str1,str2]相當于[arr addObject:str1];[arr addObject:str2];,數(shù)組里面有兩個強指針指向了對象@"123"@"456"延欠。

圖中只是字符串是常量所以在常量區(qū)陌兑,如果他們是NSDate、UIView等等則會處于堆區(qū)

下面的分析也是基于三種程度的拷貝由捎,記為CopyLevel兔综,拷貝層次,簡寫CL1狞玛、CL2软驰、CL3

  • CL1:arr數(shù)組指針,如果只發(fā)生這層拷貝心肪,則和非集合對象一樣锭亏,是淺拷貝
  • CL2:arr數(shù)組指針指向的的內容,即存儲的對象指針硬鞍。發(fā)生本層拷貝慧瘤,從非集合角度來說已經發(fā)生了內容拷貝,即深拷貝固该。但從集合角度來說锅减,還是淺拷貝。
  • CL3:arr數(shù)組里面存儲的指針指向的內容伐坏,如果發(fā)生本層拷貝怔匣,可以叫做集合的單層深拷貝。

毫無疑問著淆,CL1是肯定會進行的劫狠。重點就在于CL2于CL3.

不可變集合的copy與mutableCopy

下面代碼拴疤,不可變集合arrM1的copy與mutableCopy。arrM2:mutableCopy独泞,arr:copy

arrTwoCopy
  • 根據(jù)第一行打印結果:arrM2和arr都進行CL1拷貝
  • 第二行打印結果:arrM2與arrM1結果不同呐矾,說明進行了數(shù)組拷貝;arr與arrM1結果相同懦砂,說明沒有蜒犯,進行數(shù)組拷貝
  • 第三行打印結果:都相同,說明指向的內容沒有發(fā)生拷貝

可變集合的copy與mutableCopy

下面代碼荞膘,可變集合arrM1的copy與mutableCopy罚随。arrM2:mutableCopy,arr:copy

arrMTwoCopy
  • 根據(jù)第一行打印結果:arrM2和arr都進行CL1拷貝
  • 第二行打印結果:結果均不同羽资,說明都進行了數(shù)組拷貝淘菩;
  • 第三行打印結果:都相同,說明指向的內容沒有發(fā)生拷貝

一般結論

我們知道屠升,對于非集合對象潮改,有如下結論:

// 不可變,線程安全
[immutableObject copy] // 淺復制
[immutableObject mutableCopy] // 深復制腹暖,對于集合則是只拷?貝數(shù)組的內容汇在,數(shù)組的內容是指針,而指針的內容不會被拷?

// 可變對象脏答,線程不安全
[mutableObject copy] //深復制糕殉,對于集合則是只拷?貝數(shù)組的內容,數(shù)組的內容是指針殖告,而指針的內容不會被拷?
[mutableObject mutableCopy] //深復制阿蝶,對于集合則是只拷?貝數(shù)組的內容,數(shù)組的內容是指針黄绩,而指針的內容不會被拷?

集合的單層深拷貝赡磅,CL3層的拷貝(one-level-deep copy)

我們需要使用- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;方法,且flag為YES宝与。

arrOneLevelCopy

可以看到,三行打印結果都不一樣冶匹,即發(fā)生了CL3層的拷貝习劫。

此方法執(zhí)行后,arrM1集合里的每個對象都會收到 copyWithZone: 消息嚼隘。如果集合里的對象遵循 NSCopying 協(xié)議诽里,那么對象就會被深拷貝到新的集合,如果沒有遵循就直接崩潰了飞蛹。

等一等谤狡,好像有另一個問題:此方法只是會給集合的每個對象發(fā)送copyWithZone:方法灸眼,那么對于不可變對象,copyWithZone:的執(zhí)行還是淺拷貝墓懂。讀者大概也注意到了焰宣,圖中示例代碼,arrM1數(shù)組存的也是可變對象dict1捕仔,所以有CL3層的拷貝匕积。那如果arrM1存的不是可變對象呢?結果就是沒有CL3層的拷貝榜跌,大家可以用代碼測試下闪唆!

為啥叫單層深復制呢? 因為它只給arrM1數(shù)組存的對象發(fā)送了copyWithZone:方法钓葫,而沒有對dict1發(fā)送copyWithZone:方法悄蕾,dict1也是集合,它里面也存放著對象呢础浮。帆调。。即集合里面存放的集合霸旗。贷帮。。好繞诱告,哈哈

另外撵枢,除了此方法,集合的解檔歸檔精居,也是可以實現(xiàn)單層深拷貝的锄禽。

繞的東西就到這里,下面看些感興趣的東西:

一些坑

  • Mutable變copy的坑

有一點需要注意了:copy返回值為不可變對象靴姿,如果使用可變對象的接口就會crash沃但。例如:

- (void)arrMCopyTest {
    NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"123",@"456", nil];
    NSMutableArray *arr = [arrM copy];
    // 下面代碼崩潰
    [arr addObject:@"789"];
}

[arrM copy];返回的是不可變類型,即NSArray佛吓,向一個NSArray對象發(fā)送addObject消息當然方法找不到崩潰宵晚。

另一個問題,arr是NSMutableArray類型维雇,它指向父類NSArray編譯器為什么不報錯呢淤刃?copy返回的是id類型,編譯器不會對id(俗稱萬能指針)進行類型檢查吱型,所以會經骋菁郑看到推薦使用instancetype,而不是id

下面的類似錯誤就很常見了:

@property (nonatomic, copy) NSMutableArray *arr;

- (void)arrMCopyTest {
    NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"123",@"456", nil];
    self.arr = arrM;
    // 下面代碼崩潰
    [self.arr addObject:@"789"];
}

因為self.arr為copy修飾,那么self.arr = arrM就相當于_arr = [arrM copy]

  • 屬性指定為copy铝侵,卻沒有被copy
@property (nonatomic, copy) NSString *str;
- (void)viewDidLoad {
    NSMutableString *str = [NSMutableString stringWithFormat:@"123"];
    // self.str = str;
    _str = str;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
        [str appendString:@"456"];
        NSLog(@"change");
    });
}

這里在block里面對str進行操作灼伤,居然沒有對它進行__block修飾!_湎省狐赡!感興趣可以看看這篇博客:iOS中block的使用、實現(xiàn)底層嗜诀、循環(huán)引用猾警、存儲位置

打印結果:

2017-07-23 00:33:06.344 CopyTest[95611:31912803] 123
2017-07-23 00:33:07.518 CopyTest[95611:31912803] change
2017-07-23 00:33:08.636 CopyTest[95611:31912803] 123456

都是123456,self.str被意外改變了隆敢,如果將代碼_str = str;-->self.str = str;值就不會改變了发皿。因為相當于_str = [str copy];

所以建議除了在初始化和釋放時(init拂蝎、dealloc方法中穴墅,懶加載還是使用self.),蘋果推薦我們使用_下劃線的方式直接訪問變量温自,其它地方盡量使用self.來訪問玄货。另外我們還經常getter或者setter方法里面做一些自定義操作,如果_方式則這些自定義操作就不會被執(zhí)行悼泌。而且在block里面使用_方式訪問變量會更隱蔽的引起循環(huán)引用的問題松捉!

  • setter方法
@property (nonatomic, copy) NSString *str;

- (void)setStr:(NSString *)str {
    // _str = str; 不要這樣寫
    _str = [str copy];
}

講了這些,大家會不會猛然想到

@property (nonatomic, weak) id delegate;
_delegate = obj;

這樣會不會造成_delegate為指向的對象引用計數(shù)為0時馆里,系統(tǒng)還會不會將_delegate置為nil隘世?答案是,您多慮了鸠踪,會的丙者。這和copy不一樣。為啥不一樣营密?牽涉到runtime哈希表什么的就不在展開了械媒。。评汰。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末纷捞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子被去,更是在濱河造成了極大的恐慌兰绣,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件编振,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機踪央,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門臀玄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人畅蹂,你說我怎么就攤上這事健无。” “怎么了液斜?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵累贤,是天一觀的道長。 經常有香客問我少漆,道長臼膏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任示损,我火速辦了婚禮渗磅,結果婚禮上,老公的妹妹穿的比我還像新娘检访。我一直安慰自己始鱼,他們只是感情好,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布脆贵。 她就那樣靜靜地躺著医清,像睡著了一般。 火紅的嫁衣襯著肌膚如雪卖氨。 梳的紋絲不亂的頭發(fā)上会烙,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天,我揣著相機與錄音双泪,去河邊找鬼持搜。 笑死,一個胖子當著我的面吹牛焙矛,可吹牛的內容都是我干的葫盼。 我是一名探鬼主播,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼村斟,長吁一口氣:“原來是場噩夢啊……” “哼贫导!你這毒婦竟也來了?” 一聲冷哼從身側響起蟆盹,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤孩灯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后逾滥,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峰档,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了讥巡。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掀亩。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖欢顷,靈堂內的尸體忽然破棺而出槽棍,到底是詐尸還是另有隱情,我是刑警寧澤抬驴,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布炼七,位于F島的核電站,受9級特大地震影響布持,放射性物質發(fā)生泄漏豌拙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一鳖链、第九天 我趴在偏房一處隱蔽的房頂上張望姆蘸。 院中可真熱鬧,春花似錦芙委、人聲如沸逞敷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽推捐。三九已至,卻和暖如春侧啼,著一層夾襖步出監(jiān)牢的瞬間牛柒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工痊乾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留皮壁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓哪审,卻偏偏與公主長得像蛾魄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子湿滓,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容