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

Posted

技术标签:

【中文标题】Swift 使协议扩展成为通知观察者【英文标题】:Swift make protocol extension a Notification observer 【发布时间】:2015-10-08 17:40:44 【问题描述】:

让我们考虑以下代码:

protocol A 
    func doA()


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

  func keyboardDidShow(notification: NSNotification) 

  

现在看一个实现 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() 
   

但令人惊讶的是,这会因错误而崩溃:

keyboardDidShow:]: 无法识别的选择器发送到实例 0x7fc97adc3c60

那么我应该在视图控制器本身中实现观察者吗?它不能留在扩展中吗?

按照已经尝试过的方法。

使 A 成为类协议。 将keyboardDidShow添加到协议本身作为签名。

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

【问题讨论】:

我过去也尝试过类似的东西,但我发现 Swift 的协议扩展不适用于 Objective-C 协议和类,but apparently they somehow do,我很困惑 extension A ???你在说extension Controller 你只需要在方法中添加参数或者从选择器名称的末尾删除: @MidhunMP 。是的,它是extension A。 Swift 2 及以后的新功能。这称为协议扩展。这甚至可以为协议方法添加默认功能。 方法是func keyboardDidShow(notification: NSNotification) 匹配Selector("keyboardDidShow:") 【参考方案1】:

我通过实现NSNotificationCenter的较新的- addObserverForName:object:queue:usingBlock:方法并直接调用该方法解决了类似的问题。

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

此示例将导致在协议扩展中调用keyboardDidShow

【讨论】:

如此明显,但没想到 - 赞! 很棒的发现!错过了一个:) 如何移除观察者?假设我想要一个删除观察者的函数(也在扩展中),然后从 deinit 调用它 @tolkiana 你也可以添加func unregisterForNotification() @JamesPaolantonio 您能否更具体地说明您将如何实现unregisterForNotification 方法?要删除观察者,您需要存储addObserverForName 的返回值,但这在扩展上是不可能的【参考方案2】:

除了 James Paolantonio 的回答。可以使用关联对象实现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)
        
    

【讨论】:

【参考方案3】:

为避免崩溃,请在使用该协议的 Swift 类中实现观察者方法。

实现必须在 Swift 类本身中,而不仅仅是协议扩展,因为选择器总是引用 Objective-C 方法,而协议扩展中的函数不能用作 Objective-C 选择器。然而,如果 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.”

此外,在 Xcode 7.1 中,当在 addObserver 调用中将 self 指定为观察者时,必须向下转换为 AnyObject

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

【讨论】:

【参考方案4】:

在 Swift 中使用选择器要求你的具体类必须继承自 NSObject。要在协议扩展中强制执行此操作,您应该使用where。例如:

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) 

  

【讨论】:

【参考方案5】:

我使用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)


【讨论】:

以上是关于Swift 使协议扩展成为通知观察者的主要内容,如果未能解决你的问题,请参考以下文章

如何使用选择器:使用在 Swift 中引发异常的函数

文件上传进度的通知观察者模式仅在文件很大时才有效 - swift?

没有收到通知中心观察员 Swift

学习日记之观察者模式

设计模式—观察者模式

设计模式学习笔记-观察者模式