根據拷貝內容的不一樣,分爲深淺拷貝數組
蘋果爲何這麼設計呢?總結起來很簡單:即安全又省內存。可是要理解或者避免踩一些坑,還須要看下面的介紹安全
不得不先說到內存,又不得不說內存分區:程序底層——程序如何在RAM ROM運行,內存分配與分區app
看下面圖片:函數
obj1是定義在函數外部的全局變量,處於全局區;obj2是定義在函數內的局部變量,處於棧區。它們都指向了處於堆區的對象。測試
obj1與obj2是指針,它們指向的對象是內容,那麼如今再看深淺拷貝的現象,或者說執行的結果:淺拷貝只是多個指針指向同一對象內容,深拷貝就是每一個指針都指向了一個對象內容,互不影響。atom
自定義對象須要本身實現NSCoping協議,通常狀況下,自定義對象都是可變對象,本節討論的也都是針對系統對象
指針也是會存在堆區的,好比在block裏面咱們知道,若是指針使用了__block修飾,那麼指針會存放在堆區。spa
不管是集合對象仍是非集合對象,在收到copy和mutableCopy消息時,都遵照如下規則:線程
那麼很簡單,可變與不可變對象的轉變:設計
系統提供的集合類型,好比字典、數組、NSSet等集合類型內存基本都是以下結構:集合內存結構圖指針
咱們能夠上面代碼(代碼處於方法內)作個分析,加深對內存的理解。@"123"、@"456"
是const屬性,所以處於常量區,指針str一、str二、arr
局部變量指針處於棧區,@[]
數組內容存放位置處於堆區,數組裏面的內容存放的是指針str1與str2,固然處於堆區
其實arr = @[str1,str2]
至關於[arr addObject:str1];[arr addObject:str2];
,數組裏面有兩個強指針指向了對象@"123"
與@"456"
。
圖中只是字符串是常量因此在常量區,若是他們是NSDate、UIView等等則會處於堆區
下面的分析也是基於三種程度的拷貝,記爲CopyLevel,拷貝層次,簡寫CL一、CL二、CL3
毫無疑問,CL1是確定會進行的。重點就在於CL2於CL3.
下面代碼,不可變集合arrM1的copy與mutableCopy。arrM2:mutableCopy,arr:copy
下面代碼,可變集合arrM1的copy與mutableCopy。arrM2:mutableCopy,arr:copy
咱們知道,對於非集合對象,有以下結論:
// 不可變,線程安全 [immutableObject copy] // 淺複製 [immutableObject mutableCopy] // 深複製,對於集合則是隻拷⻉貝數組的內容,數組的內容是指針,而指針的內容不會被拷⻉ // 可變對象,線程不安全 [mutableObject copy] //深複製,對於集合則是隻拷⻉貝數組的內容,數組的內容是指針,而指針的內容不會被拷⻉ [mutableObject mutableCopy] //深複製,對於集合則是隻拷⻉貝數組的內容,數組的內容是指針,而指針的內容不會被拷⻉
咱們須要使用- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
方法,且flag爲YES。
能夠看到,三行打印結果都不同,即發生了CL3層的拷貝。
此方法執行後,arrM1集合裏的每一個對象都會收到 copyWithZone: 消息。若是集合裏的對象遵循 NSCopying 協議,那麼對象就會被深拷貝到新的集合,若是沒有遵循就直接崩潰了。
等一等,好像有另外一個問題:此方法只是會給集合的每一個對象發送copyWithZone:
方法,那麼對於不可變對象,copyWithZone:
的執行仍是淺拷貝。讀者大概也注意到了,圖中示例代碼,arrM1數組存的也是可變對象dict1,因此有CL3層的拷貝。那若是arrM1存的不是可變對象呢?結果就是沒有CL3層的拷貝,你們能夠用代碼測試下!
爲啥叫單層深複製呢? 由於它只給arrM1數組存的對象發送了copyWithZone:
方法,而沒有對dict1發送copyWithZone:
方法,dict1也是集合,它裏面也存放着對象呢。。。即集合裏面存放的集合。。。好繞,哈哈
另外,除了此方法,集合的解檔歸檔,也是能夠實現單層深拷貝的。
繞的東西就到這裏,下面看些感興趣的東西:
有一點須要注意了:copy返回值爲不可變對象,若是使用可變對象的接口就會crash。例如:
- (void)arrMCopyTest { NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"123",@"456", nil]; NSMutableArray *arr = [arrM copy]; // 下面代碼崩潰 [arr addObject:@"789"]; }
[arrM copy];
返回的是不可變類型,即NSArray,向一個NSArray對象發送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]
@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的使用、實現底層、循環引用、存儲位置
打印結果:
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方法裏面作一些自定義操做,若是_
方式則這些自定義操做就不會被執行。並且在block裏面使用_
方式訪問變量會更隱蔽的引發循環引用的問題!
@property (nonatomic, copy) NSString *str; - (void)setStr:(NSString *)str { // _str = str; 不要這樣寫 _str = [str copy]; }
講了這些,你們會不會猛然想到
@property (nonatomic, weak) id delegate; _delegate = obj;
這樣會不會形成_delegate
爲指向的對象引用計數爲0時,系統還會不會將_delegate
置爲nil?答案是,您多慮了,會的。這和copy不同。爲啥不同?牽涉到runtime哈希表什麼的就不在展開了。。。