简体   繁体   English

Swift使协议扩展成为通知观察者

[英]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: 现在看一下实现A的UIViewController子类:

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 keyboardDidShow:]:无法识别的选择器发送到实例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. 制作A类协议。 Adding keyboardDidShow to protocol itself as signature. 将keyboardDidShow添加到协议本身作为签名。

protocol A:class {
   func doA()
   func keyboardDidShow(notification: NSNotification)
}

I solved a similar problem by implementing the newer - addObserverForName:object:queue:usingBlock: method of NSNotificationCenter and calling the method directly. 我通过实现更新的- addObserverForName:object:queue:usingBlock: NSNotificationCenter的方法并直接调用方法解决了类似的问题。

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. 此示例将导致在协议扩展中调用keyboardDidShow

In addition to James Paolantonio's answer. 除了James Paolantonio的回答。 A unregisterForNotification method can be implemented using associated objects. 可以使用关联对象实现unregisterForNotification方法。

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

To avoid the crash, implement the observer method in the Swift class that uses the protocol. 要避免崩溃,请在使用该协议的Swift类中实现observer方法。

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. 实现必须在Swift类本身,而不仅仅是协议扩展,因为选择器总是引用Objective-C方法,并且协议扩展中的函数不能用作Objective-C选择器。 Yet methods from a Swift class are available as Objective-C selectors if the Swift class inherits from an Objective-C class 如果Swift类继承自Objective-C类,则可以使用Swift类中的方法作为Objective-C选择器

“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.” “如果您的Swift类继承自Objective-C类,则该类中的所有方法和属性都可用作Objective-C选择器。”

Also, in Xcode 7.1, self has to be downcast to AnyObject when specifying it as the observer in the addObserver call. 此外,在Xcode 7.1, self必须垂头丧气到AnyObject指定其作为观察者时addObserver调用。

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

Using selectors in Swift requires that your concrete class must inherit from NSObject. 在Swift中使用选择器要求您的具体类必须从NSObject继承。 To enforce this in a protocol extension, you should use where . 要在协议扩展中强制执行此操作,您应该使用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) {

  }
}

I solved it using NSObjectProtocol as below, 我使用NSObjectProtocol解决了它,如下所示,

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM