web-dev-qa-db-fra.com

Dans Swift, comment puis-je déclarer une variable d'un type spécifique conforme à un ou plusieurs protocoles?

Dans Swift), je peux définir explicitement le type d'une variable en le déclarant comme suit:

var object: TYPE_NAME

Si nous voulons aller plus loin et déclarer une variable conforme à plusieurs protocoles, nous pouvons utiliser le déclaratif protocol:

var object: protocol<ProtocolOne,ProtocolTwo>//etc

Que faire si je souhaite déclarer un objet conforme à un ou plusieurs protocoles et appartenant également à un type de classe de base spécifique? L'équivalent d'Objective-C ressemblerait à ceci:

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

Dans Swift je m'attendrais à ce qu'il ressemble à ceci:

var object: TYPE_NAME,ProtocolOne//etc

Cela nous donne la flexibilité de pouvoir gérer l'implémentation du type de base ainsi que l'interface ajoutée définie dans le protocole.

Y a-t-il une autre façon plus évidente de me manquer?

Exemple

Par exemple, disons que j’ai une usine UITableViewCell responsable de renvoyer les cellules conformes à un protocole. Nous pouvons facilement configurer une fonction générique qui renvoie les cellules conformes à un protocole:

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

plus tard, je veux retirer ces cellules tout en tirant parti à la fois du type et du protocole

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

Cela renvoie une erreur car une cellule de la vue tableau n'est pas conforme au protocole ...

Je voudrais pouvoir spécifier que la cellule est un UITableViewCell et est conforme à la MyProtocol de la déclaration de variable?

Justification

Si vous connaissez le Factory Pattern , cela vous semblerait logique de pouvoir renvoyer des objets d'une classe particulière qui implémentent une certaine interface.

Comme dans mon exemple, nous aimons parfois définir des interfaces qui ont un sens lorsqu'elles sont appliquées à un objet particulier. Mon exemple de la cellule de vue de tableau est une telle justification.

Bien que le type fourni ne soit pas exactement conforme à l'interface mentionnée, l'objet renvoyé par la fabrique l'est aussi. J'aimerais donc avoir la possibilité d'interagir avec le type de classe de base et l'interface de protocole déclarée.

89
Daniel Galasko

Dans Swift 4, il est maintenant possible de déclarer une variable qui est une sous-classe d'un type et d'implémenter un ou plusieurs protocoles en même temps.

var myVariable: MyClass & MyProtocol & MySecondProtocol

ou en tant que paramètre d'une méthode:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple l’a annoncé à la WWDC 2017 à Session 402: Quoi de neuf à Swift

Deuxièmement, je veux parler de la composition de classes et de protocoles. Donc, ici, j'ai présenté ce protocole modulable pour un élément d'interface utilisateur qui peut donner un petit effet de tremblement pour attirer l'attention sur lui-même. Et je suis allé de l'avant et j'ai étendu certaines des classes UIKit pour fournir cette fonctionnalité de shake. Et maintenant, je veux écrire quelque chose qui semble simple. Je veux juste écrire une fonction qui prend un tas de commandes tremblantes et secoue celles qui sont activées pour attirer l’attention sur elles. Quel type puis-je écrire ici dans ce tableau? C'est en fait frustrant et délicat. Donc, je pourrais essayer d'utiliser un contrôle d'interface utilisateur. Mais toutes les commandes de l'interface utilisateur ne sont pas modifiables dans ce jeu. Je pourrais essayer shakable, mais tous les shakables ne sont pas des contrôles de l'interface utilisateur. Et il n’ya en fait aucun moyen de représenter cela dans Swift 3. ) Swift 4 introduit la notion de composition d’une classe avec un nombre quelconque de protocoles.

63
Philipp Otto

Vous ne pouvez pas déclarer une variable comme

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

ni déclarer le type de retour de fonction comme

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

Vous pouvez déclarer un paramètre de fonction comme celui-ci, mais il s'agit essentiellement d'une transformation ascendante.

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

À partir de maintenant, tout ce que vous pouvez faire est comme:

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

Avec cela, techniquement cell est identique à asProtocol.

Mais, comme pour le compilateur, cell a une interface de UITableViewCell seulement, alors que asProtocol n'a que des protocoles. Ainsi, lorsque vous souhaitez appeler les méthodes de UITableViewCell, vous devez utiliser la variable cell. Lorsque vous souhaitez appeler une méthode de protocole, utilisez la variable asProtocol.

