leverTsui

要有狮子的力量, 菩萨的心肠

0%

iOS深拷贝和浅拷贝

在工作中,有时会涉及到深拷贝和浅拷贝的内容,发现有些地方理解的不够透彻,所以在网上搜集资料总结一下,主要分四个方面来介绍下iOS中深拷贝和浅拷贝:

  • 对象的拷贝;
  • 集合的拷贝;
  • 如何对集合进行深拷贝?
  • 总结

    对象的拷贝

    对对象进行拷贝,通过调用copymutableCopy方法来实现:
  • 调用 copy方法返回的对象为不可变对象,需要实现NSCopying协议 ;
  • 调用mutableCopy方法返回的对象为可变对象,需要实现NSMutableCopying协议 ;

object copying
上图是苹果文档中关于对象拷贝的实例图,从图中可知:

浅拷贝:object Aobject B及其属性使用同样的内存地址,简单说明的话,可以认为是指针复制;
深拷贝:object Aobject B及其属性使用不同的内存地址,简单说明的话,可以认为是内容复制。

下面通过分析NSStringNSMutableString、和自定义对象DBTestModel,调用copymutableCopy之后,分析其返回的对象及此对象的属性的内存地址来判断其行为是深拷贝还是浅拷贝。

NSString

NSString.png

通过打印的信息可知:

  • 对象string调用copy方法后返回的对象copySting,其所对应的内存地址和对象string一致,即为指针复制;
  • 对象string调用mutableCopy方法后返回的对象mutaCopySting,其所对应的内存地址和对象string不一致,即为指内容复制;
NSMutableString

NSMutableString.png

通过打印的信息可知:

  • 对象mutaString调用copy方法后返回的对象copyMutaString,其所对应的内存地址和对象mutaString不一致,即为内容复制;
  • 对象mutaString调用mutableCopy方法后返回的对象mutaCopyMutaString,其所对应的内存地址和对象mutaString不一致,即为指内容复制;
DBTestModel

下面为自定义的对象’DBTestModel’:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <Foundation/Foundation.h>
#import <Mantle/Mantle.h>

@interface DBTestModel : MTLModel

@property (nonatomic, copy) NSString *text;

@property (nonatomic, strong) NSArray *sourceArray;

@property (nonatomic, strong) NSMutableArray *mutableArray;

- (instancetype)initWithText:(NSString *)text
sourceArray:(NSArray *)sourceArray
mutableArray:(NSMutableArray *)mutableArray;
@end

定义了三个属性,textsourceArraymutableArray

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
- (void)testCustomObject {
NSMutableArray *mutableArray = [NSMutableArray array];
DBTestModel *model = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray];
DBTestModel *copyModel = [model copy];
DBTestModel *mutaCopyModel = [model mutableCopy];

NSLog(@"DBTestModel memory address");
NSLog(@"original :%p", model);
NSLog(@"copy :%p", copyModel);
NSLog(@"mutableCopy:%p", mutaCopyModel);
NSLog(@"\n");

NSLog(@"text memory address");
NSLog(@"original :%p", model.text);
NSLog(@"copy :%p", copyModel.text);
NSLog(@"mutableCopy:%p", mutaCopyModel.text);
NSLog(@"\n");

NSLog(@"sourceArray memory address");
NSLog(@"original :%p", model.sourceArray);
NSLog(@"copy :%p", copyModel.sourceArray);
NSLog(@"mutableCopy:%p", mutaCopyModel.sourceArray);
NSLog(@"\n");

NSLog(@"mutableArray memory address");
NSLog(@"original :%p", model.mutableArray);
NSLog(@"copy :%p", copyModel.mutableArray);
NSLog(@"mutableCopy:%p", mutaCopyModel.mutableArray);
NSLog(@"\n");
}

打印结果如下:
image.png
分析其打印数据可知:

  • DBTestModel实例对象中的属性textsourceArray调用copy后,没有产生一个新的对象,为指针复制,其余均为内容复制。

集合的拷贝

image.png

不可变集合NSArray
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
- (void)testCollectiveCopy {
NSMutableArray *mutableArray1 = [NSMutableArray array];
DBTestModel *model1 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray1];

NSMutableArray *mutableArray2 = [NSMutableArray array];
DBTestModel *model2 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray2];

