可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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)
}
回答1:
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.
回答2:
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)
}
}
}
回答3:
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")
}
}
回答4:
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) {
}
}
回答5:
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)
}
}