在将闭包传递为参数

时间:2015-06-30 14:02:03

标签: swift reactive-cocoa

我在Swift中使用ReactiveCocoa如下:

registerButton?.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNextAs(registerButtonTapped)

private func registerButtonTapped(button: UIButton){
    // Method here
}

这会创建一个保留周期。

我知道解决方案如下:

registerButton?.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNextAs({ [weak self] (button:UIButton) in
    self?.registerButtonTapped(button)
})

但是这强制我使用subscribeNextAs块而不是更好的oneliner传递方法。

知道如何在没有保留周期的情况下使用oneliner吗?

1 个答案:

答案 0 :(得分:12)

好的,所以由Jakub Vano链接的the answer很棒,但它并非相当通用。它限制您使用没有参数的函数并返回Void。使用Swift泛型,我们可以更聪明地使用接受任何参数的函数并使用任何返回类型。

首先,对于weak关系,必须使用对象。结构不能被弱化。因此,我们将实例类型约束为AnyObject,这是一个所有类都符合的协议。我们的函数声明如下所示:

func applyWeakly<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue?)

因此该函数接受一个实例,以及一个接受实例并返回Parameters -> ReturnValue函数的函数。请记住,Swift中的闭包和函数是可以互换的。整齐!

请注意,我们必须返回可选 ReturnValue,因为该实例可能会变为nil。我稍后会讨论如何解决这个问题。

好了所以现在你需要知道一个非常巧妙的技巧:Swift instance methods are actually just curried class methods,这完全符合我们的需求

现在我们可以使用 class 函数调用applyWeakly,当您使用实例调用它时,它会返回实例函数。 applyWeakly实现非常简单。

func applyWeakly<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue?) {
    return { [weak instance] parameters -> ReturnValue? in
        guard let instance = instance else { return nil }
        return function(instance)(parameters)
    }
}

超级。那你怎么用这个呢?我们来看一个非常简单的例子。我们有一个带有一个包含闭包的参数的类,该闭包将引用它自己的实例方法(这只是演示了引用循环问题 - 你的问题涉及两个对象,但我简化为一个)。

class MyClass {
    var closure: (String -> String?)!

    func doThing(string: String) -> String {
        return "hi, \(string)"
    }

    init() {
        closure = doThing // WARNING! This will cause a reference cycle
        closure = applyWeakly(self, function: MyClass.doThing)
    }
}

我们必须为closure类型使用隐式解包的可选项,以便我们可以在init函数中引用实例方法。没关系,只是这个例子的限制。

所以这很棒,而且会有效,但我们的closure类型为String -> String?,但我们的doThing类型为String -> String,这有点蠢。为了返回非可选字符串,我们需要强制解包可选()或使用unowned

无主引用就像弱引用,除非它们是非归零的。这意味着如果对象被取消分配并且您使用对它的引用,您的应用程序将会爆炸。但是,在您的情况下,它是适用的。 Here's more info关于无主和弱者。

applyUnowned函数如下所示:

func applyUnowned<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue) {
    return { [unowned instance] parameters -> ReturnValue in
        return function(instance)(parameters)
    }
}

我希望澄清事情 - 很乐意回答任何后续问题。这个问题已经在我的脑海里浮现了一段时间,我很高兴终于记下了我的想法。