MRC 工程转 ARC 工程小记

春节前抽空花了一天的时间将手头的工程从 MRC 转成了 ARC,然后陆陆续续地修复一部分因为转 ARC 引起的内存泄漏和崩溃,到目前为止工程也算是比较稳定了,抽空记上一笔。(虽说这种事情这辈子估计都只会做这么一次了,但是可以留点经验给后来的童鞋)

这个工程启动于 12 年底 13 年初,一开始人手少工期短,需要尽快地出 demo,同时抱着对面世才一年多的 ARC 不太信任的态度沿用了最熟悉的 MRC。但是随着工程投入的人手增多,使用 MRC 的各种缺点也暴露无遗:

  • 零星的内存泄漏增多,导致每次发版本之前都要捋一遍:费时费力不讨好。虽然我一直觉得 iOS 下这种零星的内存泄漏并不是多大的事,真正压死一个 App 的泄漏永远是某些 Bitmap 的泄漏。但是这种零星泄漏仍旧会带来很多麻烦,所以还是需要解决一下。
  • 无法享受到 ARC 下 weak 关键字带来的好处:总有童鞋忘记在对象析构时去置空 delegate。系统控件如 UIScrollView,MKMapView 的 delegate 仍旧需要自己释放,但是自定义 procotol 并将属性设为 weak 的 delegate 就可以获得 weak 属性带来的好处。
  • ARC 下编译器对于 retain cycle 的检测更为严格。(个人使用后的感觉)
  • 越来越多的第三方库只提供 ARC 版本,虽然打标记可以解决问题,但增加了无谓的工作。

基于以上 4 点理由,于是选了春节前一个月高风黑夜悄悄地完成工程的 ARC 转换。23333333333333333

准备工作

  1. 一个 MRC 模式的工程。(嗯!)

  2. 一个合适版本的 XCode。(你是鸡丁?不,我是喜儿肉丝) 虽然 XCode4 之后就支持了 ARC 的自动转换,但是对 ObjC++ 的支持却还是在 XCode5 之后。

  3. 一台性能彪悍的机器。个人悲惨经历:某天下午用自己那台老爷机做了一次转换,结果在最后一步机器直接卡死,重启后 XCode 也无法使用,最后只得重装 XCode 了事。

使用 XCode 做最基本的转换

  1. 开启即使出错也继续编译的选项:”Preferences” -> “General” -> “continue building after error” 。当转换开始后工程将出现大量的错误,与其每次 fix 一个错误再来一遍,还不如一口气让编译器把所有的错误都先汇报出来,再一一解决。(我们的工程大约 20 来万代码,检查出了近 200 个错误)

  2. 检查第三方库和自己的代码。对于第三方库,有 ARC 版本就进行替换,没有则打上 - fno-objc-arc 的标记 (当然如果第三方库比较简单,也可以直接做转换)。而自己的代码则推荐全部做 ARC 的转换。

  3. 使用 XCode 提供的 Convert to Objective-C ARC 功能,选择当前需要转换的工程并执行。

  4. 正常情况会出现比较多的错误和 retain-cycle 的 warning,推荐优先解决掉所有 error,而 warning 暂时不处理,等转换完毕编译通过后再进行处理。而 error 和 warning 一般也就是下面几种情况:

  • 对于 NSObject 和 CF 对象没有使用 bridge cast,大多数情况下直接按照 XCode 的推荐方式进行 fix 即可。
  • 原来 MRC 下使用了 ARC 下不允许使用的方法,如 NSMakeCollectable。
  • 原先使用 __block 关键字避免循环引用的地方在 ARC 往往会引起循环引用,原因是 __block 在 MRC 和 ARC 下的语意不同,MRC 下生成的 __block 结构体内只是简单地指向原值地址,而 ARC 下则是由 __block 结构体持有了原值,使用 __weak 进行修改即可。

处理完毕后重复第三步直到顺利编译通过。

后续处理

完成前面的步骤整个转换就算完成了百分之九十,但是正所谓行百里者半九十,接下去的任务更加艰巨,更需要认真对待。

  1. 检查工程内的所有文件,包括是否设置了合理的编译选项和被包含在工程内:XCode 似乎有个 bug,在转换完成后部分文件会被移出工程,部分文件原先打好的 - fno-objc-arc 标记也会被重置。

  2. 运行 Instrument,检查内存泄漏:此时存在的内存泄漏大多是一些对 ARC 不适用的 MRC 写法。典型的情况便是将 delegate 作为类内部成员变量,在转换为 ARC 后系 XCode 并不会在这些变量前面打上 weak 的标记,导致了循环引用,需要自己手动添加。

  3. 对你的程序进行冒烟,尽量走完主流程,检查是否有必现崩溃。一般都是由错误的 bridge cast 和使用 MRC 时的不规范写法引起: 如万恶的 [self retain],在 ARC 转换时 XCode 直接去掉这句话,这样就导致原先依赖于此的类往往在初始化后就直接被释放,造成野指针访问。(吐槽下,无论是 MRC 还是 ARC 下,自己去拥有自己这种做法都是不太好的做法)

  4. 扔给 QA 继续测试……23333333333333333333333