Si vous êtes sûr que la cellule est conforme aux protocoles, vous n'avez pas à utiliser if let ... as? ... {}. comme:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>
30
rintaro

Malheureusement, Swift ne prend pas en charge la conformité du protocole au niveau de l'objet. Toutefois, il existe une solution un peu gênante qui peut vous aider.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

Ensuite, partout où vous devez faire quelque chose que UIViewController a, vous pouvez accéder à l’aspect .viewController de la structure et tout ce dont vous avez besoin de l’aspect protocole, vous référencez le .protocol.

Par exemple:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

Désormais, chaque fois que vous avez besoin de mySpecialViewController pour effectuer tout ce qui est lié à UIViewController, il vous suffit de référencer mySpecialViewController.viewController et chaque fois que vous en avez besoin pour effectuer une fonction de protocole, vous référencez mySpecialViewController.protocol.

Espérons que Swift 4 nous permettra de déclarer un objet auquel des protocoles sont attachés dans le futur. Mais pour l’instant, cela fonctionne.

J'espère que cela t'aides!

2
Michael Curtis

EDIT: Je me suis trompé, mais si quelqu'un d'autre lit ce malentendu comme moi, je laisse cette réponse. Le PO a demandé si la vérification de la conformité au protocole de l'objet d'une sous-classe donnée était une autre histoire, comme le montre la réponse acceptée. Cette réponse parle de conformité de protocole pour la classe de base.

Je me trompe peut-être, mais ne parlez-vous pas d'ajouter la conformité de protocole à la classe UITableCellView? Dans ce cas, le protocole est étendu à la classe de base et non à l'objet. Reportez-vous à la documentation d'Apple sur Déclarer l'adoption d'un protocole avec une extension , ce qui dans votre cas ressemblerait à quelque chose comme:

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

En plus de la documentation déjà citée Swift), voir également l'article de Nate Cooks Fonctions génériques pour les types incompatibles avec d'autres exemples.

Cela nous donne la flexibilité de pouvoir gérer l'implémentation du type de base ainsi que l'interface ajoutée définie dans le protocole.

Y a-t-il une autre façon plus évidente de me manquer?

C'est exactement ce que fait l'adoption de protocole: faire en sorte qu'un objet adhère au protocole donné. Sachez toutefois que l’un des inconvénients est qu’une variable d’un type de protocole donné ne = pas sait quoi que ce soit en dehors du protocole. Mais ceci peut être contourné en définissant un protocole qui possède toutes les méthodes/variables/...

Bien que le type fourni ne soit pas exactement conforme à l'interface mentionnée, l'objet renvoyé par la fabrique l'est aussi. J'aimerais donc avoir la possibilité d'interagir avec le type de classe de base et l'interface de protocole déclarée.

Si vous souhaitez qu'une méthode générique, une variable soit conforme à un type de protocole et à une classe de base, vous risquez de ne pas avoir de chance. Mais il semble que vous deviez définir le protocole suffisamment large pour avoir les méthodes de conformité nécessaires, et en même temps assez étroit pour pouvoir l’adopter pour les classes de base sans trop de travail (c’est-à-dire tout simplement en déclarant qu'une classe est conforme à protocole).

1
holroy

Une fois, j'ai eu une situation similaire en essayant de lier mes connexions d'interacteur génériques dans Storyboards (IB ne vous autorisera pas à connecter des points de vente à des protocoles, mais uniquement des instances d'objet), que je contournais simplement en masquant l'ivar public de classe de base avec un calcul privé. propriété. Bien que cela n'empêche pas une personne d'effectuer des affectations illégales en soi, cela fournit un moyen pratique d'empêcher en toute sécurité toute interaction indésirable avec une instance non conforme au moment de l'exécution. (c’est-à-dire empêcher l’appel de méthodes déléguées à des objets non conformes au protocole.)

Exemple:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

Le "outputReceiver" est déclaré facultatif, de même que le "protocolOutputReceiver" privé. En accédant toujours à outputReceiver (un délégué.k.a.) via cette dernière (la propriété calculée), je filtre effectivement tous les objets qui ne sont pas conformes au protocole. Maintenant, je peux simplement utiliser un chaînage facultatif pour appeler en toute sécurité un objet délégué, qu'il implémente ou non le protocole, voire qu'il existe.

Pour appliquer cela à votre situation, vous pouvez avoir l'ivar public de type "YourBaseClass?" (par opposition à AnyObject) et utilisez la propriété privée calculée pour appliquer la conformité du protocole. FWIW.

0
quickthyme