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 使协议扩展成为通知观察者的主要内容,如果未能解决你的问题,请参考以下文章