web-dev-qa-db-fra.com

Swift extension d'une classe UNIQUEMENT lorsqu'elle est conforme à un protocole spécifique

Salut là =) Je viens d'être confronté à un problème de conception où je dois (essentiellement) faire ce qui suit:

Je veux injecter un peu de code sur viewWillAppear: De toute sous-classe UIViewController conforme à un protocole MyProtocol. Expliqué dans le code:

protocol MyProtocol
{
    func protocolFunction() {
        //do cool stuff...
    }
}

extension UIViewController where Self: MyProtocol //<-----compilation error
{
    public override class func initialize()
    {
        //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
    }

    //  MARK: - Swizzling

    func xxx_viewWillAppear(animated: Bool)
    {
        self.xxx_viewWillAppear(animated)

        //invoke APIs from  
        self.protocolFunction() // MyProtocol APIs
        let viewLoaded = self.isViewLoaded // UIViewController APIs

    }
}

Le problème principal ici est que j'ai besoin de 2 choses dans l'extension UIVIewController:

  1. Appelez les API MyProtocol et UIViewController
  2. Remplacez UIViewController méthode initialize() afin de pouvoir balancer viewWillAppear:

Ces 2 capacités semblent incompatibles (au Swift 3) car:

  1. Nous ne peut pas étendre les classes avec des conditions (c'est-à-dire extension UIViewController where Self: MyProtocol)
  2. si au lieu de cela étendre le protocole, nous POUVONS ajouter des conditions extension MyProtocol where Self: UIViewController mais nous NE POUVONS PAS remplacer les méthodes d'une classe dans une extension de protocole, ce qui signifie que nous pouvons ' t public override class func initialize() qui est nécessaire pour le swizzling.

Je me demandais donc s'il y avait quelqu'un qui pourrait offrir une solution Swifty à ce problème auquel je fais face? =)

Merci d'avance!!

21
Robertibiris

Eh bien, jusqu'à présent, je n'ai trouvé aucun moyen vraiment satisfaisant de le faire, mais j'ai décidé de publier ce que j'ai fini par faire pour ce problème particulier. En un mot, la solution se présente comme suit (en utilisant l'exemple de code d'origine):

protocol MyProtocol
{
    func protocolFunction() {
        //do cool stuff...
    }
}

extension UIViewController //------->simple extension on UIViewController directly
{
    public override class func initialize()
    {
        //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
    }

    //  MARK: - Swizzling

    func xxx_viewWillAppear(animated: Bool)
    {
        self.xxx_viewWillAppear(animated)

        //------->only run when self conforms to MyProtocol
        if let protocolConformingSelf = self as? MyProtocol { 
            //invoke APIs from  
            protocolConformingSelf.protocolFunction() // MyProtocol APIs
            let viewLoaded = protocolConformingSelf.isViewLoaded // UIViewController APIs
        }

    }

}

Désavantages:

  • Pas "la Swift" de faire les choses
  • La méthode swizzling sera invoquée et prendra effet sur TOUS UIViewControllers, même si nous validons uniquement ceux qui sont conformes au protocole MyProtocol pour exécuter les lignes de code sensibles.

J'espère vraiment que cela aidera quelqu'un d'autre face à une situation similaire =)

6
Robertibiris

Vous étiez près de la solution. Juste besoin de faire autrement. Étendre le protocole uniquement si sa partie de UIViewController.

protocol MyProtocol
{
  func protocolFunction() {
    //do cool stuff...
  }
}

extension MyProtocol where Self: UIViewController {
  public override class func initialize()
  {
    //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
  }

  //  MARK: - Swizzling

  func xxx_viewWillAppear(animated: Bool)
  {
    self.xxx_viewWillAppear(animated)

    //invoke APIs from  
    self.protocolFunction() // MyProtocol APIs
    let viewLoaded = self.isViewLoaded // UIViewController APIs
  }
}
13
Klemen