一文说通 copy / mutableCopy 和 shallow copy / deep copy 区别

1,307 阅读3分钟

结论

在 Cocoa 中不存在真正的 deep copy

接下来,我们需要明确关于 shallow copy 和 deep copy 的定义(源自 wiki


Shallow copy

创建一个新对象 B,并将 A 的字段值复制到 B 中。如果 A 中的字段值是原始类型,复制原始类型的值到 B 中;如果字段值是对象的引用,则复制这个引用。

Deep copy

这是一个递归的复制过程。字段会被解引用,此时会生成引用的对象的副本。也就是说 deep copy 会完整的复制一个新的对象(其中引用的对象都会生成新的实例)。

Screen_Shot_2021-03-25_at_3.35.04_PM.png

(需要注意图中并没有体现递归处理,但是我们要知道因为被解引用所以完整的深拷贝是有递归的处理逻辑的)


Copy

copy 方法实际是调用了 copyWithZone: 只不过 NSZone 这个参数已经不再被 OC 使用了。所以这相当于为空的参数。查看 copyWithZone:

返回对象会被隐式的 retain,所以消息的发送方,需要负责 release。 如果对于接受消息的对象,考虑可变和不可变的特性,则返回的副本是不可变的。否则,副本的确切性质由类决定。

重点是最后一句话,副本的确切性质和接受这个消息的类有关系。

例1:

NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, nil];

id arr1 = [arr copy];  // arr1 无法响应 addObject: 这样的消息, arr1 是一个新的 NSArray 实例

例2:


NSString *str = @"123";

str1  = [str copy]; // 这里 str 和 str1 是全等的,没有生成新的字符串对象。

MutableCopy

返回的对象由发送方隐式保留,发送方负责释放它。无论原始副本是否可变,返回的副本都是可变的。

划重点,网上的很多 Blog 都认为这个就是深拷贝了,实际上不是的。因为按照文档的说法这里只是发生了一次浅拷贝。对于容器类型来说也只是有一个额外的方法来执行 one-level-deep copy,但也不是 true deep copy

NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];

为什么有 copy 和 mutableCopy 的出现?

  • 表面原因是为了类型的转换(NSMutableArray, NSArray, NSMutableString 以及 NSString 之类)。

  • 实际是为了减少不必要的内存开销(不需要修改的东西,当然不需要分配新的内存空间)。

如何实现 deep copy

对于集合类型只提供了至多一层的拷贝方法(one-level-deep copy)。例如:

NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];

如果想要实现一个真正的 deep copy,苹果是这么推荐的:

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

需要归档和反归档来实现,或者序列化和反序列化。

总结

  1. 不可变对象调用 copy,不会生成新的对象,因为没有必要。指针指向同一地址即可满足。

  2. 不可变对象调用 mutableCopy,生成新的对象,因为需要修改而原对象不支持修改。

  3. 可变对象调用 copy,生成新的对象,因为预期是获得一个不可变对象。

  4. 可变对象调用 mutableCopy,生成新对象,可变对象的修改不应影响到原对象,所以会生成新对象。

参考资料

  1. Copying Collections

  2. ObjectCopying

  3. MemoryManagement