Objective-C Copy

NSObject拷贝,深拷贝,浅拷贝,值对象、集合类对象的拷贝

Posted by Ted on December 14, 2017

一、NSObject的copy实现

苹果官方文档对copy的描述

NSObject进行拷贝的方法是调用copy方法

newobj =  [obj copy]

前提是实现NSCopying协议的copyWithZone:方法,否则会导致出现找不到selector的崩溃。**unrecognized selector sent to instance**

NSObject does not itself support the NSCopying protocol. Subclasses must support the protocol and implement the copyWithZone: method. A subclass version of the copyWithZone: method should send the message to super first, to incorporate its implementation, unless the subclass descends directly from NSObject.

NSObject 本身并不支持 NSCopying 协议. 子类必须遵从协议并且实现 copyWithZone: 方法. 除非是直接继承自 NSObject.子类在实现 copyWithZone: 必须先发送消息给 super

简单例子

// Person.h

@interface Person : NSObject<NSCopying>

@property (nonatomic, copy)NSString   *name;
@property (nonatomic, assign)NSUInteger  *age;

@end
//Person.m

@implementation Person

- (id)copyWithZone:(NSZone *)zone{
    Person *copyPerson = [[Person allocWithZone:zone] init];
    copyPerson.age = _age;
    copyPerson.name = _name;
    return copyPerson;
}

@end

img

二、深拷贝(Deep Copy)和浅拷贝(Shallow Copy)

源自苹果官方文档

An object can be copied if its class adopts the NSCopying protocol and implements its single method, copyWithZone:. If a class has mutable and immutable variants, the mutable class should adopt the NSMutableCopying protocol (instead of NSCopying) and implement the mutableCopyWithZone: method to ensure that copied objects remain mutable. You make a duplicate of an object by sending it a copy or mutableCopy message. These messages result in the invocation of the appropriate NSCopying or NSMutableCopying method.

如果一个对象的类采用了NSCopying协议并且实现了它的copyWithZone:方法,那么这个对象就可以被拷贝。

如果一个类具有可变和不可变的变体:那么可变类应该采用NSMutableCopying协议(而不是NSCopying)并实现mutableCopyWithZone:方法来确保拷贝的对象保持可变。

通过发送一个copy或mutableCopy消息来拷贝一个对象。这些消息导致调用适当的NSCopying或NSMutableCopying方法。

Copies of objects can be shallow or deep. Both shallow- and deep-copy approaches directly duplicate scalar properties but differ on how they handle pointer references, particularly references to objects (for example, NSString *str). A deep copy duplicates the objects referenced while a shallow copy duplicates only the references to those objects. So if object A is shallow-copied to object B, object B refers to the same instance variable (or property) that object A refers to. Deep-copying objects is preferred to shallow-copying, especially with value objects.

对象的copy可以是浅的或深的。浅拷贝和深拷贝方法都直接copy属性,但不同之处在于它们如何处理指针引用,特别是对对象的引用(例如NSString * str)。深拷贝复制所引用的对象,而浅拷贝仅复制对这些对象的引用。 因此,如果对象A被浅拷贝到对象B,则对象B引用对象A引用的同一个实例变量(或属性)。 深拷贝对象比浅拷贝更受欢迎,尤其是对于值对象。

img

三、值对象的深拷贝,浅拷贝

在值对象对象(NSString, NSNumber,NSData, NSDate,NSvalue)中:对immutable对象进行copy操作,是指针复制(浅复制),mutableCopy操作时内容复制(深);对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //深复制
  • [mutableObject copy] //深复制
  • [mutableObject mutableCopy] //深复制

img

从示例中可以看到,NSString作为不可变对象,copy为浅复制,只是拷贝了一份引用,地址不变,mutableCopy则是深复制。

四、集合类对象的深拷贝,浅拷贝

部分源自苹果官方文档

1、浅拷贝

When you create a shallow copy, the objects in the original collection are sent a retain message and the pointers are copied to the new collection.

只会对集合里的对象的指针进行复制到新的集合里,有两种方法来进行浅拷贝

NSArray *shallowCopyArray = [someArray copyWithZone:nil];
NSDictionary *shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:NO];

2、深拷贝

There are two ways to make deep copies of a collection. You can use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter. If you create a deep copy of a collection in this way, each object in the collection is sent a copyWithZone: message. If the objects in the collection have adopted the NSCopying protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects. If the objects do not adopt the NSCopyingprotocol, attempting to copy them in such a way results in a runtime error. However, copyWithZone: produces a shallow 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 2

有两种方式进行深拷贝:

你可以用initWithArray:copyItems: 第二个参数传 YES,如果用这种方法,集合里的每个对象都会被发送一个 copyWithZone: 消息,如果集合里的对象已经适配了NSCopying协议,那么这些对象的引用就会被拷贝到新的集合里。如果这些对象没有适配NSCopying协议,这种拷贝方式就会报runtime错误。然而,copyWithZone: 产生的是一个浅拷贝,这种类型的深拷贝,是指对集合对象的进行深拷贝。集合里的对象依旧是浅拷贝。

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

img

通过对比对象地址我们可以发现,NSArray的集合对象是已经进行了深拷贝,但是集合里的每个对象都是进行的浅拷贝。

在集合类对象(NSArray、NSDictionary、NSSet)中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制(浅复制)。用代码简单表示如下:

  • [immutableObject copy] // 浅复制
  • [immutableObject mutableCopy] //单层深复制 //NSArray层级的深复制
  • [mutableObject copy] //单层深复制
  • [mutableObject mutableCopy] //单层深复制

真*深拷贝

如果你需要一个真正的深拷贝:集合里的对象也要进行深拷贝,那么要NSKeyedArchiver,里面的每个对象都要遵从NSCoding协议

img

通过内存地址比对,可以发现,所有的对象都进行了深拷贝。

以上代码都可以在Github下载