在iOS开发中深拷贝和浅拷贝是一个被大家说烂的话题了,但是今天还是要拿出来说一说。原因是,前段时间在微信朋友圈看到一个朋友发的关于深拷贝和浅拷贝的总结,当时看了一眼,我想他对深拷贝和浅拷贝没有理解,而且被网上很多关于这方面的技术博客误导了,我搜索了一下,发现网上很多的博客跟他下面总结的一样。而且他去面试时,很多面试官也是这么理解深拷贝和浅拷贝的。
他的总结如下图,大家可以看看,想想自己是否也是这么理解深拷贝和浅拷贝的:
下面让我们先来看几个个例子,如果你认可上图的总结,那么看完下面的例子之后,请先想好结果,然后再对照答案,看看自己的结果是否正确。
1. NSMutableArray对象的拷贝
我们先来看看对可变数组NSMutableArray
对象进行拷贝的例子:
//1.创建四个可变字符串
NSMutableString *av_0 = [NSMutableString stringWithString:@"Harry"];
NSMutableString *av_1 = [NSMutableString stringWithString:@"Jamie"];
NSMutableString *av_2 = [NSMutableString stringWithString:@"Micheal"];
NSMutableString *av_3 = [NSMutableString stringWithString:@"Susan"];
//2.创建一个可变数组,用来存放上面四个可变的字符串
NSMutableArray *array_0 = [NSMutableArray arrayWithObjects:av_0,av_1, nil];
//3.我们调用方法 mutableCopy 和 copy 来拷贝数组 array_0
NSMutableArray *array_1 = [array_0 mutableCopy];
NSArray *array_2 = [array_0 copy];
//4. 修改可变字符串 av_0
[av_0 appendString:@" Muhammed"];
//5. 最后我们输出 array_0,array_1,array_2 中的元素,请先思考结果如何是什么
NSLog(@"array_0 = %@ \n array_1 = %@\n array_2 = %@",array_0,array_1,array_2);
首先我们先按照图copy_00.JPG
中关于NSMutableArray
的总结来分析下结果,根据上图我们无论对NSMutableArray
调用mutableCopy
,还是copy
方法,都是深拷贝。因此,当我们对数组array_0
调用拷贝方法之后,同时也会对其中的两个元素av_0
和av_1
进行拷贝。那么当我们改变av_0
之后,对数组array_1
和array_2
中的元素av_0
应该是没有影响的,array_1
和array_2
的输出仍然是Harry
。但是实际结果真的如此吗?让我们看看最后的输出结果:
array_0 = (
"Harry Muhammed",
Jamie
)
array_1 = (
"Harry Muhammed",
Jamie
)
array_2 = (
"Harry Muhammed",
Jamie
)
接着我们再向数组array_0
和array_1
中分别添加一个元素"av_2",av_3。看看三个数组array_0
,array_1
,array_2
中的元素个数是如何变化的,它们之间是否会彼此影响:
[array_0 addObject:av_2];
[array_1 addObject:av_3];
//请思考 array_0,array_1,array_2 的输出是什么?
NSLog(@"array_0 = %@ \n array_1 = %@\n array_2 = %@",array_0,array_1,array_2);
其输出结果如下:
array_0 = (
"Harry Muhammed",
Jamie,
Micheal
)
array_1 = (
"Harry Muhammed",
Jamie,
Susan
)
array_2 = (
"Harry Muhammed",
Jamie
)
由上面的结果我们可以看出,当我们向可变数组array_0
和array_1
中添加元素后,对数组array_02
中的元素个数并没有影响,并且array_0
和array_1
也没彼此影响。
//创建一个不可变数组 array_3
NSArray *array_3 = @[av_2,av_3];
//拷贝数组 array_3 分别赋值给 array_4,array_5
NSMutableArray *array_4 = [array_3 mutableCopy];
NSArray *array_5 = [array_3 copy];
//修改数组 array_3 中的元素 av_2
[av_2 appendString:@" Jackson"];
//请思考 array_3,array_4,array_5 中的元素是什么?
NSLog(@"array_3 = %@ \n array_4 = %@\n array_5 = %@",array_3,array_4,array_5);
array_3 = (
"Micheal Jackson",
"Susan Boyle"
)
array_4 = (
"Micheal Jackson",
"Susan Boyle"
)
array_5 = (
"Micheal Jackson",
"Susan Boyle"
)
//创建数组 array_6,array_7
NSMutableArray *array_6 = [[NSMutableArray alloc] initWithArray:array_3 copyItems:NO];
NSMutableArray *array_7 = [[NSMutableArray alloc] initWithArray:array_3 copyItems:YES];
[av_3 appendString:@" Boyle"];
//请思考 array_6,array_7中的元素是什么?
NSLog(@"array_6 = %@ \n array_7 = %@",array_6,array_7);
array_6 = (
"Micheal Jackson",
"Susan Boyle"
)
array_7 = (
"Micheal Jackson",
Susan
)
2. NSArray对象的拷贝
下面让我们再来看看对不可变数组NSArray
对象进行拷贝的例子:
//1.创建四个可变字符串
NSMutableString *av_0 = [NSMutableString stringWithString:@"Harry"];
NSMutableString *av_1 = [NSMutableString stringWithString:@"Jamie"];
NSMutableString *av_2 = [NSMutableString stringWithString:@"Micheal"];
NSMutableString *av_3 = [NSMutableString stringWithString:@"Susan"];
//2.创建一个不可变数组 array_3
NSArray *array_3 = @[av_2,av_3];
//3.拷贝 array_3 分别赋值给 array_4,array_5
NSMutableArray *array_4 = [array_3 mutableCopy];
NSArray *array_5 = [array_3 copy];
NSArray *array_6 = array_3;
//4.修改数组 array_3 中的元素 av_2
[av_2 appendString:@" Jackson"];
//5.输出数组 array_3, array_4, array_5, array_6 中的元素,请先思考结果如何
NSLog(@"array_3 = %@ \n array_4 = %@\n array_5 = %@ \n array_6 = %@",array_3,array_4,array_5,array_6);
现在我们也按照图copy_00.JPG
中关于NSArray
的总结来分析结果。当我们调用copy
方法对不可变数组NSArray
的对象进行拷贝时,是浅拷贝;而调用mutableCopy
是深拷贝。因此,当我们通过调用copy
方法对array_3
进行拷贝,并赋值给array_5
,此时array_3
和array_5
都指向堆中的同一块内存空间;而通过调用mutableCopy
方法拷贝的结果array_4
和array_3
指向堆中不同的内存空间。那么当我们改变av_2
时,array_3
和array_5
中的av_2
会变化,变成Micheal Jackson
;而array_4
中的av_2
会不变化,仍然是Micheal
。但实际结果真会如此吗,让我们一起来见证奇迹:
array_3 = (
"Micheal Jackson",
Susan
)
array_4 = (
"Micheal Jackson",
Susan
)
array_5 = (
"Micheal Jackson",
Susan
)
array_6 = (
"Micheal Jackson",
Susan
)
我们重新创建一个NSArray
对象,并赋值给array_3
,并看看array_3
,array_4
,array_5
,array_6
的输出结果,代码及运行结果如下:
array_3 = [NSArray arrayWithObjects:av_0,av_1, nil];
NSLog(@"array_3 = %@ \n array_4 = %@\n array_5 = %@ \n array_6 = %@",array_3,array_4,array_5,array_6);
array_3 = (
Harry,
Jamie
)
array_4 = (
"Micheal Jackson",
Susan
)
array_5 = (
"Micheal Jackson",
Susan
)
array_6 = (
"Micheal Jackson",
Susan
)
4.关于 copy 和 mutableCopy
通过上面的两个例子我们可看出,对NSArray
和NSMutableArray
对象无论通过copy
方法,还是mutableCopy
方法进行拷贝,都是浅拷贝。我们只拷贝了容器对象本身,并不复制其中的数据。而copy
和mutableCopy
拷贝的结果 是可变对象和不可变对象。无论当前实例是否可变,若需要获取其可变版本的拷贝,均应该调用mutableCopy
方法。同理,若需要不可变的拷贝,则均应该调用copy
方法获取。
在Foundation框架中的所有collection类在默认情况下都执行浅拷贝,也就是说只拷贝容器对象本身,而不复制其中的数据。这样做的原因在于,容器内的对像未必都能拷贝(即遵守copy协议),而调用者也未必想在拷贝容器时一并拷贝其中的对象。
5. NSArray 和 NSMutableArray 的深拷贝
通过上面的分析我们知道,对数组对象通过copy
和mutableCopy
方法进行拷贝,都是浅拷贝。若我们想对数组进行深拷贝,可以调用该类的初始化方法- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
来执行深拷贝。调用该方法的前提是,数组中的每个元素必须遵守copy
协议。
NSArray *array_7 = @[av_0,av_1];
NSArray *array_8 = [[NSArray alloc] initWithArray:array_7 copyItems:NO];
NSArray *array_9 = [[NSArray alloc] initWithArray:array_7 copyItems:YES];
[av_0 appendString:@" Potter"];
NSLog(@"array_7 = %@ \n array_8 = %@ \n array_9 = %@ ",array_7,array_8,array_9);
若flag
参数设为YES,则会向数组中的每个元素发送copy消息,用拷贝好的元素创建新的数组,并将其返回给调用者。当改变av_0
时,array_9
中的av_0
仍然是Harry
,而array_7
和array_8
中的av_0
则变成 Harry Potter
。输出结果如下:
array_7 = (
"Harry Potter",
Jamie
)
array_8 = (
"Harry Potter",
Jamie
)
array_9 = (
Harry,
Jamie
)
对于Foundation框架中的所有collection(字典,数组,集合)类都提供了深拷贝的初始化方法。但是对于自定义类,因为没有专门的深拷贝协议,所以其具体的执行方式由每个类来确定,我们只需决定自己所写的类是否要提供深拷贝的方法即可。此外,遵守了copy
协议的对象不一定都会执行深拷贝。在大部分情况下,执行的都是浅拷贝。如果需要在某对象上执行深拷贝,除非该文档说明它是用深拷贝来实现copy
协议的,否则要么寻找能够执行深拷贝的相关方法,要么自己编写方法实现深拷贝。
6.深拷贝和浅拷贝
用一个与实例对象相同的内容,生成一个新的对象,这个过程就是复制。其中只复制对象的指针成为浅拷贝
,而复制具有新的内存空间的对象成为深拷贝
。
由上图可以看出,变量A指向了一个对象,而这个对象的实例变量又指向了另一个对象。把变量A复制到变量B时,
图A
只复制对象的指针,将对象代入到变量中;图B
将变量A指向的对象原样复制一份,并通过指针来共享这个对象和复制体中的实例变量;图C
复制对象中的实例变量,并递归的进行对象复制。下图是苹果官方文档关于深拷贝和浅拷贝的说明示例图: