根據(jù)拷貝內(nèi)容的不同春叫,分為深淺拷貝
深拷貝:指針賦值,且內(nèi)容拷貝
淺拷貝:只是簡(jiǎn)單的指針賦值
蘋(píng)果為什么這么設(shè)計(jì)呢泣港?總結(jié)起來(lái)很簡(jiǎn)單:即安全又省內(nèi)存暂殖。但是要理解或者避免踩一些坑,還需要看下面的介紹
內(nèi)存
不得不先說(shuō)到內(nèi)存当纱,又不得不說(shuō)內(nèi)存分區(qū):程序底層——程序如何在RAM ROM運(yùn)行呛每,內(nèi)存分配與分區(qū)
看下面圖片:
obj1是定義在函數(shù)外部的全局變量,處于全局區(qū)坡氯;obj2是定義在函數(shù)內(nèi)的局部變量晨横,處于棧區(qū)。它們都指向了處于堆區(qū)的對(duì)象廉沮。
obj1與obj2是指針颓遏,它們指向的對(duì)象是內(nèi)容,那么現(xiàn)在再看深淺拷貝的現(xiàn)象滞时,或者說(shuō)執(zhí)行的結(jié)果:淺拷貝只是多個(gè)指針指向同一對(duì)象內(nèi)容叁幢,深拷貝就是每個(gè)指針都指向了一個(gè)對(duì)象內(nèi)容,互不影響坪稽。
自定義對(duì)象需要自己實(shí)現(xiàn)NSCoping協(xié)議曼玩,一般情況下,自定義對(duì)象都是可變對(duì)象窒百,本節(jié)討論的也都是針對(duì)系統(tǒng)對(duì)象
指針也是會(huì)存在堆區(qū)的黍判,比如在block里面我們知道,如果指針使用了__block修飾篙梢,那么指針會(huì)存放在堆區(qū)顷帖。
返回值的一些基本規(guī)則
無(wú)論是集合對(duì)象還是非集合對(duì)象,在收到copy和mutableCopy消息時(shí),都遵守以下規(guī)則:
1 copy返回immutable對(duì)象贬墩;
2 mutableCopy返回mutable對(duì)象榴嗅;
那么很簡(jiǎn)單,可變與不可變對(duì)象的轉(zhuǎn)變:
不可變對(duì)象→可變對(duì)象的轉(zhuǎn)換:不可變對(duì)象.mutableCopy陶舞。
可變->不可變:可變對(duì)象.copy;
集合拷貝
系統(tǒng)提供的集合類型嗽测,比如字典、數(shù)組肿孵、NSSet等集合類型內(nèi)存基本都是如下結(jié)構(gòu):集合內(nèi)存結(jié)構(gòu)圖
我們可以上面代碼(代碼處于方法內(nèi))做個(gè)分析唠粥,加深對(duì)內(nèi)存的理解。@"123"停做、@"456"是const屬性晤愧,因此處于常量區(qū),指針str1蛉腌、str2养涮、arr局部變量指針處于棧區(qū),@[]數(shù)組內(nèi)容存放位置處于堆區(qū),數(shù)組里面的內(nèi)容存放的是指針str1與str2眉抬,當(dāng)然處于堆區(qū)
其實(shí)arr = @[str1,str2]相當(dāng)于[arr addObject:str1];[arr addObject:str2];,數(shù)組里面有兩個(gè)強(qiáng)指針指向了對(duì)象@"123"與@"456"懈凹。
圖中只是字符串是常量所以在常量區(qū)蜀变,如果他們是NSDate、UIView等等則會(huì)處于堆區(qū)
下面的分析也是基于三種程度的拷貝介评,記為CopyLevel库北,拷貝層次,簡(jiǎn)寫(xiě)CL1们陆、CL2寒瓦、CL3
CL1:arr數(shù)組指針,如果只發(fā)生這層拷貝坪仇,則和非集合對(duì)象一樣杂腰,是淺拷貝
CL2:arr數(shù)組指針指向的的內(nèi)容,即存儲(chǔ)的對(duì)象指針椅文。發(fā)生本層拷貝喂很,從非集合角度來(lái)說(shuō)已經(jīng)發(fā)生了內(nèi)容拷貝,即深拷貝皆刺。但從集合角度來(lái)說(shuō)少辣,還是淺拷貝。
CL3:arr數(shù)組里面存儲(chǔ)的指針指向的內(nèi)容羡蛾,如果發(fā)生本層拷貝漓帅,可以叫做集合的單層深拷貝。
毫無(wú)疑問(wèn),CL1是肯定會(huì)進(jìn)行的忙干。重點(diǎn)就在于CL2于CL3.
不可變集合的copy與mutableCopy
下面代碼器予,不可變集合arrM1的copy與mutableCopy。arrM2:mutableCopy豪直,arr:copy
根據(jù)第一行打印結(jié)果:arrM2和arr都進(jìn)行CL1拷貝
第二行打印結(jié)果:arrM2與arrM1結(jié)果不同劣摇,說(shuō)明進(jìn)行了數(shù)組拷貝;arr與arrM1結(jié)果相同弓乙,說(shuō)明沒(méi)有末融,進(jìn)行數(shù)組拷貝
第三行打印結(jié)果:都相同,說(shuō)明指向的內(nèi)容沒(méi)有發(fā)生拷貝
可變集合的copy與mutableCopy
下面代碼暇韧,可變集合arrM1的copy與mutableCopy勾习。arrM2:mutableCopy,arr:copy
根據(jù)第一行打印結(jié)果:arrM2和arr都進(jìn)行CL1拷貝
第二行打印結(jié)果:結(jié)果均不同懈玻,說(shuō)明都進(jìn)行了數(shù)組拷貝巧婶;
第三行打印結(jié)果:都相同,說(shuō)明指向的內(nèi)容沒(méi)有發(fā)生拷貝
一般結(jié)論
我們知道涂乌,對(duì)于非集合對(duì)象艺栈,有如下結(jié)論:
// 不可變,線程安全
[immutableObject copy] // 淺復(fù)制
[immutableObject mutableCopy] // 深復(fù)制湾盒,對(duì)于集合則是只拷?貝數(shù)組的內(nèi)容湿右,數(shù)組的內(nèi)容是指針,而指針的內(nèi)容不會(huì)被拷?
// 可變對(duì)象罚勾,線程不安全
[mutableObject copy] //深復(fù)制毅人,對(duì)于集合則是只拷?貝數(shù)組的內(nèi)容,數(shù)組的內(nèi)容是指針尖殃,而指針的內(nèi)容不會(huì)被拷?
[mutableObject mutableCopy] //深復(fù)制丈莺,對(duì)于集合則是只拷?貝數(shù)組的內(nèi)容,數(shù)組的內(nèi)容是指針送丰,而指針的內(nèi)容不會(huì)被拷?
集合的單層深拷貝缔俄,CL3層的拷貝(one-level-deep copy)
我們需要使用- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;方法,且flag為YES蚪战。
可以看到牵现,三行打印結(jié)果都不一樣,即發(fā)生了CL3層的拷貝邀桑。
此方法執(zhí)行后瞎疼,arrM1集合里的每個(gè)對(duì)象都會(huì)收到 copyWithZone: 消息。如果集合里的對(duì)象遵循 NSCopying 協(xié)議壁畸,那么對(duì)象就會(huì)被深拷貝到新的集合贼急,如果沒(méi)有遵循就直接崩潰了茅茂。
等一等,好像有另一個(gè)問(wèn)題:此方法只是會(huì)給集合的每個(gè)對(duì)象發(fā)送copyWithZone:方法太抓,那么對(duì)于不可變對(duì)象空闲,copyWithZone:的執(zhí)行還是淺拷貝。讀者大概也注意到了走敌,圖中示例代碼碴倾,arrM1數(shù)組存的也是可變對(duì)象dict1,所以有CL3層的拷貝掉丽。那如果arrM1存的不是可變對(duì)象呢跌榔?結(jié)果就是沒(méi)有CL3層的拷貝,大家可以用代碼測(cè)試下捶障!
為啥叫單層深復(fù)制呢僧须? 因?yàn)樗唤oarrM1數(shù)組存的對(duì)象發(fā)送了copyWithZone:方法,而沒(méi)有對(duì)dict1發(fā)送copyWithZone:方法项炼,dict1也是集合担平,它里面也存放著對(duì)象呢。锭部。暂论。即集合里面存放的集合。拌禾。空另。好繞,哈哈
另外蹋砚,除了此方法,集合的解檔歸檔摄杂,也是可以實(shí)現(xiàn)單層深拷貝的坝咐。
繞的東西就到這里,下面看些感興趣的東西:
一些坑
Mutable變copy的坑
有一點(diǎn)需要注意了:copy返回值為不可變對(duì)象析恢,如果使用可變對(duì)象的接口就會(huì)crash墨坚。例如:
- (void)arrMCopyTest {
NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"123",@"456", nil];
NSMutableArray *arr = [arrM copy];
// 下面代碼崩潰
[arr addObject:@"789"];
}
[arrM copy];返回的是不可變類型,即NSArray映挂,向一個(gè)NSArray對(duì)象發(fā)送addObject消息當(dāng)然方法找不到崩潰泽篮。
另一個(gè)問(wèn)題,arr是NSMutableArray類型柑船,它指向父類NSArray編譯器為什么不報(bào)錯(cuò)呢帽撑?copy返回的是id類型,編譯器不會(huì)對(duì)id(俗稱萬(wàn)能指針)進(jìn)行類型檢查鞍时,所以會(huì)經(jīng)晨骼看到推薦使用instancetype扣蜻,而不是id
下面的類似錯(cuò)誤就很常見(jiàn)了:
@property (nonatomic, copy) NSMutableArray *arr;
- (void)arrMCopyTest {
NSMutableArray *arrM = [NSMutableArray arrayWithObjects:@"123",@"456", nil];
self.arr = arrM;
// 下面代碼崩潰
[self.arr addObject:@"789"];
}
因?yàn)閟elf.arr為copy修飾备典,那么self.arr = arrM就相當(dāng)于_arr = [arrM copy]
屬性指定為copy冬三,卻沒(méi)有被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里面對(duì)str進(jìn)行操作,居然沒(méi)有對(duì)它進(jìn)行__block修飾1烀s狭拧芳肌!感興趣可以看看這篇博客:iOS中block的使用、實(shí)現(xiàn)底層肋层、循環(huán)引用亿笤、存儲(chǔ)位置
打印結(jié)果:
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;值就不會(huì)改變了责嚷。因?yàn)橄喈?dāng)于_str = [str copy];。
所以建議除了在初始化時(shí)(init方法中)掂铐,蘋(píng)果推薦我們使用_下劃線的方式直接訪問(wèn)變量罕拂,其它地方盡量使用self.來(lái)訪問(wèn)。另外我們還經(jīng)常getter或者setter方法里面做一些自定義操作全陨,如果_方式則這些自定義操作就不會(huì)被執(zhí)行爆班。而且在block里面使用_方式訪問(wèn)變量會(huì)更隱蔽的引起循環(huán)引用的問(wèn)題!
setter方法
@property (nonatomic, copy) NSString *str;
- (void)setStr:(NSString *)str {
// _str = str; 不要這樣寫(xiě)
_str = [str copy];
}
講了這些辱姨,大家會(huì)不會(huì)猛然想到
@property (nonatomic, weak) id delegate;
_delegate = obj;
這樣會(huì)不會(huì)造成_delegate為指向的對(duì)象引用計(jì)數(shù)為0時(shí)柿菩,系統(tǒng)還會(huì)不會(huì)將_delegate置為nil?答案是雨涛,您多慮了枢舶,會(huì)的。這和copy不一樣替久。為啥不一樣凉泄?牽涉到runtime哈希表什么的就不在展開(kāi)了。蚯根。后众。