Objective-C copy,看我就够了

一、从面向对象到Objective-C概览copy

1、面向对象:

In object-oriented programming, object copying is creating a copy of an existing object, a unit of data in object-oriented programming. The resulting object is called an object copy or simply copy of the original object. Copying is basic but has subtleties and can have significant overhead. There are several ways to copy an object, most commonly by a copy constructor or cloning. Copying is done mostly so the copy can be modified or moved, or the current value preserved. If either of these is unneeded, a reference to the original data is sufficient and more efficient, as no copying occurs.

在面向对象的程序设计中,对象的copy就是创建一个已经存在的对象的copy。这种对象的创建的结果被称为原始对象的copy。copy是很基础的,但是也有其精巧的地方,并且可能造成巨大的消耗。有很多种方式可以copy对象,最常用的就是copy构造器和克隆。copy经常用于对象的修改、移动和保护。如果上述的几种应用都不需要,持有原始对象的引用就足够了,并不需要copy。

2、OC:

In Objective-C, the methods copy and mutableCopy are inherited by all objects and intended for performing copies; the latter is for creating a mutable type of the original object. These methods in turn call the copyWithZone and mutableCopyWithZone methods, respectively, to perform the copying. An object must implement the corresponding copyWithZone method to be copyable.

在OC中,copy和mutableCopy两个方法是被所有对象继承的(有点小毛病,应该指所有继承自NSObject的类),这两个方法就是为copy准备的。其中,mutableCopy是为了创建原始对象的可变类型的copy。这两个方法分别调用copyWithZone和mutableCopyWithZone两个方法来进行copy。一个对象必须实现copyWithZone或者mutableCopyWithZone,才能进行copy或者mutableCopy。

那么,我们可以从以上获取到什么信息?

  • copy经常用于对象的修改、移动和保护。如果上述的几种应用都不需要,持有原始对象的引用就足够了,并不需要copy。
  • 一个类必须实现copyWithZone或者mutableCopyWithZone,才能进行copy或者mutableCopy。

下一阶段,本文将展开讲述OC中的copy相关信息以及如何使用copy方法。

二、Objective-C中copy相关

1、OC中的copy相关内容

  • 在XCode 里Foundation.framework下的Headers里,也在系统里找到原文件:/System/Library/Frameworks/Foundation.framework/Versions/C/Headers/NSObject.h
  • 在/usr/include/objc 下面找到 runtime 的 NSObject.h
  • 修饰属性的关键字copy

2、这里需要注意的有以下几点

  • 若想使用copymutableCopy,需要分别实现NSCopying协议和NSMutableCopying协议,即实现copyWithZone:mutableCopyWithZone:方法。
  • 继承自NSObject的大部分框架类均默认实现了NSCopying,并且一些具备可变类型的类如NSString、NSArray、NSDictionary,以及它们的可变类型类NSMutableString、NSMutableArray和NSMutableDictionary也实现了NSMutableCopying。(查了大部分常用类,均实现了NSCopying,所以暂时这么说吧,可能有人说NSNumber并没有实现NSCopying,那你可以看一下它的父类NSValue,其实现了NSCopying)
  • 对于一些自定义类,需要自己实现NSCopying。具体方式且看下部分。

三、非容器对象的深浅copy

首先,我们谈一下非容器对象的深浅copy,这些非容器对象,包含常用的NSString、NSNumber等,也包括我们自定义的一些非容器类的实例。下面分三个三面进行分析。

1、首先说说深浅copy

准则
浅copy:指针复制,不会创建一个新的对象。
深copy:内容复制,会创建一个新的对象。

此处,不进行过多的解释,从下面的结果分析中,按例子来理解。

2、框架类的深浅copy

准则
探究框架类深copy还是浅copy,需要清楚的是该类如何实现的NSCopying和NSMutableCopy的两个方法copyWithZone:和mutableCopyWithZone:。然而OC并不开源,并且本文这里也不会进行源码的推测。
那么,我们应该遵循怎样一个原则呢?如下:

  • 对immutableObject,即不可变对象,执行copy,会得到不可变对象,并且是浅copy。
  • 对immutableObject,即不可变对象,执行mutableCopy,会得到可变对象,并且是深copy。
  • 对mutableObject,即可变对象,执行copy,会得到不可变对象,并且是深copy。
  • 对mutableObject,即可变对象,执行mutableCopy,会得到可变对象,并且是深copy。

