Effective Objective-C 2.0 Tips 总结 Chapter 5,6,7
Chapter 5 内存管理
- Tips 29 理解引用计数
- 引用计数是 Objective-C 内存管理的基础,包括 ARC 也是建立在引用计数的基础之上,理解引用计数机制,能更好的帮你使用 ARC
- 引用计数的基本原理是每个对象都有一个当前有多少对象希望他存活的计数器,当这个计数器归零那么这个对象就会被释放
- 查看引用计数的方法叫做
retainCount
但是实际并不建议使用这个方法调试代码 - 如果对象 A 需要对象 B 存活,那么 A 需要调用 B 的
retain
方法,如果对象 A 不再需要对象 B 存活了,那么需要调用对象 B 的release
方法或者autorelease
方法 - 调用
release
并不会使对象被释放,对象释放被释放取决于引用计数是否为 0 - 所有的对象最终都间接或直接的被一个根对象所引用,macOS 应用是
NSApplication
对象,iOS 则是UIApplication
对象,这两个对象都是应用启动时创建的单例 - 对象的
alloc
方法返回的对象由调用者持有 -
autorelease
并不会马上减少对象的引用计数,而是在下一次 Event Loop(事件循环)时减少,以达到延迟释放对象的效果 -
autorelease
通常用在函数返回对象的情况,保证对象跨越函数调用边界
-
Tips 30 以 ARC 简化引用计数
- ARC 只是自动为代码添加内存管理相关的代码
- 在 ARC 下,不允许调用
retain
,release
,autorelease
,dealloc
- ARC 调用这些方法时,不通过消息派发机制,直接调用底层 C 语言版本,以提升性能
-
alloc
,new
,copy
,mutableCopy
命名开头的方法,其返回的对象归调用者所有 - ARC 能够自动优化内存管理代码,减少不必要的内存管理操作
- ARC 能够自动的帮我们解决大部分的内存管理问题,所以没啥特别的要求,建议都使用 ARC
- 变量内存管理语义修饰符
-
__strong
:默认,强引用,表示需要保留这个值 -
__weak
:弱引用,表示不保留这个值,并且如果系统回收这个对象,那么在获取此变量的值的时候会的到nil
-
__unsafe_unretained
:不安全的引用,不保留此值,系统回收这个对象的时候,不会清空变量的值 -
__autoreleasing
:把对象“按引用传递”给方法时使用,表示此值在方法返回时自动释放
-
- ARC 是通过在编译时在我们的代码中插入对应的内存管理代码,并且只适用于 Objective-C 的代码,CoreFoundation 创建的对象,还是需要人工管理内存
-
Tips 31 在
dealloc
方法中只释放引用,并解除监听- 对象在被系统收回的时候会执行
dealloc
方法 -
dealloc
方法中需要做的事情:- 释放对象所拥有的引用,持有的对象
- 清理观察者
- 清理通知
- 如果不使用 ARC,那么需要调用
[super dealloc]
方法
-
dealloc
方法中不适合做的事情:- 释放开销较大或系统内稀缺的资源(文件描述符,套接字,大量内存等),因为
dealloc
方法并不会在特定时机调用,一般对于使用这样资源的对象都需要提供名字类似open
和close
的方法处理申请和释放资源的行为 - 执行异步任务
- 释放开销较大或系统内稀缺的资源(文件描述符,套接字,大量内存等),因为
- 对象在被系统收回的时候会执行
-
Tips 32 使用异常时留意内存管理问题
- C++ 和 Objective-C 的异常互相兼容,可以相互抛出捕获
- 不使用 ARC 的情况,可以使用异常来处理错误
- 使用 ARC 的情况,默认情况下不可以使用异常来处理错误
- 通过
-fobjc-arc-exceptions
标志可以打开 ARC 处理异常的代码,但是会影响运行期性能,并增加应用大小 - 一般来说,只有在应用必须直接终止时才应该抛出异常
-
Tips 33 使用弱引用避免循环引用
- 发生循环引用最简单的情况——两个对象相互持有,复杂的情况是对象间的引用关系是闭环的
- 循环引用会导致内存泄漏
- 非 ARC 的情况下使用
assign
或者unsafe_unretained
来修饰弱引用属性 - ARC 的情况下使用
weak
来修饰弱引用的属性,因为weak
的属性在对象被释放后会自动设置为 nil
-
Tips 34 使用
@autorelease
代码块降低内存峰值- 系统自动创建的线程(主线程或是 GCD 中的线程)默认都有自动释放池,每次执行“事件循环”的时候会情况,因此一般情况下无需担心自动释放池创建的问题,页不需要自己来创建一个
@autorelease
块 - 合理利用
autorelease
可以降低应用程序的内存峰值,例如循环创建大量对象的时候
- 系统自动创建的线程(主线程或是 GCD 中的线程)默认都有自动释放池,每次执行“事件循环”的时候会情况,因此一般情况下无需担心自动释放池创建的问题,页不需要自己来创建一个
-
Tips 35 使用 Xcode 提供的“僵尸对象(Zombie Object)”调试内存管理问题
- 僵尸对象可以响应所有消息,响应的方式是打赢一条包含消息内容以及接受者的消息,然后终止应用程序
- 僵尸对象用于调试代码是否会使用到已经被销毁的对象
-
Tips 36 不要使用
retainCount
-
retainCount
在反映调用者有多少对象引用,以及调试内存管理都没有任何帮助 -
retainCount
在 ARC 环境下将会编译错误
-
Chapter 6 Block & GCD
块(block) 和 GCD 是苹果多线程编程的核心,块是一种可以用在 C,C++,Objective-C 嗲码中的“词法闭包”,使用 block 开发者可以把代码像对象一样传递,让代码在不同的上下文中(context)运行,并且块可以访问定义他范围内的全部变量。
GCD 把线程抽象为“派发队列(dispatch queue)”,开发者将块放入队列,由 GCD 负责处理所有调度事宜。
- Tips 37 理解“块”
- 块通过
^
来定义 - 定义好的块可以像函数一样使用,也可以像变量一样传递
- 块可以捕获在他被声明范围内所有的变量
- 块捕获的变量必须通过增加
__block
修饰符才能修改 - 块会持有所捕获的对象类型变量
- 定义在类实例方法中的块还可以使用
self
变量,能够修改实例变量,在声明时无需加_block
,在块内直接修改实例变量和使用self
来访问变量是等效的,但是如果需要通过属性来访问实例变量,需要指明self
来使用属性 - 块中使用
self
有可能会导致循环引用 - 块有可能分配在栈或堆上,也可以是全局的,分配在栈上的块可以拷贝到堆,这样和普通的 Objective-C 对象一样具备引用计数
- 块通过
- Tips 38 为常用的块类型创建
typedef
- 使用
typedef
定义块类型,方便使用 - 为同一个块签名可以定义多个类型别名,重构时只需要重构需要变化的类型
- 使用
- Tips 39 使用 handle 块降低代码分散程度
- 在创建对象时,使用 handle 块将相关业务逻辑一起声明,减少代码分散的情况
- Tips 40 用块引用其所属对象是不要出现循环引用
- 使用块时需要考虑循环引用的问题
- 设计 api 的时候,需要找到合适的机制解除循环引用,不能让客户端代码来处理
- Tips 41 多用 GCD,少用同步锁
- 使用
@synchronized
或者NSLock
效率不高,并且面对死锁的情况,处理起来很麻烦 - 使用串行队列(serial synchronization queue)可以保证代码顺序执行,但是不能保证同步
- 使用栅栏块(barrier block)可以保证数据同步处理
- 使用 GCD 比同步锁效率要高
- 使用
- Tips 42 多用 GCD,少用
performSelector
系列方法-
performSelector
系列方法的传入参数和返回值类型都很局限,并且一些情况下 ARC 无法自动添加内存释放操作导致内存泄漏,并在编译期间会提示警告-Warc-performSelector-leaks
- 最好的方法是将任务封装到块里,然后使用 GCD 来执行
-
- Tips 43 判断什么时候使用 GCD 什么时候使用
NSOperationQueue
- 在需要纯使用 Objective-C API 的时候
- 执行的任务需要取消的时候
- 操作间存在依赖关系的时候
- 需要使用 KVO 来观察执行状况的时候
- 需要指定操作优先级的时候
- 需要重用的时候
- Tips 44 通过 Dispatch Group 机制根据系统资源状况来执行任务
- 一系列任务可以归入到一个 dispatch group 中,在这组任务执行完后开发者会收到通知
- GCD 会根据系统资源来调度一个 dispatch group 中的任务
- Tips 45 使用
diapatch_once
来执行只需运行一次的线程安全代码- 标记应该声明在 static 或是 global 作用域中
- 可以实现单例
- Tips 46 不要使用
dispatch_get_current_queue
- 会导致死锁
- 函数行为不可预期
Chapter 7 系统框架
- Tips 47 熟悉系统框架
- 系统框架的核心是 Foundation 和 CoreFoundation
- 音频视频,数据库,网络都可以用系统框架来处理
- C 语言会为优秀的 Objective-C 开发者助力
- Tips 48 多用块枚举,少用 for 循环
- 使用“块枚举”本身能通过 GCD 来并发的执行遍历操作
- 如果知道 collection 中有什么对象,那么应该修改块签名指出对象类型
- Tips 49 对自定义内存管理语义的 collection 使用无缝桥接(toll-free-bridging)
- 通过无缝桥接,可以来回转换 Foundation 中的 Objective-C 对象和 CoreFoundation 中的 C 结构体
-
__bridge
告诉 ARC 如何处理转换所涉及的 Objective-C 对象 - 在 CoreFoundation 层面创建 collection 的时候,可以指定许多回调函数,这些函数表示此 collection 应如何处理其元素,然后可以通过无缝桥接技术,将其转换成具备特殊内存管理语义的 Objective-C collection
- Tips 50 构建缓存时使用
NSCache
而非NSDictionary
-
NSCache
提供了自动删减功能,不会拷贝键,并且是线程安全的 - 搭配
NSPurgeableData
使用,可以实现自动数据清楚的功能
-
- Tips 51 精简
initialize
与load
的实现代码-
load
方法没有复写机制 - 各个类的
load
方法调用时机是不确定的,所以在load
方法中使用其他类是不安全的 - 首次使用某个类之前,系统会发送
initialize
消息,initialize
消息尊存复写机制,所以要判断当前需要初始化的是哪个类 -
load
和initialize
都应该精简,这样有助于提高应用的响应能力,也能减少循环引用的概率 - 无法在编译期设定的全局常量,可以放在
initialize
方法中初始化
-
- Tips 52
NSTimer
会持有其目标对象-
NSTimer
会持有目标对象直到计时器本身失效为止 - 对于反复执行任务的计时器,很容易产生循环依赖
- 可以扩展
NSTimer
使用块来解决循环依赖的问题
-