web-dev-qa-db-fra.com

Swift - Exige que les classes implémentant le protocole soient des sous-classes d'une certaine classe

Je crée plusieurs classes NSView, qui prennent toutes en charge une opération spéciale, que nous appellerons transmogrify. À première vue, cela semble être l'endroit idéal pour un protocole:

protocol TransmogrifiableView {
    func transmogrify()
}

Cependant, ce protocole n'impose pas que chaque TransmogrifiableView soit également un NSView. Cela signifie que toutes les méthodes NSView que j'appelle sur TransmogrifiableView ne taperont pas check:

let myView: TransmogrifiableView = getTransmogrifiableView()
let theSuperView = myView.superView // error: TransmogrifiableView does not have a property called 'superview'

Je ne sais pas comment exiger que toutes les classes implémentant mon protocole soient également des sous-classes de NSView. J'ai essayé ceci:

protocol TransmogrifiableView: NSView {
    func transmogrify()
}

mais Swift se plaint que les protocoles ne peuvent pas hériter des classes. Cela n'aide pas à transformer le protocole en un protocole de classe uniquement en utilisant

protocol TransmogrifiableView: class, NSView {
    func transmogrify()
}

Je ne peux pas faire de TransmogrifiableView une superclasse plutôt qu'un protocole, car certaines de mes classes TransmogrifiableView doivent être des sous-classes d'autres vues non transmogrifiables.

Comment dois-je exiger que tous les TransmogrifiableView soient également des NSView? Je ne veux vraiment pas poivrer mon code avec des conversions "as", qui sont de mauvaise forme et distrayantes.

37
SelectricSimian

Je pense que vous recherchez une sous-classe de NSView. Essaye ça:

protocol TransmogrifiableView {
    func transmogrify()
}

class MyNSView: NSView, TransmogrifiableView {
    // do stuff.
}

Et plus tard dans le code, acceptez les objets de type MyNSView.

Éditer

Vous voulez peut-être un Extension, voir this

extension NSView: TransmogrifiableView {
    // implementation of protocol requirements goes here
}
  • Notez que vous ne pourrez pas obtenir un NSView sans cette méthode supplémentaire.
  • Vous pouvez étendre séparément les sous-classes de NSView pour remplacer cette nouvelle méthode.

Encore une autre option consiste à créer une classe qui contient un pointeur vers une NSView et implémente des méthodes supplémentaires. Cela vous obligera également à proxy toutes les méthodes de NSView que vous souhaitez utiliser.

class NSViewWrapper: TransmogrifiableView {
    var view : NSView!
    // init with the view required.
    //  implementation of protocol requirements goes here.
    .....
   // proxy all methods from NSView.
   func getSuperView(){
       return self.view.superView
   }
}

C'est assez long et pas sympa, mais ça marchera. Je vous recommande de ne l'utiliser que si vous ne pouvez vraiment pas travailler avec des extensions (car vous avez besoin de NSViews sans la méthode supplémentaire).

5
Ramzi Khahil

À partir de Swift 4, vous pouvez maintenant définir ceci comme suit:

let myView: NSView & TransmogrifiableView

Pour plus d'informations, consultez le problème de paiement # 156 Subclass Existentials

17
Antoine

Il existe une solution de contournement en utilisant des types associés pour appliquer la sous-classe:

protocol TransmogrifiableView {
    associatedtype View: NSView = Self
    func transmogrify()
}

class MyView: NSView, TransmogrifiableView { ... } // compiles
class MyOtherClass: TransmogrifiableView { ... } // doesn't compile
17
Cristik

Vous pouvez utiliser quelque chose comme ceci:

protocol TransmogrifiableView where Self:NSView {}

Cela nécessite que toutes les instances créées dont l'une est conforme au protocole TransmogrifiableView soient sous-classées avec NSView

10
GrandFelix

Pour Swift 4, basé sur la perspicacité de @ Antoine:

Créez un protocole, puis utilisez une typealias pour donner un nom plus propre à un type conforme à la fois à une classe et au protocole.

protocol Transmogrifiable {
    func transmogrify()
}
typealias TransmogrifiableView = NSView & Transmogrifiable

Vous pouvez ensuite définir une classe qui hérite de ce type ....

class ATransmogView: TransmogrifiableView {
    func transmogrify() {
        print("I'm transmogging")
    }
}

.... ou définissez une classe qui hérite du protocole et d'une sous-classe

// this also qualifies as a TransmogrifiableView

class BTransmogView: NSTextView, Transmogrifiable {
    func transmogrify() {
        print("I'm transmogging too")
    }
}

Vous pouvez maintenant le faire.

func getTransmogrifiableView() -> TransmogrifiableView {
    return someBool ? ATransmogView() : BTransmogView()
}

Et cela se compile maintenant.

let myView: TransmogrifiableView = getTransmogrifiableView()
let theSuperView = myView.superView
3
pinch

Selon la définition, un protocole déclare simplement les exigences des "méthodes, propriétés an autres exigences". Et par "autres exigences", cela signifie qu'une superclasse n'en fait pas partie.

Un protocole définit un schéma de méthodes, de propriétés et d'autres exigences qui conviennent à une tâche ou à un élément de fonctionnalité particulier.

Pour l'instant, je ne vois pas de solution propre. Il est possible d'utiliser une clause where- pour définir un type qui est un NSView et conforme au TransmogrifiableView comme ceci:

class MyClass<T where T: NSView, T: TransmogrifiableView> {
    var aTransmogrifiableNSView: T
}

Ou vous pouvez utiliser une autre superclasse:

protocol TransmogrifiableViewProtocol {
    func transmogrify()
}

class TransmogrifiableView: NSView, TransmogrifiableViewProtocol {
    func transmogrify() {
        assert(false, "transmogrify() must be overwritten!")
    }
}

class AnImplementedTransmogrifiableView: TransmogrifiableView {
    func transmogrify() {
        println("Do the transmogrification...")
    }
}

Au final, les deux solutions ne sont pas propres et ne me satisferaient pas. Peut-être qu'un mot clé abstract- sera ajouté à Swift un jour?

3
Fabio Poloni

vérifiez cette solution ici ... Swift - méthode de classe qui doit être remplacée par la sous-classe

Cette solution permet d'hériter d'une classe et également d'avoir la vérification du temps de compilation du protocole. :)

0
JMiguel

Ce n'est toujours pas la solution idéale, mais voici un modèle que j'utilise à l'occasion:

  1. Définissez un protocole avec les méthodes que vous souhaitez appliquer.
  2. Définissez votre classe de base, implémentez tout ce que vous voulez que les enfants obtiennent gratuitement.
  3. Dans cette classe de base (à partir de # 2), ayez une variable "délégué" optionnelle du protocole que vous avez fait dans # 1.
  4. Définissez toutes les classes enfants afin qu'elles héritent de la classe de base et implémentent le protocole.

Cela permet à votre classe de base d'appeler les méthodes de protocole tout en forçant les enfants à les implémenter (par exemple, self.myDelegate? .MyProtocolMethod).

0
ghostatron