浅拷贝和深拷贝
苹果官方文档是这样解释的
There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection.
谷歌翻译
有两种对象复制:浅拷贝和深拷贝。 正常副本是一个浅拷贝,它产生一个新集合,它与原始对象共享对象的所有权。 深度副本从原始文件创建新对象,并将它们添加到新集合。
更直观的解释如下图:
NSArray的copy
NSArray内部已经实现了NSCopying协议,所以可以直接调用copy方法,假如其内部没实现的话就需要我们自己实现- (id)copyWithZone:(NSZone *)zone
方法,否则就会报错。NSMutableArray同理。
而Foundation框架中提供的所有集合默认都是浅拷贝,验证如下
新建Student和Name类,代码挺简单,就不做解释了
@interface Name : NSObject<NSCopying>
@property (nonatomic, copy) NSString *surname; // 姓
@property (nonatomic, copy) NSString *firstName; // 名
@end
@implementation Name
- (id)copyWithZone:(NSZone *)zone{
Name *copy = [[[self class] allocWithZone:zone] init];
return copy;
}
@end
@interface Student : NSObject<NSCopying>
@property (nonatomic, strong) Name *name;
@property (nonatomic, strong) NSString *address;
@end
@implementation Student
- (instancetype)init {
self = [super init];
if (self) {
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
Student *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copy];
return copy;
}
@end
然后,构建NSArray,通过mutableCopy拷贝一份并打印拷贝前后两者的地址进行比较,代码如下
Student *student = [Student new];
Name *name = [Name new];
student.name = name;
NSMutableArray *studentsArray = [NSMutableArray new];
[studentsArray addObject:student];
NSMutableArray *studentsArrayCopy = [studentsArray mutableCopy];
NSLog(@"\nstudentsArray[0]:%p",studentsArray[0]);
NSLog(@"\nstudentsArrayCopy[0]:%p",studentsArrayCopy[0]);
NSLog(@"\nstudentsArrayAddress:%p",studentsArray);
NSLog(@"\nstudentsArrayCopyAddress:%p",studentsArrayCopy);
打印结果
studentsArray[0]:0x1002013b0
studentsArrayCopy[0]:0x1002013b0
studentsArrayAddress:0x100203210
studentsArrayCopyAddress:0x100203320
可见studentsArray(原数据)和studentsArrayCopy(拷贝后的数据)地址是不同的,而数组中的元素的地址相同,可见通过mutableCopy方法实现的拷贝是浅拷贝。
数组还提供了一种copy方法,- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
,flag表示是否把数组元素也拷贝一份,如果数组的元素没有实现NSCopying协议而且flag设置成YES的话,调用该方法是会Crash的。如果数组中的所有层级都实现了NSCopying方法的话,这种方式的拷贝就是所谓的深拷贝,即完全复制一份。验证如下
Student *student = [Student new];
Name *name = [Name new];
student.name = name;
NSMutableArray *studentsArray = [NSMutableArray new];
[studentsArray addObject:student];
NSMutableArray *studentsArrayCopy = [[NSMutableArray alloc] initWithArray:studentsArray copyItems:YES];
NSLog(@"\nstudentsArray[0]:%p",studentsArray[0]);
NSLog(@"\nstudentsArrayCopy[0]:%p",studentsArrayCopy[0]);
NSLog(@"\nstudentsArrayAddress:%p",studentsArray);
NSLog(@"\nstudentsArrayCopyAddress:%p",studentsArrayCopy);
Name *nameCopy = ((Student *)studentsArrayCopy[0]).name;
NSLog(@"\nname:%p",name);
NSLog(@"\nnameCopy:%p",nameCopy);
打印结果如下
studentsArray[0]:0x100300070
studentsArrayCopy[0]:0x100300930
studentsArrayAddress:0x100300700
studentsArrayCopyAddress:0x100300a00
name:0x100300130
nameCopy:0x1003009e0
所有的原对象和copy对象都不同,可见是深拷贝。当我们要实现复制一个数组并在操作之后对原数组无影响的需求时就可以通过这种方式。
当然,如果数组层次很多的话,这方式明显不可取,幸运的是苹果提供了一种更简单的实现方式(本人没验证过)
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
还有,- (instancetype)initWithArray:(NSArray<ObjectType> *)array;
这种拷贝方式和- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
方式是一样的,当后者的flag设置成NO的时候,这两个这两方法是完全相同的。而且这样方式和直接调用copy方式的拷贝也是一样的
Student *student = [Student new];
Name *name = [Name new];
student.name = name;
NSMutableArray *studentsArray = [NSMutableArray new];
[studentsArray addObject:student];
NSMutableArray *studentsArrayCopy = [[NSMutableArray alloc] initWithArray:studentsArray copyItems:NO];
NSLog(@"\nstudentsArray[0]:%p",studentsArray[0]);
NSLog(@"\nstudentsArrayCopy[0]:%p",studentsArrayCopy[0]);
NSLog(@"\nstudentsArrayAddress:%p",studentsArray);
NSLog(@"\nstudentsArrayCopyAddress:%p",studentsArrayCopy);
Name *nameCopy = ((Student *)studentsArrayCopy[0]).name;
NSLog(@"\nname:%p",name);
NSLog(@"\nnameCopy:%p",nameCopy);
打印结果
studentsArray[0]:0x1004041d0
studentsArrayCopy[0]:0x1004041d0
studentsArrayAddress:0x1004060c0
studentsArrayCopyAddress:0x100406260
name:0x100405ef0
nameCopy:0x100405ef0
数组地址不同,内部元素相同。没毛病。
总结
- 浅拷贝:
1, 直接调用copy/mutableCopy方法
2,[[NSMutableArray alloc] initWithArray:studentsArray copyItems:NO]
3,[[NSMutableArray alloc] initWithArray:studentsArray]
- 深拷贝:
1,[[NSMutableArray alloc] initWithArray:studentsArray copyItems:YES]
,前提要自己实现每一层级的NSCopying协议
2,[NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]]