如何将正确的 viewModel 注入单个 viewController

Posted

技术标签:

【中文标题】如何将正确的 viewModel 注入单个 viewController【英文标题】:How to inject the right viewModel to a single viewController 【发布时间】:2018-04-09 11:52:11 【问题描述】:

我将 Swinject 用于我的 DI 解决方案,并使用 SwinjectStoryboard 扩展名对其进行扩展。

我正在努力将正确的viewModel 动态注入特定的viewContoller。具体场景如下:

MyViewController 有一个名为var viewModel: ViewModeling 的属性。

有两种不同的视图模型符合ViewModeling 协议,我们称之为:firstViewModelsecondViewModel。 我的故事板只包含一个控制器及其MyViewController

问题

动态注入正确的 viewModel 作为 MyViewController 的依赖项(所以只有在运行时我才会知道是注入第一个还是第二个)

我能够在服务级别上做到这一点(2 个服务遵循的一个协议,以及每个使用不同服务的 2 个不同 viewModel 可以使用特定名称解析所需的一个)

我正在努力在 viewController 级别上执行此操作,尝试将相同的视图控制器注入特定的 viewModel(两者都符合相同的协议)。

目前我的预感是 SwinjectStoryboard 不允许我使用它的故事板 ID 实例化视图控制器(就像我通常会做的那样),此外还定义了几个将在运行时解析的不同名称。

我错过了什么吗?

【问题讨论】:

【参考方案1】:

您没有错过任何东西 - 您正在寻找的行为目前确实无法通过 SwinjectStoryboard 实现。

您可以拥有多个 storyboardInitCompleted 和不同的 names,但它们对应于在情节提要参数 swinjectRegistrationName 中输入的名称(有关更多信息,请参阅 docs) - 为了使用它,您需要拥有故事板中视图控制器的多个副本。

在我看来,理想的解决方案是注册参数,即您将拥有一个enum ViewModelType ,它将在storyboardInitCompleted 中用于解析正确的视图模型。不幸的是,此功能尚未最终确定(请参阅this PR)。

在当前状态下,我可能会尝试将视图模型的选择从注入逻辑移动到应用程序逻辑 - 即你可以有一些

protocol ViewModelProvider 
    var viewModel: ViewModeling  get 

它将被注入到视图控制器中,并根据某些应用程序状态提供正确的视图模型。

另一种规避问题的方法是放弃 SwinjectStoryboard 注册,并使用基本的 Swinject 实例化视图控制器:

container.register(MyViewController.self, name: "name") 
    let vc = SwinjectStoryboard.create(name: "MyStoryboard", bundle: nil).instantiateViewController(withIdentifier: "identifier")
    vc.viewModel = $0.resolve(ViewModeling.self)
    return viewModel


let vc = SwinjectStoryboard.defaultContainer.resolve(MyViewController.self, name: "name")

【讨论】:

我觉得应该是return vc【参考方案2】:

我的模式,如何将ViewModel 注入ViewController

// ------------------------------------------------------------------------------------------
// Option 1, when you use Storyboards
// ------------------------------------------------------------------------------------------

import Foundation

class MyViewController 

    var viewModel: MyViewModel!

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var titleLabel: UILabel!

    override func viewDidLoad() 
        super.viewDidLoad()

        bindViewModel()
    

    func bindViewModel() 

    

    // Then, you can inject ViewModel in prepare function
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) 
        super.prepare(for: segue, sender: sender)

        guard let myViewController = segue.destination as? MyViewController else  return 
        myViewController.viewModel = MyViewModel(myDependency: myDependency)
    


// ------------------------------------------------------------------------------------------
// Option 2, when you use xibs
// ------------------------------------------------------------------------------------------

import Foundation

class MyViewController 

    let viewModel: MyViewModel

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var titleLabel: UILabel!

    init(viewModel: MyViewModel) 
        self.viewModel = viewModel

        super.init(nibName: nil, bundle: nil)
    

    required init?(coder aDecoder: NSCoder) 
        fatalError("init(coder:) has not been implemented")
    

    override func viewDidLoad() 
        super.viewDidLoad()

        bindViewModel()
    

    func bindViewModel() 

    

    // Then, you can inject ViewModel in constructor:
    func showMyViewController() 
        let vm = MyViewModel(myDependency: myDependency)
        let vc = MyViewController(viewModel: vm)
        self.present(vc, animated: true, completion: nil)
    

【讨论】:

以上是关于如何将正确的 viewModel 注入单个 viewController的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Xamarin 和 Autofac 将构造函数依赖项注入 ViewModel?

如何使用 Knockout.js 将多个 View 绑定到单个 ViewModel

使用 Swinject 将 ViewModel 条件注入 ViewController

如何使用 Xamarin.Forms.DependencyService 注入具有构造函数注入的 ViewModel

无法使用 Dagger2 将对象注入 ViewModel 类

如何在 Android/Kotlin App 上通过 Koin 注入在 BaseActivity 中初始化/注入通用 ViewModel