[iOS 内存管理] 浅拷贝(Shallow Copy)与深拷贝
概念
拷贝的方式有两种:浅拷贝(Shallow Copy)
和深拷贝(Deep Copy)
。 从字面意思理解,浅拷贝,只是拷贝了对象的指针,而不是拷贝对象本身。 深拷贝,是直接拷贝整个对象的内存到另一块内存中。
如下图所示:左侧是浅拷贝,右侧是深拷贝
- 浅拷贝就是拷贝对象的指针,深拷贝就是拷贝对象本身。
拷贝操作
在Objective-C中,通过两个方法 copy
和mutableCopy
可以执行拷贝操作,其中copy是获得一个不可变对象,而mutableCopy是获得一个可变对象。并且两个方法分别调用copyWithZone
和mutableCopyWithZone
两个方法来进行拷贝操作,一个类必须实现copyWithZone
或者mutableCopyWithZone
,才能进行copy
或者mutableCopy
。
拷贝的类型
浅拷贝(shallow copy)
浅拷贝有很多中方法,当你进行浅拷贝时,会向原始的集合发送retain消息,这时引用计数就会 +1 ,同时指针就被拷贝到新的集合中去。
下面是一个实现浅拷贝的例子:
NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];
深拷贝(deep copy)
集合的深拷贝有两种方法。
- 可以用
initWithArray:copyItems:
将第二个参数设置为YES
即可深拷贝,如:
NSDictionary shallowCopyDict = [[NSDictionary alloc]initWithDictionary:someDictionary copyItems:YES];
如果你用这种方法深拷贝,集合里的每个对象都会收到copyWithZone:
消息。如果集合里的对象都遵循NSCopying
协议,那么对象就会被深拷贝到新的集合。如果对象没有遵循 NSCopying
协议,而尝试用这种方法进行深拷贝,会在运行时出错。copyWithZone:
这种拷贝方式只能够提供单层内存拷贝(one-level-deep copy)
,而非真正的深拷贝。
- 第二个方法是将集合进行归档(archive),然后解档(unarchive),
如:
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
这里需要注意:
* 第一种方式copyWithZone:这种拷贝方式只能够提供单层内存拷贝(one-level-deep copy),而非真正的深拷贝。
* 第二种方式归档和解档才是实现真正的深拷贝。
one-level-deep copy 集合的单层深拷贝
这里需要区分一个概念性的问题:
如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深拷贝,还是浅拷贝?对此,苹果官网文档有这样一句话描述:
This kind of copy is only capable of producing a one-level-deep copy.
If you only need a one-level-deep copy, you can explicitly call for one as in Listing
从文中可以看出,苹果认为这种拷贝不是真正的深拷贝,而是将其称为单层深拷贝(one-level-deep copy)。因此,网上有人对浅拷贝、深拷贝、单层深拷贝做了概念区分。
* 浅拷贝(shallow copy):在浅拷贝操作时,对于被拷贝对象的每一层都是指针拷贝。
* 深拷贝(one-level-deep copy):在深拷贝操作时,对于被拷贝对象,至少有一层是深拷贝。
* 完全拷贝(real-deep copy):在完全拷贝操作时,对于被拷贝对象的每一层都是对象拷贝
当然,这些都是概念性的东西。掌握住最核心的问题是
进行拷贝操作时,被拷贝的是指针还是内容即可
系统对象的拷贝
不管是集合类对象,还是非集合类对象,当接收到copy和mutableCopy消息时,都遵循以下准则:
- copy返回不可变对象(immutable);所以,如果对copy返回值使用mutable对象接口就会crash;
- mutableCopy返回可变对象(mutable);
下面将针对非集合类对象和集合类对象的copy和mutableCopy方法进行具体的阐述
非集合对象的copy和mutableCopy
系统非集合类对象指的是 NSString
, NSNumber
... 之类的对象。下面先看个非集合类immutable对象拷贝的例子
非集合&不可变
NSString* str = @"test string";
NSString* strCy = [str copy];
NSMutableString* strMCy = [strCy mutableCopy];
//! 打印输出
NSLog(@" str :%p %p", str, &str);
NSLog(@" strCy :%p %p", strCy, &strCy);
NSLog(@"strMCy :%p %p", strMCy, &strMCy);
//!
str :0x100001040 0x7fff5fbff7e8
strCy :0x100001040 0x7fff5fbff7e0
strMCy :0x1004002f0 0x7fff5fbff7d8
可见
- str和strCy的地址是相同的,所以进行了指针拷贝即浅拷贝
- str和strMCy的地址是不同的,所以进行了内容拷贝即深拷贝
非集合&可变
//! Test 1
NSMutableString* str = [NSMutableString stringWithString:@"m1Str test"];
NSString* strCy = [str copy];
NSMutableString* mStrCy = [str copy];
NSMutableString* mStrMCy = [str mutableCopy];
NSLog(@" str :%p %p", str, &str);
NSLog(@" strCy :%p %p", strCy, &strCy);
NSLog(@" mStrCy :%p %p", mStrCy, &mStrCy);
NSLog(@"mStrMCy :%p %p", mStrMCy, &mStrMCy);
//!
str :0x100308bd0 0x7fff5fbff7e8
strCy :0xdea10af20184a5 0x7fff5fbff7e0
mStrCy :0xdea10af20184a5 0x7fff5fbff7d8
mStrMCy :0x100308df0 0x7fff5fbff7d0
//! Test 2
[mStrCy appendString:@"mstr append"]; // **Crash**
[str appendString:@" str "];
[mStrMCy appendString:@" mStrMCy "];
- 从Test 1可以看出非集合&可变对象无论是
copy
还是mutableCopy
,都是内容拷贝深拷贝。 - Test 2中会出现Crash,原因就是因为mStrCy虽然是可变对象,但是所对应的内容是copy而来的不可变对象。
非集合拷贝结论
从上述三个实验可以看出
非集合 | copy | mutableCopy |
---|---|---|
不可变对象 | **浅 ** | **深 ** |
可变对象 | **深 ** | **深 ** |
集合对象的copy和mutableCopy
集合类对象是指NSArray、NSDictionary、NSSet ... 之类的对象。
集合&不可变
NSArray* arr = @[ @[ @"a", @"b" ], [@[ @"c", @"d" ] mutableCopy], @"AA", [NSMutableString stringWithString:@"a"] ];
NSArray *arrCy = [arr copy];
NSMutableArray *arrMCy = [arr mutableCopy];
//! 分别打印变量的 内存地址 及对象地址
NSLog(@" arr :%p %p [%p %p %p %p]", arr, &arr, arr[0], arr[1], arr[2], arr[3]);
NSLog(@" arrCy :%p %p [%p %p %p %p]", arrCy, &arrCy, arrCy[0], arrCy[1], arrCy[2], arrCy[3]);
NSLog(@"arrMCy :%p %p [%p %p %p %p]", arrMCy, &arrMCy, arrMCy[0], arrMCy[1], arrMCy[2], arrMCy[3]);
//! 输出
arr :0x100403430 0x7fff5fbff7a0 [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]
arrCy :0x100403430 0x7fff5fbff798 [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]
arrMCy :0x1004034f0 0x7fff5fbff790 [0x100401260 0x1004032a0 0x1000010d8 0x1004033a0]
可见
- 不可变集合的copy操作是浅拷贝
- 不可变集合的mutableCopy操作是单层深拷贝拷贝
- 集合内部的元素仍然是浅拷贝,无论该元素是否是集合是否可变
下面在测试一下可变集合的拷贝实验
集合&可变
NSMutableArray* arr = [NSMutableArray arrayWithObjects:@[ @"a", @"b" ], [NSMutableArray arrayWithObject:@"A"], @"AA", [NSMutableString stringWithString:@"a"], nil];
NSArray* arrCy = [arr copy];
NSMutableArray* arrMCy = [arr mutableCopy];
NSLog(@" arr :%p %p [%p %p %p %p]", arr, &arr, arr[0], arr[1], arr[2], arr[3]);
NSLog(@" arrCy :%p %p [%p %p %p %p]", arrCy, &arrCy, arrCy[0], arrCy[1], arrCy[2], arrCy[3]);
NSLog(@"arrMCy :%p %p [%p %p %p %p]", arrMCy, &arrMCy, arrMCy[0], arrMCy[1], arrMCy[2], arrMCy[3]);
arr :0x1005023d0 0x7fff5fbff7d0 [0x1005000d0 0x100502140 0x1000010b8 0x100502320]
arrCy :0x100502500 0x7fff5fbff7c8 [0x1005000d0 0x100502140 0x1000010b8 0x100502320]
arrMCy :0x1005025c0 0x7fff5fbff7c0 [0x1005000d0 0x100502140 0x1000010b8 0x100502320]
可以看出
- 可变集合的copy和mutableCopy都是单层深拷贝
集合拷贝结论
集合 | copy | mutableCopy |
---|---|---|
不可变对象 | **浅 ** | 单层深 |
可变对象 | **单层深 ** | **单层深 ** |
自定义对象的拷贝
浅拷贝
首先创建Person.h和Person.m, 实现<NSCopying>协议
#Person.h
@interface Person : NSObject <NSCopying>
@property (nonatomic,copy) NSString *name;
@end
#Person.m
@implementation Person
@synthesize name;
//实现copyWithZone方法
- (id)copyWithZone:(NSZone *)zone {
Person *p = [[self class] allocWithZone:zone];
p.name = [self name];
return p;
}
@end
Person* person = [[Person alloc] init];
[person setName:@"leo"];
NSArray* arr1 = [[NSArray alloc] initWithObjects:person, @"AA", @[ @"AA" ], [NSMutableArray arrayWithObjects:@"AA", nil], nil];
NSArray* arr2 = [[NSArray alloc] initWithArray:arr1];
NSArray* arr3 = [[NSArray alloc] initWithArray:arr1 copyItems:YES];
[person setName:@"lily"];
//尝试更改name的值
//获取两个数组里的各自Person对象
Person* p1 = [arr1 objectAtIndex:0];
Person* p2 = [arr2 objectAtIndex:0];
Person* p3 = [arr3 objectAtIndex:0];
NSLog(@"arr1 :%p 非集合 :%p 不可变集合 :%p 可变集合 :%p", arr1, arr1[1], arr1[2], arr1[3]);
NSLog(@"arr2 :%p 非集合 :%p 不可变集合 :%p 可变集合 :%p", arr2, arr2[1], arr2[2], arr2[3]);
NSLog(@"arr3 :%p 非集合 :%p 不可变集合 :%p 可变集合 :%p", arr3, arr3[1], arr3[2], arr3[3]);
NSLog(@"p1:%p name:%@", p1, p1.name);
NSLog(@"p2:%p name:%@", p2, p2.name);
NSLog(@"p3:%p name:%@", p3, p3.name);
arr1 :0x100404520 非集合 :0x100002090 不可变集合 :0x1004040a0 可变集合 :0x100404230
arr2 :0x100404630 非集合 :0x100002090 不可变集合 :0x1004040a0 可变集合 :0x100404230
arr3 :0x100404790 非集合 :0x100002090 不可变集合 :0x1004040a0 可变集合 :0x100404780
p1:0x1004006b0 name:lily
p2:0x1004006b0 name:lily
p3:0x100404090 name:leo
其他
1、深浅copy对引用计数的影响
浅copy,类似strong,持有原始对象的指针,会使retainCount加一。
深copy,会创建一个新的对象,不会对原始对象的retainCount变化。
// 也许你会疑问arc下如何访问retainCount属性,这里提供了两种方式(下面代码中a代表一个任意对象,这个对象最好不要是NSString和NSNumber,因为用它们进行测试会出问题)
// kvc方式
NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)a));
// 桥接字方式
NSLog(@"Retain count %@", [a valueForKey:@"retainCount"]);
3、可变和不可变
可变和不可变上文谈的不是很多,因为本文认为这完全与NSCopying和NSMutableCopying的实现息息相关。当然,对于框架类,我们可以简单的认为,copy方法返回的就是不可变对象,mutableCopy返回的就是可变对象。如果是自定义的类,就看你怎么实现NSCopying和NSMutableCopying协议了。
4、copy和block
首先,MRR时代用retain修饰block会产生崩溃,因为作为属性的block在初始化时是被存放在栈区或静态区的,如果栈区的block内调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一旦出了block的初始化作用域,就会引起崩溃。所有MRC中使用copy修饰,将block拷贝到堆上。
其次,在ARC时代,因为ARC自动完成了对block的copy,所以修饰block用copy和strong都无所谓。
5、strong和shallowCopy
这个问题困惑了很久,最后只能得出一个结论,浅copy和strong引用的区别仅仅是浅copy多执行一步copyWithZone:方法。
声明:本文非原创,仅仅整理一些开发技能知识文章,以作存档学习用
参考
http://www.jianshu.com/p/eb1b732b737d
http://www.jianshu.com/p/fd938ff32579