小蓝书学习(七)
- 前言
- 第47条:熟悉系统框架
- 第48条:多用枚举块,少用for循环
- 第50条:构建缓存使选用NSCache而非NSDictionary
- 第51条:精简initialize与load的实现代码
- 第52条:别忘了NSTimer会保留其目标对象
前言
虽然不使用系统框架也可以写出OC代码,但几乎没有人这么做,即便我们经常使用的NSObecj
这个标准的根类,也是属于Foundation
框架,而非语言本身。
第47条:熟悉系统框架
将一系列代码封装成动态库,并在其中放入描述其接口的头文件,这样做出来的东西就是框架。
有时为iOS平台构建的第三方框架所使用的是静态库,这是由于iOS应用程序中不允许在其中包括动态库。这些东西严格意义上来说不是真正的框架。
开发者碰到的主要框架就是Foundation
,例如NSObject
,NSArray
,NSDictionary
等类都在其中。该框架中的类都是NS这个前缀,同时这个框架也是OC应用程序的“基础”。
CoreFoundation
这个框架与Foundation
框架相伴,从技术上来讲,这个框架并不是一个OC框架,但是它是编写OC应用程序时所应熟悉的重要框架,Foundation
框架中的许多功能,都可以在这个框架中找到对应的C语言API。
无缝桥接这个功能可以把CoreFoundation
中的C语言数据平滑的转化为Foudation
中的OC对象,例如Foundation
中的NSString
和CoreFoundation
中的CFString
。
除了这些数据库以外,还有很多系统库,下面这张图片进行展示(包括但不限于这些):
通过上面这几个系统库可以看出,OC很重要的一个特点就是,经常需要使用底层的C语言API。用C语言实现API的优点是:可以绕过OC的运行期系统,从而提升执行速度。
要点:
- 许多系统框架都可以直接使用,其中最重要的是
Foundation
和CoreFoundation
,这两个框架提供了构建应用程序所需的许多核心功能。 - 很多常见任务都能用框架来做,比如音频与视频处理、网络通信、数据管理等
- 请记住:用纯C语言所写的框架和OC写成的一样重要,如果想成为优秀的OC开发者,对于C语言的核心概念应该熟悉掌握。
第48条:多用枚举块,少用for循环
在编程中经常需要列举collection
,当前的OC语言中有许多办法来实现这个功能。通常有标准的C语言循环、OC1.0中的NSEnumerator
以及OC2.0中的快速枚举,在引入块这一概念之后,还有几种新的方法。
for循环
for (int i = 0; i < objects.count; i++) {/* ... */
}
使用OC1.0中的NSEnumerator
遍历
NSEumerator
是一个抽象基类,其中只定义了两个方法:
- (NSArray*) allobjects
- (id) nextObject//这个方法比较关键,每次调用这个方法,内部都会更新,知道末端下一个返回nil,表示达到了末端。
这里笔者以遍历数组为例:
NSArray* anArray = /* ... */
NSEumerator* enumerator = [anArray objectEnumerator];
id object;
while ((object = [enumerator nextobject]) != nil) {/* ... */
}
快速遍历:
快速遍历是使用for...in
来实现。具体操作为:
NSArray* arr = /* ... */
for(id object in arr) {/* ... */
}
基于块的遍历方式:
在当前OC语言中,最新引入的一种做法就是基于块来遍历。NSArray
中定义了下面这个方法,他可以实现基本的遍历功能:
-(void) enumerateObjectsUsingBlock:(void(^)(id obj, NSUInteger idx, BOOL * _Nonnull stop))block
当我们遍历字典与set的时候:
NSDictionary* aDictionary = /* ... */aDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {}
使用该方法来遍历的另一个好处就是,能够更改块的方法签名,以免进行类型转换操作,从效果上来讲,相当于把本来要执行的类型转换操作交给块方法签名来做。
使用这个方法也可以执行反向遍历。数组、字典、set都实现了前述方法的另一个版本,令开发者可以向其转入“选项掩码”
要点:
- 遍历
collection
有四种方式。最基本的办法是for
循环,其次是NSEnumerator
遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”。 - “块枚举法”本身就能通过GCD 来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。
- 若提前知道待遍历的
collection
含有何种对象,则应修改块签名,指出对象的具体类型。
第50条:构建缓存使选用NSCache而非NSDictionary
当我们开发应用程序时,需要从网络上下载图片时,我们通常会选择将图片保存到字典中,但是,其实在Foundation
框架下,有一个类NSCache
更好,它是该框架专门为处理这种任务而设计的。
NSCache
胜过NSdictionary
的地方在于,当系统资源即将耗尽的时候,它可以自动删减缓存。如果使用普通的字典,就要自己编写挂钩,在系统发出“低内存”通知手动删减缓存。而NSCache
则会自动删减,由于其是foundation框架的一部分,所以与开发者相比,他能在更深的层面插入挂钩。同时,NSCache
会删减“最久未使用的”对象。
NSCache
并不会“拷贝”键,而是会“保留”它。此行为用NSDictionary
也可以实现然而需要编写相当复杂的代码。NSCache
对象不拷贝键的原因在于:很多时候,键都是由不支持拷贝操作的对象来充当的。因此,NSCache
不会自动拷贝键,所以说在键不支持拷贝操作的情况下,该类用起来比字典更方便。另外,NSCache
是线程安全的而NSDictionary
则绝对不具备此优势,意思就是:在开发者自己不编写加锁代码的前提下多个线程便可以同时访问NSCache
。对缓存来说,线程安全通常很重要,因为开发者可能要在某个线程中读取数据,此时如果发现缓存里找不到指定的键,那么就要下载该键所对应的数据了。而下载完数据之后所要执行的回调函数,有可能会放在背景线程中运行,这样的话,就等于是用另外一个线程来写入缓存了。
要点:
- 实现缓存时应选用
NSCache
而非NSDictionary
对象。因为NSCache
可以提供优雅的自动删减功能,而且是“线程安全的”,此外,它与字典不同,并不会拷贝键。 - 可以给
NSCache
对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”它们仅对NSCache 起指导作用。 - 将
NSPurgeableData
与NSCache
搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData
对象所占内存为系统所丢弃时,该对象自身也会从缓存中移除。 - 如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
第51条:精简initialize与load的实现代码
load方法:
原型:+ (void) load
对于加入运行期系统中的每个类及分类来说,必定会调用此方法,而且仅调用一次,执行顺序为:先调用类再调用分类。
在执行load方法时,整个运行期系统都处于“脆弱状态”。在执行子类的load方法之前必定会先执行所有超类的load方法。故而在load方法中使用其他类是不安全的。
这时候我们可以使用initialize
方法。
initialize方法
对于每个类来说,该方法会在程序首次用该类之前调用,且只调用一次。他与load调用有几个微妙区别:
- 惰性调用:只有当程序用到了相关的类时,才会调用。等于说应用程序不用先把每个类的
initialize
都执行一遍。 - 运行期系统在执行该方法时,处于正常状态。
initialize
方法与其他消息一样,如果某个类未实现它,而期超类实现了,那么就会运行超类的实现代码。
要点:
- 在加载阶段,如果类实现了
load
方法,那么系统就会调用它。分类里也可以定义此方法,类的load
方法要比分类中的先调用。与其他方法不同,load
方法不参与覆写机制。 - 首次使用某个类之前,系统会向其发送
initialize
消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类。 load
与initialize
方法都应该实现得精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率。- 无法在编译期设定的全局常量,可以放在
initialize
方法里初始化。
第52条:别忘了NSTimer会保留其目标对象
计时器是一种很方便也很有用的对象。Foundation框架中有一个NSTimer,开发者可以指定绝对的日期与时间,以便到时间可以执行任务。但是计时器会保留目标对象,所以反复的执行任务通常会导致应用程序出现问题。下面展示一段代码:
这样写代码会存在一个保留环的问题,下面展示一个图来演示这种情况:
那么这个问题我们可以通过块来解决。虽然计时器当前并不直接支持块,但是可以用下面这段代码来解决问题: