2017-03-07 6 views
2

J'ai un projet Swift 3 où je déclare un protocole avec un type associé à ceci:Vérifiez si deux objets mettent en œuvre un protocole Swift et son type associé

protocol ViewModelContainer { 
    associatedtype ViewModelType 
    var viewModel: ViewModelType! { get set } 
} 

Et je veux vérifier si deux objets implémenter ViewModelContainer et il est associé au type ViewModelType pour effectuer l'affectation de manière «générique».

Idéalement, je voudrais faire quelque chose comme ceci:

if let container = container as? ViewModelContainer, let model = model as? container.ViewModelType { 
    container.viewModel = model 
} 

Mais je ne peux pas jeter container-ViewModelContainer:

Protocole « ViewModelContainer » ne peut être utilisé comme une contrainte générique parce qu'il a le Soi ou les exigences de type associées


Ma solution actuelle est de revenir à des classes spécifiques et leurs types associés directement, mais il laisse mon code très bavard et sujette aux erreurs:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 
    if let vc = segue.destination as? MediaPlaySelectionViewController, let vm = sender as? MediaPlaySelectionViewModel { 
     vc.viewModel = vm 
    } 
    if let vc = segue.destination as? SearchResultsViewController, let vm = sender as? SearchResultsViewModel { 
     vc.viewModel = vm 
    } 
    if let vc = segue.destination as? ReviewDetailsViewController, let vm = sender as? ReviewDetailsViewModel { 
     vc.viewModel = vm 
    } 
    if let vc = segue.destination as? ReviewComposerViewController, let vm = sender as? ReviewComposerViewModel { 
     vc.viewModel = vm 
    } 
} 

J'essayé d'utiliser UIViewController générique s, mais est resté coincé parce que Objective-C doesn't recognize generic Swift classes et donc ne peut pas être utilisé dans Storyboard.

+0

C'est vraiment stupide ... mais si vous faites un protocole vide, puis faire tous les types conformes à 'ViewModelContainer' conforme à ce protocole, vous vérifier la conformité à ce protocole sans se heurter à ce problème. – BallpointBen

+0

Je suppose que la vraie question est pourquoi est-ce que 'container' n'est pas statiquement typé comme un type qui se conforme à' ViewModelContainer'? Vous pourriez bien être à la recherche d'un effaceur de type. – Hamish

+0

Pourquoi ne pas faire du 'ViewModelType' en tant que protocole? – dichen

Répondre

0

Il était plus délicat que prévu (donc je supprimé mon post précédent pour éviter toute confusion) mais je crois que cela devrait fonctionner pour vous:

protocol ViewModelContainerVC 
{ 
    mutating func setModel(_ :Any) 
} 

protocol ViewModelContainer:class,ViewModelContainerVC 
{ 
    associatedtype ViewModelType 
    var viewModel: ViewModelType! { get set } 
} 

extension ViewModelContainer 
{ 
    mutating func setModel(_ model:Any) 
    { if model is ViewModelType { viewModel = model as! ViewModelType } } 
} 

Vous pouvez ensuite utiliser la ViewModelContainerVC pour la coulée de type et affectation:

if let container = container as? ViewModelContainerVC 
{ 
    container.setModel(model) 
} 

[EDIT] pour référence future, voici la même chose avec un retour Bool pour la compatibilité de type:

protocol ViewModelContainerVC 
{ 
    @discardableResult mutating func setModel(_ :Any) -> Bool 
} 

extension ViewModelContainer 
{ 
    @discardableResult mutating func setModel(_ model:Any) -> Bool 
    { 
     if let validModel = model as? ViewModelType 
     { viewModel = validModel; return true } 
     return false 
    } 
} 

qui permettra une condition combinée:

if var container = container as? ViewModelContainerVC, 
    container.setModel(model) 
{ ... } 
+0

C'est une bonne solution de contournement. Mais pourquoi utiliser 'is' et' as! 'Au lieu de' if let' ou 'as?' Directement? – redent84

+0

Aucune raison particulière, je l'ai juste senti mieux transmis l'intention. –

+0

Cela semble très non-swifty: La méthode 'setModel' échoue silencieusement lorsqu'il est appelé avec le mauvais type, mais le compilateur n'aide pas. Vous pouvez l'appeler sur n'importe quel 'ViewModelContainer' avec un argument de n'importe quel type. –

0

Voici l'idée de changer le associatedtype ViewModelType en protocole.

protocol ViewModelProtocol { 
} 

protocol ViewModelContainer { 

    var viewModel: ViewModelProtocol? { get set } 
} 

class MediaPlaySelectionViewModel: ViewModelProtocol { 

    var title: String? 
    func play() { 
     print("playing") 
    } 
} 

class SearchResultsViewModel: ViewModelProtocol { 

    var results: [String]? 
} 

class MediaPlaySelectionViewController: UIViewController, ViewModelContainer { 

    var viewModel: ViewModelProtocol? 

    // So the view itself know which kind of vm it wants. 
    var myViewModel: MediaPlaySelectionViewModel? { 
     return viewModel as? MediaPlaySelectionViewModel 
    } 

    override func viewWillAppear(_ animated: Bool) { 
     print(myViewModel?.title ?? "Undefined") 
    } 
} 

class SearchResultsViewController: UIViewController, ViewModelContainer { 

    var viewModel: ViewModelProtocol? 

    // So the view itself know which kind of vm it wants. 
    var myViewModel: SearchResultsViewModel? { 
     return viewModel as? SearchResultsViewModel 
    } 

    override func viewWillAppear(_ animated: Bool) { 
     print(myViewModel?.results?.joined(separator: ", ") ?? "No Result") 
    } 
} 

class MenuViewController: UITableViewController { 

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 

     // NOTE: Swift doesn't allow me to use 'let' here 
     if var container = segue.destination as? ViewModelContainer, let cell = sender as? UITableViewCell, let vm = viewModel(for: cell) { 
      container.viewModel = vm 
     } 
    } 

    // NOTE: One difficulty here, how could you decide which ViewModel to prepare? I guess you need a Factory. 
    func viewModel(for cell: UITableViewCell) -> ViewModelProtocol! { 

     if let index = tableView.indexPath(for: cell) { 

      if index.item == 0 { 

       let vm = MediaPlaySelectionViewModel() 
       vm.title = "My Video" 

       return vm 
      } 
      else if index.item == 1 { 

       let vm = SearchResultsViewModel() 
       vm.results = ["Apple", "Banana"] 

       return vm 
      } 
     } 

     return nil 
    } 
} 
+1

Eh bien, je préfère un peu de verbosité sur le 'prepare' plutôt que beaucoup plus de verbosité sur le modèle de vue ** et ** perdre la sécurité de type – redent84