Ramis
Ramis

Reputation: 16539

Protocol extension and addTarget is crashing with NSInvalidArgumentException

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

Upvotes: 1

Views: 1022

Answers (3)

Ramis
Ramis

Reputation: 16539

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.

Upvotes: 0

emrys57
emrys57

Reputation: 6806

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?

Upvotes: 1

Sumeet
Sumeet

Reputation: 1055

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.

Upvotes: 0

Related Questions