浅拷贝和深拷贝

浅拷贝和深拷贝,这算是一个老生常谈的话题了吧。

概念

先总结下吧。

浅拷贝:对内容引用地址的拷贝,不拷贝内容

深拷贝:对内容进行拷贝,因此也会生成新的引用地址

非集合类型

以 NSString 为 🌰 吧。

1
2
3
4
5
6
7
8
9
10
11
12
NSString *str = @"str";
NSString *strC = str.copy;
NSMutableString *strMC = str.mutableCopy;
NSLog(@"\nstr: %@-%p \nstrC: %@-%p \nstrMC: %@-%p",str, str,strC, strC, strMC, strMC);
/*result
str: str-0x100002108
strC: str-0x100002108
strMC: str-0x100523fa0
*/

可见,NSString 进行 copy 后,只是拷贝了引用地址,但是使用 mutableCopy 后,则经过拷贝内容,从而得到新的引用地址,并且得到的是可变字符串 — NSMutableString。

那么,如果是以 NSMutableString 为 🌰 呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NSMutableString *mstr = [NSMutableString stringWithFormat:@"%@", @"mstr"];
NSString *mstrC = mstr.copy;
NSString *mstrMC = mstr.mutableCopy;
NSLog(@"\nmstr: %@-%p \nmstrC: %@-%p \nmstrMC: %@-%p",mstr, mstr,mstrC, mstrC, mstrMC, mstrMC);
/* result
mstr: mstr-0x100731b10
mstrC: mstr-0x7274736d45
mstrMC: mstr-0x100731a50
*/
NSLog(@"%@",@([mstrC isKindOfClass:NSMutableString.class]));
/* result
0
*/

当一个 NSMutableString 进行拷贝时,只有深拷贝。并且使用 copy 得到的并不是一个 NSMutableString,而只是一个 NSString 而已。

集合类型

先以 NSArray 为🌰 吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
NSArray *arr = @[@"AA"];
NSArray *arrC = arr.copy;
NSMutableArray *arrMC = arr.mutableCopy;
NSLog(@"\narr: %@-%p \narrC: %@-%p \narrMC: %@-%p",arr, arr,arrC, arrC, arrMC, arrMC);
/* result
arr: (
AA
)-0x10073a740
arrC: (
AA
)-0x10073a740
arrMC: (
AA
)-0x10073d100
*/

由此可见,NSArray 使用 copy 是进行浅拷贝,而 mutableCopy 则是深拷贝,并且得到一个可变集合类型。而使用 NSMutableArray 进行 copy 和 mutableCopy,前者得到的是一个 NSArray,后者得到的是一个 NSMutableArray。

然而事实上,集合类型的 mutableCopy 其实不算是真正的深拷贝。原因是集合类型使用 mutableCopy 后,虽然是拷贝了集合中的元素内容,但实际上只是拷贝了其引用地址。按照官方的说法,这样进行的深拷贝,其实只是一级深拷贝。

This kind of copy is only capable of producing a one-level-deep copy.

另外,除了使用 copy 和 mutableCopy 外,还可以使用以下方式来进行拷贝。

1
2
3
NSArray *arrCIS = [[NSArray alloc] initWithArray:arr copyItems:NO];
NSArray *arrCID = [[NSArray alloc] initWithArray:arr copyItems:YES];

根据 copyItems 的 YES/NO ,来决定是否单层深复制。

按照官方的说法,使用这个方法进行拷贝,实际上是对数组中每个元素进行浅拷贝,也就是调用 copyWithZone 方法。因此要求元素必须实现 NSCoping 协议,否则会引起错误闪退。并且,使用这个方法进行拷贝,会对原数组进行retain,然后得到一个新数组。

从上述例子,可以知道对于单纯的 copy,不管 NSArray 或者 NSString 等不可变对象,引用地址并不会进行改变。因此,使用initWithArray: copyItems只有对可变对象的元素进行 copy,才有比较明显的区分。但也是仅仅做到单层深复制而已。以下是来自官方的描述。

  • copyWithZone: makes the surface level immutable. All deeper levels have the mutability they previously had.
  • initWithArray:copyItems: with NO as the second parameter gives the surface level the mutability of the class it is allocated as. All deeper levels have the mutability they previously had.
  • initWithArray:copyItems: with YES as the second parameter gives the surface level the mutability of the class it is allocated as. The next level is immutable, and all deeper levels have the mutability they previously had.
  • Archiving and unarchiving the collection leaves the mutability of all levels as it was before.

那么要如何进行真正意义上的深拷贝呢?那么则需要通过序列化和反序列化来达到目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NSArray *arr = @[@"AA"];
NSArray *arrKU = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:arr]];
NSLog(@"\narr: %@-%p \narrKU: %@-%p",arr, arr, arrKU, arrKU);
/* result
arr: (
AA
)-0x1028091b0
arrKU: (
AA
)-0x100561730
*/
NSLog(@"\n first element \narr: %@-%p \narrKU: %@-%p", arr.firstObject, arr.firstObject, arrKU.firstObject, arrKU.firstObject);
/* result
first element
arr: AA-0x1000021a8
arrKU: AA-0x414125
*/

小小概括下吧~

