协议扩展和 addTarget 因 NSInvalidArgumentException 而崩溃

Posted

技术标签:

【中文标题】协议扩展和 addTarget 因 NSInvalidArgumentException 而崩溃【英文标题】:Protocol extension and addTarget is crashing with NSInvalidArgumentException 【发布时间】:2016-02-09 12:20:37 【问题描述】:

协议扩展和 addTarget 崩溃并显示消息:由于未捕获的异常 'NSInvalidArgumentException' 导致应用程序终止,原因:'-[Test.UIButton touchDown:]: unrecognized selector sent to instance 0x157eee8e0'

touchDown函数无法识别的问题在哪里?

protocol MyButtonProtocol 
    var holdTimer: NSTimer?  get set 

extension MyButtonProtocol where Self: UIButton 
    func enable() 
        addTarget(self, action: "touchDown:", forControlEvents: UIControlEvents.TouchDown)
    
    mutating func touchDown(sender: UIButton) 
        print("Touch down!")
        holdTimer = NSTimer(timeInterval: 1, target: self, selector: Selector("didTimeOut"), userInfo: nil, repeats: true)
    

// Usage:
let button = UIButton()
button.enable()

【问题讨论】:

UIButton 不符合 MyButtonProtocol ? 这看起来像一个错误。也在这里:***.com/questions/31060365/… 或者它是一个功能? ***.com/questions/31431753/… 每个人都对此感到困惑。这是另一个:bugs.swift.org/browse/SR-544。因此,来自协议扩展的方法不能用于 Objective-C 调用,就像任何使用“选择器”的东西一样。感谢您的努力,Ramis,这非常有帮助。 【参考方案1】:

这一切都很奇怪。

你的代码

let button = UIButton()
button.enable()

似乎不完整,因为实例button 没有采用协议MyButtonProtocol

如果我写

class MyButton: UIButton, MyButtonProtocol 
    var holdTimer: NSTimer?
    func touch3(sender: AnyObject?) 
        print("touch3 \(sender)")
    

然后

let myButton = MyButton()
myButton.enable() // this works ok
print("touch3: \(myButton.respondsToSelector(Selector("touch3:")))")
print("touchDown: \(myButton.respondsToSelector(Selector("touchDown:")))")
print("enable \(myButton.respondsToSelector(Selector("enable")))")

然后我看到输出

touch3: true
touchDown: false
enable false

所以程序成功调用了enable() 方法,但respondsToSelector 似乎没有检查协议扩展中的方法。 enable 正在工作,因为如果我将调用更改为

addTarget(self, action: "touch3:", forControlEvents: UIControlEvents.TouchDown)

那确实成功到达touch3

这是NSObject.respondsToSelector 实现中的错误吗?

几周前我确实注意到我无法用协议扩展中的函数覆盖super 中的函数。我以为这只是我误解的语言功能。也许这是同一问题的另一个症状?

【讨论】:

感谢您的评论。我没有运气做了更多的测试。我向苹果团队询问了这种行为。我会继续发帖的。 Apple 的回答:工程部门根据以下信息确定此问题的行为符合预期:Objective-C 无法使用 Swift 协议扩展。 Objective-C 只能使用类的扩展。我们现在关闭此错误报告。【参考方案2】:
protocol MyButtonProtocol 
    var holdTimer: NSTimer?  get set 

extension MyButtonProtocol where Self: UIButton 
    var holdTimer: NSTimer? 
        get 
            return holdTimer
        
        set 
            holdTimer = newValue;
        
    

    func enable() 
        addTarget(self, action: "touchDown:", forControlEvents: UIControlEvents.TouchDown)
    
    mutating func touchDown(sender: UIButton) 
        print("Touch down!")
        holdTimer = NSTimer(timeInterval: 1, target: self, selector: Selector("didTimeOut"), userInfo: nil, repeats: true)
    


extension UIButton: MyButtonProtocol 


// Usage:
let button = UIButton()
button.enable()

注意为 UIButton 编写的扩展,这是必需的,因为您已经编写了一个协议,但 UIButton 需要实现它才能使其生效。我还在协议扩展中为holdTimer 编写了getter 和setter。您也可以在 UIButton 扩展名中编写此代码。

【讨论】:

myButton.respondsToSelector(Selector("touchDown:")) 在这个实现中仍然返回false,并且在调用touchDown 时仍然会出现异常。【参考方案3】:

Apple Bug 报告团队的回答:

工程部门已确定此问题的行为符合预期 关于以下信息:Swift 协议扩展不是 可用于 Objective-C。只有类的扩展才能被 Objective-C。

【讨论】:

以上是关于协议扩展和 addTarget 因 NSInvalidArgumentException 而崩溃的主要内容,如果未能解决你的问题,请参考以下文章

UITapGestureRecognizer 和 addTarget 的区别

UIbutton frame 和 addtarget 不同

Swift:UIScrollView 和 UIControl - addTarget | addGestureRecognizer - 没有按预期工作

对 addTarget:action:forControlEvents 的 addTarget 指针行为感到困惑:

在同一个类中调用UIButton AddTarget

具有自定义视图的 UIButton - addTarget:action:forControlEvents: 不起作用