通常我們?cè)谑褂聾property聲明屬性的時(shí)候,對(duì)于
NSString、NSArray、NSDictionary經(jīng)常會(huì)使用copy,以及block的時(shí)候也會(huì)使用copy,接下來(lái)就是和所說(shuō)copy和mutableCopy。先來(lái)思考幾個(gè)問(wèn)題:
- copy與mutableCopy有什么區(qū)別?
- 使用copy/mutableCopy和直接賦值有什么區(qū)別?
- 深淺拷貝的區(qū)別?
- 自定義對(duì)象如何實(shí)現(xiàn)NSCopying協(xié)議?
- block為什么需要使用copy?
copy和mutableCopy
在需要復(fù)制對(duì)象的時(shí)候,會(huì)用到NSObject類提供的copy和mutableCopy方法,通過(guò)這兩個(gè)方法即可復(fù)制已有對(duì)象的副本。最常用的是賦值NSString、NSArray、NSDictionary這一類對(duì)象,那么copy和mutableCopy究竟是什么?它們有何區(qū)別?
copy
copy拷貝出來(lái)的對(duì)象類型總是不可變類型(例如, NSString, NSArray, NSDictionary等等)
mutableCopy
mutableCopy拷貝出來(lái)的對(duì)象類型總是可變類型(例如, NSMutableString, NSMutableArray, NSMutableDictionary等等)
代碼舉例:
NSString * str = @"hello world";
[str copy]; // 拷貝出內(nèi)容為hello world的NSString類型的字符串
[str mutableCopy]; // 拷貝出內(nèi)容為hello world的NSMutableString類型的字符串
打印出類名:

同樣的,對(duì)于不可變的NSArray和可變的NSMutableArray來(lái)說(shuō),這樣的關(guān)系總是成立的:
[NSMutableArray copy] => NSArray
[NSArray mutableCopy] => NSMutableArray
使用copy/mutableCopy和直接賦值有什么區(qū)別?
先看一個(gè)例子
NSMutableArray * arr1 = [NSMutableArray array];
[arr1 addObject:@"A"];
NSArray * arr2 = [NSArray array];
arr2 = arr1;
[arr1 addObject:@"C"];
NSLog(@"arr1 = %@", arr1);
NSLog(@"arr2 = %@", arr2);
這段代碼輸入如下:

arr1是可變數(shù)組,arr2是一個(gè)不可變數(shù)組,明明可變數(shù)組添加對(duì)象在賦值之后,arr2也被影響到了。
如果將arr2 = arr1修改為如下:
arr2 = [arr1 copy];
然后輸出就正常了

這是為什么呢?
原因其實(shí)是和OC的多態(tài)特性有關(guān),表面上arr2是一個(gè)NSArray類型的對(duì)象,實(shí)際上是指向一個(gè)NSMutableArray類型的對(duì)象,也就是arr1。
我們通過(guò)打印arr1和arr2兩個(gè)對(duì)象來(lái)看就知道了:
-
在直接賦值的方式下打?。?/p>
image -
在使用copy的方式下打印:
image
一目了然,直接賦值之后,arr1和arr2完全就是同一個(gè)對(duì)象,指向同一個(gè)地址,所以賦值之后再給arr1添加對(duì)象,打印出的結(jié)果肯定也是一樣的。而如果使用copy之后賦值,就是兩個(gè)完全不一樣的對(duì)象,后續(xù)的操作也不會(huì)有影響。
深拷貝(deep copy)與淺拷貝(shallow copy)的區(qū)別?
首先得清楚什么是深拷貝和淺拷貝?
深拷貝
拷貝出來(lái)的對(duì)象與原對(duì)象地址不一致,修改拷貝對(duì)象的值對(duì)源對(duì)象的值沒(méi)有任何影響。 深拷貝是直接拷貝整個(gè)對(duì)象內(nèi)容到另一塊內(nèi)存中。
淺拷貝
拷貝出來(lái)的對(duì)象與原對(duì)象地址一致,修改拷貝對(duì)象的值會(huì)直接影響源對(duì)象的值。
可以用一句話總結(jié):淺復(fù)制就是指針拷貝;深復(fù)制就是內(nèi)容拷貝
或許會(huì)聽(tīng)過(guò)這樣的說(shuō)法:copy都是淺拷貝, mutableCopy都是深拷貝
這種淺顯的理解是錯(cuò)誤的,可以看到之前使用copy的方式下打印出來(lái)的對(duì)象的地址是不一樣的,是深拷貝,這說(shuō)明用從一個(gè)可變對(duì)象copy出一個(gè)不可變對(duì)象時(shí), 是深拷貝而不是淺拷貝。
在Foundation框架中,所有的collectioon類在默認(rèn)的情況下都執(zhí)行淺拷貝,也就是說(shuō)只拷貝容器對(duì)象本身,不復(fù)制其中的數(shù)據(jù)。這樣做的目的是,容器內(nèi)的對(duì)象未必都能拷貝,而且調(diào)用者也未必想在拷貝容器時(shí)一并拷貝其中的某個(gè)對(duì)象。
不過(guò)通常情況下,執(zhí)行的都是淺拷貝,如果你所寫(xiě)的對(duì)象需要深拷貝,那么可以考慮增加一個(gè)專門執(zhí)行深拷貝的方法。
自定義對(duì)象如何實(shí)現(xiàn)NSCopying協(xié)議
雖然copy方法是在NSObject中的,如果我們自定義一個(gè)類(比如Person),向該類的對(duì)象發(fā)送copy消息,會(huì)得到如下結(jié)果:
Person *p = [[Person alloc] init];
Person *p2 = [p copy];