NSMutableArray *mutableArray3 = [NSMutableArray array];
DBTestModel *model3 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray3];
NSArray *array = @[model1,model2,model3];
NSArray *copyArray = [array copy];
NSMutableArray *mutaCopyArray = [array mutableCopy];

NSLog(@"\nNSArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
array,copyArray,mutaCopyArray);

DBTestModel *firstCopyModel = [copyArray firstObject];
DBTestModel *firstMutableCopyModel = [mutaCopyArray firstObject];
NSLog(@"\nDBTestModel memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1,firstCopyModel,firstMutableCopyModel);

NSLog(@"\nDBTestModel sourceArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1.sourceArray,firstCopyModel.sourceArray,firstMutableCopyModel.sourceArray);
}

打印结果如下:

NSArray.png

分析其打印数据可知:

  • NSArray实例对象调用mutableCopy方法为内容复制外,其余的均为指针拷贝。
可变集合NSMutableArray

测试代码如下:

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
- (void)testCollectiveMutacopy {
NSMutableArray *mutableArray1 = [NSMutableArray array];
DBTestModel *model1 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray1];

NSMutableArray *mutableArray2 = [NSMutableArray array];
DBTestModel *model2 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray2];

NSMutableArray *mutableArray3 = [NSMutableArray array];
DBTestModel *model3 = [[DBTestModel alloc] initWithText:@"text"
sourceArray:@[@"test1",@"test2"]
mutableArray:mutableArray3];

NSArray *array1 = @[model1,model2,model3];
NSMutableArray *mutaArray = [NSMutableArray arrayWithArray:array1];
NSMutableArray *copyMutaArray = [mutaArray copy];
NSMutableArray *mutaCopyMutaArray= [mutaArray mutableCopy];

NSLog(@"\nNSMutableArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
mutaArray,copyMutaArray,mutaCopyMutaArray);

DBTestModel *firstCopyModel = [copyMutaArray firstObject];
DBTestModel *firstMutableCopyModel = [mutaCopyMutaArray firstObject];
NSLog(@"\nDBTestModel memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1,firstCopyModel,firstMutableCopyModel);

NSLog(@"\nDBTestModel sourceArray memory address\noriginal :%p\ncopy :%p\nmutableCopy:%p\n",
model1.sourceArray,firstCopyModel.sourceArray,firstMutableCopyModel.sourceArray);
}

测试结果如下:

NSMutableArray.png

分析其打印数据可知:
NSMutableArray 实例对象调用copymutableCopy方法为内容复制外,数组内容的元素均为指针拷贝。
结合上述测试代码得出的测试数据,得出如下的表格:
image.png

从上图可以看出,NSArrayNSMutableArray对象调用copymutableCopy时,得到的集合中的元素均为指针拷贝,如果想要实现集合对象的深拷贝,应该怎么办呢?

  • 如何对集合进行深拷贝?

  • 集合的单层深复制 (one-level-deep copy)
    可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制,如
    1
    NSArray *copyArray = [[NSArray alloc] initWithArray:array copyItems:YES];
    如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。同时集合里的对象需遵循 NSCopying 协议,否则会崩溃。
    image.png

得到的结果如下:

image.png

从打印结果可以看出,拷贝后和拷贝前的数组中的DBTestModel对象的sourceArray的内存地址是相同的,这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。

  • A true deep copy
    使用归档来实现:
1
2
NSArray *copyArray = [NSKeyedUnarchiver unarchiveObjectWithData:
[NSKeyedArchiver archivedDataWithRootObject:array]];

####小结
在项目中遇到需要需要对对象进行拷贝时,需要理解以下两点:
1、对系统自带的不可变对象进行copy时,为指针复制;
2、对集合类对象进行copymutableCopy时,集合类的元素均为指针复制;
3、如只需对集合进行单层深拷贝,可以使用initWithArray:copyItems:类似的方法,将第二个参数设为YES来实现,如需实现集合的完全复制,可以使用归解档来实现;
4、第三方库 Mantle中的MTLModel类有实现NSCodingNSCopying协议,自定义的类继承MTLModel类即可实现NSCodingNSCopying协议。

参考链接

Object copying
Copying Collections