iOS开发之深复制和浅复制

概述

  • 浅复制
    并不拷贝对象本身,仅仅是拷贝指向对象的指针。
  • 深复制
    直接拷贝整个对象内存到另一块内存中。

无论是深复制还是浅复制,被复制的对象都会被复制一份。浅复制只是复制指针,而深复制除了复制指针外,还会复制指针指向的内容。如下图所示:
copy.png

copy 与 mutableCopy

不论是集合类对象,还是非集合类对象,接受到copymutableCopy消息时,都会循序一下准则。

  • copy返回immutable对象。如果对copy返回值使用mutable对象接口,就会crash。
  • mutableCopy返回mutable对象。

非集合类对象的 copy 与 mutableCopy

系统非集合类对象指的是NSStringNSNumber之类的对象。下面来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不可变对象复制
NSString *str = @"haha";
NSString *strCopy = [str copy];
NSMutableString *strMCopy = [str mutableCopy];

NSLog(@"%p", str);
NSLog(@"%p", strCopy);
NSLog(@"%p", strMCopy);

---------------------------------输出---------------------------------
2018-04-09 18:21:08.339512+0800 copyTest[42882:16259329] 0x10314b078
2018-04-09 18:21:08.339685+0800 copyTest[42882:16259329] 0x10314b078
2018-04-09 18:21:08.339757+0800 copyTest[42882:16259329] 0x60c0002568c0

我们可以看到,strCopystr的地址一样,说明进行了指针复制,而strMCopystr地址不一样,说明进行了内容复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 可变对象复制
NSMutableString *mStr = [NSMutableString stringWithString:@"hahaha"];
NSString *mStrCopy = [mStr copy];
NSString *mStrMCopy = [mStr mutableCopy];

NSLog(@"%p", mStr);
NSLog(@"%p", mStrCopy);
NSLog(@"%p", mStrMCopy);

---------------------------------输出---------------------------------
2018-04-09 18:21:08.339932+0800 copyTest[42882:16259329] 0x60800005d100
2018-04-09 18:21:08.340003+0800 copyTest[42882:16259329] 0xa006168616861686
2018-04-09 18:21:08.340051+0800 copyTest[42882:16259329] 0x608000241020

此时mStrmstrCopymStrMCopy返回的对象内存地址都不一样,说明进行了内容复制。

可以总结为以下情况:

[immutableObject copy] 浅复制,不可变对象copy后,生成不可变对象,依然无法修改,指针指向同一块内存。
[immutableObject mutableCopy] 深复制,不可变对象mutableCopy后,生成可变对象,可以修改,指针指向不同内存。
[mutableObject copy] 深复制,可变对象copy后,生成不可变对象,变成无法修改,指针指向不同内存。
[mutableObject mutableCopy] 深复制,可变对象mutableCopy后,生成可变对象,可以修改,指针指向不同内存。

集合类对象的 copy 与 mutableCopy

集合类对象是指NSArray、NSDictionary、NSSet之类的对象。下面来看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 不可变对象复制
NSArray *array1 = @[@1, @"asdas"];

NSArray *array1Copy = [array1 copy];
NSMutableArray *array1MCopy = [array1 mutableCopy];

NSLog(@"%p", array1);
NSLog(@"%p", array1Copy);
NSLog(@"%p", array1MCopy);
---------------------------------输出---------------------------------
2018-04-09 18:33:17.954789+0800 copyTest[42935:16321517] 0x604000030160
2018-04-09 18:33:17.954860+0800 copyTest[42935:16321517] 0x604000030160
2018-04-09 18:33:17.954935+0800 copyTest[42935:16321517] 0x60400004aad0

可以看到array1array1Copy内存地址是一样的,而array1array1MCopy内存地址是不一样的。说明copy进行了指针复制,而mutableCopy进行了内容复制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

// 可变对象复制
NSMutableArray *array = [NSMutableArray array];
[array addObject:@2];

NSArray *arrayCopy = [array copy];
NSMutableArray *arrayMCopy = [array mutableCopy];

NSLog(@"%p", array);
NSLog(@"%p", arrayCopy);
NSLog(@"%p", arrayMCopy);

---------------------------------输出---------------------------------
2018-04-09 18:33:17.954271+0800 copyTest[42935:16321517] 0x60400004ab30
2018-04-09 18:33:17.954446+0800 copyTest[42935:16321517] 0x6040000065a0
2018-04-09 18:33:17.954526+0800 copyTest[42935:16321517] 0x60400004ab60

此时arrayarrayCopyarrayMCopy返回的对象内存地址都不一样,说明进行了内容复制。
同样我们可以得出结论:

  • 在集合类对象中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copymutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。

    [immutableObject copy] // 浅复制
    [immutableObject mutableCopy] //单层深复制
    [mutableObject copy] //单层深复制
    [mutableObject mutableCopy] //单层深复制

这个代码结论和非集合类的非常相似。但是如果要对集合中的对象复制元素怎么办?如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深复制,还是浅复制?对此,苹果官网文档有这样一句话描述

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 2

1
2
Listing 2 Making a deep copy:
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];

对于集合类型的对象,将 initWithArray:copyItems:第二个参数设置成YES时,会对集合内每一个元素发送copyWithZone:消息,元素进行复制,但是对于元素中指针类型的成员变量,依然是浅拷贝,因此这种拷贝被称为单层深拷贝(one-level-deep copy)

如何进行深复制呢?

如果想进行完全的深复制,可以先通过NSKeyedArchiver将对象归档,再通过 NSKeyedUnarchiver 将对象解归档。由于在归档时,对象中每个成员变量都会收到 encodeWithCoder:消息,相当于将对象所有的数据均序列化保存到磁盘上(可以看成换了种数据格式的复制),再通过initWithCoder:解归档时,就将拷贝过的数据经过转换后读取出来,深复制。

1
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

小结

  • 浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
  • 深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
  • 完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

补充

1
2
3
4
5
6
7
8
NSString *str = @"string";
NSLog(@"%p", str);

str = @"newString";
NSLog(@"%p", str);
---------------------------------输出---------------------------------
2018-04-09 18:47:58.582265+0800 copyTest[43000:16423995] 0x10c96d158
2018-04-09 18:47:58.582298+0800 copyTest[43000:16423995] 0x10c96d178

此处修改的是内存地址。所以第二行应该这样理解:将@”newStirng”当做一个新的对象,将这段对象的内存地址赋值给str

Powered by Hexo and Hexo-theme-hiker

Copyright © 2013 - 2019 Acan's blog All Rights Reserved.

访客数 : | 访问量 :