如果觉得我写的还不错,请关注我的新浪微博@杨浩宇-小橘爷,最新文章即时推送~
浅拷贝与深拷贝
在上一篇文章中,我们了解了 copy 的基础知识和实现方法。对于数组来说,其中放置的可能是不可变字符串(常量字符串对象是不可变的)。当我们使用可变字符串代替不可变字符串填充数组时,数组中的字符串就是可以改变的了。
当我们使用 mutableCopy 方法对装满了可变字符串的数组进行了拷贝之后,修改原始的数组的第一个字符串的值,你会发现,拷贝的字符串的第一个值也发生了改变。或许你能够理解为什么 dataArray 的第一个元素发生改变,但不明白为什么它的副本也会改变。从集合中获取元素时,就得到了这个元素的一个新引用,但并不是一个新副本。所以 dataArray[0] 返回的对象与 dataArray 中的第一个元素都指向内存中的同一个对象。随后,修改字符串对象的副作用就是同时改变了 dataArray 的第一个元素。
为什么你制作的副本的第一个元素也发生了改变?这与默认的浅复制方式有关。它意味着使用 mutableCopy 方法复制数组时,在内存中为新的数组对象分配了空间,并将单个元素复制到新数组中。然而将原始数组中的每个元素复制到新位置意味着:仅将引用从一个数组元素复制到另一个数组元素。这样做的最终结果,就是这两个数组中的元素都指向内存中的同一个字符串。这与将一个对象赋值给另一个对象没有区别。
若要为数组中的每个元素创建完全不同的副本,需要执行所谓的深拷贝。这就意味着要创建数组中的每个对象内容的副本,而不仅仅是这些对象的引用副本(并且考虑一下,如果数组中的元素本身是数组对象,深复制意味着该如何处理)。然而使用 Foundation 类的 copy 和 mutableCopy 方法时,深复制并不是默认执行的。
使用归档深拷贝对象
在之前,我们尝试创建了包含可变字符串元素的数组副本,并且了解了如何进行浅复制。也就是说,没有复制实际的字符串本身,只是复制对它们的引用。
可以使用 Foundation 的归档功能来创建对象的深复制。例如,通过 dataArray 归档到一个缓冲区,然后把它解归档,将结果指派给 dataArray2,从而 dataArray 复制给 dataArray2。对于这个过程,不需要使用文件,归档和解归档过程都可以在内存中发生。
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSData *data;
NSMutableArray *dataArray = [NSMutableArray arrayWithObjects:
[NSMutableString stringWithString:@"one"],
[NSMutableString stringWithString:@"two"],
[NSMutableString stringWithString:@"three"],
nil
];
NSMutableArray *dataArray2;
NSMutableString *mStr;
// 使用归档进行深拷贝
data = [NSKeyedArchiver archivedDataWithRootObject:dataArray];
dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
return 0;
}
上述代码中的复制操作是通过以下两行来实现的:
data = [NSKeyedArchiver archivedDataWithRootObject:dataArray];
dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
甚至可以避免中间赋值,只用一条语句来执行复制,语句如下:
dataArray2 = [[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:dataArray]];
下次需要生成一个对象(或不支持 NSCopying 协议的对象)的深复制时,应该记住这项技术。