全面了解Objective-C(Copy)

copy 老生常谈了。估计是个iOS开发者都知道这事。
这篇文章就稍微全面写一写。 原文章 传送门
1、系统对象的copy与mutableCopy NSObject类提供了copymutableCopy方法,通过这两个方法即可复制已有对象的副本,本文将会详细介绍关于对象复制的内容。
copy方法用于复制对象的副本。
通常来说,copy方法总是返回对象的不可修改的副本,即使对象本身是可修改的。
例如,程序调用NSMutableStringcopy方法,将会返回不可修改的字符串对象,
mutableCopy方法用于复制对象的可变副本。
通常来说,mutableCopy方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy方法复制出来的副本也是可修改的。
例如,程序调用NSStringmutableCopy方法,将会返回一个NSMutableString对象。
下图详细阐述了NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionary分别调用copymutableCopy方法后的结果:
全面了解Objective-C(Copy)
文章图片

2、深复制与浅复制 对象拷贝有两种方式:浅复制和深复制。
顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;
深复制是直接拷贝整个对象内容到另一块内存中。
再简单些说:浅复制就是指针拷贝;深复制就是内容拷贝。
如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深复制,还是浅复制?对此,苹果官网文档有这样一句话描述:

This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy…
If you need a true deep copy, such as when you have an array of arrays…
从文中可以看出,苹果认为这种复制不是真正的深复制,而是将其称为单层深复制(one-level-deep copy)。因此,有人对浅复制完全深复制单层深复制做了概念区分。
当然,这些都是概念性的东西,没有必要纠结于此。只要知道进行拷贝操作时,被拷贝的是指针还是内容即可。
一般来说,完全深复制的实现难度大很多,尤其是当该对象包含大量的指针类型的实例变量时,如果某些实例变量里再次包含指针类型的实例变量,那么实现完全深复制会更加复杂。
上面图中的深复制(单层或者完全)就是因为集合对象中可能会包含指针类型的实例变量,从而导致深复制不完全。
3、自定义对象的复制 使用copymutableCopy复制对象的副本使用起来确实方便,那么我们自定义的类是否可调用copymutableCopy方法来复制副本呢?
例子
我们先定义一个SXYPerson类,代码如下:
@interface SXYPerson : NSObject@property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSString *name; @end

【全面了解Objective-C(Copy)】然后尝试调用SXYPersoncopy方法来复制一个副本:
SXYPerson *person1 = [[SXYPerson alloc] init]; //创建一个SXYPerson对象 person1.age = 20; person1.name = @"苏小妖"; SXYPerson *person2 = [person1 copy]; //复制副本

运行程序,将会发生崩溃,并输出以下错误信息:
[SXYPerson copyWithZone:]: unrecognized selector sent to instance 0x608000030920

上面的提示:SXYPerson找不到copyWithZone:方法。
然后,
我们将复制副本的代码换成如下:
SXYPerson *person2 = [person1 mutableCopy]; //复制副本

再次运行程序,程序同样崩溃了,并输出去以下错误信息:
[SXYPerson mutableCopyWithZone:]: unrecognized selector sent to instance 0x600000221120

上面的提示:SXYPerson找不到mutableCopyWithZone:方法。
全面了解Objective-C(Copy)
文章图片

Q:大家可能会觉得疑惑,程序只是调用了copymutableCopy方法,为什么会提示找不到copyWithZone:mutableCopyWithZone:方法呢?
A:其实当程序调用对象的copy方法来复制自身时,底层需要调用copyWithZone:方法来完成实际的复制工作,copy返回实际上就是copyWithZone:方法的返回值;mutableCopymutableCopyWithZone:方法也是同样的道理。
全面了解Objective-C(Copy)
文章图片

Q:那么怎么做才能让自定义的对象进行copy与mutableCopy呢?
A:需要做以下事情:
1.让类实现NSCopying/NSMutableCopying协议。
2.让类实现copyWithZone:/mutableCopyWithZone:方法
所以让我们的SXYPerson类能够复制自身,我们需要让SXYPerson实现NSCopying协议;然后实现copyWithZone:方法:
@interface SXYPerson : NSObject @property (nonatomic, assign) NSInteger age; @property (nonatomic, copy) NSString *name; @end

#import "SXYPerson.h"@implementation SXYPerson- (id)copyWithZone:(NSZone *)zone {SXYPerson *person = [[[self class] allocWithZone:zone] init]; person.age = self.age; person.name = self.name; return person; } @end

运行之后发现我们实现了对象的复制:
全面了解Objective-C(Copy)
文章图片

同时需要注意的是如果对象中有其他指针类型的实例变量,且只是简单的赋值操作:person.obj2 = self.obj2.
其中obj2是另一个自定义类,如果我们修改obj2中的属性,我们会发现复制后的person对象中obj2对象中的属性值也变了,因为对于这个对象并没有进行copy操作,这样的复制操作不是完全的复制,
如果要实现完全的复制,需要将obj2对应的类也要实现copy,然后这样赋值:person.obj2 = [self.obj2 copy]
全面了解Objective-C(Copy)
文章图片

(
如果对象很多或者层级很多,实现起来还是很麻烦的。如果需要实现完全复制同样还有另有一种方法,那就是归档:
SXYPerson *person2 = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:person1]];

)
这样我们就实现了自定义对象的复制,需要指出的是如果重写copyWithZone:方法时,其父类已经实现NSCopying协议,并重写过了copyWithZone:方法,那么子类重写copyWithZone:方法应先调用父类的copy方法复制从父类继承得到的成员变量,然后对子类中定义的成员变量进行赋值:
- (id)copyWithZone:(NSZone *)zone {id obj = [super copy]; //对子类定义的成员变量赋值 ... return obj; }

关于mutableCopy的实现与copy的实现类似,只是实现的是NSMutableCopying协议与mutableCopyWithZone:方法。
对于自定义的对象,在我看来并没有什么可变不可变的概念,因此实现mutableCopy其实是没有什么意义的,在此就不详细介绍了。
3、定义属性的copy指示符 如下段代码,我们在定义属性的时候使用了copy指示符:
#import @interface SXYPerson : NSObject(NSCopying)(因识别问题此处圆括号替换尖括号)@property (nonatomic, copy) NSMutableString *name; @end

使用如下代码来进行测试:
SXYPerson *person1 = [[SXYPerson alloc] init]; //创建一个SXYPerson对象 person1.name = [NSMutableString stringWithString:@"苏小妖"]; [person1.name appendString:@"123"];

运行程序会崩溃,并且提示以下信息:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'

这段错误提示不允许修改personname属性,这是因为程序定义name属性时使用了copy指示符,该指示符置顶调用setName:方法时(通过点语法赋值时,实际上是调用对应的setter方法),程序实际上会使用参数的副本对name实际变量复制。也就是说,setName:方法的代码如下:
- (void)setName:(NSMutableString *)name {_name = [name copy]; }

copy方法默认是复制该对象的不可变副本,
虽然程序传入的NSMutableString,但程序调用该参数的copy方法得到的是不可变副本。
因此,程序赋给SXYPerson对象的name实例变量的值依然是不可变字符串。
注意:
定义合成gettersetter方法时并没有提供mutableCopy指示符。因此即使定义实例变量时使用了可变类型,但只要使用copy指示符,实例变量实际得到的值总是不可变对象。

    推荐阅读