次のコードを考えてみましょう:
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)
}
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
が呼び出されます。
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)
}
}
}
クラッシュを回避するには、プロトコルを使用するSwiftクラスにオブザーバーメソッドを実装します。
セレクタは常にObjective-Cメソッドを参照し、プロトコル拡張内の関数はObjective-Cセレクタとして使用できないため、実装はプロトコル拡張だけでなくSwiftクラス自体に存在する必要があります。ただし、SwiftクラスがObjective-Cクラスを継承している場合、SwiftクラスのメソッドをObjective-Cセレクターとして使用できます。
「SwiftクラスがObjective-Cクラスから継承する場合、クラス内のすべてのメソッドとプロパティはObjective-Cセレクターとして使用できます。」
また、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")
}
}
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) {
}
}
以下のように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)
}
}