【问题标题】:Swift: Using protocol extension results in "unrecognized selector sent to instance"Swift:使用协议扩展会导致“无法识别的选择器发送到实例”
【发布时间】:2016-03-05 02:25:22
【问题描述】:

我正在尝试向所有符合MyProtocol 协议的 UIViewControllers 添加 on-tap 功能。

下面是我的做法:

import UIKit

protocol MyProtocol: class{
    var foo: String? {get set}
    func bar()
}


extension MyProtocol where Self: UIViewController {
    func bar() {
        print(foo)
    }
}


class TestViewController: UIViewController, MyProtocol{
    var foo: String?

    override func viewDidLoad() {
        super.viewDidLoad()

        foo = "testing"
        let tapGesture = UITapGestureRecognizer(target: self, action: "bar")
}

点击屏幕时会出现以下结果:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: unrecognized selector sent to instance

我了解该错误,但不知道如何解决。谁能建议如何做到这一点?

【问题讨论】:

    标签: ios swift swift2


    【解决方案1】:

    问题在于 Objective-C 对协议扩展一无所知。因此,您不能使用协议扩展以Objective-C 的消息传递机制可以看到的方式将方法注入到类中。您需要在视图控制器类本身中声明bar

    (我意识到这正是您试图避免做的事情,但我无能为力。)

    一种解决方法可能是这样的:

    override func viewDidLoad() {
        super.viewDidLoad()
    
        foo = "testing"
        let tapGesture = UITapGestureRecognizer(target: self, action: "baz")
        self.view.addGestureRecognizer(tapGesture)
    }
    
    func baz() {
        self.bar()
    }
    

    我们现在使用 Objective-C 的消息传递机制来调用 baz,然后使用 Swift 的消息传递机制来调用 bar,这当然是可行的。我意识到它不像你想象的那么干净,但至少现在bar 的实现可以存在于协议扩展中。

    (当然,另一种解决方案是做我们在协议扩展存在之前所做的事情:让您的所有视图控制器都继承自一些包含bar 的通用自定义 UIViewController 子类。)

    【讨论】:

    • 在这里也可以看到我的回答:stackoverflow.com/questions/31197860/… 这有点相关。
    • 感谢您的解释。我正在将 bar() 方法添加到 viewControllers 中,就像您现在建议的那样作为穷人的替代方案,因为这种解决方法会让将来继承代码的人感到困惑。干杯。
    • 出于好奇,你能用@objc标记协议扩展中的方法吗?
    • @JoshuaSullivan 试试看
    • @matt 请查看我的“答案”。这没什么特别的,因为你是绝对正确的,但是最后一句话(sn-p 下面)可能很有价值......特别是,如果 Swift 中定义的 objc 函数可以从 objc 代码调用
    【解决方案2】:

    马特的回答是正确的。你可以在你的操场上玩耍,看看它是如何工作的。在下一个 sn-p 中,您可以看到即使某些对象可以执行 objc 选择器,但如果使用不当,它可能会使您的代码崩溃:-)

    //: Playground - noun: a place where people can play
    import Foundation
    protocol Bar {}
    extension Bar {
        func bar()->Self {
            print("bar from protocol Bar extension")
            dump(self)
            return self
        }
    }
    class C: NSObject {
        func dummy() {
            print("dummy from C")
            dump(self)
        }
    }
    extension NSObject {
        func foo()->NSObject {
            print("foo from NSObject extension")
            dump(self)
            return self
        }
    }
    let c = C()
    c.respondsToSelector("foo")     // true
    c.respondsToSelector("dummy")   // true
    c.foo()
    let i = c.performSelector("foo")// prints the same and DON'T crash
    c.dummy()   // works
    //c.performSelector("dummy")    // if uncommented then
    /* prints as expected
    dummy from C
    ▿ C #0
      - super: <__lldb_expr_517.C: 0x7fcca0c05a60>
    */
    // and crash!!! because performSelector returns Unmanaged<AnyObject>!
    // and this is not what we return from dummy ( Void )
    
    let o = NSObject()
    o.foo()
    extension NSObject: Bar {}
    // try to uncomment this extension and see which version is called in following lines
    /*
    extension NSObject {
        func bar() -> Self {
            print("bar implemented in NSObject extension")
            return self
        }
    }
    */
    
    c.bar() // bar protocol extension Bar is called here
    o.bar() // and here
    
    o.respondsToSelector("foo") // true
    o.respondsToSelector("bar") // false as expected ( see matt's answer !! )
    o.performSelector("foo")    // returns Unmanaged<AnyObject>(_value: <NSObject: 0x7fc40a541270>)
    

    performSelector: 和朋友们有一个很大的问题:它只能与作为对象的参数和返回值一起使用!所以如果你有一些需要例如的东西布尔值或返回 void 你运气不好。

    我想向所有使用 Swift 和 ObjectiveC 混合的人推荐further reading

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多