如何在Swift中通过选择器在运行时调用通用函数?

3
我会尽力提供准确的翻译,以下是您需要翻译的内容:

我正在尝试像这样在运行时注册UI事件的回调。

func observeEvent(event: UIControlEvent){
    self.addTarget(self, action: "eventFired:", forControlEvents: event)
}

func eventFired<T>(sender: T){
    print("event fired!")
}

它崩溃了并且在运行时说找不到eventFired事件。你能帮忙解决吗?


调用选择器是 Obj-C 的一个功能,带有泛型的方法对于 Obj-C 是不可见的。 - Sulthan
1个回答

1
您不能将通用函数作为选择器字符串文字的目标。为什么呢?因为选择器不包含关于其目标参数类型的信息,只包括:
  • 选择器目标的名称(方法名称)
  • 以及(隐式地)选择器目标的参数数量。
因此,即使是具有相同名称和相同参数数量的非通用函数,Swift也不允许将其用作选择器的目标,因为这会导致选择器无法确定使用哪个目标。
例如,考虑以下(playground)示例:
class Foo : UIView {
    var button = UIButton()

    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        button.addTarget(self, action: "eventFired:", forControlEvents: UIControlEvents.TouchUpInside)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func eventFired(sender: UIButton){
        print("event fired!")
    }
}

let foo = Foo()
foo.button.sendActionsForControlEvents(.TouchUpInside)
    /* event fired! */

这个功能按预期工作,但现在尝试向Foo类添加以下方法:
class Foo : UIView { 

    // ...

    func eventFired(nothingToDoWithAButton: Int){
        print("another event fired!")
    }
}

与上面相同的示例现在会产生错误,因为选择器在调用哪个目标方法方面存在冲突。
let foo = Foo()
foo.button.sendActionsForControlEvents(.TouchUpInside)
    /* error ... */

错误:方法'eventFired'与Objective-C选择器'eventFired:'的先前声明冲突...

最后我们注意,如果我们将上述附加方法更改为

class Foo : UIView { 

    // ...

    func eventFired(nothingToDoWithAButton: Int, someBool: Bool){
        print("another event fired!")
    }
}

然后选择器按预期找到了其目标eventFired(:UIButton),因为它可以根据参数数量的不同区分两个eventFired方法。
从上面可以看出,选择器/选择器文字在它们当前的形式下太过原始,无法明确地识别和使用通用目标。这些选择器需要包括目标参数类型知识才能区分同名/同参数数量的目标。
最后请注意,选择器在Swift 2.2中将作为字符串文字被弃用,并在Swift 3.0中被移除,以支持新的表达式语法#selector,请参见:

(根据原帖下面的评论进行编辑)

您可以尝试通过让目标捕获来自参考类型的各种信号(...(:AnyObject)),并根据包含在AnyObject参数中的类型来处理事件,从而模仿某种通用选择器目标:

class Foo : UIView {
    var button = UIButton()
    var sw = UISwitch()

    init() {
        super.init(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        button.addTarget(self, action: "eventFired:", forControlEvents: .TouchUpInside)
        sw.addTarget(self, action: "eventFired:", forControlEvents: .ValueChanged)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func eventFired(sender: AnyObject){
        switch(sender) {
        case is UIButton: print("button event fired!")
        case is UISwitch: print("switch event fired!")
        case _: print("unknown event fired")
        }
    }
}

let foo = Foo()
foo.button.sendActionsForControlEvents(.TouchUpInside)
    /* button event fired! */
foo.sw.sendActionsForControlEvents(.ValueChanged)
    /* switch event fired! */

@TaiHo,您可以在目标中捕获AnyObject类型,并根据包含在AnyObject“包装器”中的实际(引用)类型处理后续逻辑,请参见我上面答案中的编辑添加。 - dfrib
真是痛苦:(。我以为我可以这样做: self.addTarget(self, action: "eventFired:", forControlEvents: event)
func eventFired2(sender: UIControl){ let className : String = sender.dynamicType.description() print(className) let classType = NSClassFromString(className) as! UIControl.Type print(classType) eventFired(sender as? classType) } 但它总是说classType不是一种类型。该死...
- Ryan Ho

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接