使用场景:
当我们涉及到拷贝时,通常使用的就是copy和mutableCopy这两个方法。
通常的使用场景是下面几种:
1.对象的mutable类型和immutable类型之间的互相转换:
这里的对象通常为NSString-NSMutableString、NSArray-NSMutableArray、NSDictionary-NSMutableDictionary,此外还有NSSet但是很少使用。
2.对block调用copy方法将其从栈赋值到堆中。
3.复制自定义的对象。
拷贝的本质:
最经常使用的场景就是对NSArray和NSMutableArray之间的转换了,甚至可能会给人造成这样一种误解:这两个方法就是专门用来干这事儿的便利方法。实际上这两个方法是定义在根基类NSObject中的:对一个对象调用copy方法后,系统会调用这个对象所属类的copyWithZone:方法(该类必须遵守NSCopying协议并实现copyWithZone:)则则发生runtime error(非编译时错误),并将这个方法的返回值作为copy方法的返回值,也就是调用copy方法会间接调用copyWithZone:,简单猜想:
- (id)copy {
return [self copyWithZone:XXX];
}
实际上你也可以直接调用copyWithZone:方法,但是想想这个时候参数zone应该传递什么呢?
苹果文档中的解释是:
可以理解为这是过去遗留下来的对底层内存的操作,在OC中已经被废弃了。也正是如此才提供了一个新的copy方法来代替它。但你仍然可以这么用:
[item copyWithZone:nil];
只有支持mutable的类需要实现NSMutableCopying中的mutableCopyWithZone:方法,例如系统框架中的NSMutableString、NSMutableArray、NSMutableDictionary,此外mutableCopy与copy同理,不再赘述。
浅拷贝、深拷贝的争论
文章中标题只写了“拷贝”而并没有提及浅拷贝、深拷贝,之所以这样是因为大家普遍对“浅”和“深”在语言表述上存在误解,而在本质上的理解都是正确的、没有争议的。下面简单介绍一下深浅拷贝的本质:
首选需要明确:无论是浅拷贝还是深拷贝都只是针对引用类型而非基本数据类型。对于基本数据类型的拷贝其实就是赋值,并无拷贝的概念。
引用类型的本质是一个指针指向一个对象,即一个引用类型会占用两块内存,这两块内存分别存放两个实体:指针和对象。
浅拷贝 深拷贝浅拷贝:只拷贝指针不拷贝对象
深拷贝:既拷贝指针也拷贝对象
既然大家对这个本质都认可,那么争议来自哪里呢?
先等等,这里还需要强调一下数组NSArray的本质:
我们都知道数组中存放的是对象,也只能是对象,但实际上保存的是一系列指向对象的指针。首先创建数组后在栈中保存一个变量名为array1的指针,指向堆中的数组对象;然后向数组对象中添加一系列的指向对象的指针:item1、item2、item3,这些指针又指向了堆中另外三个区域中的对象“item1”、“item2”、“item3”。
由此可见这里面存在了一个的嵌套的模型,即一个指针指向了对象,这个对象中又包含了一些指针分别指向了另一些对象。那么现在很显然了,对于浅拷贝就是只拷贝图中蓝色的array1,争议就来自于:深拷贝时仅拷贝了一层对象(图中蓝色+红色区域)还是拷贝了两层(图中蓝色+红色+绿色区域)呢?
关于这个问题,苹果官方文档中给出了一个解释,针对上述只拷贝一层对象的情况称之为“one-level-deep-copy”,即单层深拷贝。针对复制了两层及多层的情况为“true-deep-copy”,我们称之为完全深拷贝。
单层深拷贝 完全深拷贝由此可见,只有完全深拷贝才会在堆中拷贝出新的对象(“item”)。现在,我们已经明确了浅拷贝、单层深拷贝、完全深拷贝的概念,因此以后只要把话说全了的情况下就应该不会再有争议了。(实际上,“把话说全”是很难的,生活中的矛盾也常常来源于此,摊手~)
- NSXXX和NSMutableXXX的copy和mutableCopy
在这里NSString有些不同,它不包含嵌套模型,因此它的深拷贝并不区分单层深拷贝或完全深拷贝,而是类似于开篇Item的拷贝模型,这点需要注意。
- 那么如何实现NSArray和NSDictionary这类包含嵌套模型的完全深拷贝呢?
两种方式(以NSArray为例):
1.通过NSArray提供的初始化接口:
-
(instancetype)initWithArray:(NSArray *)array copyItem:(BOOL)flag;
此时flag传递YES,并且array中对象的类必须实现NSCopying中copyWithZone:方法。
例如:
NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
2.重写NSArray的copyWithZone:方法:
重写时创建一个新的NSArray,将当前数组中的对象遍历出来并依次向这些对象调用copy,再保存到新创建的数组中。由于这种方法需要继承NSArray,而苹果建议最好不要继承NSArray,而且实现起来比较麻烦,因此建议采用第一种方式。 -
如何实现两层以上的完全深拷贝?
苹果文档中给我们提供的建议是:归档。
网友评论