Swift:接受具有不同参数的闭包

时间:2016-01-16 20:27:48

标签: ios swift generics

假设我有一个函数接受带有发件人的回调,如下所示:

func performAction(aNumber: Double, completion: (sender: UIButton) -> Void) {
    // Does some stuff here
    let button = getAButtonFromSomewhere()
    completion(button)
}

因此,调用此函数的一种可能方法是通过传递现有函数来进行回调,而不是就地定义闭包:

performAction(10, completion: myCallback)

func myCallback(sender: UIButton) {
    sender.setTitle("foo", forState: .Normal)
}

回到我对performAction的定义中,如何定义完成块以接受UIButton或其中的任何子类?

例如,假设我有一个名为UIButton的{​​{1}}子类。所以在我的回调中,我只对接受CustomButton感兴趣。我想这样做:

CustomButton

但是编译器不会允许它,因为performAction(10, completion: myCallback) // This produces a compiler error: func myCallback(sender: CustomButton) { sender.setTitle("foo", forState: .Normal) } // This works, but forces me to cast to my custom class: func myCallback(sender: UIButton) { let realButton = sender as! CustomButton realButton.setTitle("foo", forState: .Normal) } 的定义要求回调专门接受performAction(即使UIButton a CustomButton子类)。

我希望UIButton是通用的,以便它可以打包在一个库中,并与任何performAction子类一起使用。这可以在Swift中做到吗?

编辑:我试图简化上面的例子我正在做的事情,但我认为这只是引起了混乱。这是我正在努力工作的实际代码,感谢@ luk2302的一些改进:

UIButton

现在唯一突破的部分是我评论的行,public extension UIButton { private class Action: AnyObject { private var function: Any init(function: Any) { self.function = function } } // Trickery to add a stored property to UIButton... private static var actionsAssocKey: UInt8 = 0 private var action: Action? { get { return objc_getAssociatedObject(self, &UIButton.actionsAssocKey) as? Action } set(newValue) { objc_setAssociatedObject(self, &UIButton.actionsAssocKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) } } internal func performAction(sender: UIButton) { if let function = self.action!.function as? () -> Void { function() // THIS IS WHERE THINGS BREAK NOW: } else if let function = self.action!.function as? (sender: self.Type) -> Void { function(sender: self) } } public func addTarget(forControlEvents event: UIControlEvents, action: () -> Void) { self.action = Action(function: action) self.addTarget(self, action: "performAction:", forControlEvents: event) } public func addTarget<B: UIButton>(forControlEvents: UIControlEvents, actionWithSender: (sender: B) -> Void) { self.action = Action(function: actionWithSender) self.addTarget(self, action: "performAction:", forControlEvents: forControlEvents) } } (sender: self.Type)self或其中的某个子类。)

所以这稍微偏离了我原来的问题,但我怎样才能将UIButton转换为接受与function相同类型的发件人的闭包?如果我对类型进行硬编码,则此代码可以正常工作,但它应该能够用于任何 self子类。

2 个答案:

答案 0 :(得分:1)

您可以将UIButton子类设为performAction函数的泛型参数,但是在将其传递给回调之前,需要先将按钮转换为除非你也有一种“获得”正确类型按钮的通用方法。

// performAction() works with any type of UIButton
func performAction<B: UIButton>(aNumber: Double, completion: (sender: B) -> Void)
{
    // Assuming getAButtonFromSomewhere returns UIButton, and not B, you must cast it.
    if let button = getAButtonFromSomewhere() as? B {
        completion(sender: button)
    }
}

答案 1 :(得分:1)

问问自己:如果将(CustomButton -> Void)类型的闭包传递为completion,然后getAButtonFromSomewhere返回UIButton的实例,会发生什么?然后代码无法调用闭包,因为UIButton不是CustomButton

编译器根本不允许您将(CustomButton -> Void)传递给(UIButton -> Void),因为(CustomButton -> Void)(UIButton -> Void)更具限制性。请注意,您可以将(UIButton -> Void)传递给(CustomButton -> Void)类型的闭包,因为(UIButton -> Void) 较少限制 - 您可以将传递的所有内容传递给第一个到第二个好。

因此,使func使用泛型类型,如@jtbandes建议或使用您的初始方法稍微改进一下:

func myCallback(sender: UIButton) {
    if let realButton = sender as? CustomButton {
        realButton.setTitle("foo", forState: .Normal)
    }
}

只要setTitle的返回值不是getAButtonFromSomewhere,两种解决方案都会导致CustomButton无法调用。