web-dev-qa-db-fra.com

Extension de protocole sur un protocole ObjC

J'ai un protocole Objective-C qui est principalement utilisé par des objets Objective-C et un ou deux objets Swift.

Je voudrais étendre le protocole en Swift et ajouter 2 fonctions. L'une pour s'inscrire à une notification et une autre pour gérer la notification.

Si j'ajoute ces

func registerForPresetLoadedNotification() {
    NSNotificationCenter.defaultCenter().addObserver(self as AnyObject,
                                                     selector: #selector(presetLoaded(_:)),
                                                     name: kPresetLoadedNotificationName,
                                                     object: nil)
}

func presetLoaded(notification: NSNotification) {

}

Je reçois une erreur sur le #selector qui dit Argument of '#selector' refers to a method that is not exposed to Objective-C

Si je marque ensuite presetLoaded comme @objc Je reçois une erreur qui dit @objc can only be used with members of classes, @objc protocols, and concrete extensions of classes

Je ne peux pas non plus marquer l'extension de protocole comme @objc

Lorsque je crée le protocole Objective-C en tant que protocole Swift, j'obtiens la même erreur.

Y a-t-il un moyen d'y parvenir qui fonctionnera pour Objective-C et Swift classes qui utilisent le protocole?

16
system

En effet, vous ne pouvez pas vraiment marquer une fonction d'une extension de protocole comme @ objc (ou dynamic, ce qui est d'ailleurs équivalent). Seules les méthodes d'une classe peuvent être distribuées par le runtime Objective-C.

Dans votre cas particulier, si vous voulez vraiment passer par l'extension de protocole, je peux proposer la solution suivante (en supposant que votre protocole d'origine est nommé ObjcProtocol).

Faisons un wrapper pour notre gestionnaire de notification:

final class InternalNotificationHandler {
    private let source: ObjcProtocol

    init(source: ObjcProtocol) {
        // We require source object in case we need access some properties etc.
        self.source = source
    }

    @objc func presetLoaded(notification: NSNotification) {
        // Your notification logic here
    }
}

Maintenant, nous devons étendre notre ObjcProtocol pour introduire la logique requise

import Foundation
import ObjectiveC

internal var NotificationAssociatedObjectHandle: UInt8 = 0

extension ObjcProtocol {
    // This stored variable represent a "singleton" concept
    // But since protocol extension can only have stored properties we save it via Objective-C runtime
    private var notificationHandler: InternalNotificationHandler {
        // Try to an get associated instance of our handler
        guard let associatedObj = objc_getAssociatedObject(self, &NotificationAssociatedObjectHandle)
            as? InternalNotificationHandler else {
            // If we do not have any associated create and store it
            let newAssociatedObj = InternalNotificationHandler(source: self)
            objc_setAssociatedObject(self,
                                     &NotificationAssociatedObjectHandle,
                                     newAssociatedObj,
                                     objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            return newAssociatedObj
        }

        return associatedObj
    }

    func registerForPresetLoadedNotification() {
        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: #selector(notificationHandler.presetLoaded(_:)),
                                                         name: kPresetLoadedNotificationName,
                                                         object: self)
    }

    func unregisterForPresetLoadedNotification() {
        // Clear notification observer and associated objects
        NSNotificationCenter.defaultCenter().removeObserver(self,
                                                            name: kPresetLoadedNotificationName,
                                                            object: self)
        objc_removeAssociatedObjects(self)
    }
}

Je sais que cela pourrait ne pas sembler si élégant, alors j'envisagerais vraiment de changer une approche fondamentale.

ne note: Vous voudrez peut-être restreindre votre extension de protocole

extension ObjcProtocol where Self: SomeProtocolOrClass
5
Dmytro Kabyshev

J'ai trouvé un moyen de le faire :) Évitez simplement @objc tous ensemble: D

//Adjusts UITableView content height when keyboard show/hide
public protocol KeyboardObservable: NSObjectProtocol {
    func registerForKeyboardEvents()
    func unregisterForKeyboardEvents()
}

extension KeyboardObservable where Self: UITableView {

    public func registerForKeyboardEvents() {
        let defaultCenter = NotificationCenter.default

    var tokenShow: NSObjectProtocol!
    tokenShow = defaultCenter.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] (notification) in
        guard self != nil else {
            defaultCenter.removeObserver(tokenShow)
            return
        }
        self!.keyboardWilShow(notification as NSNotification)
    }

    var tokenHide: NSObjectProtocol!
    tokenHide = defaultCenter.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] (notification) in
        guard self != nil else {
            defaultCenter.removeObserver(tokenHide)
            return
        }
        self!.keyboardWilHide(notification as NSNotification)
    }

    private func keyboardDidShow(_ notification: Notification) {
        let rect = ((notification as NSNotification).userInfo![UIKeyboardFrameBeginUserInfoKey] as! NSValue).cgRectValue
        let height = rect.height
        var insets = UIEdgeInsetsMake(0, 0, height, 0)
        insets.top = contentInset.top
        contentInset = insets
        scrollIndicatorInsets = insets
    }

    private func keyboardWillHide(_ notification: Notification) {
        var insets = UIEdgeInsetsMake(0, 0, 0, 0)
        insets.top = contentInset.top
        UIView.animate(withDuration: 0.3) { 
            self.contentInset = insets
            self.scrollIndicatorInsets = insets
        }
    }

    public func unregisterForKeyboardEvents() {
        NotificationCenter.default.removeObserver(self)
    }

}

Exemple

class CreateStudentTableView: UITableView, KeyboardObservable {

  init(frame: CGRect, style: UITableViewStyle) {
    super.init(frame: frame, style: style)
    registerForKeyboardEvents()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }
}
2
user1951992