符合ObservableObject的SwiftUI 类 应该是Singleton?

SwiftUI Classes that conforms ObservableObject should be Singleton?

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

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

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

这不仅完全没有必要,而且还意味着如果没有共享状态,您不能在屏幕上拥有同一视图的多个实例。如果您想在屏幕上同时支持分屏和 运行 您的应用的 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)
    }
}

我就是这样实现我的场景的。我们能说这是正确的方法吗?谢谢...

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