作者:@nixzhu
如果你从ObjC的MRC时代走来,一定觉得ARC很好,因为它将我们从繁琐的内存管理中解放出来。Swift也使用ARC来管理内存。
简单地描述ARC,即,对于一个对象(其实应该讲“引用类型”,例如闭包也是引用类型),若它的引用计数为0,那么系统将回收它的内存。我们创建一个对象后,通常会将其“赋值”给一个变量。在这个变量的生命周期里,它将“持有”对象,对象的引用计数+1。如果这个变量是个局部变量,它的生命将很快走到尽头,以至于它不再能持有这个对象,这个对象的引用计数将-1。如果这个变量是全局变量,它的生命周期和整个app一样,那这个对象也就“永远”不会被释放。注意,单例也是全局变量的一种。而引用计数为0的对象将被销毁,由此,内存得以回收再利用。
对于ARC的使用,只有一种需要注意的情况,那就是“循环引用”。对于两个(或多个)对象(引用类型),如果它们互相引用,也就是互相持有,那么它们就永远不会被释放,结果就是内存泄漏。少量的内存泄漏可能不会造成问题,但因为本应该被销毁的对象一直存在,也可能带来潜在的Bug。
除了深刻理解“面向对象”的含义,搞清楚“谁持有谁”,只需要在必要的时候使用捕捉列表来标记被捕获的对象为weak
或者unowned
来打破循环引用即可。
因此,就只剩下一些注意事项:
-
隐式捕捉
通常是在闭包里使用了外部变量,注意不只限于
self
,闭包将隐式捕捉它们,也就是持有它们。还有一种情况是嵌套函数,也就是定义在函数內的函数,它们也可能使用其函数体之外的变量,这时候它们和闭包一样,你可以把它们看做为函数式定义的闭包。 -
GCD的内存管理
当你利用GCD切换线程时,其闭包会捕获某些对象,这些对象的生命周期就会受到GCD的影响。因为GCD的实际调度时刻由系统决定,因此,这些对象的生命周期也就不太“稳定”,它们可能会活得比你预计的更久。
-
对值类型的捕捉
有时候闭包会捕获一些值类型的变量,要注意的是它捕捉的实际上是一份拷贝。如果你在外部修改了这些值类型,闭包将无法感知到,在闭包内部修改它们也一样,外部也不知道,因为这些值发生了写时复制,本质上是两份不同的数据。所以如果你的闭包依赖外部值的改变,那么你很可能不应该捕捉它们,而要利用
self
等来访问这些值变量,以得到最新的改变。
虽然我很推荐新手在使用闭包时明确地利用捕捉列表来指定其捕捉的变量为weak
,以打破可能的循环引用,但如果你非常清楚引用类型之间的引用关系和它们的生命周期,那么你也可以使用unowned
或者直接捕捉。
最后,请注意你在闭包里使用的每一个变量,如果它不是在闭包内定义的,请小小思考一下。
今天是6月4日,我也少不得提一下六四事件。今日的一切,都由过去的一个个选择所成就。明日的一切,将由今日以及今日的一个个选择所成就。
欢迎转载,但请一定注明出处! https://github.com/nixzhu/dev-blog