类型 使用 copy 使用 mutableCopy
可变对象 地址变化,并且得到一个不可变对象 地址变化
不可变对象 地址不变 地址变化

自定义对象

自定义对象,如果是要实现 copy 功能,那么需要 NSCopying 协议。

1
2
3
4
5
6
7
8
9
10
@interface Person : NSObject<NSCopying>
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person *copyPerson = [[[self class] allocWithZone:zone] init];
return copyPerson;
}
@end

另外,如果自定义对象有自定义对象属性的话,那么需要保证其也实现 NSCopying。

copy & Strong

我们在使用 @property 增加属性时,经常性用到 copy 和 strong 关键字。网络上也有很多文章在谈论,比如 NSString 到底是用 copy 还是 strong 呢。诸如此类的。

先看 🌰 吧。

不可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, copy) NSString *cStr;
@property (nonatomic, strong) NSArray *sArr;
@property (nonatomic, copy) NSArray *cArr;
@implementation XXXXX
.....
NSString *sourceStr = @"str";
self.strongStr = sourceStr;
self.cStr = sourceStr;
NSLog(@"\nsourceStr: %p\nstrongStr: %p\ncopyStr: %p", sourceStr, self.strongStr,self.cStr);
/* result:
sourceStr: 0x108efbb20
strongStr: 0x108efbb20
copyStr: 0x108efbb20
*/
NSArray *arr = @[@"a"];
self.cArr = arr;
self.sArr = arr;
NSLog(@"\narr: %p-%@\ncArr: %p-%@\nsArr: %p-%@", arr,arr,self.cArr, self.cArr,self.sArr,self.sArr);
/* result:
arr: 0x60000000ff80-(
a
)
cArr: 0x60000000ff80-(
a
)
sArr: 0x60000000ff80-(
a
)
*/
....
@end

可见,不可变对象,比如 NSString ,NSArray,甭管使用 copy 还是 strong 关键字,实际上都是进行了浅拷贝。

可变对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@property (nonatomic, strong) NSMutableString *smStr;
@property (nonatomic, copy) NSMutableString *cmStr;
@property (nonatomic, strong) NSMutableArray *smArr;
@property (nonatomic, copy) NSMutableArray *cmArr;
@implementation XXXXX
.....
// NSMutableString
NSMutableString *msStr = [NSMutableString stringWithFormat:@"%@",@"str"];
self.smStr = msStr; // strong NSMutableString property
self.cmStr = msStr; // copy NSMutableString property
NSLog(@"\nmsStr: %p\nsmStr: %p\ncmStr: %p", msStr, self.smStr,self.cmStr);
/* result
msStr: 0x60400024db00
smStr: 0x60400024db00
cmStr: 0xa000000007274733
*/
NSLog(@"cmStr %@ NSMutableString", [self.cmStr isKindOfClass:NSMutableString.class] ? @"is": @"isn't");
/* result
cmStr isn't NSMutableString
*/
NSLog(@"smStr %@ NSMutableString", [self.smStr isKindOfClass:NSMutableString.class] ? @"is": @"isn't");
/* result
smStr is NSMutableString
*/
[msStr appendString:@"str"];
NSLog(@"\nmsStr: %p-%@\nsmStr: %p-%@\ncmStr: %p-%@", msStr,msStr,self.smStr, self.smStr,self.cmStr,self.cmStr);
/* result
msStr: 0x60400024db00-strstr
smStr: 0x60400024db00-strstr
cmStr: 0xa000000007274733-str
*/
// NSMutableArray
NSMutableArray *mArr = [NSMutableArray arrayWithArray:arr];
self.cmArr = mArr;
self.smArr = mArr;
NSLog(@"\nmArr: %p-%@ cmArr: %p-%@ smArr: %p-%@", mArr,mArr,self.cmArr, self.cmArr,self.smArr,self.smArr);
/* result
mArr: 0x604000244410-(
a
) cmArr: 0x6040000170a0-(
a
) smArr: 0x604000244410-(
a
)
*/
NSLog(@"cmArr %@ NSMutableArray", [self.cmArr isKindOfClass:NSMutableArray.class] ? @"is": @"isn't");
/* result
cmArr isn't NSMutableArray
*/
NSLog(@"smArr %@ NSMutableArray", [self.smArr isKindOfClass:NSMutableArray.class] ? @"is": @"isn't");
/* result
smArr is NSMutableArray
*/
[mArr addObject:@"b"];
NSLog(@"\nmArr: %p-%@ cmArr: %p-%@ smArr: %p-%@", mArr,mArr,self.cmArr, self.cmArr,self.smArr,self.smArr);
/* result
mArr: 0x604000244410-(
a,
b
) cmArr: 0x6040000170a0-(
a
) smArr: 0x604000244410-(
a,
b
)
*/
....
@end

可见,对于可变对象而言,使用 copy 关键字后,进行了浅拷贝,并且会得到对应的不可变对象。而使用 strong 关键字,实际上是对旧对象的一种持有吧。

尾声

浅拷贝和深拷贝,大概就这些吧。

参考

  1. Memory Management Programming Guide for Core Foundation - Copy Functions
  2. What is the difference between a deep copy and a shallow copy?
  3. Object copying
  4. Copying Collections