检查两个对象是否实现了Swift协议及其关联类型

时间:2017-03-07 17:25:31

标签: swift swift-protocols associated-types

我有一个Swift 3项目,我正在声明一个具有相关类型的协议:

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

我想检查两个对象是否实现ViewModelContainer并且它是关联类型ViewModelType以便以“通用”方式进行分配。

理想情况下,我想做这样的事情:

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

但我无法将container投射到ViewModelContainer

  

协议'ViewModelContainer'只能用作通用约束,因为它具有Self或相关类型要求

我目前的解决方法是直接回退到特定的类及其相关类型,但它使我的代码非常冗长且容易出错:

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
    }
}

我尝试使用通用UIViewController,但由于Objective-C doesn't recognize generic Swift classes而卡住,因此无法在Storyboard中使用。

2 个答案:

答案 0 :(得分:0)

以下是将associatedtype ViewModelType更改为协议的想法。

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 :(得分:0)

它比我想象的要复杂(所以我删除了我以前的帖子以避免混淆)但我相信这对你有用:

 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 } }
 }

然后,您可以使用ViewModelContainerVC进行类型转换和赋值:

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

[EDIT]供将来参考,这与Bool返回类型兼容性相同:

 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 
    }
 }

这将允许组合条件:

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