第18条:尽量使用不可变对象


1、设计类的时候,应充分运用属性来封装数据。

2应该尽量把对外公布出来的属性设为只读,而且只在确有必要时才将属性对外公布。


3若属性仅可于对象内部修改,则在“class-continuation分类”中将其由readonly属性扩展为readwrite属性。

这种做法下,如果该属性是nonatomic,那么可能会产生“竞争条件”(race condition, 竞态条件)。在对象内部写入属性时,对象外的观察者也许正读取该属性。若想避免此问题,可以在必要时通过“派发队列”(dispatch queue,参见第41条)等手段,将(包括对象内部的)所有数据存取操作都设为同步操作。

a)现在,只能于实现代码内部设置这些属性了。其实更准确地说,在对象外部,仍然能通过“键值编码”(Key-Value Coding, KVC)技术设置这些属性值。这样做等于违规地绕过了本类所提供的API。

b)还可以使用类型信息查询功能查出属性属性所对应的实例变量在内存布局中的偏移量,以此来人为设置这个实例变量的值。这样做绕过本类的公共API还要不合规范。

上面额外的加入了绕过公共API来设置属性的两种方式。
但这是一种破坏,可能出现问题。所以建议,还是尽量编写不可变的对象。

4不要把可变的collection作为属性公开,而就提供相关的方法,以此修改对象中的可变collection。

例:
.h
Person 类中
有一个属性
@property(nonatomic, strong, readonly)NSSet *friends;
两个方法
- addFriend
- removeFriend

.m
变量
NSMutableSet *_internalFriends;
- (NSSet*)friends {
  return [_internalFriends copy];
}

- addFriend {
  _internalFriends
}

- removeFriend {
  _internalFriends
}


也可以直接用NSMutableSet来实现friends属性,令该类不借助addFriend: removeFriedn: 方法而直接操作此属性。
但是,这种过分解耦(decouple)数据的做法很容易出bug。比方说,在添加或删除朋友时,Person对象可能还要执行其他相关操作,若是采用这种做法,那就等于直接从底层修改了其内部用于存放朋友对象的set。在Person对象不知情时,直接从底层修改set可能会令对象内的各数据之间互不一致。

所以建议:不要在返回的对象上查询类型以确定其是否可变。
例:
  NSSet *friends = person.friends;
  if ([friends isKindOfClass:[NSMutableSet class]]) {
    NSMutableSet *mutableFriends = (NSMutableSet*)firends;
    // .....
  }
应该竭力避免这种做法。不要假设friends所用的那个NSSet一定是可变的。
5这依然说明:不宜从底层直接修改对象中的数据。

原文地址:https://www.cnblogs.com/Pikdays/p/5734098.html