2015-10-08 14 views
19

Consideriamo il seguente codice:Swift protocollo di estensione make un osservatore di notifica

protocol A { 
    func doA() 
} 

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

    func keyboardDidShow(notification: NSNotification) { 

    } 
} 

Ora guardate una sottoclasse UIViewController che implementa A:

class AController: UIViewController, A { 
    override func viewDidLoad() { 
     super.viewDidLoad() 
     self.registerForNotification() 
     triggerKeyboard() 
    } 

    func triggerKeyboard() { 
     // Some code that make key board appear 
    } 

    func doA() { 
    } 
} 

Ma sorprendentemente questa si blocca con un errore:

keyboardDidShow:]: unrecognized selector sent to instance 0x7fc97adc3c60

Quindi devo implementare l'osservatore nel controller di visualizzazione stesso? Non può rimanere nell'estensione?

A seguito di cose già provate.

rendendo A un protocollo di classe. Aggiunta di keyboardDidShow al protocollo stesso come firma.

protocol A:class { 
    func doA() 
    func keyboardDidShow(notification: NSNotification) 
} 
+0

ho provato qualcosa di simile anche in passato, ma ho scoperto che le estensioni del protocollo di Swift non funzionano con i protocolli di Objective-C e classi, [ma apparentemente lo fanno in qualche modo] (http://stackoverflow.com/questions/27097688/can-objective-c-code-call-swift-extension-on-class), sono confuso – Kametrixom

+0

'estensione A { } '??? Stai parlando di 'extension Controller {}' –

+0

Hai solo bisogno di aggiungere il parametro al metodo o delete: dalla fine del nome del selettore –

risposta

1

Per evitare il crash, implementare il metodo observer nella classe Swift che utilizza il protocollo.

L'implementazione deve essere nella classe Swift stessa, non solo nell'estensione del protocollo, poiché un selettore fa sempre riferimento a un metodo Objective-C e una funzione all'interno di un'estensione di protocollo non è disponibile come selettore Objective-C. Ancora metodi da un classe Swift sono disponibili come selettori Objective-C se la classe Swift eredita da una classe 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.”

Inoltre Xcode 7.1, self deve essere abbattuto per AnyObject quando specifica come l'osservatore nella chiamata 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") 
    } 
} 
28

ho risolto un problema simile attuando il metodo più recente - addObserverForName:object:queue:usingBlock: di NSNotificationCenter e chiamando direttamente il metodo.

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

Questo esempio causerà la chiamata di keyboardDidShow nell'estensione del protocollo.

+0

così ovvio, ma non ci pensavo - cudos! – manmal

+0

Grande scoperta! Mancato quello :) –

+3

Come si rimuove l'osservatore? diciamo che voglio avere una funzione che rimuove l'osservatore (anche nell'estensione) e poi chiamarlo dal deinit – tolkiana

1

L'utilizzo dei selettori in Swift richiede che la classe concreta debba ereditare da NSObject. Per applicare questo in un'estensione di protocollo, è necessario utilizzare where. Ad esempio:

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

    } 
} 
1

In aggiunta alla risposta di James Paolantonio. Un metodo unregisterForNotification può essere implementato utilizzando oggetti associati.

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) 
     } 
    } 
} 
0

ho risolto utilizzando NSObjectProtocol come sotto,

@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) 
} 
}