版本 | 時間 |
---|---|
V1.0 | 2019-01-20 |
前言
今天一個朋友跟我討論一下這個深拷貝
和淺拷貝
的話題,然后感覺自己說的也不是很清楚,所以又特地找了一些資料城丧,讓自己再了解學習一下。
正文
概念
淺拷貝
淺拷貝
指的是復制引用對象的指針豌鹤,而不是復制引用對象本身亡哄,拷貝出來的對象指針和引用對象的指針指向同一塊內存地址,當這塊內存地址銷毀的時候傍药,指向這塊內存地址的所有指針也需要重新定義磺平,不然就會造成野指針錯誤魂仍。
深拷貝
深拷貝
指的是復制引用對象的內容拐辽,也就是說內容的所占有的內存地址需要重新分配拣挪,復制完之后,內存中的值是完全相同的俱诸,但是內存地址是不一樣的菠劝,拷貝得到的對象和引用對象之間是互不影響,也互不干擾睁搭。
應用場景
淺拷貝場景
1.使用retain
操作赶诊,返回的對象是否可變與被拷貝對象保持一致,在iOS
中园骆,使用retain
關鍵字進行引用計數舔痪,這是一種更加保險的淺拷貝;
2.對于不可變對象進行copy
操作的是淺拷貝,引用計數每次+1
;
深拷貝場景
1.對可變對象進行copy
操作锌唾,引用計數不改變
2.使用mutableCopy
操作的始終是深拷貝锄码,引用計數不改變,返回的是一個可變對象晌涕。
代碼驗證
代碼一
NSArray *list = [[NSArray alloc] initWithObjects:@"1",@"2",@"3",@"4", nil];
NSLog(@"\n被拷貝對象的內存地址:%p",list);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSArray *listCopy = [list copy];
NSLog(@"\n拷貝對象的內存地址:%p",listCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSMutableArray *listMutableCopy = [list copy];
NSLog(@"\n拷貝對象的內存地址:%p",listMutableCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSMutableArray *mutablelistMutableCopy = [list mutableCopy];
NSLog(@"\n拷貝對象的內存地址:%p",mutablelistMutableCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
打印結果:
被拷貝對象的內存地址:0x600000aca370
====被拷貝對象的引用計數:1
拷貝對象的內存地址:0x600000aca370
====被拷貝對象的引用計數:2
拷貝對象的內存地址:0x600000aca370
====被拷貝對象的引用計數:3
拷貝對象的內存地址:0x600000ad75d0
====被拷貝對象的引用計數:3
由此可見對NSArray
進行copy
操作滋捶,返回的地址是一樣的,每次進行copy
操作時余黎,被拷貝對象的引用計數都會+1
重窟,是淺拷貝
,而對NSArray
進行mutableCopy
操作惧财,返回的地址是不一樣的巡扇,被拷貝對象的引用計數沒有發(fā)生變化,是深拷貝
。
代碼二
NSMutableArray *list = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3",@"4", nil];
NSLog(@"\n被拷貝對象的內存地址:%p",list);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSArray *listCopy = [list copy];
NSLog(@"\n拷貝對象的內存地址:%p",listCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSMutableArray *listMutableCopy = [list copy];
NSLog(@"\n拷貝對象的內存地址:%p",listMutableCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
NSMutableArray *mutablelistMutableCopy = [list mutableCopy];
NSLog(@"\n拷貝對象的內存地址:%p",mutablelistMutableCopy);
NSLog(@"\n====被拷貝對象的引用計數:%ld",CFGetRetainCount((__bridge CFTypeRef)list));
打印結果:
被拷貝對象的內存地址:0x600002d7d1d0
====被拷貝對象的引用計數:1
拷貝對象的內存地址:0x600002d78600
====被拷貝對象的引用計數:1
拷貝對象的內存地址:0x600002d786c0
====被拷貝對象的引用計數:1
拷貝對象的內存地址:0x600002d78690
====被拷貝對象的引用計數:1
由此可見對NSMutableArray
無論是進行copy
還是mutableCopy
操作垮衷,返回的地址都是不一樣的厅翔,都新開辟了新的內存空間,并且被拷貝對象的引用計數不變帘靡,屬于是深拷貝
知给。
得到的結論: 對不可變對象
進行copy
,是淺拷貝
描姚,該對象引用計數會+1
;對可變對象
進行無論是進行copy
或者mutableCopy
涩赢,則是深拷貝
,引用計數不變轩勘。
深拷貝筒扒、淺拷貝的實現
如果需要實現自定義對象的拷貝,那么該自定義對象的類必須實現NSCopying
或者NSMutableCopy
協(xié)議绊寻,并且實現copyWithZone
或者mutableCopyWithZone
方法花墩。
主要用到的兩個方法:
- (id)copyWithZone:(nullable NSZone *)zone;
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
注意:如果一個自定義對象中需要實現NSCopying
或者NSMutableCopy
的話悬秉,那么這個自定義對象中如果包含其他的自定義對象的話,那么其他的自定義對象也需要實現copy
或者mutableCopy
協(xié)議冰蘑。
面試相關
野指針
野指針
表示的是不為NULL
指針和泌,指向不可用內存的指針(也就是說內存已經被釋放或者壓根不存在的指針)。
野指針
常見定位方式:
1.通過多次重現問題祠肥,進行近一步的有效信息武氓,獲取發(fā)生問題的大概位置;
2.通過Xcode
提供的Malloc Scribble
對已釋放內存進行數據填充仇箱,從而保證野指針訪問必然Crash县恕;
3.通過Zombie Objects
將已釋放的對象標記成Zombie
對象,再次對該對象發(fā)送消息時剂桥,會發(fā)生Crash并且輸出相關的調用信息忠烛。
copy
、mutableCopy
和retain
的關系
copy
對于可變對象
來說是深拷貝
权逗,引用計數不會變化
美尸,而對于不可變對象
來說是淺拷貝
,引用計數每次都會+1
旬迹。
mutableCopy
始終是深拷貝
火惊,引用計數不會發(fā)生變換
,始終返回的是一個可變對象奔垦。
retain
始終是淺拷貝
,引用計數每次都會+1
屹耐。
block
為什么需要使用copy
來修飾
block
通過存儲方式可以分為棧區(qū)block
,堆區(qū)block
,全局block
。block
在MRC
環(huán)境下常用copy
來修飾椿猎,在MRC
中惶岭,block
的內存地址顯示在棧區(qū),棧區(qū)的特點是創(chuàng)建的對象隨時可能會被系統(tǒng)銷毀犯眠,一旦被銷毀后再次調用按灶,可能會造成程序Crash,對block
進行copy
后,block會被存放在堆區(qū)筐咧,存放在堆上的block
,也就有了引用計數鸯旁,后續(xù)的復制操作都不會真的執(zhí)行復制,而是增加block
對象的引用計數量蕊,這樣block
就不會被系統(tǒng)銷毀铺罢,而是需要開發(fā)者釋放了(在MRC
中引用計數需要手動管理,在ARC
中引用計數可以通過系統(tǒng)管理)残炮。在ARC
中Block
都會在堆區(qū)韭赘,系統(tǒng)會默認對block
進行copy
操作。
String
用copy
和strong
的區(qū)別
字符串用copy
是將字符串本身拷貝一份势就,在內存中存在兩個字符串泉瞻,但是內存地址不一樣;
字符串用strong
是將字符串的指針拷貝一份脉漏,在內存中只有一個字符串,且內存地址是一樣的;
如果需要修改字符串類容的話袖牙,那么需要使用strong
來聲明侧巨,用copy
來修飾的字符串不會有變化;
開發(fā)過程中如果使用的字符串是獨立的話,建議使用copy
贼陶,內存中會copy
一個新的內容出來刃泡,不會收到其他賦值的改變巧娱。
總結
深拷貝
和淺拷貝
的本質區(qū)別就是內存地址是否相同碉怔。
淺拷貝的引用對象和拷貝得到的對象就好像是一個人和他的影子,這個人掛了禁添,他的影子也就沒了
深拷貝的引用對象和拷貝得到的對象就好像是一個人和他的克隆人撮胧,這個人掛了,但是克隆人還是可以活著的老翘。