# 深拷贝与浅拷贝

对象拷贝有两种方式：浅拷贝和深拷贝。顾名思义，浅拷贝，并不拷贝对象本身，仅仅是拷贝指向对象的指针；深拷贝是直接拷贝整个对象内存到另一块内存中。

## 定义

### 浅拷贝 copy

* 指针拷贝 - 将指针中的地址值拷贝一份.
* 只复制指向对象的指针，而不复制引用对象本身。
* copy返回的为imutable对象.如果对copy返回值使用mutable对象接口就会crash

### 深拷贝 mutableCopy

* 对象拷贝 - 直接拷贝内容,内存中存在了两份独立对象本身。
* mutableCopy返回mutable对象
* 在多层数组中,对于被复制对象的每一层都是对象复制.

### 单层深拷贝

* 这种方式只能够提供一层内存拷贝(one-level-deep copy)，并非真正的深拷贝。
* 在多层数组中，对第一层进行内容拷贝，其它层进行指针拷贝.在深复制操作时，对于被复制对象，至少有一层是深复制。

## 一：对非集合类对象的copy操作：

在非集合类对象中：

1、对不可变对象进行copy操作，是指针复制，mutableCopy操作时内容复制；

* 不可变的非集合类对象的copy,对象的内存地址没有改变，只是指针的地址改变了。
* 不可变非集合类对象的mutableCopy,对象的内存地址会改变。

2、对可变对象进行copy和mutableCopy都是内容复制,直接拷贝了对象,对象的内存地址会改变。

* 进行可变对象的copy时,因为copy默认返回的是不可变的，所以对一个可变的字符串进行拷贝的时候，因为类型转变了，默认对其进行深拷贝,对象的内存地址会改变。
* 对可变对象进行mutableCopy时,系统要保证旧的对象和新的对象都是可变的，且他们之间不会相互影响,因此对象的内存地址也会改变。

| object          |             |     |
| --------------- | ----------- | --- |
| immutableObject | copy        | 浅复制 |
| immutableObject | mutableCopy | 深复制 |
| mutableObject   | copy        | 深复制 |
| mutableObject   | mutableCopy | 深复制 |

验证:

```
NSString*string =@"origin";
NSString*stringCopy = [string copy];//浅复制复制后的对象不可变
NSMutableString*mStringCopy = [string mutableCopy];//深复制复制后的对象可变
NSLog(@"%p - %p - %p", string, stringCopy, mStringCopy);
```

输出内容: 0x100001080 - 0x100001080 - 0x1003003e0

通过内存地址的打印可以看出,copy NSString,生成的新对象和原string的内存地址是一样的，copy NSMutableString 生成的新对象与原string的内存地址是不一样的。

```
NSMutableString*string2 = [NSMutableString stringWithString:@"origin2"];
NSString*stringCopy2 = [string2 copy];//深复制复制后的对象不可变
NSMutableString*mStringCopy2 = [string2 mutableCopy];//深复制复制后的对象可变
NSLog(@"%p - %p - %p", string2, stringCopy2, mStringCopy2);
```

输出内容: 0x100103920 - 0x326e696769726f75 - 0x1001039b0

可以看出mStringCopy2、stringCopy2和string2的内存地址都是不一样的,此时都是做内容拷贝。

## 二：集合类对象的copy与mutableCopy

在集合类对象中：

1、不可变对象进行copy操作，是指针拷贝，内存地址不更改.mutableCopy操作时,是内容拷贝,指针地址会更改.

2、可变的集合类对象, copy 与 mutableCopy 都是内容复制。

* 集合对象的内容复制仅限于对象本身，对象元素仍然是指针复制。即这里的内容拷贝仅仅是拷贝"集合类对象"这个对象，集合内的元素仍然是指针拷贝。所以可以定义为是\[单层深拷贝]。

| object          |             |       |
| --------------- | ----------- | ----- |
| immutableObject | copy        | 浅复制   |
| immutableObject | mutableCopy | 单层深复制 |
| mutableObject   | copy        | 单层深复制 |
| mutableObject   | mutableCopy | 单层深复制 |

验证:

```
NSArray* array =@[@"a", @"b", @"c", @"d"];
NSArray* copyArray = [array copy];//浅复制复制后的对象不可变
NSMutableArray* mCopyArray = [array mutableCopy];//单层深复制复制后的对象可变
NSLog(@"%p - %p - %p", array, copyArray, mCopyArray);
```

输出结果:0x100600450 - 0x100600450 - 0x1006038e0

可以看出:copy NSArray 生成的对象和原来的array内存地址是一样的，copy NSMutableArray 生成的对象和原来的array内存地址不一样。

```
NSMutableArray* array2 = [NSMutableArray arrayWithObjects: @"a", @"b", @"c", nil];
NSArray* copyArray2 = [array copy];//单层深复制复制后的对象不可变
NSMutableArray*mCopyArray2 = [array mutableCopy];//单层深复制复制后的对象可变
NSLog(@"%p - %p - %p", array2, copyArray2, mCopyArray2);
```

输出结果: 0x100106fd0 - 0x100600450 - 0x100107020

可以看出mCopyArray2、copyArray2和array2的内存地址都是不一样的。

## 三：集合类对象的深复制

集合对象的深复制有两种方法。以数组为例.

1.可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制.

```
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
```

使用这种方法深复制，集合里的每个对象都会收到 copyWithZone: 消息。

如果集合里的对象遵循 NSCopying 协议，那么对象就会被深复制到新的集合。

如果对象没有遵循NSCopying协议，而尝试用这种方法进行深复制，会在运行时出错。

copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy)，而非真正的深复制。

2.将集合进行归档(archive)，然后解档(unarchive).

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

## 其他

### 自定义对象的copy

在Objective-C中并不是所有的对象都支持Copy，MutableCopy，遵守NSCopying协议的类才可以发送Copy消息，遵守NSMutableCopying协议的类才可以发送MutableCopy消息。

### NSString property属性为什么建议用copy不用strong

1、对源头是NSMutableString的字符串，retain仅仅是指针引用，增加了引用计数器，这样源头改变的时候，用这种retain方式声明的变量（无论被赋值的变量是可变的还是不可变的），它也会跟着改变;而copy声明的变量，它不会跟着源头改变，它实际上是深拷贝。

2、对源头是NSString的字符串，无论是retain声明的变量还是copy声明的变量，当第二次源头的字符串重新指向其它的地方的时候，它还是指向原来的最初的那个位置，也就是说其实二者都是指针引用，也就是浅拷贝。这两者对内存计数的影响都是一样的，都会增加内存引用计数，都需要在最后的时候做处理。

3、其实说白了，对字符串为啥要用这两种方式？还是因为安全问题，比如声明的一个NSString \*str变量，然后把一个NSMutableString \*mStr变量的赋值给它了，如果要求str跟着mStr变化，那么就用retain;如果str不能跟着mStr一起变化，那就用copy。而对于要把NSString类型的字符串赋值给str，那两都没啥区别。不会影响安全性，内存管理也一样。
