从目标视图更新SwiftUI列表数据会导致意外行为

从目标视图更新SwiftUI列表数据会导致意外行为,swiftui,combine,Swiftui,Combine,我经常遇到这样的情况,列表将@ObservedObject数据存储中的一些项目显示为导航链接 选择NavigationLink时,将显示详细视图。此视图有一个简单的切换连接到其视图模型类上的@已发布变量 当出现DetailView时(onAppear:),其视图模型设置已发布的var,该var控制切换为真,并触发一个异步请求,该请求将更新主数据存储,导致上一屏幕中的列表也更新 问题是,当发生这种情况时(从细节视图中触发的操作重新加载列表),DetailViewModel的多个实例似乎被保留,并且

我经常遇到这样的情况,
列表
@ObservedObject
数据存储中的一些项目显示为
导航链接

选择
NavigationLink
时,将显示详细视图。此视图有一个简单的
切换
连接到其视图模型类上的
@已发布
变量

当出现DetailView时(
onAppear:
),其视图模型设置已发布的
var
,该var控制
切换
,并触发一个异步请求,该请求将更新主数据存储,导致上一屏幕中的
列表
也更新

问题是,当发生这种情况时(从细节视图中触发的操作重新加载
列表
),DetailViewModel的多个实例似乎被保留,并且DetailView开始接收来自错误的发布者的事件

第一次到达详细信息屏幕时,行为正确(如下面的代码所示),切换设置为
true
,并且存储更新,在导航回
列表时,然后再次导航到另一个详细视图,在出现时切换设置为
true
,但这次当重新加载存储的代码执行时,切换设置回
false

我的理解是,当重新加载
列表
并创建新的DetailView和ViewModel(作为
导航链接
的目标)时,
isOn
变量的初始值控制
切换
(即
false
)正在以某种方式触发对当前显示屏幕的
切换的更新

我是不是遗漏了什么

import SwiftUI
import Combine

class Store: ObservableObject {
    static var shared: Store = .init()
    @Published var items = ["one", "two"]
    private init() { }
}

struct ContentView: View {
    @ObservedObject var store = Store.shared
    
    var body: some View {
        NavigationView {
            List(store.items, id: \.self) { item in
                NavigationLink(item, destination: ItemDetailView())
            }
        }
    }
}

struct ItemDetailView: View {
    @ObservedObject var viewModel = ItemDetailViewModel()
    
    var body: some View {
        VStack {
            Toggle("A toggle", isOn: $viewModel.isOn)
            Text(viewModel.title)
            Spacer()
        }   .onAppear(perform: viewModel.onAppear)
    }
}

class ItemDetailViewModel: ObservableObject {
    @ObservedObject var store: Store = .shared
    @Published var isOn = false

    var title: String {
        store.items[0]
    }
    
    func onAppear() {
        isOn = true
        asyncOperationThatUpdatesTheStoreData()
    }
    
    private func asyncOperationThatUpdatesTheStoreData() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) { [weak self] in
            self?.store.items = ["three", "four"]
        }
    }
}

您控制生命周期和对象的方式不是此UI框架中的模式。VieModel不会神奇地重新发布单例值类型;它将使用该值进行实例化,然后在不再次签入共享实例的情况下改变其状态,除非重新构建它

类存储:ObservableObject{
静态变量共享:Store=.init()
结构ContentView:View{
@ObservedObject var store=store.shared
有很多潜在的可行模式。例如:

  • 创建
    class RootStore:ObservableObject
    并将其作为@StateObject存放在应用程序中。StateObject的生存期是该视图层次结构的生存期。您可以通过以下方式将其(a)直接公开给子视图。environmentObject(store)或(b)创建一个容器对象,例如提供ViewModels的工厂,并通过环境传递它,而不向视图公开存储,或者(c)同时执行a和b

  • 如果您在另一个类中引用存储,请保持弱引用
    弱变量存储:存储?
    。如果您在@Published上保持该根存储的状态,则可以直接订阅该发布服务器或根存储自己的ObjectWillChangePublisher。您还可以使用其他组合发布服务器,如CurrentValueSubject来实现与@Published相同

  • 有关代码示例,请参见

    struct ItemDetailView: View {
        @ObservedObject var viewModel = ItemDetailViewModel()
    
    class ViewModel: ObservableObject {
        @Published var items: [String] = Store.shared.items