1

Protocol extension and addTarget is crashing with message: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Test.UIButton touchDown:]: unrecognized selector sent to instance 0x157eee8e0'

Where is the problem that touchDown function is unrecognized?

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()
Ramis
  • 13,985
  • 7
  • 81
  • 100
  • `UIButton` doesn't conform to `MyButtonProtocol` ? – Mike Pollard Feb 09 '16 at 14:18
  • This looks like a bug. Also here: http://stackoverflow.com/questions/31060365/swift-2-protocol-extensions-respondstoselector Or is it a feature? http://stackoverflow.com/questions/31431753/swift-protocol-extensions-overriding – emrys57 Feb 09 '16 at 15:02
  • 1
    Everyone is confused by this. Here's another one: https://bugs.swift.org/browse/SR-544 . So, Methods from protocol extensions are not usable with objective-C calls, like anything that uses "Selector". Thanks for pursuing this, Ramis, this is very helpful. – emrys57 Feb 19 '16 at 10:33

3 Answers3

1

This is all very odd.

Your code

let button = UIButton()
button.enable()

seems incomplete, because the instance button is not adopting the protocol MyButtonProtocol.

If I write

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

then

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")))")

then I see the output

touch3: true
touchDown: false
enable false

so the program is successfully calling the enable() method but respondsToSelector does not seem to be checking the methods in the protocol extension. enable is working because if I change the call to

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

then that does successfully reach touch3.

Is that a bug in the implementation of NSObject.respondsToSelector?

I did notice a couple of weeks ago that I could not override a function in super with a function in a protocol extension. I thought it was just a language feature I misunderstood. Maybe that was another symptom of the same problem?

emrys57
  • 6,679
  • 3
  • 39
  • 49
  • Thanks for commenting. I did more testing without luck. I asked Apple team about this behavior. I will keep posting. – Ramis Feb 10 '16 at 09:09
  • Answer from the Apple: Engineering has determined that this issue behaves as intended based on the following information: Swift protocol extensions are not available to Objective-C. Only extensions to classes can be used by Objective-C. We are now closing this bug report. – Ramis Feb 19 '16 at 10:21
0
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()

Notice the extension written for UIButton, this is needed since you have written a protocol but UIButton needs to implement it inorder for it to be effective. I have also written the getter and setter for holdTimer in the protocol extension. You can also write this inside the UIButton extension.

Sumeet
  • 1,055
  • 8
  • 18
  • `myButton.respondsToSelector(Selector("touchDown:"))` still returns `false` with this implementation and it still gives an exception on the call to `touchDown`. – emrys57 Feb 09 '16 at 14:39
0

Answer from the Apple Bug Report Team:

Engineering has determined that this issue behaves as intended based on the following information: Swift protocol extensions are not available to Objective-C. Only extensions to classes can be used by Objective-C.

Ramis
  • 13,985
  • 7
  • 81
  • 100