Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -409,224 +409,21 @@ self.mutableArray = array;
return copy;
}
```
但在实际的项目中,不可能这么简单,遇到更复杂一点,比如类对象中的数据结构可能并未在初始化方法中设置好,需要另行设置。举个例子,假如 CYLUser 中含有一个数组,与其他 CYLUser 对象建立或解除朋友关系的那些方法都需要操作这个数组。那么在这种情况下,你得把这个包含朋友对象的数组也一并拷贝过来。下面列出了实现此功能所需的全部代码:

```Objective-C
// .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文件



```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***这个问题,


如果抛开本例来回答的话,如下:



```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 是如何生成并添加到这个类中的

Expand Down