ios的淺複製和深複製 (NSCoping協議)
最近在用NSArray 陣列對資料進行操作時,將該陣列進行 MutableCopy 到一個NSMutableArray陣列中,然後再在這個NSMutableArray陣列中對資料進行操作的時候,發現原來NSArray 陣列中的資料也被修改了。仔細研究,發現copy和MutableCopy並沒有我想像的那麼簡單。
首先關於copy和mutableCopy的行為:不管是NSString這種元素類、還是NSArray這樣的容器類、還是Mutable和非Mutable類,copy和mutableCopy呼叫後表現的行為到底是什麼樣的完成取決於類本身NSCopying和NSMutableCopying協議是如何實現的。並且,如果嘗試使用自定義類(例如,人類(person類)、書類(book類))中的copy方法等類似的操作,將會收到一條異常出錯的訊息,它可能如下所示:
-[Fraction copyWithZone:]: unrecognized selector sent to instance 0x7fabb8414380
這種錯誤,是對於自定義類,要實現使用自己的類進行復制,必須根據<NSCopying>協議實現其中的一兩個方法。實現<NSCopying>協議時,類必須實現copyWithZone:方法來響應copy訊息。如果想要區分可變副本和不可變副本,那麼copyWithZone:應該返回不可變副本,而mutableCopyWithZone:應該返回可變副本。產生物件的可變副本並不要求被複制的物件本身也是可變的(反之亦然),想要產生不可變副本的可變副本是很合理的(例如,字串物件)。
以下對於copy和mutableCopy的詳細分析引用 http://blog.csdn.net/omegayy/article/details/7311839
1. 元素資料的copy和mutableCopy。
常用的字串NSString類,示例程式碼如下:
NSString* string = @”hello world”; NSString* stringCopy = [string copy];// stringCopy與string地址相同,retainCount+ 1 NSMutableString* stringMCopy = [string mutablecopy];// stringMCopy與string地址不同 NSMutableString* stringM1 = [stringMCopy copy];//地址與stringMCopy不同,且為不可修改 NSMutableString* stringM2 = [stringMCopy mutablecopy];//地址與stringMCopy不同,可修改
可以基本推出NSString和NSMutableString中兩個協議的實現
NSString:
- (id)copywithZone:(NSZone*)zone
{
return self;
}
- (id)mutableCopywithZone:(NSZone*)zone
{
NSMutableString* copy =[[NSMutableString alloc] initxxxxxx];
....
return copy;
}
NSMutableString:
- (id)copywithZone:(NSZone*)zone
{
NSString* copy = [[NSStringalloc] initxxxxxx];
....
return copy;//所以不可修改
}
- (id)mutableCopywithZone:(NSZone*)zone
{
NSMutableString* copy =[[NSMutableString alloc] initxxxxxx];
....
return copy;
}
2. 容器類的copy和mutableCopy。
常用類NSArray和NSMutableArray,看如下示例程式碼:
Class1* obj1= ....;//正常初始化
NSArray* array = [[NSArray alloc] initWithObjects:obj1, nil];
NSArray* arrayCopy = [array copy];//地址不變,retaincount+1
NSMutableArray* arrayMCopy = [array mutableCopy];//地址改變,但是陣列中成員指標和obj1相同,淺拷貝
NSMutableArray* arrayM1 = [arrayMCopy Copy];//地址改變,但是陣列中成員指標和obj1相同,淺拷貝。arrayM1為NSArray不可修改
NSMutableArray* arrayM2 = [arrayMCopy mutableCopy];//地址改變,但是陣列中成員指標和obj1相同,淺拷貝
推斷
NSArray:
- (id)copywithZone:(NSZone*)zone
{
//偽碼
return [self retain];
}
- (id)mutableCopywithZone:(NSZone*)zone
{
NSMutableArray* copy = [[NSMutableString alloc] initxxxxxx];
for (id element in self) {
[copy addObject:element];//element retian count + 1
....
}
return copy;
}
NSMutableArray:
- (id)copywithZone:(NSZone*)zone
{
NSArray* copy = [[NSArray alloc] initXXX];
/*把每個element加入到copy陣列,retainCount+1*/
....
return copy;
}
- (id)mutableCopywithZone:(NSZone*)zone
{
NSMutableArray* copy = [[NSMutableString alloc] initxxxxxx];
for (id element in self) {
[copy addObject:element];//element retian count + 1
....
}
return copy;
}
3. 深拷貝
官方文件中介紹兩種實現深拷貝的方法:
a. 用Array的initWithArray: copyItems函式,如下:
NSArray *deepCopyArray=[[NSArray alloc] initWithArray: someArraycopyItems: YES];
呼叫後,會對原NSArray中的每個元素呼叫其copy函式,並把返回的id加入到新的陣列中。所以這是依賴於Obj物件類實現的深拷貝,如果- (id)copywithZone:(NSZone*)zone是重新分配一塊記憶體賦值後返回,那麼就是真正的深拷貝。如果直接返回自身,那麼它只是淺拷貝。
b. 用archiver方式:
NSArray* trueDeepCopyArray = [NSKeyedUnarchiverunarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:oldArray]];
這是真正意義上的深拷貝,不依賴於實際類Copying協議的實現。
最後需要注意的是:
如果你的類可以產生子類,那麼copyWithZone:方法將被繼承。在這種情況下,該方法中的程式行:
//假如父類為Book類 無子類是可以這樣實現
Book *newbook = [[Book allocWithZone:zone] init];
//有子類時,需要這樣實現
Book *newBook = [[[self class] allocWithZone:zone] init];
這樣,可以從該類分配一個新的物件,而這個類的copy的接收著(例如,如果它產生了一個名為NewFraction 的子類,那麼應該確保在繼承的方法中分配了新的NewFraction物件,而不是Fraction物件)。
如果編寫一個類的copyWithZone:方法,而該類的超類也實現了<NSCopying>協議,那麼應該先呼叫超類的copy方法以複製繼承來的例項變數,然後加入自己的程式碼以複製想要新增到該類中的任何附加的例項變數(如果有的話)。