Comment exiger qu'un protocole ne puisse être adopté que par une classe spécifique

89

Je veux ce protocole:

protocol AddsMoreCommands {
     /* ... */
}

à adopter uniquement par les classes qui héritent de la classe UIViewController. Cette page me dit que je peux spécifier qu'elle n'est adoptée que par une classe (par opposition à une structure) en écrivant

protocol AddsMoreCommands: class {
}

mais je ne vois pas comment exiger qu'elle ne soit adoptée que par une classe particulière. Cette page parle plus tard de l'ajout de whereclauses aux extensions de protocole pour vérifier la conformité, mais je ne vois pas non plus comment l'adapter.

extension AddsMoreCommands where /* what */ {
}

Y a-t-il un moyen de faire cela? Merci!

emrys57
la source

Réponses:

114
protocol AddsMoreCommands: class {
    // Code
}

extension AddsMoreCommands where Self: UIViewController {
    // Code
}
Roee84
la source
4
J'ai tellement failli l'avoir ... J'ai écrit selfau lieu de Self:-( Merci beaucoup, ça marche bien!
emrys57
Pour moi, cela provoque une certaine étrangeté syntaxique lorsque j'utilise cela en conjonction avec le casting.
Chris Prince
3
Cela ne fonctionnera pas si vous devez inclure une propriété dans le protocole, car les extensions ne peuvent pas contenir de propriétés stockées.
shim
1
Il peut avoir stocké des propriétés dont vous avez seulement besoin d'utiliser ceci: objc_getAssociatedObject (self, & KeyName) comme? PropertyType
Michał Ziobro
Cela nécessite également un casting lorsqu'une déclaration let / var a un type AddsMoreCommandsmais une méthode à laquelle vous la transmettez attend unUIViewController
GoatInTheMachine
77

Cela peut également être réalisé sans extension:

protocol AddsMoreCommands: class where Self: UIViewController {
   // code
}

EDITED 2017/11/04 : Comme Zig l'a souligné, cela semble générer un avertissement sur Xcode 9.1. Actuellement, il y a un problème signalé sur le projet Swift (SR-6265) pour supprimer l'avertissement, je vais garder un œil dessus et mettre à jour la réponse en conséquence.

EDITED 2018/09/29 : classest nécessaire si la variable qui stockera l'instance doit être faible (comme un délégué). Si vous n'avez pas besoin d'une variable faible, vous pouvez omettre le classet écrire simplement ce qui suit et il n'y aura aucun avertissement:

protocol AddsMoreCommands where Self: UIViewController {
   // code
}
rgkobashi
la source
5
Quelle coïncidence est-ce que j'ai cliqué sur une question de deux ans et que j'ai trouvé une solution parfaite postée il y a une heure 😲
Oscar Apeland
Xcode 9.1 me donne maintenant un avertissement à propos de ce dicton: Contrainte de mise en page redondante 'Self': 'AnyObject'. Contrainte de contrainte de mise en page 'Self': 'AnyObject' implicite ici. Changer mon code au format de la réponse acceptée semble être plus bon.
Zig
2
Depuis Xcode 9.1, les protocoles de classe uniquement utilisent désormais à la AnyObjectplace de class. protocol AddsMoreCommands: AnyObject where Self: UIViewController { // code }
dodgio
@dodgio reçoit toujours le même avertissement en utilisantAnyObject
rgkobashi
1
@ DávidPásztor vous avez raison, cependant si vous voulez l'utiliser sur un modèle structurel tel que la délégation, pour pouvoir affaiblir la propriété, il est nécessaire d'ajouter explicitement «classe» :)
rgkobashi
48

En raison d'un problème dans la réponse précédente, je me suis retrouvé avec cette déclaration:

protocol AddsMoreCommands where Self : UIViewController { 
    // protocol stuff here  
}

aucun avertissement dans Xcode 9.1

Massmaker
la source
5
Corrigez-moi si je me trompe, mais le problème avec cela par rapport à la solution ci-dessus (qui génère un avertissement dans Xcode 9.1 et versions ultérieures), est que vous ne pouvez pas déclarer le délégué comme faible?
Kyle Goslan
Aussi, quand j'utilise cette solution avec Swift 4.1, je dois propriétés de la distribution du AddsMoreCommandsà UIViewControllerce que je voulais éviter ...
fl034
8
Pour éviter le casting de type, vous pouvez faire ceci:typealias AddsMoreCommandsViewController = UIViewController & AddsMoreCommands
plu
33

Désormais, dans Swift 5, vous pouvez y parvenir en:

protocol AddsMoreCommands: UIViewController {
     /* ... */
}

Très pratique.

Wojciech Kulik
la source