在Swift中处理函数引用时避免保留周期

本文使用RxSwift来说明其观点。 如果您不熟悉该库,则可以阅读其 GitHub存储库上的简介

非常感谢 Hugo Saynac 的表现,使我意识到了这个问题,并提出了我将在下面描述的第二种解决方案。

与许多语言一样,Swift允许开发人员引用实例方法,并在以后使用它引用或调用此方法。 尽管语法使它很容易利用,但其底层实现可能会导致创建一些棘手的保留周期。

在本文中,我将解释这种保留周期何时产生,是什么原因造成的,最后是避免它们的难易程度。

方法参考如何工作?

让我们考虑以下类:

要了解真正的原因,我们需要深入研究方法引用的工作方式。

这是什么意思 ? 我们需要记住, printInput是一个实例方法,因此它的引用需要知道在调用该方法时self将引用哪个值。 即使像我们的示例中那样,该方法实际上并没有使用self ,这也是正确的,并且这是很有意义的,因为子类可能会很好地覆盖该方法并在其新实现中使用self

这就是WillNeverBeFreed.printInput为我们提供方法引用的原因,该方法的self值尚未绑定。 它采用函数的形式,该函数以self的值作为参数,并返回对实际实例方法的引用。 因此,对该方法的绑定引用存储了一个强指针,该指针将用于self

当我们调用.subscribe(onNext: printInput) ,我们将绑定到self的方法的引用作为参数传递给了参数,这导致了保留周期。

两种可能的解决方案

为了解决这个问题,我们基本上需要找到一种方法的参考方法,同时又不能以很强的方式捕捉self

进行的第一种方法是定义一个像这样的函数:

它所做的是在一个弱变量中捕获self的值,直到实际调用该方法为止,如果仍然为self分配了值,它将在该点调用它,否则它将不执行任何操作。

然后我们像这样使用它:

另一种合理的方法是将方法声明为计算属性返回的闭包:

(在这种情况下,我们在闭包中不使用self,因此不会捕获它。但是,如果使用并捕获了[weak self] ,则将指示[weak self]以避免捕获强引用)

然后,使用它的语法恰好是我们要实现的语法:

一切都是权衡

上述两种解决方案都设法解决了保留周期的问题,但是每种解决方案都有其特定的缺点。

使用第一个解决方案将使语法稍微简洁一些,但是它具有使用实际方法的优势,既可以覆盖它们又可以调用其超级实现。

第二种解决方案保留了更简洁的语法,但是是以使用闭包而不是方法为代价的,这是Swift 3所付出的代价:闭包可以在子类中覆盖,但是覆盖的闭包需要在使用之间进行选择[weak self]并调用超级实现。