From e71a4b1dd7038d162921dd51e9ce70d9bcf000b1 Mon Sep 17 00:00:00 2001 From: Ruqi Date: Thu, 29 Oct 2015 18:17:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=85=B3=E4=BA=8E?= =?UTF-8?q?=E6=B7=B1=E6=8B=B7=E8=B4=9D=E7=9B=B8=E5=85=B3=E7=9A=84=E6=8F=8F?= =?UTF-8?q?=E8=BF=B0=E5=92=8C=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 文中的 `- deepCopy` 方法并不能实现深拷贝。 `CYLUser` 的 `- copyWithZone` 方法里,`_friends` 成员的的赋值使用的 `- mutableCopy` 是浅拷贝,只是创建了`NSMutableSet` 对象;导致 `- deepCopy`方法中, _friends 的每一个对象的 _friends 列表并未创建实例。 为了测试 `NSArray`, `NSSet` 容器类初始化行为,我写了测试代码, ``` NSMutableArray *objects = [NSMutableArray array]; [objects insertObject:[NSDate date] atIndex:0]; if ([objects count] > 0) { NSArray *arr = [[NSArray alloc] initWithArray: objects]; for (int i=0; i - -@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文件 - - - - ```Objective-C -// .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:person]; -} - -- (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 中的元素。若需要深拷贝的话,则可像下面这样,编写一个专供深拷贝所用的方法: - - - ```Objective-C -- (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***这个问题, From 4127d6b11a22580e3d884d4f5f104df5170a61f7 Mon Sep 17 00:00:00 2001 From: Ruqi Date: Thu, 29 Oct 2015 18:51:27 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20*=E9=87=8D=E5=86=99?= =?UTF-8?q?=E5=B8=A6=20copy=20=E5=85=B3=E9=94=AE=E5=AD=97=E7=9A=84=20sette?= =?UTF-8?q?r*=20=E7=9A=84=E7=AD=94=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 首先,if 的确比对象 copy 要快。 另外,判断对象是否相同是为了保证代码执行的正确性。在非 ARC 环境下,如果穿入对象 retain count 为 1,不做判断的话,release 之后对象内存释放,下一步 copy 也会出错。 --- ...10\357\274\210\344\270\212\357\274\211.md" | 103 +----------------- 1 file changed, 4 insertions(+), 99 deletions(-) diff --git "a/01\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210/\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210\357\274\210\344\270\212\357\274\211.md" "b/01\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210/\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210\357\274\210\344\270\212\357\274\211.md" index 03f5778..cd7e6a6 100644 --- "a/01\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210/\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210\357\274\210\344\270\212\357\274\211.md" +++ "b/01\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210/\343\200\212\346\213\233\350\201\230\344\270\200\344\270\252\351\235\240\350\260\261\347\232\204iOS\343\200\213\351\235\242\350\257\225\351\242\230\345\217\202\350\200\203\347\255\224\346\241\210\357\274\210\344\270\212\357\274\211.md" @@ -411,114 +411,19 @@ self.mutableArray = array; ``` -至于***如何重写带 copy 关键字的 setter***这个问题, - - -如果抛开本例来回答的话,如下: - - - -```Objective-C -- (void)setName:(NSString *)name { - //[_name release]; - _name = [name copy]; -} -``` - -不过也有争议,有人说“苹果如果像下面这样干,是不是效率会高一些?” - +***重写带 copy 关键字的 setter***: ```Objective-C - (void)setName:(NSString *)name { if (_name != name) { - //[_name release];//MRC +#if __has_feature(objc_arc) + [_name release]; +#endif _name = [name copy]; } } ``` - - -这样真得高效吗?不见得!这种写法“看上去很美、很合理”,但在实际开发中,它更像下图里的做法: - -![enter image description here](http://i.imgur.com/UwV9oSn.jpeg) - -克强总理这样评价你的代码风格: - -![enter image description here](http://i.imgur.com/N77Lkic.png) - -我和总理的意见基本一致: - - -> 老百姓 copy 一下,咋就这么难? - - - - -你可能会说: - - -之所以在这里做`if判断` 这个操作:是因为一个 if 可能避免一个耗时的copy,还是很划算的。 -(在刚刚讲的:《如何让自己的类用 copy 修饰符?》里的那种复杂的copy,我们可以称之为 “耗时的copy”,但是对 NSString 的 copy 还称不上。) - - -但是你有没有考虑过代价: - - -> 你每次调用 `setX:` 都会做 if 判断,这会让 `setX:` 变慢,如果你在 `setX:`写了一串复杂的 `if+elseif+elseif+...` 判断,将会更慢。 - -要回答“哪个效率会高一些?”这个问题,不能脱离实际开发,就算 copy 操作十分耗时,if 判断也不见得一定会更快,除非你把一个“ @property他当前的值 ”赋给了他自己,代码看起来就像: - -```Objective-C -[a setX:x1]; -[a setX:x1]; //你确定你要这么干?与其在setter中判断,为什么不把代码写好? -``` - -或者 - - -```Objective-C -[a setX:[a x]]; //队友咆哮道:你在干嘛?!! -``` - -> 不要在 setter 里进行像 `if(_obj != newObj)` 这样的判断。(该观点参考链接:[ ***How To Write Cocoa Object Setters: Principle 3: Only Optimize After You Measure*** ](http://vgable.com/blog/tag/autorelease/) -) - - -什么情况会在 copy setter 里做 if 判断? -例如,车速可能就有最高速的限制,车速也不可能出现负值,如果车子的最高速为300,则 setter 的方法就要改写成这样: - - -```Objective-C --(void)setSpeed:(int)_speed{ - if(_speed < 0) speed = 0; - if(_speed > 300) speed = 300; - _speed = speed; -} -``` - - - -回到这个题目,如果单单就上文的代码而言,我们不需要也不能重写 name 的 setter :由于是 name 是只读属性,所以编译器不会为其创建对应的“设置方法”,用初始化方法设置好属性值之后,就不能再改变了。( 在本例中,之所以还要声明属性的“内存管理语义”--copy,是因为:如果不写 copy,该类的调用者就不知道初始化方法里会拷贝这些属性,他们有可能会在调用初始化方法之前自行拷贝属性值。这种操作多余而低效)。 - -那如何确保 name 被 copy?在初始化方法(initializer)中做: - - ```Objective-C - - (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; - } - - ``` - - ###6. @property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的