web-dev-qa-db-fra.com

Comment déclarer une variable qui a un type et implémente un protocole?

Mon application dispose d'un protocole pour les contrôleurs de vue de détail, indiquant qu'ils doivent avoir une propriété viewModel:

protocol DetailViewController: class {
    var viewModel: ViewModel? {get set}
}

J'ai également quelques classes différentes qui implémentent le protocole:

class FormViewController: UITableViewController, DetailViewController {
    // ...
}

class MapViewController: UIViewController, DetailViewController {
    // ...
}

Mon contrôleur de vue principal a besoin d'une propriété qui peut être définie sur n'importe quelle sous-classe UIViewController qui implémente le protocole DetailViewController.

Malheureusement, je ne trouve aucune documentation sur la façon de procéder. Dans Objective-C, ce serait trivial:

@property (strong, nonatomic) UIViewController<DetailViewController>;

Il semble qu'il n'y ait pas de syntaxe disponible dans Swift pour ce faire. Le plus proche que je suis venu est de déclarer un générique dans ma définition de classe:

class MasterViewController<T where T:UIViewController, T:DetailViewController>: UITableViewController {
    var detailViewController: T?
    // ...
}

Mais alors je reçois une erreur disant que "la classe 'MasterViewController' n'implémente pas les membres requis de sa superclasse"

Il semble que cela devrait être aussi facile à faire dans Swift que dans Objective-C, mais je ne trouve rien nulle part qui suggère comment je pourrais y aller.

42
Frank Schmitt

À partir de Swift 4, vous pouvez maintenant le faire.

Swift 4 implémenté SE-0156 (existentiels de classe et de sous-type).

L'équivalent de cette syntaxe Objective-C:

@property (strong, nonatomic) UIViewController<DetailViewController> * detailViewController;

Ressemble maintenant à ceci dans Swift 4:

var detailViewController: UIViewController & DetailViewController

Essentiellement, vous arrivez à définir une classe à laquelle la variable se conforme et le nombre N de protocoles qu'elle implémente. Voir le document lié pour des informations plus détaillées.

10
Senseful

Je pense que vous pouvez y arriver en ajoutant une extension (vide) à UIViewController puis en spécifiant votre attribut detailViewController en utilisant un protocole composé de l'extension vide et de votre DetailViewController. Comme ça:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

Maintenant, toutes les sous-classes de UIViewController satisfont au protocole UIViewControllerInject. Ensuite avec ça, simplement:

typealias DetailViewControllerComposed = protocol<DetailViewController, UIViewControllerInject>

class MasterViewController : UITableViewController {
  var detailViewController : DetailViewControllerComposed?
  // ...
}

Mais ce n'est pas particulièrement "naturel".

=== Modifier, Addition ===

En fait, vous pourriez faire un peu mieux si vous définissez votre DetailViewController en utilisant mon UIViewControllerInject suggéré. Comme tel:

protocol UIViewControllerInject {}
extension UIViewController : UIViewControllerInject {}

protocol DetailViewController : UIViewControllerInject { /* ... */ }

et maintenant vous n'avez pas besoin de composer explicitement quelque chose (mon DetailViewControllerComposed) et vous pouvez utiliser DetailViewController? comme type pour detailViewController.

13
GoZoner

Une autre façon serait d'introduire des classes de base intermédiaires pour les contrôleurs de vue UIKit appropriés qui implémentent le protocole:

class MyUIViewControler : UIViewController, DetailViewController ...
class MyUITableViewController : UITableViewController, DetailViewController ...

Ensuite, vous héritez de vos contrôleurs de vue de ces contrôleurs de vue, pas ceux de UIKit.

Ce n'est pas naturel non plus, mais cela ne force pas tous vos UIViewControllers à satisfaire le protocole UIViewControllerInject comme l'a suggéré GoZoner.

2
tng