了解iOS 6开发第三篇:内存管理 - 手动引用计数与自动引用计数
本文不会举例介绍MRC,如果不了解MRC,最好先简单了解一下,再回来读这篇文章。
从iOS 5开始,iOS开发就可以使用自动引用计数(Automatic Reference Counting)了。一直想写一篇详细的笔记,来整理这方面的信息,但一直拖到现在。这篇文章是
WWDC 2012 Session 406 - Adopting Automatic Reference Counting的笔记,如果对LLVM感兴趣,可以去llvm.org深入了解一下。
关于自动引用计数(Automatic Reference Counting, aka ARC)
很多工程师都是最近一、两年才开始接触iOS开发的。但大多数工程师都接触过Java/C#这样的静态编译语言,或者python/ruby这样的动态脚本语言。这些“现代”语言的都提供Garbage Collection的机制,由GC来负责内存的回收。我先强调一点:ARC是编译器提供的机制,而不是GC这种运行时提供的机制。要了解ARC,必须明白什么是引用计数,必须从MRC开始。此外,你会遇到很多旧的开源代码和静态库(比如广告平台)。这些很可能不支持ARC,所以还是要理解MRC。
关于手动引用计数(Manully Reference Counting, aka MRC)
Objective-C的MRC,简单来说就是一句话:谁创建,谁释放。复杂一点的解释如下:
- 所有由alloc、copy(mutablecopy)、retain、new创建的object,必须手动release(或autorelease)
- 反之,则不需要也不可以
- autorelease不是自动释放,是AutoreleasePool的实例,在runloop中被”稍后“释放。
MRC有什么问题
常犯的错误:
- Crash! Reject! 内存管理是导致app crash的最主要原因。也是App Store审核时拒审的最多的原因;
- 规则简单,但细节太多,依赖于工程师的清醒头脑和良好习惯,依赖于命名规范
- Dangling Pointer,delegate需要手动设置为nil
- 触发NSError、或者有复杂的条件分支逻辑时,忘记释放内存
需要使用一堆工具,帮助你检测代码:
- Instruments: Allocations, Leaks, Zombies
- Static Analyzer
- Heap
- ObjectAlloc
- vmmap
- Debugger
工程师需要做的事情:
- 理解MRC,写正确的代码
- 正确的命名规范
- 使用五花八门的辅助工具,来保证代码没问题
- 保持清醒的头脑,不犯低级错误
这不是工程师所擅长的事情,这个工作应该由编译器来做才对—LLVM 3.1
什么是ARC
- Objective-C对象的自动管理机制
- 由LLVM编译器来保证
- 与MRC完全兼容
- 引入新的运行时特性:弱指针、性能优化
ARC不是
- 新的运行时模型
- 自动malloc/free, CF等等(不负责纯C代码的内存管理)
- 不是GC。不扫描堆、不暂停进程运行、没有不确定的内存释放
ARC如何工作
- 编译器在编译期,帮你动态添加retain/release/autorelease
- 返回同样结果的不同API,在内存方面,没有区别(把alloc/init模式与convenient method当成一样就可以了)
ARC有什么好处
- 不用写retain/release/autorelease这样的代码,甚至连dealloc方法都不需要(前提是类不需要管理C指针)
- 更好的性能,ARC智能地在”适当“的地方插入retain/release
- Block just work
- Less code, less bug
- 少浪费脑细胞和脑时钟周期
如何打开ARC
- Xcode的Project Setting中,设置Objective-C Automatic Reference Counting为YES
- Xcode 4.2+,新的Project默认为ARC
为了实现ARC,LLVM编译器必须保证4条规则
- 不能调用或实现跟引用计数相关的方法(retain/release/autorelease/retaincount等),否则产生编译错误
- C的结构体里面,不能有Object指针;如果必须使用,那么可以用Objective-C的类代替
- 编译器必须清楚void*是否被retained。引入新的API来转换Objective-C和Core Foundation类型的对象
- 编译器必须清楚自动释放的对象,因此AutoreleasePool不能是对象,而只是语义上的符号。@autoreleasepool directive甚至可能在MRC中使用
如何理解ARC
- 从对象的从属关系角度考虑:强引用(retain)保证对象的存在,或没有强引用,则对象被自动销毁
- 想清楚程序的对象图
- 不要再考虑retain, release, autorelease了
弱引用(必须在iOS 5中使用)
- 安全的”弱指针“,引用的对象被销毁时,自动变成nil;避免了Dangling Pointer
- 在property中使用weak关键字声明,ivar中使用_weak声明
循环引用
- ARC依然存在循环引用的可能
- 手动设置某一个引用为nil,破坏循环引用
- 使用弱引用来避免
性能
- 与MRC没有实质的性能区别,甚至更好(有时候好得多),内存峰值比MRC低
- 没有GC的开销;没有延迟的销毁,没有进程暂停,没有不能确定的释放
ARC的优点
- 更容易理解
- 写更少的代码,更少的bug
- 更容易维护,甚至更好的性能
- 安全、稳定
iOS的什么版本才能支持ARC
- iOS 6发布后,以iOS 5.0作为Deployment Target完全没问题。所以都可以使用ARC。如果你必须支持5.0以下的设备,那么看后两条。
- ARC由LLVM 3.1保证,LLVM 3.1在安装Xcode 4.2自动安装;也就是说只要Xcode 4.2以上的版本,可以设置的Deployment Target的最低版本,都支持ARC。笔者在4.0+的app中使用ARC。
- 弱引用(Weak Reference)必须在iOS 5以上的设备才支持
总结
笔者其实觉得MRC很容易掌握,profile的工具也容易掌握。但ARC实实在在的从安全、可维护性、代码量、性能上有全面的提升。所以除非维护旧的代码,笔者都建议使用ARC。不用担心app依赖第三方的MRC代码,可以通过编译器设置文件的编译属性来混合使用ARC和MRC。
如果想更多地了解LLVM是如何智能地保证ARC,请阅读文章头部引用的两篇文章,里面有详细的解释和代码例子。
附小广告一则:唱吧iOS团队诚招iOS工程师,推荐成功即奖励6000元现金或iPhone 6一部,详见这篇blog。