【问题标题】:SwiftUI Classes that conforms ObservableObject should be Singleton?SwiftUI 符合 ObservableObject 的 Classes 应该是 Singleton 吗?
【发布时间】:2021-01-05 09:18:04
【问题描述】:

我在 SwiftUI 中被认为是新手,并且我有以下 ViewModel。但我不确定 MyViewModel 应该是单例的。这种用法对吗?什么是符合 ObservableObject 的最佳实践/用法?

class MyViewModel: ObservableObject {
    static let shared: MyViewModel = MyViewModel()
    
    @Published var result: String = ""
    
    private init() { }
    
    // some functions
}

struct ContentView: View {
    @ObservedObject private var vm = MyViewModel.shared
    
    var body: some View {
        Text(vm.result)
    }
}

【问题讨论】:

  • 不应该,但可以。

标签: swift swiftui singleton observableobject


【解决方案1】:

我通过这种方式实现了我的场景。我们可以说这是正确的方法吗?谢谢...

struct RootTabView: View {
    @State var tabSelection = 0
    @State private var listVM = ListViewModel()
    
    var body: some View {
        TabView(selection: $tabSelection) {
            ListView(vm: listVM).tabItem({
                Text("Tab 1")
            }).tag(0)
            
            //Some other tabs
        }
    }
}

struct ListView: View {
    @ObservedObject var vm: ListViewModel
    
    var body: some View {
        NavigationView {
            List(vm.toDoList, id: \.self) { toDo in
                NavigationLink(destination: DetailView(vm: vm)) {
                    Text(toDo)
                }
            }
        }
        .onAppear {
            vm.getList()
        }
    }
}

struct DetailView: View {
    @ObservedObject var vm: ListViewModel
    
    var body: some View {
        Text(vm.toDoItem)
            .onAppear {
                vm.getDetail()
            }
    }
}

class ListViewModel: ObservableObject {
    @Published var toDoList: [String] = []
    @Published var toDoItem: String = ""
    
    func getList() {
        toDoList = ["a", "b", "c"]
    }
    
    func getDetail() {
        // do some stuffs
        toDoItem = "A"
    }
}

【讨论】:

  • 是的,这看起来不错。与原始问题无关的一条建议:DetailView 使用相同的 ListViewModel 作为其父视图,ListView 意味着您将无法将您的子视图与其父视图分离。我建议创建一个DetailViewModel,然后您的ListViewModel 可以存储DetailViewModels 的数组并将其注入每个DetailView。这将提高DetailView 的可重用性和解耦性,因为您无需创建ListView 就可以创建一个。
  • @DávidPásztor 我很想解耦,但我还需要我的列表来识别我的详细视图何时修改了它的项目。对于这种非常常见的情况,有没有办法绕过耦合视图模型?
  • @jeanmartin 这很简单,您只需创建对详细 VM 的 @Published 属性的订阅,然后对列表 VM 中的更改做出反应。列表VM知道详细VM是完全可以的,只有详细VM不应该知道列表VM。
【解决方案2】:

为什么你认为视图模型应该是单例?尤其是,为什么一个符合ObservableObject 的类需要一个单例实例?这是个坏主意。

这不仅是绝对没有必要的,这也意味着你不能在屏幕上拥有相同视图的多个实例,而它们没有共享状态。如果您想支持分屏并在屏幕上同时运行应用的 2 个场景,这在 iPad 上尤其糟糕。

除非万不得已,否则不要将任何东西设为单例。

在 SwiftUI Views 上存储 @ObservedObjects 时要记住的唯一重要的事情是它们永远不应该在视图中初始化。当@ObservedObject 更改(或其@Published 属性之一更改)时,将重新加载存储它的View。这意味着如果您在View 内创建对象,则每当对象更新时,视图本身都会创建该对象的新实例。

所以这是个坏主意,而且行不通:

struct ContentView: View {
    // Never do this
    @ObservedObject private var vm = MyViewModel()
    
    var body: some View {
        Text(vm.result)
    }
}

相反,您需要将视图模型注入您的View(通过在父视图或协调器等中创建它,无论您从何处创建ContentView)。

struct ParentView: View {
    @State private var childVM = MyViewModel()

    var body: some View {
        ContentView(vm: childVM)
    }
}


struct ContentView: View {
    @ObservedObject private var vm: MyViewModel
 
    // Proper way of injecting the view model
    init(vm: MyViewModel) {
        self.vm = vm
    }
   
    var body: some View {
        Text(vm.result)
    }
}

【讨论】:

  • 我将在 NavigationLink() 中使用 ContentView。而且我认为如果我不使用 MyViewModel 的单例实例,它会一次又一次地创建。为了防止这种情况,我使用了单例。这种方式(注入VM)也适合在NavigationLink中使用吗?
  • @KenanNur 单身确实解决了娱乐问题,但这不是正确的解决方案。是的,注入也适合在NavigationLink使用。
  • 只是指出,在 iOS 14 中,如果您想让视图模型不被父级注入,您现在可以使用 @StateObject
  • @DávidPásztor 我添加了我的新场景。你能看看吗?谢谢
猜你喜欢
  • 1970-01-01
  • 2022-01-12
  • 2012-01-10
  • 1970-01-01
  • 2022-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-11
相关资源
最近更新 更多