A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

Wineo

初级黑马

  • 黑马币:21

  • 帖子:10

  • 精华:0

© Wineo 初级黑马   /  2017-6-11 14:17  /  1539 人查看  /  1 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

ARC 下的内存管理问题

ARC 能够解决 iOS 开发中 90% 的内存管理问题,但是另外还有 10% 内存管理,是需要开发者自己处理的,这主要就是与底层 Core Foundation 对象交互的那部分,底层的 Core Foundation 对象由于不在 ARC 的管理下,所以需要自己维护这些对象的引用计数。

对于 ARC 盲目依赖的 iOS 新人们,由于不知道引用计数,他们的问题主要体现在:

过度使用 block 之后,无法解决循环引用问题。
遇到底层 Core Foundation 对象,需要自己手工管理它们的引用计数时,显得一筹莫展。
循环引用(Reference Cycle)问题

引用计数这种管理内存的方式虽然很简单,但是有一个比较大的瑕疵,即它不能很好的解决循环引用问题。如下图所示:对象 A 和对象 B,相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。因为对象 A 的销毁依赖于对象 B 销毁,而对象 B 的销毁与依赖于对象 A 的销毁,这样就造成了我们称之为循环引用(Reference Cycle)的问题,这两个对象即使在外界已经没有任何指针能够访问到它们了,它们也无法被释放。


不止两对象存在循环引用问题,多个对象依次持有对方,形式一个环状,也可以造成循环引用问题,而且在真实编程环境中,环越大就越难被发现。下图是 4 个对象形成的循环引用问题。



解决循环引用问题主要有两个办法,第一个办法是我明确知道这里会存在循环引用,在合理的位置主动断开环中的一个引用,使得对象得以回收。如下图所示:



主动断开循环引用这种方式常见于各种与 block 相关的代码逻辑中。例如在我们公司开源的 YTKNetwork 网络库中,网络请求的回调 block 是被持有的,但是如果这个 block 中又存在对于 View Controller 的引用,就很容易产生从循环引用,因为:

Controller 持有了网络请求对象
网络请求对象持有了回调的 block
回调的 block 里面使用了 self,所以持有了 Controller
解决办法就是,在网络请求结束后,网络请求对象执行完 block 之后,主动释放对于 block 的持有,以便打破循环引用。相关的代码见:

// https://github.com/yuantiku/YTKNetwork/blob/master/YTKNetwork/YTKBaseRequest.m
// 第 147 行:
- (void)clearCompletionBlock {
    // 主动释放掉对于 block 的引用
    self.successCompletionBlock = nil;
    self.failureCompletionBlock = nil;
}
不过,主动断开循环引用这种操作依赖于程序员自己手工显式地控制,相当于回到了以前 “谁申请谁释放” 的内存管理年代,它依赖于程序员自己有能力发现循环引用并且知道在什么时机断开循环引用回收内存(这通常与具体的业务逻辑相关),所以这种解决方法并不常用,更常见的办法是使用弱引用 (weak reference) 的办法。

弱引用虽然持有对象,但是并不增加引用计数,这样就避免了循环引用的产生。在 iOS 开发中,弱引用通常在 delegate 模式中使用。举个例子来说,两个 ViewController A 和 B,ViewController A 需要弹出 ViewController B,让用户输入一些内容,当用户输入完成后,ViewController B 需要将内容返回给 ViewController A。这个时候,View Controller 的 delegate 成员变量通常是一个弱引用,以避免两个 ViewController 相互引用对方造成循环引用问题,如下所示:

1 个回复

倒序浏览
很详细的解释,收藏了.
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马