在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]
并调用超级实现。