编写高质量iOS与OS X代码的52个有效方法 - 学习笔记 5、6、7

487 阅读10分钟


第五章 内存管理

第29条、理解引用计数

29.1、引用计数的工作原理

在引用计数框架下,对象有个计数器,用以表示当前有多少个事物想令此对象继续存活下去。这在OC中叫做 “保留记数”,也叫 “引用计数”。

引用计数机制通过可以递增递减的计数器来管理内存。对象创建好之后,其保留计数至少为1。若保留计数为正,则对象继续存活。当保留计数降为0时,对象就被销毁了。

NSObject 协议声明了下面三个方法用于操作计数器:

  • retain 递增保留计数
  • release 递减保留计数
  • autorelease 自动释放

为避免在不经意间使用了无效对象,一般调用完 release 之后都会清空指针。这样能保证不会出现可能指向无效对象的指针,这种指针通常被称为 “悬挂指针”。

29.2、属性存取方法中的内存管理

//访问属性时,会用到相关实例变量的获取方法及设置方法。
//若属性为“strong关系”,则设置的属性值会被保留。
- (void)setFoo:(id)foo {
    [foo retain];   //先保留新值
    [_foo release]; //并释放旧值
    _foo = foo;     //然后再将新值设置上去
}

29.3、自动释放池

在 OC 的引用计数架构中。调用 release 会立刻递减对象的保留计数,这里可该用 autorelease,此方法会在稍后递减计数,通常在洗下一次 “事件循环” 时递减,不过也可能提前执行。

autorelease 能延长对象生命期,使其在跨越方法调用边界后依然可以存活一段时间。


第30条、以 ARC 简化引用计数

可以通过编译器的 “静态分析器” 来指明程序里引用计数出问题的地方。可以根据需要,预先加入适当的保留或释放操作以避免内存管理的问题。

由于 ARC 会自动执行 retain、release、autorelease等操作,所以直接在ARC下调用这些内存管理方式是非法的。

ARC 在调用这些方法时,并不通过消息派发机制,而是直接调用其底层C语言版本,这样做性能更好,因为保留及释放操作需要频繁操作,所以直接调用底层函数能节省很多CPU周期。比如:ARC会调用与 retain 等价的底层函数 objc_retain。

30.1、使用ARC时必须遵循的方法命名规则

将内存管理语义在方法名中表示出来早已成为OC的惯例,而 ARC 则将之确立为硬性规定。这些规则简单滴体现在方法名上。若方法名以下列词语开头,则其返回的对象归调用者所有:

  • alloc
  • new
  • copy
  • mutableCopy

ARC 通过命命约定将内存管理规则标准化,除了会自动调用 “保留” 与 “释放” 方法外,使用ARC 还有其他好处,他可以执行一些手工操作很难甚至无法完成的优化。

如:ARC 会把能够互相抵消的 retain、release、autorelease 操作约简。如果发现在同一个对象上执行了多次 “保留” 与 “释放” 操作,那么 ARC 有时可以成对地移除这两个操作。

30.2、变量的内存管理语义

注意与属性的内存管理语义区分

可用下列修饰符来改变局部变量与实例变量的语义:

  • __strong:默认语义,保留此值;
  • __unsafe_unretained:不保留此值,这么做可能不安全,因为等到再次使用变量时,其对象可能已经回收了;
  • __weak:不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空;
  • __autoreleasing:把对象 “按引用传递” 给方法时,使用这个特殊的修饰符。此值在方法返回时自动释放。


第31条、在 dealloc 方法中只释放引用并解除监听

  • 在 dealloc 方法里,应该做的事情就是释放指向其他对象的引用,并取消原来订阅的 KVO 或 NSNotifaicationCenter等通知,不要做其他事情;
  • 如果对象持有文件描述等系统资源,那么应该专门编写一个方法来释放此种资源。这样的类要和其使用者约定:用完资源后必须调用 close 方法;
  • 执行异步任务的方法不应在 dealloc 里调用,只能在正常状态下执行的那些方法也不应在 dealloc 里调用,因为此时对象已经处于正在回收的状态了;
  • 在 dealloc 里也不要调用属性的存取方法。


第32条、编写 “异常安全代码” 时留意内存管理问题

  • 捕获异常时,一定要注意将 try 块内所创立的对象清理干净;
  • 在默认情况下,ARC 不生成安全处理异常所需的清理代码。开启编译器标志后,可生成这种代码,不过会导致应用程序变大,而且会降低运行效率。


第33条、以弱引用避免保留环

  • 将某些引用设为 weak,可避免出现 “保留环”;
  • weak 引用可以自动清空,也可以不自动清空。自动清空是随着 ARC 而引入的新特性,由运行期系统来实现。在具备自动清空功能的弱引用上,可以随意读取其数据,因为这种引用不会指向已经回收过的对象。


第34条、以 “自动释放池块” 降低内存峰值

  • 自动释放池排布在栈中,对象收到 autorelease 消息后,系统将其放入最顶端的池里;
  • 合理运用自动释放池,可降低应用程序的内存峰值;
  • @autoreleasepool 这种新式写法能创建出更为轻便的自动释放池。


第35条、用 “僵尸对象” 调试内存管理问题

  • 待续


第36条、不要使用 retainCount

NSObject 协议中定义 - (NSUInteger)retainCount 用于查询对象当前的保留计数。然而 ARC 已经将此方法废弃了。

