陰差陽錯伏伯,前兩天和一個小伙伴在一起聊天。聊到關(guān)于 copy
和 strong
的問題。這個在ARC[Automatic Reference Counting)]
下慢慢淡化的一個東東。交流之中讓我受益匪淺,原來 copy
和 strong
還可以這么玩拗窃。
以下內(nèi)容在 demo 中均有體現(xiàn)
1.首先我們先看一下到底出現(xiàn)了什么問題
使用 copy 修飾這個可變數(shù)組
@property (copy, nonatomic) NSMutableArray *copAry;
// 直接崩潰測試
- (void)testCash {
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
self.copAry = arr;
[self.copAry removeObject:@1]; // 直接崩潰
NSLog(@"self.copyAry = %@", self.copAry);
}
報錯reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'
那么為什么使用 strong 來修飾就不會有這個問題呢。
我們來看一個例子:
-
定義一個
CJPerson
類泌辫,.m 中先不實(shí)現(xiàn)setterr
方法
在控制器看一下打印結(jié)果
NSMutableArray *names = [@[@"zhangsan"] mutableCopy];
CJPerson *person = [[CJPerson alloc] init];
person.sAry = names;// strong
person.cAry = names;// copy
[names addObject:@"lisi"];
NSLog(@"sAry = %@, cAry = %@", person.sAry, person.cAry);
/* 輸出結(jié)果:
sAry = (
zhangsan,
lisi
), cAry = (
zhangsan
)
*/
- 歸根結(jié)底之所以出現(xiàn)這樣問題随夸,那是因?yàn)?
ARC
情況下strong
和copy
對屬性setter
方法重寫的區(qū)別。
strong:setter
時調(diào)用了[sAry retain]
方法震放,實(shí)現(xiàn)了指針拷貝宾毒,也就是淺拷貝。
copy:setter
時調(diào)用了[cAry copy]
方法殿遂,實(shí)現(xiàn)了內(nèi)容拷貝诈铛,也就是深拷貝。
也就是在 .m 文件中系統(tǒng)默認(rèn)幫我們實(shí)現(xiàn)的是:
-
具體可以看內(nèi)存地址圖詳解:(簡單粗糙墨礁,歡迎大神指正)
2.那么這個與 copy
修飾 NSMutableArray
導(dǎo)致崩潰有什么關(guān)系呢幢竹?
原來不管是集合類對象(NSArray,NSDictionary,NSSet...),還是非集合類對象(NSString),接收到copy或者mutableCopy消息時恩静,都需遵循以下準(zhǔn)則:
-
copy
返回的都是不可變對象焕毫,所以如果對copy
返回值去調(diào)用可變對象的接口就會 crash - mutableCopy 返回的都是可變對象
所以在- (void)testCash
方法中執(zhí)行到self.copAry = arr;
ARC 環(huán)境下setter
方法執(zhí)行了copy
方法蹲坷,導(dǎo)致原本NSMutableArray
類型數(shù)組變成NSArray
類型,在調(diào)用removeObject:
方法時邑飒,自然會出現(xiàn)這個錯誤reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'
3.那么 NSArray
類型為什么需要使用 copy
來修飾
- 我們繼續(xù)使用
copy
和strong
來定義變量
@property (strong, nonatomic) NSArray *arr1;
@property (copy, nonatomic) NSArray *arr2;
// 為什么 NSArray 需要使用 copy
- (void)testUserCopyWithAry {
NSMutableArray *arr = [NSMutableArray arrayWithObjects:@(1), @(2), @(3), nil];
self.arr1 = arr;
self.arr2 = arr;
[arr addObject:@(4)];
NSLog(@"arr1 = %@, arr2 = %@", self.arr1, self.arr2);
/* 輸出結(jié)果:
arr1 = (
1,
2,
3,
4
), arr2 = (
1,
2,
3
)
*/
}
簡直白的說就是:如果定義一個數(shù)組循签,使用 strong
來修飾的話,如果這個數(shù)組在外界被修改的話疙咸,那么這個用 strong
修改的數(shù)組變量的值也會跟著變化县匠。為什么?還是因?yàn)?strong
進(jìn)行了指針拷貝撒轮。在內(nèi)存中乞旦,兩個變量指向的是同一塊內(nèi)存地址。所以為了避免值在外接發(fā)生改變而影響自身值的變化腔召,我們通常選擇使用 copy
進(jìn)行修飾杆查。
4.接著再上兩個測試?yán)樱瑢Ρ瓤摧敵鼋Y(jié)果
- (void)test01 {
NSArray *array = @[@1,@2,@3,@4];
NSArray *copyArr = [array copy];
NSArray *mCopyArr = [array mutableCopy];
NSMutableArray *mcArr = [array copy];
NSMutableArray *mmCopyArr = [array mutableCopy];
NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
/* 輸出結(jié)果
array: 0x60000024ce10
copyArr: 0x60000024ce10
mCopyArr: 0x60000024cd80
mcArr: 0x60000024ce10
mmCopyArr: 0x60000024ce70
*/
}
- (void)test02 {
NSArray *tarray = @[@1,@2,@3,@4];
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObjectsFromArray:tarray];
NSArray *copyArr = [array copy];
NSArray *mCopyArr = [array mutableCopy];
NSMutableArray *mcArr = [array copy];
NSMutableArray *mmCopyArr = [array mutableCopy];
NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
/* 輸出結(jié)果
array: 0x60000024cd20
copyArr: 0x60000024cc90
mCopyArr: 0x60000024ce40
mcArr: 0x60000024cde0
mmCopyArr: 0x60000024d020
*/
}
小結(jié)一下:
- NSArray的copy ---->指針拷貝---->淺拷貝
- NSArray的mutableCopy臀蛛,NSMutableArray的copy, NSMutableArray的mutableCopy 均為深拷貝,即內(nèi)容拷貝崖蜜。
- 關(guān)于NSString(非集合類對象),NSDictionary及其對應(yīng)的可變類型都可以此類推浊仆。
5.淺拷貝、深拷貝
全篇在講淺拷貝豫领、深拷貝抡柿,追究他們追究到底是什么。
段子手的理解:淺復(fù)制好比你和你的影子等恐,你完蛋洲劣,你的影子也完蛋。深復(fù)制好比你和你的克隆人课蔬,你完蛋囱稽,你的克隆人還活著。
淺拷貝
- 淺拷貝就是對內(nèi)存地址的復(fù)制二跋,目標(biāo)對象和原對象指向同一片內(nèi)存地址战惊。
注意
多個對象共用一塊地址時,當(dāng)內(nèi)存銷毀的時候扎即,指向這片內(nèi)存地址的幾個指針需要重新定義才可以使用吞获,否則出現(xiàn)野指針現(xiàn)象。
在 iOS的淺拷貝 中谚鄙,通常會使用retain
關(guān)鍵字進(jìn)行引用計(jì)數(shù)各拷。因?yàn)樗瓤梢宰寧讉€指針共同指向同一內(nèi)存地址,也可以在release
的時候 由于計(jì)數(shù)的存在闷营,不會讓內(nèi)存銷毀烤黍,從而出現(xiàn)野指針的現(xiàn)象。
深拷貝
- 深拷貝也就是內(nèi)容拷貝。目標(biāo)對象雖然和原對象的值一樣蚊荣,但是所指向的內(nèi)存地址不一樣初狰。可以說深拷貝把原對象地址也拷貝了互例,而內(nèi)存地址是自主分配的奢入。因內(nèi)存地址不一樣,兩個對象也就互不影響媳叨、互不干涉了腥光。
在 iOS的深拷貝 中,通常會使用copy
和mutableCopy
方法
// 深拷貝
- (void)testDeepCopy {
NSString *str = @"abcdefg";
NSString *cStr = [str copy];
NSMutableString *mStr = [str mutableCopy];
[mStr appendString:@"!!"];
NSLog(@"\n str = %@ = %p,\n cStr = %@ = %p,\n mStr = %@ = %p", str, str, cStr, cStr, mStr, mStr);
}
/* 輸出結(jié)果:
str = abcdefg = 0x109dd8090,
cStr = abcdefg = 0x109dd8090,
mStr = abcdefg!! = 0x604000057e50
*/
再次驗(yàn)證了第 4 個模塊
番外篇
淺拷貝的 retain
和 深拷貝中提到的 copy
有什么區(qū)別呢
可以觀看這篇文章:copy 和 retain 到底有啥區(qū)別
6.拷貝構(gòu)造
當(dāng)然在 iOS
中并不是所有的對象都支持copy
糊秆,mutableCopy
武福,遵守 NSCopying
協(xié)議的類可以發(fā)送 copy
消息,遵守NSMutableCopying
協(xié)議的類才可以發(fā)送 mutableCopy
消息痘番。
假如發(fā)送了一個沒有遵守上訴兩協(xié)議而發(fā)送 copy
或者 mutableCopy
, 那么就會發(fā)生異常捉片。但是默認(rèn)的 iOS
類并沒有遵守這兩個協(xié)議。如果想自定義一下 copy
那么就必須遵守 NSCopying
, 并且實(shí)現(xiàn) copyWithZone:
方法汞舱,如果想自定義一下mutableCopy
那么就必須遵守 NSMutableCopying
, 并且實(shí)現(xiàn) mutableCopyWithZone:
方法伍纫。
- 自定義 model,遵守
NSCopying
和NSMutableCopying
協(xié)議
@interface CJObj : NSObject<NSCopying, NSMutableCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *icon;
@end
-
實(shí)現(xiàn):
在
ViewController
中打印結(jié)果
// 拷貝構(gòu)造
- (void)copyConstruct {
CJObj *obj = [[CJObj alloc] init];
obj.name = @"zhangsan";
obj.icon = @"icon";
NSMutableArray *arr = [NSMutableArray array];
NSMutableArray *copyAry = [NSMutableArray array];
[arr addObject:obj];
[copyAry addObject:[arr[0] copy]];
//[copyAry addObject:[arr[0] mutableCopy]];
CJObj *obj2 = arr[0];
obj2.name = @"lisi";
obj2.icon = @"obj2_icon";
NSLog(@"arr.name = %@, arr.icon = %@", ((CJObj *)arr[0]).name, ((CJObj *)arr[0]).icon);
NSLog(@"copyArr.name = %@, copyArr.icon = %@", ((CJObj *)[copyAry objectAtIndex:0]).name, ((CJObj *)[copyAry objectAtIndex:0]).icon);
/* 輸出結(jié)果:
arr.name = lisi, arr.icon = obj2_icon
copyArr.name = zhangsan, copyArr.icon = icon
*/
}
-
內(nèi)存地址圖詳解:(簡單粗糙昂芜,歡迎大神指正)
以上內(nèi)容在 demo 中均有體現(xiàn)
感謝:
- 本文第3莹规、4模塊出自這里
- 淺拷貝、深拷貝
- 拷貝構(gòu)造部分
相關(guān)閱讀: