拷貝:顧名思義就是將一個(gè)對(duì)象復(fù)制一份出來紊馏。說到iOS中得拷貝操作薄腻,大概用的最多的應(yīng)該就是數(shù)組的拷貝操作方妖。如果自己的類想支持拷貝操作,那就要實(shí)現(xiàn)NSCopy協(xié)議拌屏。
- (id)copyWithZone:(NSZone *)zone;
與之對(duì)應(yīng)的還有一個(gè)可變版本拷貝操作,需要實(shí)現(xiàn)NSMutableCopying協(xié)議术荤。
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
自定義這種網(wǎng)上還是比較多倚喂,這里主要介紹字符串,數(shù)組,字典的Copy和mutableCopy操作瓣戚,以及衍伸出的淺拷貝和深拷貝的問題端圈。
1. NSString
大家一般申明字符串屬性的時(shí)候都會(huì)用這種方式
@property (nonatomic, copy) NSString *str;
當(dāng)然也會(huì)有這種
@property (nonatomic, strong) NSString *str;
那到底應(yīng)該使用哪一種比較好,還是說兩者都可以使用呢子库,接下來將進(jìn)一步來分析他們舱权。
當(dāng)我們給字符串屬性賦值的時(shí)候,實(shí)際調(diào)用的是set方法:
首先我們來分析strong這種方式:
- (void)setStr:(NSString *)str {
_str = str;
}
?接下來測試一下:
NSString *a = @"One";
self.str = a;
NSLog(@"變量a所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
變量a所指向的地址--0x10a91c088,內(nèi)容--One
self.str所指向的地址--0x10a91c088,內(nèi)容--One
如上述代碼可以看到仑嗅,將變量a賦值給self.str的時(shí)候宴倍,其實(shí)就是讓self.str指向了變量a所指向的地址,也就是字符串One在堆中得地址仓技。所以self.str和變量a指向的是同一個(gè)地址鸵贬。
//給a重新賦值
a = @"Two";
NSLog(@"變量a所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
變量a所指向的地址--0x10a91c088,內(nèi)容--Two
self.str所指向的地址--0x10a91c0e8,內(nèi)容--One
當(dāng)我們給變量a重新賦值的時(shí)候,其實(shí)是讓變量a重新指向了新的地址脖捻,也就是字符串Two在堆中的地址阔逼,但是self.str并不會(huì)改變。無論變量a怎么賦值地沮,都不會(huì)影響到self.str嗜浮,這也是我們所希望看到的。
//定義一個(gè)可變的字符串a(chǎn)
NSMutableString *a = [NSMutableString stringWithString:@"One"];
self.str = a;
NSLog(@"變量b所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p",self.str);
//a中追加了字符串123
[a appendString:@"123"];
NSLog(@"變量b所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
變量a所指向的地址--0x7fc51bca40e0,內(nèi)容--One
self.str所指向的地址--0x7fc51bca40e0,內(nèi)容--One
//a中追加了字符串123
變量b所指向的地址--0x7fc51bca40e0,內(nèi)容--One123
self.str所指向的地址--0x7fc51bca40e0,內(nèi)容--One123
上述代碼中摩疑,我們聲明了一個(gè)可變字符串變量a危融,并賦值one。當(dāng)我們?cè)谧兞縜中追加了字符串123之后雷袋,self.str的內(nèi)容也隨之改變了专挪。因?yàn)樽兞縜和self.str所指向的地址是同一個(gè)。當(dāng)我們給a追加字符串的時(shí)候,并沒有重新讓a指向新的地址寨腔。所以當(dāng)我們給其中一個(gè)追加內(nèi)容的時(shí)候速侈,另一個(gè)也會(huì)隨之改變。假如我們?cè)谀硞€(gè)場景中又用到了變量a迫卢,并給變量a追加了一些新的內(nèi)容倚搬,就會(huì)導(dǎo)致self.str的內(nèi)容也被改變了
接下來我們分析copy
- (void)setStr:(NSString *)str {
_str = [str copy];
}
給字符串賦值的時(shí)候,在其內(nèi)部使用拷貝的操作乾蛤。我們按照之前的方式進(jìn)行測試每界。
NSString *a = @"One";
self.str = a;
NSLog(@"變量a所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
a = @"Two";
NSLog(@"變量a所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
變量a所指向的地址--0x104aad088,內(nèi)容--One
self.str所指向的地址--0x104aad088,內(nèi)容--One
變量a所指向的地址--0x104aad0e8,內(nèi)容--Two
self.str所指向的地址--0x104aad088,內(nèi)容--One
從上面的代碼可以看出,結(jié)果完全是和strong是一樣的家卖。但是內(nèi)部是有細(xì)微差別的眨层。再其內(nèi)部的set方法中 str = [a copy] 進(jìn)行了一份淺拷貝,也就是所謂的指針拷貝上荡,使str指向了變量a所指向的地址趴樱。
接下來我們將變量a定義成可變字符串
NSMutableString *a = [NSMutableString stringWithString:@"One"];
self.str = a;
NSLog(@"變量b所指向的地址--%p",a);
NSLog(@"self.str所指向的地址--%p",self.str);
//a中追加了字符串123
[a appendString:@"123"];
NSLog(@"變量b所指向的地址--%p,內(nèi)容--%@",a,a);
NSLog(@"self.str所指向的地址--%p,內(nèi)容--%@",self.str,self.str);
變量a所指向的地址--0x7fc4f940fbe0
self.str所指向的地址--0xa00000000656e4f3
//a中追加了字符串123
變量a所指向的地址--0x7fc4f940fbe0,內(nèi)容--One123
self.str所指向的地址--0xa00000000656e4f3,內(nèi)容--One
大家是不是發(fā)現(xiàn)不一樣的地方了?當(dāng)我們?cè)谧兞縜中追擊加了字符串之后酪捡,self.str并沒有改變其內(nèi)容叁征。也就是說self.str并不會(huì)受到變量a影響。
我們仔細(xì)觀察上面輸出的地址逛薇。當(dāng)把變量a賦值給self.str之后捺疼,打印他們的地址,我們發(fā)現(xiàn)self.str指向的地址不是變量a所指向的地址永罚。這是因?yàn)楫?dāng)我們把變量a賦值給self.str時(shí)候啤呼,再其內(nèi)部進(jìn)行了一次深拷貝,也就是把整個(gè)對(duì)象進(jìn)行了拷貝呢袱。
set方法內(nèi)部
str = [a copy];
因?yàn)槲覀冏兞縜是一個(gè)可變字符串媳友,可變版本進(jìn)行copy執(zhí)行,會(huì)產(chǎn)生一個(gè)不可變的版本(NSString)产捞,此時(shí)是一個(gè)深拷貝醇锚,在堆中重新開辟一個(gè)新的內(nèi)存,里面存放了相同的內(nèi)容One坯临。str指向了這個(gè)新的內(nèi)存地址焊唬,只是這個(gè)新對(duì)象是一個(gè)不可變的版本。所以我們看到兩次輸出的地址是完全不一樣的看靠。
當(dāng)我們接下來給變量a追加字符串的時(shí)候赶促,也就不會(huì)影響到self.str中的內(nèi)容了。
所以定義字符串屬性的時(shí)候建議使用copy關(guān)鍵字
2.字符串中Copy和mutableCopy操作
NSString *str = @"123";
NSString *copyStr = [str copy];
NSLog(@"str所指向的地址--%p",str);
NSLog(@"copyStr所指向的地址--%p",copyStr);
str所指向的地址--0x105e40088
copyStr所指向的地址--0x105e40088
上面的代碼可以看出挟炬,當(dāng)一個(gè)不可變的字符串進(jìn)行copy之后返回的還是一個(gè)不可變的字符串鸥滨,并且是一個(gè)淺拷貝(指針拷貝),他們還是都指向同一個(gè)地址嗦哆。
NSString *str = @"123";
NSMutableString *mutableCopyStr = [str mutableCopy];
NSLog(@"str所指向的地址--%p",str);
NSLog(@"mutableCopyStr所指向的地址--%p",mutableCopyStr);
str所指向的地址--0x102859088
mutableCopyStr所指向的地址--0x7fade2e3b170
上面的代碼可以看出,當(dāng)一個(gè)不可變字符串進(jìn)行mutableCopy之后返回一個(gè)可變的對(duì)象,進(jìn)行了一次深拷貝婿滓,他們分別指向了不同的地址老速,只是內(nèi)容還是一樣的。
NSMutableString *str = [NSMutableString stringWithString:@"123"];
NSMutableString *copyStr = [str copy];
NSLog(@"str所指向的地址--%p,內(nèi)容--%@",str,str);
NSLog(@"copyStr所指向的地址--%p,內(nèi)容--%@",copyStr,copyStr);
str所指向的地址--0x7fcac163fc30,內(nèi)容--123
copyStr所指向的地址--0xa000000003332313,內(nèi)容--123
上面的代碼可以看出,當(dāng)一個(gè)可變字符串進(jìn)行copy之后返回了一個(gè)不可變的對(duì)象凸主,進(jìn)行了一次深拷貝橘券,他們分別指向了不同的地址,只是內(nèi)容還是一樣的卿吐。
NSMutableString *str = [NSMutableString stringWithString:@"123"];
NSMutableString *mutableCopyStr = [str mutableCopy];
NSLog(@"str所指向的地址--%p,內(nèi)容--%@",str,str);
NSLog(@"mutableCopyStr所指向的地址--%p,內(nèi)容--%@",mutableCopyStr,mutableCopyStr);
str所指向的地址--0x7fd138d0bd40,內(nèi)容--123
mutableCopyStr所指向的地址--0x7fd138d0aef0,內(nèi)容--123
這最后一組代碼展示了旁舰,當(dāng)一個(gè)可變字符串進(jìn)行mutableCopy之后同樣返回了一個(gè)可變的字符串,進(jìn)行了一次深拷貝嗡官,他們分別指向了不同的地址箭窜,只是內(nèi)容還是一樣的。
從上面的四組代碼可以看出衍腥,可變版本與不可變版本之間是可以互相轉(zhuǎn)換的磺樱,那么到底是進(jìn)行了深拷貝還是淺拷貝,不單單只能看到是執(zhí)行了copy還是mutableCopy紧阔,還要結(jié)合被拷貝的對(duì)象到底是可變的還是不可變的坊罢,從而判斷出到底是深拷貝還是淺拷貝
- 不可變版本->copy = 不可變版本(淺拷貝)
- 不可變版本->mutableCopy = 可變版本(深拷貝)
- 可變版本->copy = 不可變版本(深拷貝)
- 可變版本->mutableCopy = 可變版本(深拷貝)
3.數(shù)組
數(shù)組拷貝操作在日超操作中應(yīng)該是用的比較多得了续担,接下來我們來看看關(guān)于數(shù)組部分的拷貝擅耽。
NSArray *array = @[[NSMutableString stringWithString:@"1"],[NSMutableString stringWithString:@"2"]];
NSArray *copyArray = [array copy];
NSMutableArray *mutableCopyArray = [array mutableCopy];
NSLog(@"array所指向的地址%p",array);
NSLog(@"copyArray所指向的地址%p",copyArray);
NSLog(@"mutableCopyArray所指向的地址%p",mutableCopyArray);
NSLog(@"array第一個(gè)元素所指向的地址%p,array第二個(gè)元素所指向的地址%p",array[0],array[1]);
NSLog(@"copyArray第一個(gè)元素所指向的地址%p物遇,copyArray第二個(gè)元素所指向的地址%p",copyArray[0],copyArray[1]);
NSLog(@"mutableCopyArray第一個(gè)元素所指向的地址%p乖仇,mutableCopyArray第二個(gè)元素所指向的地址%p",mutableCopyArray[0],mutableCopyArray[1]);
array所指向的地址0x7f92a8ead290
copyArray所指向的地址0x7f92a8ead290
mutableCopyArray所指向的地址0x7f92a8eacf70
array中第一個(gè)元素所指向的地址0x7f92a8eaa870,array中第二個(gè)元素所指向的地址0x7f92a8e1dca0
copyArray中第一個(gè)元素所指向的地址0x7f92a8eaa870询兴,copyArray中第二個(gè)元素所指向的地址0x7f92a8e1dca0
mutableCopyArray中第一個(gè)元素所指向的地址0x7f92a8eaa870乃沙,mutableCopyArray中第二個(gè)元素所指向的地址0x7f92a8e1dca0
我們觀察上述代碼和打印出來的地址,當(dāng)NSArray進(jìn)行copy之后返回了一個(gè)不可變的NSArray诗舰,他們所指向的地址都是同一個(gè),進(jìn)行了淺拷貝警儒。然后當(dāng)NSArray進(jìn)行mutableCopyArray之后返回了一個(gè)可變的數(shù)組NSMutableArray,他們指向了不同的地址眶根。和上一節(jié)中字符串結(jié)論是一致的蜀铲。
接下來我們看數(shù)組中得元素,我們發(fā)現(xiàn)不管是進(jìn)行了copy還是mutableCopy属百,這些元素的地址都沒有變過记劝,說明數(shù)組中得元素都是進(jìn)行了淺拷貝,進(jìn)行了指針拷貝而已族扰。為了進(jìn)一步證實(shí)這個(gè)說法厌丑。我們接下來修改數(shù)組中得元素:
//給第一個(gè)元素中追加字符串one定欧,然后分別打印這幾個(gè)數(shù)組
[array[0] appendString:@"one"];
NSLog(@"%@",array);
NSLog(@"%@",copyArray);
NSLog(@"%@",mutableCopyArray);
2015-11-26 15:45:11.632 NSCopying[3379:206715] (
1one,
2
)
2015-11-26 15:45:11.632 NSCopying[3379:206715] (
1one,
2
)
2015-11-26 15:45:11.632 NSCopying[3379:206715] (
1one,
2
)
我們可以看到每一份數(shù)組的中第一個(gè)元素都發(fā)生了變化。這也說明了數(shù)組中的元素只是進(jìn)行了一份淺拷貝怒竿。
//接著上面的代碼砍鸠,我們向mutableCopyArray添加一點(diǎn)元素
[mutableCopyArray addObject:[NSMutableString stringWithString:@"3"]];
NSLog(@"array--%@",array);
NSLog(@"copyArray--%@",copyArray);
NSLog(@"mutableCopyArray--%@",mutableCopyArray);
2015-11-26 15:56:38.776 NSCopying[3455:212121] array--(
1,
2
)
2015-11-26 15:56:38.776 NSCopying[3455:212121] copyArray--(
1,
2
)
2015-11-26 15:56:38.776 NSCopying[3455:212121] mutableCopyArray--(
1,
2,
3
)
根據(jù)上一節(jié)字符串中結(jié)論,mutableCopyArray它是一個(gè)深拷貝愧口,所以給它添加元素睦番,并不會(huì)影響其他兩個(gè)數(shù)組中得元素。
//我們給mutableCopyArray中第一個(gè)元素賦值一個(gè)新的字符串耍属,并打印
mutableCopyArray[0] = [NSMutableString stringWithString:@"123"];
NSLog(@"array--%@",array);
NSLog(@"copyArray--%@",copyArray);
NSLog(@"mutableCopyArray--%@",mutableCopyArray);
2015-11-26 16:04:07.464 NSCopying[3486:215020] array--(
1,
2
)
2015-11-26 16:04:07.464 NSCopying[3486:215020] copyArray--(
1,
2
)
2015-11-26 16:04:07.465 NSCopying[3486:215020] mutableCopyArray--(
123,
2,
3
)
上面的代碼創(chuàng)建了一個(gè)新的NSMutableString對(duì)象并將其內(nèi)存地址賦給指針mutableCopyArray[0]托嚣,而其他數(shù)組指針指向的對(duì)象不變,所以輸出時(shí)其他數(shù)組不受影響厚骗。
//接下來我們刪除mutableCopyArray中第二個(gè)元素示启,并打印
[mutableCopyArray removeObjectAtIndex:1];
NSLog(@"array--%@",array);
NSLog(@"copyArray--%@",copyArray);
NSLog(@"mutableCopyArray--%@",mutableCopyArray);
2015-11-26 16:10:03.562 NSCopying[3509:217545] array--(
1,
2
)
2015-11-26 16:10:03.563 NSCopying[3509:217545] copyArray--(
1,
2
)
2015-11-26 16:10:03.563 NSCopying[3509:217545] mutableCopyArray--(
1
)
移除的是mutableCopyArray指向的索引結(jié)合中的第二個(gè)元素,即移除的是對(duì)對(duì)象的引用领舰,而不是移除數(shù)組中的第二個(gè)對(duì)象夫嗓。并不會(huì)會(huì)影響到array,copyArray冲秽。
總結(jié):對(duì)于數(shù)組進(jìn)行的copy和mutableCopy結(jié)論可以參照上一節(jié)中字符串結(jié)論舍咖,數(shù)組特殊地方在于其內(nèi)部的元素,只是進(jìn)行了簡單的淺拷貝锉桑,如果數(shù)組中得元素它是一個(gè)可變對(duì)象排霉,比如一個(gè)可變字符串,如果你追加了字符串民轴,再或者可以是一個(gè)自定義的類攻柠,你修改其中某一個(gè)屬性,其他的數(shù)組中得元素也會(huì)隨之受到相同的修改
4.字典
關(guān)于字典拷貝后裸,原理和上一節(jié)的數(shù)組是一樣的瑰钮,而且其內(nèi)部的value也是淺拷貝。
NSDictionary *dict = @{@"key1":[NSMutableString stringWithString:@"1"],@"key2":[NSMutableString stringWithString:@"2"]};
NSDictionary *copyDict = [dict copy];
NSMutableDictionary *mutableCopyDict = [dict mutableCopy];
NSLog(@"dict所指向的地址%p",dict);
NSLog(@"copyDict所指向的地址%p",copyDict);
NSLog(@"mutableCopyDict所指向的地址%p",mutableCopyDict);
NSLog(@"%p",dict[@"key1"]);
NSLog(@"%p",copyDict[@"key1"]);
NSLog(@"%p",mutableCopyDict[@"key1"]);
2015-11-26 16:37:31.952 NSCopying[3619:229287] dict所指向的地址0x7fcab0e6c250
2015-11-26 16:37:31.953 NSCopying[3619:229287] copyDict所指向的地址0x7fcab0e6c250
2015-11-26 16:37:31.953 NSCopying[3619:229287] mutableCopyDict所指向的地址0x7fcab0e6c290
2015-11-26 16:37:31.953 NSCopying[3619:229287] 0x7fcab0e689a0
2015-11-26 16:37:31.954 NSCopying[3619:229287] 0x7fcab0e689a0
2015-11-26 16:37:31.954 NSCopying[3619:229287] 0x7fcab0e689a0
字典對(duì)象間的拷貝還是遵循第一節(jié)中字符串那個(gè)結(jié)論微驶,根據(jù)打印出來的地址可以看出浪谴,字典內(nèi)部的value值和數(shù)組中得元素一樣都是進(jìn)行了淺拷貝。
//給dict[@"key1"]追加字符串
[dict[@"key1"] appendString:@"one"];
NSLog(@"dict%@",dict);
NSLog(@"copyDict%@",copyDict);
NSLog(@"mutable%@",mutableCopyDict);
2015-11-26 16:42:19.316 NSCopying[3657:231891] dict{
key1 = 1one;
key2 = 2;
}
2015-11-26 16:42:19.821 NSCopying[3657:231891] copyDict{
key1 = 1one;
key2 = 2;
}
2015-11-26 16:42:23.133 NSCopying[3657:231891] mutable{
key1 = 1one;
key2 = 2;
}
因?yàn)関alue是淺拷貝因苹,value又是可變字符串轉(zhuǎn)苟耻,當(dāng)追加字符串進(jìn)去的時(shí)候,其他字典也會(huì)隨之受到影響容燕。
//我們給mutableCopyDict中添加一個(gè)key-value
[mutableCopyDict setObject:@"3" forKey:@"key3"];
NSLog(@"dict%@",dict);
NSLog(@"copyDict%@",copyDict);
NSLog(@"mutable%@",mutableCopyDict);
2015-11-26 16:46:07.813 NSCopying[3678:234004] dict{
key1 = 1;
key2 = 2;
}
2015-11-26 16:46:07.813 NSCopying[3678:234004] copyDict{
key1 = 1;
key2 = 2;
}
2015-11-26 16:46:07.813 NSCopying[3678:234004] mutable{
key1 = 1;
key2 = 2;
key3 = 3;
}
跟上一節(jié)的數(shù)組原理一樣梁呈,mutableCopyDict本身是一個(gè)深拷貝,所以給它內(nèi)部添加元素蘸秘,對(duì)應(yīng)刪除元素官卡,都不會(huì)影響到其他幾個(gè)字典蝗茁。提醒:字典中得key你會(huì)發(fā)現(xiàn)它必須實(shí)現(xiàn)NSCopying協(xié)議,所以key它本身也進(jìn)行了拷貝寻咒,為什么要這么做哮翘,大家自己去思考吧。
小結(jié):可變與不可變對(duì)象是可以互相轉(zhuǎn)換的毛秘。數(shù)組中的元素和字典中得value都是進(jìn)行了淺拷貝饭寺,如果想實(shí)現(xiàn)深拷貝,需要自己額外的去實(shí)現(xiàn)了叫挟。