SwiftUI + MVVM + DI

Posted

技术标签:

【中文标题】SwiftUI + MVVM + DI【英文标题】: 【发布时间】:2021-12-30 12:25:16 【问题描述】:

我花了一段时间对此进行研究,发现了很多 SwiftUI、MVVM 和 DI 的示例,但没有一个组合在一起,所以我假设我误解了一些东西。

我有一个新的 SwiftUI 应用程序并打算使用上述内容。

我有以下

一个依赖容器

protocol ViewControllerFactory 
    func makeFirstViewController() -> First_ViewModel
    func makeSecondViewController() -> Second_ViewModel


class DependencyContainer : ObservableObject 
    let database = AppDatabase()


extension DependencyContainer: ViewControllerFactory 
    func makeFirstViewController() -> First_ViewModel 
        return First_ViewModel(appDatabase: database)
    

    func makeSecondViewController() -> Second_ViewModel 
        return Second_ViewModel()
    

在我的应用入口点我有:

@main
struct MyApp: App 
    var body: some Scene 
            
        let container = DependencyContainer()
    
        WindowGroup 
            First_View()
            .environment(\.container, container)
        
    


private struct Container: EnvironmentKey 
  static let defaultValue = DependencyContainer()


extension EnvironmentValues 
   var container: DependencyContainer 
      get  self[Container.self] 
      set  self[Container.self] = newValue 
    

现在我遇到了问题 如何在视图中使用容器?

struct First_View: View
    @Environment(\.container) var container
    @ObservedObject private var firstViewModel : First_ViewModel

    init()
        _firstViewModel = container.makeFirstViewController()
    

报错

“无法将类型 'First_ViewModel' 的值分配给类型 'ObservedObject'”

如果我像下面这样注入容器,

struct First_View: View     
    @ObservedObject private var firstViewModel : First_ViewModel
    
    init (container : DependencyContainer)
        _firstViewModel = container.makeFirstViewController()

它可以工作,但不是我认为它应该工作的方式

【问题讨论】:

【参考方案1】:

@Environment() 属性包装器类型依赖于一组已定义的键名。您可以创建自己的 (this blog post from Use Your Loaf is a good description of how to do this)。不过,这是为值类型设计的。

但是,由于您已经将 DependencyContainer 声明为 ObservableObject,您可以使用 @EnvironmentObject 属性包装器:

struct MyApp: App 
  @StateObject var container = DependencyContainer()

  var body: some View 
    WindowGroup 
      First_View()
        .environmentObject(container)
    
  

然后,在您需要访问的任何子视图中,您在声明中使用 @EnvironmentObject 包装器。请注意,您必须包含类类型;您可以定义多个环境对象,但每个类最多定义一个。

struct First_View: View 
  @EnvironmentObject var container: DependencyContainer
  // ...

还要注意@EnvironmentObject 假设它能够找到DependencyContainer 的实例 - 如果找不到,它将崩溃。当您在应用程序级别定义一个不应该成为问题的应用程序时。但是,在您的 SwiftUI 预览中,您需要指定一个合适的实例,否则预览子系统可能会崩溃。

因此,如果您有一个静态方法来准备适合预览版的依赖项,您可以编写:

struct First_View_Previews: PreviewProvider 
  static var previews: some View 
    First_View()
      .environmentObject(DependencyContainer.previewInstance)
  

【讨论】:

以上是关于SwiftUI + MVVM + DI的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI - MVVM之ViewModel

使用 MVVM 在 SwiftUI 中显示警报

SwiftUI-MVVM

如何在 SwiftUI 中实现 MVVM 模式?视图不会重新渲染

SwiftUI 中 MVVM 模式中多个切换的绑定变量数组

如何让@AppStorage 在 MVVM / SwiftUI 框架中工作?