copy 老生常谈了。估计是个iOS开发者都知道这事。
这篇文章就稍微全面写一写。 原文章 传送门
1、系统对象的copy与mutableCopy NSObject
类提供了copy
和mutableCopy
方法,通过这两个方法即可复制已有对象的副本,本文将会详细介绍关于对象复制的内容。
copy方法用于复制对象的副本。
通常来说,copy
方法总是返回对象的不可修改的副本,即使对象本身是可修改的。
例如,程序调用NSMutableString
的copy
方法,将会返回不可修改的字符串对象,
mutableCopy方法用于复制对象的可变副本。
通常来说,mutableCopy
方法总是返回对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy
方法复制出来的副本也是可修改的。
例如,程序调用NSString
的mutableCopy
方法,将会返回一个NSMutableString
对象。
下图详细阐述了NSString
、NSMutableString
、NSArray
、NSMutableArray
、NSDictionary
、NSMutableDictionary
分别调用copy
与mutableCopy
方法后的结果:
文章图片
2、深复制与浅复制 对象拷贝有两种方式:浅复制和深复制。
顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;
深复制是直接拷贝整个对象内容到另一块内存中。
再简单些说:浅复制就是指针拷贝;深复制就是内容拷贝。
如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况是属于深复制,还是浅复制?对此,苹果官网文档有这样一句话描述:
This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy…从文中可以看出,苹果认为这种复制不是真正的深复制,而是将其称为单层深复制(one-level-deep copy)。因此,有人对
If you need a true deep copy, such as when you have an array of arrays…
浅复制
、完全深复制
、单层深复制
做了概念区分。当然,这些都是概念性的东西,没有必要纠结于此。只要知道进行拷贝操作时,被拷贝的是指针还是内容即可。
一般来说,完全深复制的实现难度大很多,尤其是当该对象包含大量的指针类型的实例变量时,如果某些实例变量里再次包含指针类型的实例变量,那么实现完全深复制会更加复杂。
上面图中的深复制(单层或者完全)就是因为集合对象中可能会包含指针类型的实例变量,从而导致深复制不完全。
3、自定义对象的复制 使用
copy
和mutableCopy
复制对象的副本使用起来确实方便,那么我们自定义的类是否可调用copy
与mutableCopy
方法来复制副本呢?例子
我们先定义一个
SXYPerson
类,代码如下:@interface SXYPerson : NSObject@property (nonatomic, assign) NSInteger age;
@property (nonatomic, copy) NSString *name;
@end
【全面了解Objective-C(Copy)】然后尝试调用
SXYPerson
的copy
方法来复制一个副本: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:
方法。文章图片
Q:大家可能会觉得疑惑,程序只是调用了
copy
和mutableCopy
方法,为什么会提示找不到copyWithZone:
与mutableCopyWithZone:
方法呢?A:其实当程序调用对象的
copy
方法来复制自身时,底层需要调用copyWithZone:
方法来完成实际的复制工作,copy返回实际上就是copyWithZone:
方法的返回值;mutableCopy
与mutableCopyWithZone:
方法也是同样的道理。文章图片
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
运行之后发现我们实现了对象的复制:
文章图片
同时需要注意的是如果对象中有其他指针类型的实例变量,且只是简单的赋值操作:
person.obj2 = self.obj2
.其中
obj2
是另一个自定义类,如果我们修改obj2
中的属性,我们会发现复制后的person
对象中obj2
对象中的属性值也变了,因为对于这个对象并没有进行copy
操作,这样的复制操作不是完全的复制,如果要实现完全的复制,需要将
obj2
对应的类也要实现copy
,然后这样赋值:person.obj2 = [self.obj2 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:'
这段错误提示不允许修改
person
的name
属性,这是因为程序定义name
属性时使用了copy
指示符,该指示符置顶调用setName:
方法时(通过点语法赋值时,实际上是调用对应的setter方法),程序实际上会使用参数的副本对name实际变量复制。也就是说,setName:
方法的代码如下:- (void)setName:(NSMutableString *)name {_name = [name copy];
}
copy
方法默认是复制该对象的不可变副本, 虽然程序传入的
NSMutableString
,但程序调用该参数的copy
方法得到的是不可变副本。因此,程序赋给
SXYPerson
对象的name
实例变量的值依然是不可变字符串。注意:
定义合成
getter
、setter
方法时并没有提供mutableCopy
指示符。因此即使定义实例变量时使用了可变类型,但只要使用copy
指示符,实例变量实际得到的值总是不可变对象。