深拷貝(mutableCopy)就是內容拷貝,即指拷貝對象的具體內容,而內存地址是自主分配的,拷貝結束之后,兩個對象雖然存的值是相同的,但是內存地址不一樣,兩個對象也互不影響,互不干涉;淺拷貝(copy)就是指針拷貝,即對內存地址的復制,讓目標對象指針和源對象指向同一片內存空間。
在OC中,若要進行對象的拷貝,則該對象所屬的類必須遵守NSCopying和NSMutableCopy協議,并重寫copyWithZone:和mutableCopyWithZone:方法。而系統原生類,之所以可以直接進行拷貝是因為它已幫我們自動做了這些事。
iOS中并不是所有的對象都支持copy,mutableCopy,遵守NSCopying 協議的類可以發送copy消息,遵守NSMutableCopying 協議的類才可以發送mutableCopy消息。假如發送了一個沒有遵守上訴兩協議而發送 copy或者 mutableCopy,那么就會發生異常。但是默認的ios類并沒有遵守這兩個協議。如果想自定義一下copy 那么就必須遵守NSCopying,并且實現 copyWithZone: 方法,如果想自定義一下mutableCopy 那么就必須遵守NSMutableCopying,并且實現 mutableCopyWithZone: 方法。
首先明白:Copy和MutableCopy的區別
Copy 返回一個不可變對象的副本,MutalbeCopy返回一個可變對象的副本。
一、系統的非容器類對象
這里指的是NSString、NSNumber等等一類的對象。
NSString * string = @"test";
NSString * stringCopy = [string copy];//沒有產生新對象
NSMutableString * stringMuCopy = [string mutableCopy];//產生新對象
[stringMuCopy appendString:@"!!"];
查看內存(圖1)可以發現,string和stringCopy指向的是同一塊內存區域(又叫apple弱引用weak reference),此時stringCopy的引用計數和string的一樣都為2(這里需要注意一下,因為@"test"對象是常量數據段,不是堆上的對象,所以string與stringCopy實際都是指向一個非堆上的對象,他們的引用計數應該是-1,可以通過程序驗證。我們所說的引用計數實際上是用于管理堆上申請的對象。)。而stringMCopy則是我們所說的真正意義上的復制,系統為其分配了新內存,但指針所指向的字符串還是和string所指的一樣。
再看下邊的例子:
NSMutableString * string = [NSMutableString stringWithFormat:@"test"];
NSString * stringCopy = [string copy];// 產生新對象
NSMutableString * mstringCopy = [string copy];// 產生新對象
NSMutableString * stringMuCopy = [string mutableCopy];// 產生新對象
其中 stringCopy和mstringCopy分配的是同一塊內存,與其他兩個NSString對象所分配的內存都是不一樣的。但是對于mStringCopy其實是個imutable對象,所以上述會報錯(如圖2)。
小結:
1.copy:對于可變對象為深復制,引用計數不改變;對于不可變對象是淺復制, ,相當于retain,引用計數每次加一。始終返回一個不可變對象。
2.mutableCopy:始終是深復制,引用計數不改變。始終返回一個可變對象。
**二、系統的容器類對象 **
指NSArray,NSMutableArray,NSDictionary等。對于容器類本身,上面討論的結論也是適用的,需要探討的是復制后容器內對象的變化。
//copy返回不可變對象,mutablecopy返回可變對象
NSArray * array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray * arrayCopy1 = [array1 copy];
//arrayCopy1是和array同一個NSArray對象(指向相同的對象),包括array里面的元素也是指向相同的指針
NSLog(@"%p,%p,%lu,%lu",array1,arrayCopy1,(unsigned long)[arrayCopy1 retainCount],(unsigned long)[array1 retainCount]);
NSMutableArray * mArrayCopy1 = [array1 mutableCopy];
//mArrayCopy1是array1的可變副本,指向的對象和array1不同,但是其中的元素和array1中的元素指向的是同一個對象。mArrayCopy1還可以修改自己的對象
[mArrayCopy1 addObject:@"de"];
NSLog(@"%p,%p",mArrayCopy1[0],array1[0]);
array1和arrayCopy1是指針復制,而mArrayCopy1是對象復制,mArrayCopy1還可以改變期內的元素:刪除或添加。但是注意的是,容器內的元素內容都是指針復制。
由一下例子做測試
NSArray * mArray1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray * mArrayCopy1 = [mArray1 copy];
NSLog(@"mArray1 retain count: %lu",(unsigned long)[mArray1 retainCount]);
NSMutableArray *mArrayMCopy1 = [mArray1 mutableCopy];
NSLog(@"mArray1 retain count: %lu",(unsigned long)[mArray1 retainCount]);
//mArrayCopy2,mArrayMCopy1和mArray1指向的都是不一樣的對象,但是其中的元素都是一樣的對象——同一個指針
//一下做測試
NSMutableString *testString = [mArray1 objectAtIndex:0];
//testString = @"1a1";//這樣會改變testString的指針,其實是將@“1a1”臨時對象賦給了testString
[testString appendString:@" tail"];//這樣以上三個數組的首元素都被改變了
由此可見,對于容器而言,其元素對象始終是指針復制。如果需要元素對象也是對象復制,就需要實現深拷貝。
三、自定義對象
我們定義的對象,那么我們自己要實現NSCopying,NSMutableCopying這樣就能調用copy和mutablecopy了
看下邊例子:
ZJPerson * p = [[ZJPerson alloc]init];
p.age = 20;
p.height = 170.0;
ZJPerson * copyP = [p copy];//這里崩潰
崩潰日志:
看崩潰信息ZJPerson應該先實現:
- (id)copyWithZone:(NSZone *)zone;
測試:
#import "ZJPerson.h"
@interface ZJPerson()<NSCopying>
@end
@implementation ZJPerson
-(id)copyWithZone:(NSZone *)zone{
return @"iOS俱哥";
}
@end
可以看出copyWithZone重新分配新的內存空間,則:
-(id)copyWithZone:(NSZone *)zone{
ZJPerson * person = [[ZJPerson allocWithZone:zone]init];
return person;
}
測試屬性值:
ZJPerson * p = [[ZJPerson alloc]init];
p.age = 20;
p.height = 170.0;
ZJPerson * copyP = [p copy];//這里崩潰
NSLog(@"p = %p copyP = %p",p ,copyP);
NSLog(@"age = %d height = %d", copyP.age, copyP.height);
雖然copy了份新的對象,然而age,height值并未copy,那么:
-(id)copyWithZone:(NSZone *)zone{
ZJPerson * person = [[ZJPerson allocWithZone:zone]init];
person.age = self.age;
person.height = self.height;
// 這里self其實就要被copy的那個對象,很顯然要自己賦值給新對象,所以這里可以控制copy的屬性
return person;
}
demo下載
參考文章:
深拷貝與淺拷貝(mutableCopy與Copy)詳解 iOS
iOS 淺談:深.淺拷貝與copy.strong