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

27

让我们考虑以下代码:

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的协议和类一起使用,但显然它们在某种程度上可以,我感到困惑。 - Kametrixom
你只需要在方法中添加参数或者删除选择器名称末尾的冒号。 - Leo Dabus
@MidhunMP。是的,这是extension A{}。从Swift 2开始的新功能,叫做协议扩展。它甚至可以向协议方法添加默认功能。 - Swift Hipster
1
该方法是 func keyboardDidShow(notification: NSNotification),与 Selector("keyboardDidShow:") 匹配。 - Swift Hipster
你是否为此提交了错误报告或功能请求? - Pradip Vaghasiya
显示剩余2条评论
5个回答

36

我通过实现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被调用。


如此明显,却没想到 - 赞! - manmal
7
如何移除观察者?假设我想要一个函数来移除观察者(在扩展中也是如此),然后只需从析构函数调用该函数。 - tolkiana
在我的情况下,我需要使用 weak,而不是 unowned - Mark Leonard
@MarkLeonard 这样做可以让视图控制器被释放吗? - James Paolantonio
5
@JamesPaolantonio,您能更具体地说明如何实现“unregisterForNotification”方法吗?为了移除观察者,您需要存储“addObserverForName”的返回值,但是在扩展中这是不可能的。 - Leonardo
显示剩余4条评论

3
除了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)
        }
    }
}

1
为避免崩溃,请在使用协议的Swift类中实现观察者方法。实现必须在Swift类本身中进行,而不仅仅是协议扩展,因为选择器始终指向Objective-C方法,而协议扩展中的函数不可用作Objective-C选择器。但是,如果Swift类继承自Objective-C类,则可以将Swift类的方法作为Objective-C选择器使用。同时,在Xcode 7.1中,在addObserver调用中指定self作为观察者时,必须将其转换为AnyObject。{{link1:“如果您的Swift类继承自Objective-C类,则该类中的所有方法和属性都可以作为Objective-C选择器使用。”}}
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")
    }
}

1
在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) {

  }
}

0

我用以下的方式解决了它,使用了 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)
}
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接