EffectiveObjective-C2.0 笔记 - 第七部分
7. 系统框架
7.1 熟悉系统框架
1. 框架:将一系列代码封装成动态库,并在其中放入描述其接口的头文件。
平时我们第三方框架用的是静态库,因为iOS 应用程序不允许其中包含动态库。
2. Foundation、CoreFoundation 框架平时用的比较多,“无缝桥接” 可以将这两种框架的对象平滑转换。
3. 常用框架:
- CFNetwork
- CoreAudio
- AVFoundation
- CoreData
- CoreText
7.2 多用块枚举,少用for 循环
遍历collection 有四种方式。最基本的办法就是for 循环,其次是NSEnumerator 遍历法及快速遍历法,最新、最先进的方式则是 “块枚举法”。
1. for 循环
简单粗暴,遍历数组还可以,但是对于遍历字典或者set,就不太友好。
2. 使用Objective-C 1.0 的NSEnumerator 来遍历
1 | NSArray *array = @[@"A",@"B",@"C"]; |
这种遍历使用相对比较统一,数组、字典和set 都可以这样子写,并且还有多种 “枚举器” 可供使用,例如反向遍历数组的枚举器。
3. 快速遍历
1 | for (<#type *object#> in <#collection#>) { |
for in 这个更加简洁,如果某个类的对象支持快速遍历,那么就可以宣称自己遵从名为NSFastEnumeration 的协议,从而令开发者可以采用此语法来迭代改对象。此协议只定义了一个方法:
1 | - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len; |
由于NSEnumerator 对象也实现了NSFastEnumeration 协议,所以能用来执行快速遍历。但是快速遍历拿不到当前操作对象的下标。
1 | NSArray *array = @[@"A",@"B",@"C"]; |
4. 基于块的遍历方式
1 | NSArray *array = @[@"A",@"B",@"C"]; |
“块枚举法” 本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历则无法轻易实现这一点。
此方式对于其他相比,在遍历时候可以直接在块中获取更多信息,而且这种对于字典的遍历也是非常友好的,一次性可以返回键和值。并且还可以支持反向遍历。
7.3 对自定义其内存管理语义的collection使用无缝桥接
无缝桥接
使用 “无缝桥接” 计数,可以在定义于Foundation框架中的Objective-C类和定义于CoreFoundation框架中的C数据结构之间相互转换。
1. 三种转换方式
- __bridge 只是声明类型转变,但是不做内存管理规则的转变
- __bridge_retained 表示将指针类型转变的同时,将内存管理的责任由原来的Objective-C 交给Core Foundation 来处理,也就是ARC 转变成 MRC
- __bridge_transfer 表示将管理的责任由Core Foundation 转交给Objective-C,即将MRC转变成ARC
- Foundation中字典对象无缝桥接:
Foundation中字典对象,对其键的内存管理语义为 “拷贝”,而值的语义是 “保留”。只能通过强大的无缝桥接技术,否则无法改变其语义。
CoreFoundation 框架的字典类型是CFDictionary,可变版本是CFMutableDictionary。
1 | //CFMutableDictionary 用CFDictionaryCreateMutable 来创建 |
7.4 构建缓存时选用NSCache而非 NSDictionay
1. 实现缓存时应选用NSCache而非NSDictionary 对象。
NSCache 是专门来处理缓存的,在系统资源将要耗尽时,它可以自动删减缓存。而且是线程安全的,此外,它与字典不同,并不会拷贝健。
2. 可以给NSCache 对象设置上限
可以给NSCache 对象设置上限,用以限制缓存中的对象总个数及总成本,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的 “硬限制”,它们仅对NSCache其指导作用。
3. NSPurgeableData 与 NSCache 搭配使用
NSPurgeableData类是NSMutableData的子类,而且实现了NSDiscardableContent协议。将NSPurgeableData 与 NSCache 搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData 对象所占内存为系统丢弃时,该对象也会从缓存中移除。
4. 如果缓存使用得当,那么应用程序的响应速度就能提高。
只有那种 “重新计算起来很费事的” 数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
7.5 精简initalize与load的实现代码
1. 在加载阶段,如果类实现了load 方法,那么系统就会调用它。分类里也可以定义此方法,类的load 方法要比分类中的先调用。与其他方法不同,load 方法不参与覆写机制。
对于加入运行期系统中的每个类及分类,必定会调用load这个方法,而且仅调用一次。意思就是程序启动的时候需要加载load方法,这个时候运行期系统也是出于 “脆弱状态”,在执行子类的load方法之前,必定会先执行所有超类的load 方法。
如果load 代码还依赖了其他类,那类的load 也必然会先执行,我们无法判断每个类的载入顺序,所以load 方法使用其他类是不安全的。
load 方法不遵从继承规则,如果某个类没实现load 方法,那么不管其各级超类是否实现此方法,系统都不会调用。
load 方法要实现的精简点,因为应用程序在执行load 方法会阻塞。load 一般作为调试用,很少用来做初始化操作。
2. load 与initialize 方法都应该实现得精简一些,这有助于保持应用程序的响应能力,也能减少引入 “依赖环” 的几率。
想执行与类相关的初始化操作,可以使用
+(void)initialize
这个方法,它跟load 有以下几个区别:- 这个方法是在首次使用这个类的时候调用,类似 “惰性调用” ,只有用到这个类才会调用。
- 运行期在执行该方法的时候,是出于正常状态的,此时是可以安全调用任意类的任意方法,而且运行期系统会确保initialize 方法一定在 “线程安全的环境” 中执行。其他线程都要先阻塞,等initialize 执行完。
- initialize 方法跟其他方法一样,某个类没有实现它,而超类方法实现了,那么就会运行超类的实现代码。
也就是说initalize 与 load 的实现代码要精简些。
若某个全局状态无法在编译期初始化,则可以放在initalize 里来做。(例如Objectice-C 对象,创建实例之前必须先激活运行期系统)
7.6 别忘了NSTimer会保留其目标对象
1. 计时器放在运行循环里,它才能正常触发任务。
1 | + scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: |
2. 计时器保留环
计时器会保留其目标对象,等到自身 “失效” 时再释放此对象,调用invalidate 方法可令计时器失效,另外,一次性的计时器在触发完任务之后也会失效。设置成重复执行模式的计时器,要注意 “保留环” 问题。
3. 如何解决外界不调用invalidate方法也不产生 “保留环” 的问题。
可以用块来解决这个问题,其实就是将timer的target 对象不要指向持有timer的对象,这里用的方法是让timer 的taerget 指向自己。
1 | //定义 |