文章目录
前言
本次博客撰写小蓝书的最后一章系统框架
四十七、熟悉系统框架
OC的Foundation框架,像NSObject NSArray, NSDictionary等类都在其中。Foundation框架里的类都是用NS前缀,因为OC之前作为NeXTSTEP操作系统确定的。
将一系列代码封装为动态库,并在其中放入描述其接口的头文件,这样做出来的东西就叫框架。
CoreFoundation框架
Foundation框架提供了collection等基础核心功能,而且还提供了字符串处理等复杂功能。还存在一个CoreFoundation框架,在之前了解过他是不属于OC框架之内的,但是OC应用程序的编写离不开这个框架,Foundation框架的许多功在CoreFoundation框架都可以找到对应的C语言API
他其中的很多类都和Foundation框架相似,并且我们还可以通过“无缝桥接”功能实现CoreFoundation框架中的C语言数据结构平滑转换为Foundation框架中的OC对象,也可以反向转换。无缝桥接技术是用某些相当复杂的代码实现出来的,这些代码可以使运行期系统把CoreFoundation框架中的对象视为普通的OC对象。
NSString所对应的就是CFString对象。
其他框架:
OC编程的重要特点就是:经常需要使用底层的C语言级 API。用c语言实现 API的好处是,可以统过 Objeotive-C 的运行期系统,从而提升执行速度
要点
- 许多系统框架都可以直接使用。其中最重要的是Foundation与CoreFoundation,这两个框架提供了构建应用程序所需的许多核心功能。
- 很多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等。
- 请记住:用纯C写成的框架与用OC写成的一样重要,若想成为优秀的OC开发者,应该掌握C语言的核心概念。
四十八、多用块枚举,少用for循环
语言中引人“块” 这一特性后,又多出来儿种新的通历方 式,而这几种方式容易为开发者所忽视。采用这几种新方式遍历collection 时,可以传人块, 而collection 中的每个元素都可能会放在块里运行一遍,这种做法通常会大幅度简化编码过程以下进行各种遍历方法详细说明:
for循环
根据定义,因为字典与set对象是无需的,所以无法通过下标访问,所以需要先获取字典里的所有键值或是set中的所有对象,这两种情况下都可以放到数组中实现,就如上述代码所显示
NSEnumerator遍历
NSEnumerator是个抽象基类,其中只定义了两个方法,供其具体子类来实现:
关键的是其中的nextObject对象,它返回枚举里的下个对象,当返回不为nil的时候就会一直调用下一个对象,常用while语句
对于set与字典写法其实也与标准的for循环相似,只是代码多了一些,其优势在于无论遍历哪一种collection,都可以采用这套相似的语法
快速遍历
快速遍历是OC2.0引入的语法功能,引入了in关键字,语法更加简洁了collection的遍历过程。尤其是字典类
基于块的遍历方式
参数1是每次枚举的对象, idx是下标, stop则是代表是否停止遍历
使用这种方法遍历时既能获取对象,也能知道其下标
[t enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@ %lu", obj, (unsigned long)idx);
}];
对于字典,还可获得其key与object
此方式大大胜; 过其他 方式的地方在于:遍历时可以直接从块里获取更多信息。在遍历数 组时,可以知道当前所针对的下标。遍历有序set (
NSOrderedset )时也一样。而在遍历字典 时, 无须额外编码,即可同时获取键与值,因而省去了根据给定键来获取对应值这一步。用
这种方式遍历字典,可以同时得知键与值,这很可能比其他方式快很多,因为在字典内部的 数据结构中,键与值本来就是存储在一起的。
同时还可通过选项掩码进行反向遍历
要点
- 遍历collection有四种方式。最基本的办法是for循环,其次是NSEnumerator遍历法及快速遍历法,最新、最先进的方式则是“块枚举法”。
- “块枚举法”本身就能通过GCD来并发执行遍历操作,无须另行编写代码。而采用其他遍历方式则无法轻易实现这一点。
- 若提前知道待遍历的collection含有何种对象,则应修改块签名,指出对象的具体类型
四十九、对自定义其内存管理语义的collection使用无缝桥接
Objective-C的系统库包含相当多的collection 类,其中有各种数组、各种字典、各种 setoFoundation框架定义了这些collection及其他各种collection所对应的Objective-C类 。与 之相似,CoreFoundation 框架也定义了一套C语言API,用于操作表示这些collection 及其他 各种collection 的数据结构。
例如,NSAray 是Foundation 框架中表示数组的Objective-C类, 而CFArray 则是CoreFoundation 框架中的等价物。这两种创建数组的方式也许有区别,然而 有 项 强 大 的 功 能 可 在 这 两 个 类 型 之 间 平 滑 转 换, 它 就 是 “ 无 缝 桥 接” ( t o l l - f r e e b r i d g i n g ) 。
转换操作中的__bridge
告诉ARC如何处理转换所涉及的OC对象。__bridge
本身的意思是:ARC仍然具备这个OC对象的所有权。而__bridge_retained
则与之相反,意味着ARC将交出对象的所有权。与之相似,反向转换可通过__bridge_transfer
来实现,也就是将对象的所有权交给ARC。这三种转换方式称为“桥式转换”
- (void)seamlessBridging {
NSArray *testArray = @[@"111", @"222", @"333",];
CFArrayRef aCFArray = (__bridge CFArrayRef)testArray;
NSLog(@"cfArratSize = %li", CFArrayGetCount(aCFArray));
}
要点
- 通过无缝桥接技术,可以在Foundation框架中的OC对象与CoreFoundation框架中的C语言数据结构之间来回转换。
- 在CoreFoundation层面创建collection时,可以指定许多回调函数,这些函数表示此collection应如何处理其元素。然后,可运用无缝桥接技术,将其转换成具备特殊内存管理语义的OC collection。
五十、构建缓存时选用NSCache而非NSDictionary
开 发 M a c O s × 或 i 0 s 应 用 程 序 时 , 经 常 会 遇 到 一个 问 题 , 那 就 是 从 因 特 网 下 载 的 图 片 应如何来缓存。首先能想到的好办法就是把内存中的图片保存到字典里,这样的话,稍后使 用 时 就 无 须 再 次 下 载 了。 有 些 程 序 员 会 不 假 思 索 , 直 接 使 用 N S D i c t i o n a r y 来 做 (准 确 来 说 , 是使用其可变版本),因为这个类很常用。其实,NSCache 类更好,它是Foundation 框架专 为处理这种任务而设计的。
NSCache 胜过NSDictionary 之处在于,当系统资源將要耗尽时,它可以自动删减缓存。
另外,NSCache 是线程安全的。 而NSDictionary 则绝对不具备此优势
要点
- 实现缓存时应选用NSCache而非NSDictionary对象。因为NSCache可以提供优雅的自动删减功能,而且是“线程安全的”,此外,它与字典不同,并不会拷贝键。
- 可以给NSCache对象设置上限,用以限制缓存中的对象总个数及“总成本”,而这些尺度则定义了缓存删减其中对象的时机。但是绝对不要把这些尺度当成可靠的“硬限制”,它们仅对NSCache起指导作用。
- 将NSPurgeableData与NSCache搭配使用,可实现自动清除数据的功能,也就是说,当NSPurgeableData对象所内存为系统所丢弃时,该对象自身也会从缓存中移除。
- 如果缓存使用得当,那么应用程序的响应速度就能提高。只有那种“重新计算起来很费事的”数据,才值得放入缓存,比如那些需要从网络获取或从磁盘读取的数据。
五十一、精简initialize与load的实现代码
在OC里一个类必须初始化才能使用,大多数类继承与NSObject这个根类,提供了load
和initalize
方法
load
加入运行期系统中的每个类和分类来说,会调用此方且仅调用一次,当类和分类的程序载入系统的时候就会执行这个方法,调用顺序是类大于分类。
总结一句话就是不要用load
initalize
该方法是在程序首次使用该类之前调用且仅有一次,是由运行期系统调用的,不通过代码调用。和load存在区别
- 惰性调用:当程序用到了相关类的时候才会调用,类似于懒加载模式。而load是所有类不管用不用先load方法之后再说
- 其次运行期在执行该方法的时候是系统正常状态,也就是安全状态,不影响调用类的其他方法,此为线程安全。
- 当某个类没实现initialize方法,超类实现后会调用超类的方法,和大多数消息是一样的。
initalize方法尽量精简
- 首先,大家都不想看到自己的应用程序“挂起”,若写的太过繁琐,导致其运行很慢那就适得其反了。
- 开发者无法控制类的初始化时机。
- 最后,如果某个类的实现代码很复杂,那么其中可能会直接或间接用到其他类。若那些类尚未初始化,则系统会迫使其初始化。然而,本类的初始化方法此时尚未运行完毕。其他类在运行其initialize方法时,有可能会依赖本类中的某些数据,而这些数据此时也许还未初始化好,就会造成依赖环
initalize方法只应该用来设置内部数据,不能在内部调用其他的方法。
initalize还可以初始化某个无法在编译器初始化的全局变量
但别忘了单例类也可以实现该目的
要点
- 在加载阶段,如果类实现了load方法,那么系统就会调用它。分类里也可以定义此方法,类的load方法要比分类中的先调用。与其他方法不同,load方法不参与覆写机制。
- 首次使用某个类之前,系统会向其发送initialize消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是那个类。
- load与initialize方法都应该实现的精简一些,这有助于保持应用程序的响应能力,也能减少引入“依赖环”的几率。
- 无法在编译期设定的全局常量,可以放在initialize方法里初始化。
五十二、别忘了NSTimer会保留其目标对象
Foundation框架中有个类叫NSTimer,开发者可以指定绝对的日期与时间,以便到时执行任务,计时器要和“运行循环(run loop)”相关联,运行循环到时候会触发任务。创建NSTimer时,可以将其“预先安排”在当前的运行循环中,也可以先创建好,然后由开发者自己来调度。无论采用哪种方式,只有把计时器放在运行环里,它才能正常触发任务
创建计时器
_pollTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(pDoPoll) userInfo:nil repeats:YES];
保留环
创建计时器的时候,由于目标对象是self,所以要保留此实例。然而,因为计时器是用实例变量存放的,所以实例也保留了计时器,于是,就产生了保留环。所以说,调用stopPolling,或者令系统将此实例回收,只有这样才能打破保留环。
因为是类和这个类中的实例出现了保留环,不管你外界怎么对这个类释放,这个计时器始终都会保留这个类,而这个类也会保留这个计时器,互相引用保留导致他们的计数永远都不会降为0
如果从外界直接先调用stop方法,代码没办法自己检测。
使用块的特点打破保留环
使用块和weak关键字合理的打破保留环,块可以传递代码,这一功能可以利用
这个办法为何能解快“ 保留环〞问题呢?大家马上就会明白。这段代码将计时器所应执 行的任务封装成“ 块”,在调用计时器西数时,把它作为userlnfo 参数传进去。该参数可用来 存 放 “ 不 透 明 值 ” ( o p a q u e v a l u e )。 , 只 要 计 时 器 还 有 效 , 就 会 一直 保 留 着 它 。 传 人 参 数 时 要 通 过copy方法将block拷贝到“堆” 上(之前在blk提过,copy方法把块变成了有引用计数的对象。),否则等到稍后要执行它的时候,该块 可能己经无效了。计时器现在的target 是NSTimer 类对象,这是个单例,因此计时器是否会 保 留 它, 其 实 都 无 所 谓 。 此 处 依 然 有 保 留 环 , 然 而 因 为 类 对 象 ( c l a s s o b j e c t ) 无 须 回 收 , 所 以 不用担心。
此处依然有保留环,使用weak关键字打破它
这 段 代 码 采 用 了一 种 很 有 效 的 写 法 , 它 先 定 义 了一 个 弱 引 用 , 令 其 指 向 s e l f , 然 后 使 块 捕获这个引用,而不直接去捕获普通的sel f 变量。也就是说,sel f 不会为计时器所保留。当 块开始执行时,立刻生成strong 引用,以保证实例在执行期间特续存活。
采用这种写法之后,如果外界指向EOCClass实例的最后 一个引用将其释放,则该实例 就 可 为 系 统 所 回 收 了。
要点
- NSTimer对象会保留其目标,直到计时器本身失效为止,调用invalidate方法可令计时器失效,另外,一次性的计时器在触发完成任务之后也会失效。
- 反复执行任务的计时器,很容易引人保留环,如果这种计时器的目标对象又保留了计时器本身,那肯定会导致保留环。这种环状保留关系,可能是直接发生的,也可能是通过对象图里的其他对象间接发生的。
- 可以扩充NSTimer的功能,用“块” 来打破保留环。不过,除非NSTimer将来在公共接口里提供此功能,否则必须创建分类,将相关实现代码加入其中。