什么是自动引用计数(ARC)和引用循环

ARC 全称 Automatic Reference Counting,中文译名为:自动引用计数。通过 ARC 的机制,当创建一个新对象时,它的引用计数为 1,当有一个新的指针指向这个对象时,其引用计数会自动加 1,当某个指针不再指向这个对象时,其引用计数会自动减 1。当对象的引用计数变为 0 时,说明这个对象不再被任何指针指向,然后系统会自动销毁对象,回收内存。

声明:笔者自身对 ARC 也是初学,而本文介绍的 ARC 知识也都是自己的一些理解,同时尽量不涉及过于原理性的内容,一切以普通工程师实用为目标原则。其中可以想象在很多地方会有理解的错误,还请多包涵。如您发现问题,也往不吝赐教指正,感激不尽。

引用循环

得益于 ARC 的工作,开发者不需要太过于关心内存泄露的问题,但是总有意外情况发生。

想象一下,当两个对象已经不再被需要,但这两个对象相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减 1。由于这两个对象互相引用,所以他们各自的引用计数都为 1,因此这两个对象永远不会被销毁。这种情况称之为强引用循环(Strong Reference Cycle)

弱引用

想要解决这个问题,可以将引用计数对象之间的引用关系指定为弱引用。 如果不手动指定,那所有引用都默认会是强引用。

弱引用不会增加对象的引用计数。此外,弱引用始终为可选类型。 这意味着当引用计数变为零时,引用可以自动设置为 nil

具体的实现方法就是,在成员变量前加上 weak 修饰符。

无主引用

无主引用也是弱引用,它与弱引用的区别就在于弱引用始终是可选的,并且在引用的对象取消初始化时会自动变为 nil。 这就是为什么必须将弱引用的成员变量定义为可选 var类型(因为变量需要更改)。

而无主引用不是可选类型。 如果尝试访问已取消初始化对象的无主引用的成员变量,则会触发强制解包 nil 的错误。

与闭包有关的引用循环

当成员变量相互引用时,会出现对象内部的引用循环。 与对象一样,闭包也是引用类型,所以也可能会导致循环。

由于闭包对使用它们的对象有一个强引用,所以如果对象对闭包有一个强引用,同时在闭包的代码块中又对该对象本身有一个强引用(比如 self),那这样就会引起引用循环。

解决这个这个问题的方式就是,在闭包内部使用 [unowned self] in 语句,这样就将闭包内部对对象的引用变为弱引用。但是需要注意的是,如果在调用闭包的时候,对象已经被释放的话,会出现错误。或者是使用 [weak self] in 语句,那在闭包内部的 self将变为可选值。

但是这样仍然没有解决在调用闭包时,对象被释放的错误,想要解决这个错误就需要在闭包内部将对对象的应用重新变为强引用,而避免对象在执行过程中被释放。

…{ [weak self] in 
    guard let self = self else { return } 
    self.somefunc()
}

这种做法的名称为 Weak-Strong Dance,从而就保证了对象在执行过程中不会被释放。