Swift make protocol extension a Notification obser

2019-01-31 12:35发布

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

5条回答
该账号已被封号
2楼-- · 2019-01-31 13:13

Using selectors in Swift requires that your concrete class must inherit from NSObject. To enforce this in a protocol extension, you should use where. For example:

protocol A {
    func doA()
}

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

  func keyboardDidShow(notification: NSNotification) {

  }
}
查看更多
We Are One
3楼-- · 2019-01-31 13:13

I solved it using NSObjectProtocol as below,

@objc protocol KeyboardNotificaitonDelegate: NSObjectProtocol {
func keyboardWillBeShown(notification: NSNotification)
func keyboardWillBeHidden(notification: NSNotification)
}

extension KeyboardNotificaitonDelegate {

func registerForKeyboardNotifications() {
    //Adding notifies on keyboard appearing
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeShown(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillBeHidden(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func deregisterFromKeyboardNotifications() {
    //Removing notifies on keyboard appearing
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillShow, object: nil)
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}
}
查看更多
放我归山
4楼-- · 2019-01-31 13:20

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.

查看更多
Explosion°爆炸
5楼-- · 2019-01-31 13:28

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")
    }
}
查看更多
劫难
6楼-- · 2019-01-31 13:32

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)
        }
    }
}
查看更多
登录 后发表回答