web-dev-qa-db-fra.com

Swift fait de l'extension de protocole un observateur de notification

Considérons le code suivant:

protocol A {
    func doA()
}

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

  func keyboardDidShow(notification: NSNotification) {

  }
}

Examinons maintenant une sous-classe UIViewController qui implémente A:

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

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

   func doA() {
   }
}

Mais étonnamment, cela se bloque avec une erreur: 

keyboardDidShow:]: sélecteur non reconnu envoyé à l'instance 0x7fc97adc3c60

Devrais-je donc implémenter l'observateur dans le contrôleur de vue lui-même? Ne peut-il pas rester dans l'extension? 

Suivre les choses déjà essayées.

rendant un protocole de classe. Ajout de keyboardDidShow au protocole lui-même en tant que signature.

protocol A:class {
   func doA()
   func keyboardDidShow(notification: NSNotification)
}
23
Swift Hipster

J'ai résolu un problème similaire en implémentant la nouvelle méthode - addObserverForName:object:queue:usingBlock: de NSNotificationCenter et en appelant directement la méthode.

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

Dans cet exemple, keyboardDidShow sera appelé dans l'extension de protocole.

32
James Paolantonio

En plus de la réponse de James Paolantonio. Une méthode unregisterForNotification peut être implémentée à l'aide d'objets associés.

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)
        }
    }
}
2
andershqst

Pour éviter le crash, implémentez la méthode observer dans la classe Swift qui utilise le protocole. 

L'implémentation doit être dans la classe Swift elle-même, et pas seulement dans l'extension de protocole, car un sélecteur fait toujours référence à une méthode Objective-C, et une fonction dans une extension de protocole n'est pas disponible en tant que sélecteur Objective-C. Pourtant, les méthodes d'une classe Swift sont disponibles en tant que sélecteurs Objective-C si la classe Swift hérite d'une classe Objective-C

“Si votre classe Swift hérite d'une classe Objective-C, toutes les méthodes et propriétés de la classe sont disponibles en tant que sélecteurs Objective-C.”

De plus, dans Xcode 7.1, self doit être déclassé en AnyObject lorsque vous le spécifiez en tant qu'observateur dans l'appel 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")
    }
}
1
Michael Rael

L'utilisation de sélecteurs dans Swift nécessite que votre classe concrète hérite de NSObject. Pour appliquer cela dans une extension de protocole, vous devez utiliser where. Par exemple:

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
Michael

Je l'ai résolu en utilisant NSObjectProtocol comme ci-dessous,

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