Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift make protocol extension a Notification observer

Let's consider the following code:

protocol A {
    func doA()
}

extension A {
  func registerForNotification() {
      NSNotificationCenter.defaultCenter().addObserver(self, selector: Selector("keyboardDidShow:"), name: UIKeyboardDidShowNotification, object: nil)
  }

  func keyboardDidShow(notification: NSNotification) {

  }
}

Now look at a UIViewController subclass that implements A:

class AController: UIViewController, A {
   override func viewDidLoad() {
      super.viewDidLoad()
      self.registerForNotification()
      triggerKeyboard()
   }

   func triggerKeyboard() {
      // Some code that make key board appear
   }

   func doA() {
   }
}

But surprisingly this crashes with an error:

keyboardDidShow:]: unrecognized selector sent to instance 0x7fc97adc3c60

So should I implement the observer in the view controller itself? Can't it stay in the extension?

Following things already tried.

making A a class protocol. Adding keyboardDidShow to protocol itself as signature.

protocol A:class {
   func doA()
   func keyboardDidShow(notification: NSNotification)
}
like image 434
Swift Hipster Avatar asked Oct 08 '15 17:10

Swift Hipster


People also ask

Can you extend a protocol Swift?

In Swift, you can even extend a protocol to provide implementations of its requirements or add additional functionality that conforming types can take advantage of. For more details, see Protocol Extensions. Extensions can add new functionality to a type, but they can't override existing functionality.

Should I remove notification observer?

specifies. If your app targets iOS 9.0 and later or macOS 10.11 and later, and you used addObserver(_:selector:name:object:) , you do not need to unregister the observer. If you forget or are unable to remove the observer, the system cleans up the next time it would have posted to it.

What is protocol and protocol extension in Swift?

Protocols let you describe what methods something should have, but don't provide the code inside. Extensions let you provide the code inside your methods, but only affect one data type – you can't add the method to lots of types at the same time.


3 Answers

I solved a similar problem by implementing the newer - addObserverForName:object:queue:usingBlock: method of NSNotificationCenter and calling the method directly.

extension A where Self: UIViewController  {
    func registerForNotification() {
        NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardDidShowNotification, object: nil, queue: nil) { [unowned self] notification in
            self.keyboardDidShow(notification)
        }
    }

    func keyboardDidShow(notification: NSNotification) {
        print("This will get called in protocol extension.")
    }
}

This example will cause keyboardDidShow to be called in the protocol extension.

like image 84
James Paolantonio Avatar answered Oct 17 '22 11:10

James Paolantonio


In addition to James Paolantonio's answer. A unregisterForNotification method can be implemented using associated objects.

var pointer: UInt8 = 0

extension NSObject {
    var userInfo: [String: Any] {
        get {
            if let userInfo = objc_getAssociatedObject(self, &pointer) as? [String: Any] {
                return userInfo
            }
            self.userInfo = [String: Any]()
            return self.userInfo
        }
        set(newValue) {
            objc_setAssociatedObject(self, &pointer, newValue, .OBJC_ASSOCIATION_RETAIN)
        }
    }
}

protocol A {}
extension A where Self: UIViewController {

    var defaults: NotificationCenter {
        get {
            return NotificationCenter.default
        }
    }

    func keyboardDidShow(notification: Notification) {
        // Keyboard did show
    }

    func registerForNotification() {
        userInfo["didShowObserver"] = defaults.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil, using: keyboardDidShow)
    }

    func unregisterForNotification() {
        if let didShowObserver = userInfo["didShowObserver"] as? NSObjectProtocol {
            defaults.removeObserver(didShowObserver, name: .UIKeyboardDidShow, object: nil)
        }
    }
}
like image 3
andershqst Avatar answered Oct 17 '22 10:10

andershqst


To avoid the crash, implement the observer method in the Swift class that uses the protocol.

The implementation has to be in the Swift class itself, not just the protocol extension, because a selector always refers to an Objective-C method, and a function within a protocol extension is not available as an Objective-C selector. Yet methods from a Swift class are available as Objective-C selectors if the Swift class inherits from an Objective-C class

“If your Swift class inherits from an Objective-C class, all of the methods and properties in the class are available as Objective-C selectors.”

Also, in Xcode 7.1, self has to be downcast to AnyObject when specifying it as the observer in the addObserver call.

protocol A {
    func doA()
}

extension A {
    func registerForNotification() {
        NSNotificationCenter.defaultCenter().addObserver(self as! AnyObject,
            selector: Selector("keyboardDidShow:"),
            name: UIKeyboardDidShowNotification,
            object: nil)
    }

    func keyboardDidShow(notification: NSNotification) {
        print("will not appear")
    }
}

class ViewController: UIViewController, A {
    override func viewDidLoad() {
        super.viewDidLoad()
        self.registerForNotification()
        triggerKeyboard()
    }

    func triggerKeyboard(){
        // Some code that makes the keyboard appear
    }

    func doA(){
    }

    func keyboardDidShow(notification: NSNotification) {
        print("got the notification in the class")
    }
}
like image 1
Michael Rael Avatar answered Oct 17 '22 12:10

Michael Rael