第二章 对象 / 消息 / 运行时
第六条:理解"属性"这一概念
-@propertty 编译器会自动写出一套存储方法,并且自动向类中添加适当类型的实例变量,并且在属性名前加"_",以此作为实例变量的名字;
- @synthesize 如果不想使用默认的实例变量命名,该关键字可用来指定实例变量的名字
@synthesize firstName = _myFristName
- @dynamic 不要自动创建实现属性所用的实例变量,也不要为其创建存取方法;而且,在编译访问属性的代码时,即使编译器发现没有定义存取方法,也不会保存,它相信这些方法能在运行时找到;
属性特质
- nonatomic 非原子属性, 默认情况下为"atomic" .具备 atomic 特质的获取方法会通过锁定机智来确保其操作的原子性.在 iOS 中其所有属性都声明为 nonatomic,因为在 iOS 中使用同步锁的开销比较大,这样会带来性能问题.一般情况下,并不要求属性必须是"原子的",因为这并不能保证"线程安全",若要实现"线程安全",还需要采取更深层的锁定机制.
要点
- 可以使用@ property 语法来定义对象中所封装的数据.
- 通过"特质"来制定存储数据所需的正确语义.
- 在设置属性所对应的实例变量时,一定要遵从该属性所什么的语义
第七条:在对象内部尽量直接访问实例变量
- 由于不经过 Objective - C 的"方法派发"步骤,所以直接访问实例变量的速度比较快.在这种情况下,编译器所生成的代码会直接访问保存对象实例变量的那块内存.
- 直接访问实例变量时,不会调用器"设置方法",这样就绕过了为2相关属性定义的"内存管理语义".如果 ARC 下直接访问一个生命为"copy"的属性,并不会拷贝该属性,只会保留新值并释放旧值.
- 如果直接访问实例变量,不会触发"键值观察" KVO.
- 通过属性来访问有助于排查预支相关的错误,因为可以在 setter getter 中添加断点,监控该属性的的调用者及其访问时机.
方案:在写入实例变量时,通过其"设置方法" 来做,而在读取实例变量时,则直接访问.
要点:
- 在对象内部读取数据时,应该直接通过实例变量来读,儿写入数据时,则应通过属性来写.
- 在初始化方法及 dealloc 方法中,应该直接通过实例变量来读写数据.
- 在懒加载时,需要通过属性读取数据实现的
第八条:理解"对象等同性"这一概念
-
操作符 "== " 比较的是两个指针本身,而不是对象本身
NSObjext协议中有两个用于判断等同性的关键方法:
-(BOOL)isEqual:(id)object;
-(NSUInterger)hash; -
如果"isEqual:"方法判断两个对象相等,那么其 "hsah"方法也必须返回同一个值.但是如果两个对象的 hash 方法返回通一个值,那么"isEqual"方法未必会认为两者相等.
-
collection(array ,dictionary,set)在检索哈希表时,会用对象的哈希码做索引.
-
set 可能会根据哈希码把对象分装到不同的数组(bin 箱子)中,在向 set中添加新对象时,要根据其哈希码找到与之相关的数组,依次检查其中各个元素,看数组中已有的对象是否和将要添加的新对象相等.如果相等,那就说明要添加的对象已经在 set 里面了.
要点:
- 若要检测对象的等同性,请提供"isEqual:"与hash方法.
- 相同的对象必须具有相同的哈希码,但是两个哈希码相同的对象却未必相同.
- 不要盲目地逐个检测每条属性,而是应该依照具体需求来制定检测方法.
- 编写 hash方法时,应该使用计算速度快而且哈希码碰撞几率低的算法.
第九条:以"类族模式"隐藏实现细节
要点:
- 类族模式可以把实现细节隐藏在一套简单的公共接口后面
- 系统框架中经常使用类族.
- 从类族的公共抽象基类中继承子类时要当心.
第十条:在既有类中使用关联对象存放自定义数据
Paste_Image.png第13条: 用"方法调配技术"调试"黑盒方法"
利用 runtime 用自己写的方法替换原有私有方法,在自己的方法里再调用私有方法,
类的方法列表会把选择子的名称映射到相关的方法实现之上,使得"动态消息派发系统"能够据此找到应该调用的方法.这些方法均以函数指针的形式来表示,这种指针叫做 IMP 其原型如下:
id (* IMP)(id,SEL,...)
要点
- 在运行期,可以向类中新增或替换选择子所对应的方法实现
- 使用另一份实现来替换原有的方法实现,这道工序叫做"方法调配",开发者常用此技术向原有实现中添加新功能
- 一般来说,只有调试程序的时候才需要在运行期修改方法实现,这种做法不宜滥用.
第14条: 理解"类对象"的用意
要点:
- 每个实例都有一个指向 Class 对象的指针,用意表明其类型,而这些 Class 对象则构成了类的继承体系
- 如果对象类型无法再编译期确定,那么就应该使用类型信息查询方法来探知
- 尽量使用类型信息查询方法来确定对象类型,而不要直接比较类对象,因为某些对象可能实现了消息转发功能.
第二章 接口与 API 设计
第15条: 用前缀避免命名空间冲突
要点:
- 选择与你公司,应用程序或二者皆有关联之名称作为类名的前缀,并在所有代码中均使用这一前缀
- 若自己所开发的程序库中用到了第三方库,则应为其中的名称加上前缀
第16条: 提供"全能初始化方法"
要点:
- 在类中提供一个全能初始化方法,并与文档里指明.其它初始化方法均应调用此方法.
- 若初始化方法与超类不同,则需覆写超类中的对应方法.
- 如果超类的初始化方法不适用于子类,那么应该覆写这个超类方法,并在其中抛出异常.
第17条: 实现 description 方法
要点:
- 实现 description 方法返回一个有意义的字符串,用以描述该实例.
- 若想在调试时打印出更详尽的对象描述信息,则应实现 debugDescription 方法.
第18条: 尽量使用不可变对象
要点:
- 尽量创建不可变的对象
- 若某属性仅可于在对象内部修改,则在"class-continuation分类"中将其由 readonly属性扩展为 readwrite属性
- 不要把可变的 collection 作为属性公开,而是提供相关的方法,以此修改对象中的可变 collection
第19条: 使用清晰而协调的命名方式
要点:
- 起名时应遵从标准的 Objective-C命名规范.这样创建出来的接口更容易为开发者所理解.
- 方法名要言简意赅,从左至右读起来要像个日常用语中的句子才好.
- 方法名离不要使用缩略后的类型名称
- 给方法起名时的第一要务是确保其风格与你自己的代码或所集成的框架相符.
第20条: 为私有方法名前加前缀
要点:
- 给私有方法的名称前加上前缀,这样可以很容易的将其同公共方法区分开.
- 不要单用一个下划线做私有方法的前缀,因为这样的做法是预留给苹果公司用的.
第21条: 理解 Objective-C 错误模型
要点:
- 只有发生了可使整个应用程序崩溃的严重错误时,才应使用异常.
- 在错误不那么严重的情况下,可以指派"委托方法"(delegate method)来处理错误,也可以把错误信息放在 NSError 对象里,经由"输出参数"返回给调用者
第22条: 理解 NSCopying协议
NSCopying 协议
- (id)copyWithZone:(NSZone*)zone{
NEPerson *copy = [[self class] allocWithZone:zone]initWithFirstName:_fristName andLastName:_lastName];
return copy;
}
NSMutableCopying 协议
- (id)mutableCopyWithZone:(NSZone*)zone;
#####深拷贝(deep copy)与浅拷贝(shallow copy)
深拷贝与浅拷贝 并不是说 copy 和 mutableCopy 的区别
深拷贝:在拷贝对象自身时,将其底层数据也一并复制过去;
浅拷贝: Foundation矿建中的所有 collection 类在默认情况下都执行浅拷贝,也就是说,只拷贝容器本身,而不复制其中数据.
浅拷贝之后的内容与原始内容均指向相同对象,儿深拷贝之后的内容所指的对象是原始内容中相关对象的一份拷贝.
如果有必要,可以增加一个执行深拷贝的方法.以 NSSet 为例,该类提供了下面的初始化方法,用以执行深拷贝,
- (id)initWithSet:(NSArray*)array copyItem:(BOOL)copyItems
若 copyItem参数设为 YES, 则该方法会向数组中的每个元素发送"copy"消息,用拷贝好的元素创建新的 set ,并将其返回给调用者.
新的深拷贝方法如下:
![深拷贝](https://img.haomeiwen.com/i1721249/1748722995198fe6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
没有专门定义深拷贝的协议,所以其具体执行方式由每个类来确定.
不是遵循 NSCopying 协议的对象都会执行深拷贝.在绝大多数情况下,执行的都是浅拷贝.
如果需要在某对象上执行深拷贝,那么除非该类的文档说它是用深拷贝来实现 NSCopying 协议的,否则,要么寻找能够执行深拷贝的相关方法,要么自己编写方法来做.
####要点:
- 若想令自己所写的对象具有拷贝功能,则实现 NSCopying 协议
- 如果自定义的对象分为可变版本和不可变版本,那么就要同事实现 NSCopying与 NSMutableCopying协议.
- 复制对象时需决定采用浅拷贝还是深拷贝,一般情况下应该尽量执行浅拷贝.
- 如果你所写的对象需要深拷贝,那么可考虑新增一个专门执行深拷贝的方法.
##第4章 协议与分类
###第23条: 通过委托与数据源协议进行对象间通信
1.delegate 属性要声明为 weak .通常扮演 delegate的那个对象也要持有本对象,直到用完本对象之后,才会释放.假如用 strong将对象与委托对象之间定为"拥有关系", 就会形成循环引用.
2.如下代码用于查找对象是否能相应特定的选择子
![Paste_Image.png](https://img.haomeiwen.com/i1721249/8f65fdd238651ffb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可是如果频繁调用代理方法,该方法除了第一次检测有用之外,后续的检测都是多余的.此时:
![代理方法](https://img.haomeiwen.com/i1721249/e9eeca48e931fff2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
创建一个结构体用以保存是否可以响应代理方法
![](https://img.haomeiwen.com/i1721249/6fb51d56a3821137.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
![结构体](https://img.haomeiwen.com/i1721249/0b4f3d67d958e10e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
在 delegate 的 set 方法里,对可否响应,进行判断并缓存
![setDelegate](https://img.haomeiwen.com/i1721249/839fa7824b41b45e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这样,在每次如下调用即可.
![调用](https://img.haomeiwen.com/i1721249/a762169dc79991a8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
本例,对代理方法,会调用很多次时,值得这种优化,如果要频繁通过数据源协议从数据源中获取多份相互独立的数据,那么这项技术极有可能会提高程序效率
####要点:
- 委托模式为对象提供了一套接口,使其可由此将相关事件告知其他对象.
- 将委托对象应该支持的接口定义成协议,在协议中把可能需要处理的事件定义成方法.
- 当某对象需要从另外一个对象中获取数据时.可以使用委托模式.这种模式即'数据源协议'(data source protocal)
- r若有必要,可实现含有段位的结构体,将委托对象是否能相应相关协议方法这一信息缓存至其中.
###第24条: 将类的实现代码分散到便于管理的数个分类之中
####要点:
- 使用分类机制把类的实现代码划分成易于管理的小块
- 将应该视为'私有'的方法归入名为 Private 的分类中,以隐藏实现细节.
###第25条: 总是为第三方类的分类名称加前缀
####要点:
- 向第三方类中添加分类时,总应给其名称加上你专用的前缀.
- 向第三方类中添加分类时,总应给其中的方法名加上你专用的前缀.
###第26条: 勿在分类中声明属性
####要点:
- 把封装数据所用的全部属性都定义在接口里.
- 在类扩展之外的分类中,可以定义存取方法,但尽量不要定义属性.
###第27条: 使用类扩展隐藏实现细节
####要点:
- 通过类拓展向类中新增实例变量
- 如果某属性在主接口中声明'只读',而类的内部又要用设置方法修改此属性,那么就在类拓展中将其拓展为'可读写'
- 把私有方法的原型声明在类拓展中
- 若想使类所遵循的协议不为人所知,可在类拓展中声明
###第28条: 通过协议提供匿名对象
####要点:
- 协议可在某种程度上提供匿名类型.具体的对象类型可以淡化成遵从某协议的 id类型,协议里规定了对象所应实现的方法.
- 使用匿名对象来隐藏类型名称(或类名)
- 如果具体类型不重要,重要的是对象能够响应(定义在协议里的)特定方法,那么可以使用匿名对象来表示.
网友评论