Swift 3 protocol extension using selector error

As Matt said, you can't implement @objc methods in a protocol. Frédéric's answer covers Notifications, but what can you do about standard Selectors?

Let's say you have a protocol & extension, like so

protocol KeyboardHandler {
    func setupToolbar()
}

extension KeyboardHandler {
    func setupToolbar() {
        let toolbar = UIToolbar()
        let doneButton = UIBarButtonItem(title: "Done",
                                         style: .done,
                                         target: self,
                                         action: #selector(self.donePressed))

    }

    @objc func donePressed() {
        self.endEditing(true)
    }
}

This will generate an error, as we know. What we can do, is take advantage of callbacks.

protocol KeyboardHandler {
    func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void))
}

extension KeyboardHandler {
    func setupToolbar(callback: (_ doneButton: UIBarButtonItem) -> Void)) {
        let toolbar = UIToolbar()
        let doneButton = UIBarButtonItem(title: "Done",
                                         style: .done,
                                         target: self,
                                         action: nil

        callback(doneButton)

    }

}

Then, add an extension for the class you want to implement your protocol

extension ViewController: KeyboardHandler {

    func addToolbar(textField: UITextField) {
        addToolbar(textField: textField) { doneButton in
            doneButton.action = #selector(self.donePressed)
        }
    }

    @objc func donePressed() {
        self.view.endEditing(true)
    }

}

Instead of setting the action on creation, set it just after creation in the callback.

This way, you still get your desired functionality and can call the function in your class (ex. ViewController) without even seeing the callbacks!


Matt's answer is correct. However, I would just add that, if you are dealing with #selector to use from a NotificationCenter notification, you could try to avoid #selector by using the closure version.

Example:

Instead of writing:

extension KeyboardHandler where Self: UIViewController {

    func startObservingKeyboardChanges() {

        NotificationCenter.default.addObserver(
            self,
            selector: #selector(keyboardWillShow(_:)),
            // !!!!!            
            // compile error: cannot be included in a Swift protocol
            name: .UIKeyboardWillShow,
            object: nil
        )
    }

     func keyboardWillShow(_ notification: Notification) {
       // do stuff
    }
}

you could write:

extension KeyboardHandler where Self: UIViewController {

    func startObservingKeyboardChanges() {

        // NotificationCenter observers
        NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in
            self?.keyboardWillShow(notification)
        }
    }

    func keyboardWillShow(_ notification: Notification) {
       // do stuff
    }
}

This is a Swift protocol extension. Swift protocol extensions are invisible to Objective-C, no matter what; it knows nothing of them. But #selector is about Objective-C seeing and calling your function. That is not going to happen because your on(tap:) function is defined only in the protocol extension. Thus the compiler rightly stops you.

This question is one of a large class of questions where people think they are going to be clever with protocol extensions in dealing with Cocoa by trying to inject Objective-C-callable functionality (selector, delegate method, whatever) into a class via a protocol extension. It's an appealing notion but it's just not going to work.