本条要点:(作者总结)

  • 若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。
  • 如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying 与 NSMutableCopying 协议。
  • 复制对象时需要决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝。
  • 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。

  使用对象时经常需要拷贝它。在 Objective-C 中,此操作通过copy 方法来完成。如果想令自己的类支持拷贝操作,那就要实现 NSCopying 协议,该协议只有一个方法:

1 - (id)copyWithZone:(NSZone *)zone;

  为何会出现 NSZone 呢?因为以前开发程序时,会据此把内存分成不同的 "区"(zone),而对象会创建在某个区里面。现在不用了,每个程序只有一个区:“默认区”(default zone)。所以说,尽管必须实现这个方法,但是你不必担心其中的zone 参数。

  copy 方法由 NSObject 实现,该方法只是以 “默认区”为参数来调用 “copyWithZone:”。我们总是想覆写 copy 方法,其实真正需要实现的却是 “copyWithZone:”方法,这个问题大家一定要注意。

  若想使某个类支持拷贝功能,只需要声明该类遵从 NSCopying 协议,并实现其中的那个方法即可。比方说,有个表示个人信息的类,可以在其接口定义中声明此类遵从 NSCopying 协议:

 1 #import <Foundation/Foundation.h>
 2
 3 @interface EOCPerson : NSObject <NSCopying>
 4
 5 @property (nonatomic, copy, readonly) NSString *firstName;
 6 @property (nonatomic, copy, readonly) NSString *lastName;
 7
 8 - (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
 9
10 @end

  然后,实现协议中规定的方法:

1 - (id)copyWithZone:(NSZone *)zone {
2     EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
3     return copy;
4 }

  在本例所实现的 “copyWithZone:”中,我们直接把待拷贝的对象交给 “全能初始化方法”(designated initializer),令其执行所有初始化工作。然而有的时候,除了要拷贝对象,还要完成其他一些操作,比如类对象中的数据结构可能并为在初始化方法中设置好,需要另行设置。举个例子,假如 EOCPerson 中含有一个数组,与其他 EOCPerson 对象创立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。下面列出了实现此功能所需要的全部代码:

 1 #import <Foundation/Foundation.h>
 2
 3 @interface EOCPerson : NSObject <NSCopying>
 4
 5 @property (nonatomic, copy, readonly) NSString *firstName;
 6 @property (nonatomic, copy, readonly) NSString *lastName;
 7
 8 - (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName;
 9 - (void)addFriend:(EOCPerson *)person;
10 - (void)removeFriend:(EOCPerson *)person;
11
12 @end

 1 #import "EOCPerson.h"
 2
 3 @implementation EOCPerson {
 4     NSMutableSet *_friends;
 5 }
 6
 7 - (instancetype)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName {
 8     if ((self = [super init])) {
 9         _firstName = [firstName copy];
10         _lastName = [lastName copy];
11         _friends = [NSMutableSet new];
12     }
13     return self;
14 }
15
16 - (void)addFriend:(EOCPerson *)person {
17     [_friends addObject:person];
18 }
19
20 - (void)removeFriend:(EOCPerson *)person {
21     [_friends removeObject:person];
22 }
23
24 - (id)copyWithZone:(NSZone *)zone {
25     EOCPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName andLastName:_lastName];
26     copy->_friends = [_friends mutableCopy];
27     return copy;
28 }
29
30 @end

  这次实现的方法比原来多了一些代码,它把本对象的 _friends 实例变量复制了一份,令 copy 对象的 _friends 实例变量指向这个复制过的 set。注意,这里使用了 -> 语法,因为 _friends 并非属性,只是个在内部使用的实例变量。其实也可以声明一个属性来表示它,不过由于该变量不会在本类之外使用,所以那么做没必要。

  这个例子提出了一个有趣的问题:为什么要拷贝 _friends 实例变量呢? 不拷贝这个变量,直接令两个对象共享同一个可变的 set 是否更简单些?如果真的那样做了,那么在给原来的对象添加一个新朋友后,拷贝过的那个对象居然也 “神奇地”(magically)与之为友了。在本例中,这显然不是我们想要的结果。然而,那个 set 若是不可变的,则无须复制,因为其中的内容毕竟不会改变,所以不用担心此类问题。如果复制了,那么内存中将会有两个一模一样的 set,反而造成浪费。

  通常情况下,应该像本例这样,采用全能初始化方法来初始化待拷贝的对象。不过有些时候不能这么做,因为全能初始化方法会产生一些 “副作用”(side effect),这些附加操作对目前要拷贝的对象无益。比如,初始化方法可能要设置一个复杂的内部数据结构,可是在拷贝后的对象中,这个数据结构立刻就要用其他数据来覆写,所以没有必要再设置一遍。

  仔细看看刚才的 “copyWithZone:” 方法,你就会发现,存放朋友对象的那个 set 是通过 mutableCopy 方法来复制的。此方法来自另一个叫做 NSMutableCopying 的协议。该协议与 NSCopying 类似,也只定义了一个方法,然而方法名不同:

1 - (id)mutableCopyWithZone:(NSZone *)zone;

  mutableCopy 这个 “辅助方法”(helper)与 copy 相似,也是用默认的 zone 参数来调 “mutableCopyWithZone:”。如果你的类分为可变版本(mutable variant)与不可变版本(immutable variant),那么就应该实现 NSMutableCopying 。若采用此模式,则在可变类中覆写 “copytWithZone:” 方法时,不要返回可变的拷贝,而应该返回一份不可变的版本。无论当前实例是否可变,若需获取其可变版本的拷贝,均应调用 mutableCopy 方法。同理,若需要不可变的拷贝,则总应通过 copy 方法来获取。

  对于不可变的 NSArray 与可变的 NSMutableArray 来说,下列关系总是成立的:

1 - [NSMutableArray copy] => NSArray
2 - [NSArray mutableCopy] => NSMutableArray;

   有个微妙的情况要注意:在可变对象上调用 copy 方法会返回另外一个不可变类的实例。这样做是为了能在可变版本与不可变版本之间自由切换。要实现此目标,还有个方法,就是提供三个方法:copy、immutableCopy、mutableCopy,其中,copy 所返回的拷贝对象与当前对象的类型一致,而另外两个方法则分别返回不可变版本与可可变版本的拷贝。但是,如果调用者并不知道其所用的实例是否真的可变,而你在上面调用 copy 方法来复制它。此时你以后拷贝后的对象应该是不可变的数组,但实际上它却是可变的。

  可以查询类型信息以判断待拷贝的实例是否可变,不过那样做比较麻烦,因为每次复制对象的时候都得查询。为了安全起见,最后还是会使用 immutableCopy 和 mutableCopy 这两个方法来复制对象,而这样做就和只有 copy与 mutableCopy 方法的设计方案毫无二致了。把拷贝方法称为copy 而非 immutableCopy 的原因在于,NSCopying 不仅设计给那些具有可变版本和不可变版本的类来用,而且还要供其他一些类使用,而那些类没有“可变” 与 “不可变”之分,所以说,把拷贝方法叫做 immutableCopy 不合适。

  在编写拷贝方法时,还要决定一个问题,就是应该执行 “深拷贝”(deep copy)还是 “前拷贝”(shallow copy)。深拷贝的意思是:在拷贝对象自身时,将其底层数据也一并复制过去。Foundation 框架中的所有 collection 类在默认情况下都执行浅拷贝,也就是说,只拷贝容器对象本身,而不复制其中数据。这样做的主要原因在于,容器内的对象未必都能拷贝,而且调用者也未必想在拷贝容器时一并拷贝其中的每个对象。

  一般情况下,我们会遵照系统框架所使用的那种模式,在自定义的类中以浅拷贝的方式实 “copyWithZone:” 方法。但如果有必要的话,也可以增加一个执行深拷贝的方法。以 NSSet 为例,该类提供了下面这个初始化方法,用以执行深拷贝:

   浅拷贝和深拷贝对比图,浅拷贝之后的内容与原始内容均指向相同对象。而深拷贝之后的内容所指的对象是原始内容中相关对象的一份拷贝。

1 - (id)initWithSet:(NSArray *)array copyItems:(BOOL)copyItems;

  若 copyItem 参数设为 YES,则该方法会向数组中的每个元素发送 copy 消息,用拷贝好的元素创建新的 set,并将其返回给调用者。

  在 EOCPerson 那个例子中,存放朋友对象的 set 是用 “copyWithZone:” 方法来拷贝的,根据刚才讲的内容可知,这种浅拷贝方式不会逐个复制 set 中的元素。若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法:

1 - (id)deepCopy {
2     EOCPerson *copy = [[[self class] alloc] initWithFirstName:_firstName andLastName:_lastName];
3     copy->_friends = [[NSMutableSet alloc] initWithSet:_firends copyItem:YES];
4     return copy;
5 }

  因为没有专门定义深拷贝的协议,所以其具体执行方式由每个类来确定,你只需决定自己所写的类是否要提供深拷贝方法即可。另外,不要假定遵从了 NSCopying 协议的对象都会执行深拷贝。在绝大多数情况下,执行的都是浅拷贝。如果需要在某个对象上执行深拷贝,那么除非该类的文档说它是深拷贝来实现 NSCopying 协议的,否则,要么寻找能执行深拷贝的相关方法,要么自己编写方法来做。

  END

转载于:https://www.cnblogs.com/chmhml/p/7237221.html

第22条:理解NSCopying 协议相关推荐

  1. 网络编程懒人入门(三):快速理解TCP协议一篇就够

    1.前言 本系列文章的前两篇<网络编程懒人入门(一):快速理解网络通信协议(上篇)>.<网络编程懒人入门(二):快速理解网络通信协议(下篇)>快速介绍了网络基本通信协议及理论基 ...

  2. python爬虫入门教程--快速理解HTTP协议(一)

    http协议是互联网里面最重要,最基础的协议之一,我们的爬虫需要经常和http协议打交道.下面这篇文章主要给大家介绍了关于python爬虫入门之快速理解HTTP协议的相关资料,文中介绍的非常详细,需要 ...

  3. 职场必懂得的22条潜规则

    职场必懂得的22条潜规则 2010-07-30 16:42:57 标签:职场 规则 [推送到技术圈] 潜规则是小聪明而不是大智慧--你学会的结果是内耗生产力,扼杀创造力:你学不会的结果是小人得志,人才 ...

  4. 提升代码内外部质量的22条经验

    2019独角兽企业重金招聘Python工程师标准>>> 本文主要关注代码的内部和外部质量,编程的价值观,代码质量的评估标准,整洁代码的匠艺以及如何维护已有的代码. 外部质量:用户所能 ...

  5. 【Http协议】深入理解HTTP协议

    来源:http://www.blogjava.net/zjusuyong/articles/304788.html 深入理解HTTP协议 1. 基础概念篇 1.1 介绍 HTTP是Hyper Text ...

  6. 【数字IC】深入浅出理解I2C协议

    深入浅出理解I2C协议 一.什么是I2C协议 二.I2C,SPI,UART协议的区别 三.I2C的信号线 四.I2C的连接方式 4.1 单主设备,单从设备 4.2 单主设备,多从设备 4.3 多主设备 ...

  7. 网络经商22条赚钱法则

    作者:韩亚飞_yue31313_韩梦飞沙 QQ:313134555 网络经商22条赚钱法则 目录 前言...2 一.领先定律...3 二.品类定律...5 三.心智定律...8 四.认知定律...10 ...

  8. rrpp协议如何修改_《技术进阶:理解RRPP协议.ppt

    理解RRPP协议 RRPP 协议的背景 RRPP协议是由EAPS协议发展来的, EAPS (Ethernet Automatic Protect Switching) EAPS协议:rfc3619 对 ...

  9. 分布式一致性协议三部曲-深入理解一致性协议Paxos

    在理解分析分布式一致性协议前,我们必须先看下CAP理论 CAP CAP是指在一个分布式系统中,一致性(Consistency).可用性(Availability).分区容错性(Partition to ...

最新文章

  1. JavaScript学习与实践(8)
  2. 关于窗口的一些小脚本
  3. [分享]iOS开发-实现view底部控件随着键盘的弹出而上移的效果
  4. python 安装html,python安装glob
  5. 【jvm】jvm jstack使用 Java线程Dump分析
  6. 使用Springboot整合Vue项目案例—登录界面
  7. mysql 时间查询_MYSQL按时间段查询语句大全
  8. 转载-【常用RGB颜色查询对照表及感情色】
  9. 有关textField左视图leftView和书写位置的设置
  10. 原创 基于微信小程序毕业设计题目选题课题 羽毛球篮球足球乒乓球场地球馆预约小程序的设计与实现(1)首页
  11. 深信服校园招聘c/c++软件开发B卷
  12. c语言响铃编程,C语言C加加编程新手快速入门基础学习
  13. 外卖项目06---套餐管理业务开发(移动端的后台代码编辑开发)
  14. Day14:网络编程入门
  15. 【codeforces 350C】Bombs
  16. 标题类、文本类、列表类、代码类、图文样式、表格布局、
  17. 分数怎么约分成最简分数?其实很简单
  18. python分析比赛_实战项目练习 ---- 【2018世界杯】用python分析夺冠球队
  19. 大创项目日志(1) 基于Python的语言特征提取
  20. 【SequoiaDB|巨杉数据库】整体架构Ⅰ

热门文章

  1. 触发Full GC执行的情况 以及其它补充信息
  2. Silverlight2.0中与Html页面元素互操作.
  3. 某程序的bug是什么意思?
  4. 小程序添加动画效果--遇到的问题
  5. 改变listview的每个item的背景色
  6. MYSQL5.5 YUM更新安装
  7. 点击页面空白处就关闭某个层是怎么做到的
  8. 一小段代码,得到项目决对路径
  9. 五个计算机软件,近五个交易日计算机软件概念股市复盘(4月19日)
  10. textarea选中行删除_如何一键删除表格空行,这个方法才最高级!