代码

结果分析

3、自定义类的深浅copy

准则
对于一个我们自定义的类型,显然比框架类容易操纵的多。此处就拿NSCopying举例(因为从没有自定义过具有可变类型的类,当然,如果有需要的话,也可以实现NSMutableCopying)。自定义的类就和2中的原则没有半毛钱关系了,一切就看你怎么实现NSCopying协议中的copyWithZone:方法。

代码

结果分析

四、容器对象的深浅copy

前文已经知道了深浅copy的区别,你也大致猜到了为什么将容器对象拿出来作为一块。对,因为容器中可能包含很多对象,而这些对象也需要区分深浅copy。往深里说,容器中可能包含容器对象,那更是麻烦了。不要急,看下面,以NSArray的深浅copy为例,将容器的深浅copy分为四种。

1、浅copy

准则
容器的浅copy,符合三.2中的原则。
代码

结果分析

2、单层深copy

准则
容器的单层深copy,符合三.2中的原则(只是深copy变成了单层深copy)。这里的单层指的是完成了NSArray对象的深copy,而未对其容器内对象进行处理。
代码

结果分析

3、双层深copy

准则
容器的双层深copy已经脱离了三.2中的原则。这里的双层指的是完成了NSArray对象和NSArray容器内对象的深copy(为什么不说完全,是因为无法处理NSArray中还有一个NSArray这种情况)。
代码

结果分析

限制
initWithArray: copyItems:会使NSArray中元素均执行copy方法。这也是我在testArr中放入NSMutableArray和NSMutableString的原因。如果我放入的是NSArray或者NSString,执行copy后,只会发生指针复制;如果我放入的是未实现NSCopying协议的对象,调用这个方法甚至会crash。这里,官方文档的描述有误。

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.

4、完全深copy

准则
如果想完美的解决NSArray嵌套NSArray这种情形,可以使用归档、解档的方式。
代码

结果分析

限制
归档和解档的前提是NSArray中所有的对象都实现了NSCoding协议。

五、拾遗

1、关键字copy

代码与结果

分析

  • 结果1为strong修饰的结果,可见 [mutableStr appendString:@”456″]修改mutableStr造成了self.str的改变,显然不安全;结果2为copy修饰的结果,可见 [mutableStr appendString:@”456″]修改mutableStr未造成self.str的改变,显然安全。(从内存地址的变化也可以看出来)
  • 这里可以推测出,copy关键字是在str属性的set方法里面返回了mutableStr的copy,而strong关键字仅仅是返回了mutableStr。

2、深浅copy对引用计数的影响

浅copy,类似strong,持有原始对象的指针,会使retainCount加一。
深copy,会创建一个新的对象,不会对原始对象的retainCount变化。

3、可变和不可变

可变和不可变上文谈的不是很多,因为本文认为这完全与NSCopying和NSMutableCopying的实现息息相关。当然,对于框架类,我们可以简单的认为,copy方法返回的就是不可变对象,mutableCopy返回的就是可变对象。如果是自定义的类,就看你怎么实现NSCopying和NSMutableCopying协议了。

4、copy和block

首先,MRR时代用retain修饰block会产生崩溃,因为作为属性的block在初始化时是被存放在静态区的,如果block内调用外部变量,那么block无法保留其内存,在初始化的作用域内使用并不会有什么影响,但一旦出了block的初始化作用域,就会引起崩溃。所有MRC中使用copy修饰,将block拷贝到堆上。
其次,在ARC时代,因为ARC自动完成了对block的copy,所以修饰block用copy和strong都无所谓。

5、strong和shallowCopy

这个问题困惑了很久,最后只能得出一个结论,浅copy和strong引用的区别仅仅是浅copy多执行一步copyWithZone:方法。

六、文献

1、https://developer.apple.com/library/prerelease/content/documentation/Cocoa/Conceptual/Collections/Articles/Copying.html#//apple_ref/doc/uid/TP40010162-SW1
2、https://en.wikipedia.org/wiki/Object_copying

七、感谢

最后,感谢来自百度的@杨飞宇同学,和他的讨论给了我很多灵感。也感谢大家的阅读,希望对您有所帮助。如果有错误的地方或者不理解的地方,希望大家在评论区积极指出。如果对您有所帮助,希望给作者一个喜欢和关注,您的支持是我最核心的动力。

1 2 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部