如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?
出题者简介: 孙源(sunnyxx),目前就职于百度
整理者简介:陈奕龙,目前就职于滴滴出行。
转载者:豆电雨(starain)微信:doudianyu
若想令自己所写的对象具有拷贝功能,则需实现 NSCopying 协议。如果自定义的对象分为可变版本与不可变版本,那么就要同时实现 NSCopying
与 NSMutableCopying
协议。
具体步骤:
- 需声明该类遵从 NSCopying 协议
实现 NSCopying 协议。该协议只有一个方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到让自己的类用 copy 修饰符,我们总是想覆写copy方法,其实真正需要实现的却是 “copyWithZone” 方法。
以第一题的代码为例:
// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 修改完的代码 typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
}; @interface CYLUser : NSObject<NSCopying> @property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex; - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex; @end
然后实现协议中规定的方法:
- (id)copyWithZone:(NSZone *)zone {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
return copy;
}
但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。举个例子,假如 CYLUser 中含有一个数组,与其他 CYLUser 对象建立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。下面列出了实现此功能所需的全部代码:
// .h文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// 以第一题《风格纠错题》里的代码为例 typedef NS_ENUM(NSInteger, CYLSex) {
CYLSexMan,
CYLSexWoman
}; @interface CYLUser : NSObject<NSCopying> @property (nonatomic, readonly, copy) NSString *name;
@property (nonatomic, readonly, assign) NSUInteger age;
@property (nonatomic, readonly, assign) CYLSex sex; - (instancetype)initWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
+ (instancetype)userWithName:(NSString *)name age:(NSUInteger)age sex:(CYLSex)sex;
- (void)addFriend:(CYLUser *)user;
- (void)removeFriend:(CYLUser *)user; @end
// .m文件
// .m文件
// http://weibo.com/luohanchenyilong/
// https://github.com/ChenYilong
// @implementation CYLUser {
NSMutableSet *_friends;
} - (void)setName:(NSString *)name {
_name = [name copy];
} - (instancetype)initWithName:(NSString *)name
age:(NSUInteger)age
sex:(CYLSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
_friends = [[NSMutableSet alloc] init];
}
return self;
} - (void)addFriend:(CYLUser *)user {
[_friends addObject:user];
} - (void)removeFriend:(CYLUser *)user {
[_friends removeObject:user];
} - (id)copyWithZone:(NSZone *)zone {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [_friends mutableCopy];
return copy;
} - (id)deepCopy {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
} @end
以上做法能满足基本的需求,但是也有缺陷:
如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法。
【注:深浅拷贝的概念,在下文中有介绍,详见下文的:用@property声明的 NSString(或NSArray,NSDictionary)经常使用 copy 关键字,为什么?如果改用 strong 关键字,可能造成什么问题?】
在例子中,存放朋友对象的 set 是用 “copyWithZone:” 方法来拷贝的,这种浅拷贝方式不会逐个复制 set 中的元素。若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法:
- (id)deepCopy {
CYLUser *copy = [[[self class] allocWithZone:zone]
initWithName:_name
age:_age
sex:_sex];
copy->_friends = [[NSMutableSet alloc] initWithSet:_friends
copyItems:YES];
return copy;
}
至于如何重写带 copy 关键字的 setter这个问题,
如果抛开本例来回答的话,如下:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
不过也有争议,有人说“苹果如果像下面这样干,是不是效率会高一些?”
- (void)setName:(NSString *)name {
if (_name != name) {
//[_name release];//MRC
_name = [name copy];
}
}
这样真得高效吗?不见得!这种写法“看上去很美、很合理”,但在实际开发中,它更像下图里的做法:
克强总理这样评价你的代码风格:
我和总理的意见基本一致:
老百姓 copy 一下,咋就这么难?
你可能会说:
之所以在这里做if判断
这个操作:是因为一个 if 可能避免一个耗时的copy,还是很划算的。 (在刚刚讲的:《如何让自己的类用 copy 修饰符?》里的那种复杂的copy,我们可以称之为 “耗时的copy”,但是对 NSString 的 copy 还称不上。)
但是你有没有考虑过代价:
你每次调用
setX:
都会做 if 判断,这会让setX:
变慢,如果你在setX:
写了一串复杂的if+elseif+elseif+...
判断,将会更慢。
要回答“哪个效率会高一些?”这个问题,不能脱离实际开发,就算 copy 操作十分耗时,if 判断也不见得一定会更快,除非你把一个“ @property他当前的值 ”赋给了他自己,代码看起来就像:
[a setX:x1];
[a setX:x1]; //你确定你要这么干?与其在setter中判断,为什么不把代码写好?
或者
[a setX:[a x]]; //队友咆哮道:你在干嘛?!!
不要在 setter 里进行像
if(_obj != newObj)
这样的判断。(该观点参考链接:How To Write Cocoa Object Setters: Principle 3: Only Optimize After You Measure)
什么情况会在 copy setter 里做 if 判断? 例如,车速可能就有最高速的限制,车速也不可能出现负值,如果车子的最高速为300,则 setter 的方法就要改写成这样:
-(void)setSpeed:(int)_speed{
if(_speed < 0) speed = 0;
if(_speed > 300) speed = 300;
_speed = speed;
}
回到这个题目,如果单单就上文的代码而言,我们不需要也不能重写 name 的 setter :由于是 name 是只读属性,所以编译器不会为其创建对应的“设置方法”,用初始化方法设置好属性值之后,就不能再改变了。( 在本例中,之所以还要声明属性的“内存管理语义”--copy,是因为:如果不写 copy,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而低效)。
那如何确保 name 被 copy?在初始化方法(initializer)中做:
- (instancetype)initWithName:(NSString *)name
age:(NSUInteger)age
sex:(CYLSex)sex {
if(self = [super init]) {
_name = [name copy];
_age = age;
_sex = sex;
_friends = [[NSMutableSet alloc] init];
}
return self;
}
如何让自己的类用 copy 修饰符?如何重写带 copy 关键字的 setter?的更多相关文章
- 12、类成员访问修饰符public/private/producted/readonly
1.private 类的私有成员 private 类的私有成员,只能在内部访问,在外部访问不到,无法被继承,我们可以将不需要被外部修改的定义为私有的 私有成员,只能在内部访问,在外部访问不到 priv ...
- C#基础--类/接口/成员修饰符,多态、重载、重写,静态和非静态
C#基础--类/接口/成员修饰符,多态.重载.重写,静态和非静态 类/接口/成员修饰符 C#修饰符---接口: 接口默认访问符是internal接口的成员默认访问修饰符是public C#修饰符--类 ...
- 深复制与浅复制&&strong,copy修饰符总结
又是一个老生常谈的话题,可是貌似这个问题,好多ios开发工程师并不能理解透彻,所以简单记录分析一下深复制与浅复制的原理以及strong,copy修饰符的原理和使用. 一.深复制与浅复制 ...
- python基础(27):类成员的修饰符、类的特殊成员
1. 类成员的修饰符 类的所有成员在上一步骤中已经做了详细的介绍,对于每一个类的成员而言都有两种形式: 公有成员,在任何地方都能访问 私有成员,只有在类的内部才能方法 私有成员和公有成员的定义不同:私 ...
- Java 面向对象(八) 权限修饰符 和 final、native 关键字
一.权限修饰符 1.概述 在 Java 中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限: public:公共的: protected:受保护的: default: ...
- Java类访问权限修饰符
一.概要 通过了解Java4种修饰符访问权限,能够进一步完善程序类,合理规划权限的范围,这样才能减少漏洞.提高安全性.具备表达力便于使用. 二.权限表 修饰符 同一个类 同一个包 不同包的子类 不同包 ...
- 类的访问修饰符_C#
访问控制修饰符: 访问控制修饰符 类内部 子类 程序集内 程序集外 Default √ Public √ √ √ √ Private √ Internal √ √ √ Protected √ √ Pr ...
- python学习day20 面向对象(二)类成员&成员修饰符
1.成员 类成员 类变量 绑定方法 类方法 静态方法 属性 实例成员(对象) 实例变量 1.1实例变量 类实例化后的对象内部的变量 1.2类变量 类中的变量,写在类的下一级和方法同一级. 访问方法: ...
- C#类的访问修饰符
默认情况下,类声明为内部的,即只有当前工程中的代码才能访问它.可以用internal访问修饰符关键字显式指定,但这不是必须的,类在定义时默认为此类型的类.但是C# 方法默认访问级别: private. ...
随机推荐
- 使用XLinq.XElement读取带Namespace(命名空间)的XML
简介 本文主要介绍通过XELemet去读取含有namespaces(xmlns)的XML,这也是偶然间发现这个问题的,一个群里的小伙伴突然问起这个,以此记录一下. 背景 一个XML文档可能包括来自多个 ...
- Oracle数据导入导出imp/exp命令总结
racle数据导入导出imp/exp就相当于oracle数据还原与备份.exp命令可以把数据从远程数据库服务器导出到本地的dmp文件,imp命令可以把dmp文件从本地导入到远处的数据库服务器中. 利用 ...
- oracle命令的缩写原型单词方便记忆总结
$ORACLE_HOME/bin下的utilities解释 Binary First Available Description adapters ...
- (java)从零开始之--装饰者设计模式
装饰者设计模式:简单定义:增强一个类的功能,而且还可以让这些装饰类互相装饰. 应用场景:当要在某个功能的基础上扩充功能,并且扩充的功能具有大量排列组合,通过继承关系会衍生出大量子类,这时候用装饰者模式 ...
- Codeforces 527E Data Center Drama(欧拉回路)
题意: 给定一个无向图连通图,把这个的无向边变成有向边,并添加最少的有向边使这个图每个结点的出度为偶数. Solution: 题目很长,并且很多条件说的不太直接,确实不太好懂. 首先先看得到的无向图, ...
- Windows2003 下 MySQL 数据库每天自动备份
1. 环境: windows server 2003 + Apache 2.0 + PHP5 + MySQL 4.0.26 . 2. 假设 PHP 安装目录为 D:/php ,MySQL 安装目录为 ...
- javascript——集合类
/** * Created by Administrator on 2015/4/14. */ function Set() { this.values = {}; this.n = 0; this. ...
- Android三种菜单简介
Android的菜单分为三种类型:选项菜单(Option Menu).上下文菜单(Context Menu).子菜单(Sub Menu). 一.选项菜单 用户点击设备上的菜单按钮(Menu),触发事件 ...
- ie11下,插入框架Html
if(navigator.userAgent.indexOf("MSIE")>0 || (navigator.userAgent.indexOf("Trident& ...
- ExtJs中动态加载机制研究(转)
觉得写的太好了,怕弄丢了,转一下:http://extjs.org.cn/node/659 昨天我们team对于extjs的动态加载机制做了些深入研究,这里先share下controller加载的结果 ...