此方法之所以无用,其首要原因在于:任何给定时间点上的 “绝对保留计数” 都无法反映对象生命期的全貌。


第6章 Block 与 GCD

第37条、理解 “块” 这一概念

  • 块是C、C++、Objective-C 中的词法闭包;
  • 块可接受参数,也可返回值;
  • 块可以分配在栈或堆上,也可以是全局的。


第38条、为常用的块类型创建 typedef

  • 以 typedef 重新定义块类型,可令块变量用起来更加简单;
  • 定义新类型时应遵从现有的命名习惯,勿使其名称与别的类型相冲突;
  • 不妨为同一个块签名定义多个类型别名。


第39条、用 handler 块降低代码分散程度

  • 在创建对象时,可以使用内联的 handler 块将相关业务逻辑一并声明;
  • 在有多个实例需要监控时,如果采用委托模式,那么经常需要根据传入的对象来切换,而若该用 handler 块来实现,则可直接将块与相关对象放在一起;
  • 设计 API 时如果用到了 handler 块,那么可以增加一个参数,使调用者可通过此参数来决定应该把块安排在哪个队列上执行


第40条、用块引用其所属对象时不要出现保留环

  • 如果块所捕获的对象直接或间接地保留了块本身,那么得当心保留环问题;
  • 一定要找个适当的时机解除保留环,而不能把责任推给 API 的调用者。

第41条、多用派发队列,少用同步锁

  • 派发队列可用来表述同步语义,这种做法要比使用 @synchronized 块或 NSLock 对此昂更简单;
  • 将同步与异步派发结合起来,可以实现与普通加锁机制一样的同步行为,而这么做却不会阻塞执行异步派发的线程;
  • 使用同步队列及栅栏块,可以令同步行为更加高效。


第42条、多用 GCD,少用 performSelector 系列方法


第43条、掌握 GCD 及操作队列的使用时机


第44条、通过 Dispatch Group 机制,根据系统资源状况来执行任务


第45条、使用 dispatch_once 来执行只需运行一次的线程安全代码


第46条、不要使用 dispatch_get_current_queue


第7章 系统框架

第47条、熟悉系统框架

将一系列代码封装为动态库,并在其中放入描述其接口的头文件,这样做出来的东西就叫框架。

Foundation 框架中的类,使用 NS 这个前缀,此前缀是在 OC语言 作为 NeXTSTEP 操作系统的编程语言时首度确定的。

CoreFoundation,与 Foundation 框架相伴。Foundation 框架中的许多功能,都可以在此框架中找到对应的C语言 API。

AppKit 及 UIKit,构建在 Foundation 与 CoreFoundation 之上的 OC类。

  • 许多系统框架都可以直接使用。其中最重要的是 Foundation 与 CoreFoundation,这两个框架提供了构建应用程序所需的许多核心功能;
  • 很多常见任务都能用框架来做,例如音频与视频处理、网络通信、数据管理等;
  • 用纯C写成的框架与用OC写成的一样重要,若想成为优秀的OC开发者,应该掌握C语言的核心概念


第48条、多用块枚举,少用for循环

  • 遍历集合有四种方式。最基本的办法就是 for 循环、其次是 NSEnumerator 遍历法、快速遍历法和 “块枚举法”;
  • “块枚举法” 本身就能通过 GCD 来并法执行遍历操作,无需另行编写代码。而采用其他遍历方法则无法轻易实现这一点;
  • 若提前知道待遍历的 collection 含有何种对象,则应修改块签名,指出对象的具体类型。


第49条、对自定义其内存管理语义的 collection 使用无缝桥接

OC 的系统库包含相当多的 collection 类,其中有各种数组、各种字典、各种set。

Foundation 框架定义了这些 collection 及其他各种 collection 所对应的 OC 类。

CoreFoundation 框架也定义了一套 C语言API,用于操作表示这些 collection 及其他各种 collection 的数据结构。

例如:

NSArray 是 Foundation 框架中表示数组的 OC 类。

CFArray 是 CoreFoundation 框架中的等价物。

这两种创建数组的方式也许有区别,然而有项强大的功能可在这两个类型之间平滑转换,他就是 “无缝桥接”。


第50条、构建缓存时选用 NSCache 而非 NSDictionary

NSCache 是 Foundation 框架专门处理缓存任务而设计的
  • 实现缓存时应选 NSCache 而非 NSDictionary 对象。因为 NSCache 可以提供优雅的自动删减功能,而且是 “线程安全的”,此外,它与字典不同,并不会拷贝键;


第51条、精简 initialize 与 load 的实现代码

  • 在加载阶段,如果类实现了 load 方法,那么系统就会调用它。分类里也可以定义此方法,类的 load 方法要比分类中的先调用。与其他方法不同,load 方法不参与覆写机制;
  • 首次使用某个类之前,系统会向其发送 initialize 消息。由于此方法遵从普通的覆写规则,所以通常应该在里面判断当前要初始化的是哪个类;
  • load 与 initialize 方法都应该实现得精简一些,这有助于保持应用程序的响应能力,也能减少引入 “依赖环” 的几率;
  • 无法在编译器设定的全局常量,可以放在 initialze 方法里初始化。


第52条、别忘了 NStimer 会保留其目标对象

  • NSTimer 对象会保留其目标,直到计时器本身失效ei zhi