查看蘋(píng)果官方文檔會(huì)發(fā)現(xiàn),如果自定義的類要實(shí)現(xiàn)copy功能,需要實(shí)現(xiàn)copyWithZone方法,(如果想要區(qū)分copy和mutableCopy,那么copyWithZone:應(yīng)該返回不可變副本,而mutableCopyWithZone:應(yīng)該返回可變副本)。這個(gè)時(shí)候可以在Person類中添加如下代碼:
@implementation Person
- (instancetype)copyWithZone:(NSZone *)zone {
Person *p = [[Person alloc] init];
p.age = self.age;
p.name = self.name;
return p;
}
之后就不會(huì)報(bào)錯(cuò),能正常的使用copy了。
但是在蘋(píng)果官方文檔上還說(shuō)了一個(gè)注意事項(xiàng):
If a subclass inherits NSCopying from its superclass and declares additional instance variables, the subclass has to override copyWithZone: to properly handle its own instance variables, invoking the superclass’s implementation first.
意思是:如果你的類可以產(chǎn)生子類,那么copyWithZone:方法將被繼承,子類中也必須重寫(xiě)copyWithZone:方法,并且要先調(diào)用父類的copyWithZone:。
這個(gè)時(shí)候在demo中增加一個(gè)Person的子類,并增加一個(gè)college屬性,那么父類Person的copyWithZone:方法需要改為:
- (instancetype)copyWithZone:(NSZone *)zone {
Person *p = [[[self class] allocWithZone:zone] init];
p.age = self.age;
p.name = self.name;
return p;
}
同時(shí)在子類Student中可以這樣實(shí)現(xiàn):
@implementation Student
- (instancetype)copyWithZone:(NSZone *)zone {
Student *stu = [super copyWithZone:zone];
if (stu) {
stu.college = self.college;
}
return stu;
}
@end
如果實(shí)現(xiàn)一個(gè)類的copyWithZone:方法,而該類的超類也實(shí)現(xiàn)了<NSCopying>協(xié)議,那么應(yīng)該先調(diào)用超類的copy方法以復(fù)制繼承來(lái)的實(shí)例變量,然后加入自己的代碼以復(fù)制想要添加到該類中的任何附加的實(shí)例變量。
block中為什么要使用copy修飾?
在使用block作為屬性的時(shí)候,通常使用的是copy
@property (copy) void (^clickBlock)(NSString * name);
使用copy修飾block其實(shí)是從MRC遺留下來(lái)的,在MRC時(shí)期,作為全局變量的block在初始化時(shí)是被存放在棧區(qū)的,這樣在使用時(shí)如果block內(nèi)有調(diào)用外部變量,那么block無(wú)法保留其內(nèi)存,如果在出了block的初始化作用域內(nèi)使用,就會(huì)引起崩潰,使用copy可以將block的內(nèi)存推入堆中,這樣讓其擁有保存調(diào)用的外部變量的內(nèi)存的能力。
在ARC下,對(duì)NSStackBLock用strong進(jìn)行強(qiáng)引用的話,好像會(huì)自動(dòng)對(duì)其進(jìn)行copy一份,變成NSMallocBLock,所以不會(huì)crash。在ARC下,其實(shí)不使用copy修飾block也是可以的。
詳細(xì)的block的實(shí)現(xiàn)可以參考唐巧關(guān)于block的講解:談Objective-C block的實(shí)現(xiàn)

