ObervableObject 被多次初始化,并且没有刷新我的视图

ObervableObject being init multiple time, and not refreshing my view

我有这样的结构

contentView {
    navigationView{
     foreach {
        NavigationLink(ViewA(id: id))
     }
    }
}

///其中View A在View Appear中包含一个请求触发器

struct ViewA: View {

    @State var filterString: String = ""

    var id: String!
    @ObservedObject var model: ListObj = ListObj()

    init(id: String) {
        self.id = id
    }

    var body: some View {
        VStack {
            SearchBarView(searchText: $filterString)
            List {
                ForEach(model.items.filter({ filterString.isEmpty || [=11=].id.contains(filterString) || [=11=].name.contains(filterString)  }), id: \.id) { item in
                    NavigationLink(destination: ViewB(id: item.id)) {
                        VStack {
                            Text("\(item.name) ")
                        }
                    }
                }
            }

        }
        .onAppear {
            self.model.getListObj(id: self.id) //api request, fill data and call objectWillChange.send()
        }

    }

}

}

ViewB 的代码与 ViewB 相同, 接收id,存储,请求api收集数据。

但是 viewB 列表没有被刷新。 我还注意到 viewB 的

@ObservedObject var model: model = model()

被多次实例化

正在调试, 我发现每个 navigationLink 实例甚至在它被触发之前都是目的地。 这通常不是问题,

但在我的情况下,我觉得 ViewB 模型被实例化了 2 次,而我的 onApear 调用了错误的模型,这是 self.objectWillChange.send() 没有刷新我的视图的原因

这里有两个问题:

  1. SwiftUI 使用值类型,每次通过 body.
  2. 都会一遍又一遍地初始化
  3. 关于#1,NavigationLink不懒惰。

#1

每次调用 ViewA.init(...) 时都会实例化一个新的 ListObjObservedObject @State 的工作方式不同 SwiftUI 在整个屏幕生命周期中都会为您仔细跟踪它。 SwiftUI 假定 @ObservedObject 的最终所有权存在于它所使用的 View 之上的某个级别。

换句话说,你应该几乎总是避免像 @ObservedObject var myObject = MyObservableObject() 这样的事情。

(请注意,即使您执行了 @State var model = ListObj(),它也会每次都被实例化。但是因为它是 @State,SwiftUI 会在调用 body 之前用原始实例替换新实例。 )

#2

除此之外,NavigationLink也不懒惰。每次实例化 NavigationLink 时,都会传递一个新实例化的 ViewA,它会实例化您的 ListObj

所以对于初学者来说,您可以做的一件事是制作一个 LazyView 来延迟实例化,直到 NavigationLink.destination.body 实际被调用:

// Use this to delay instantiation when using `NavigationLink`, etc...
struct LazyView<Content: View>: View {
    var content: () -> Content
    var body: some View {
        self.content()
    }
}

现在您可以执行 NavigationLink(destination: LazyView { ViewA() }) 并且 ViewA 的实例化将推迟到 destination 实际显示。

只需使用 LazyView 即可解决您当前的问题 只要它是层次结构中的顶层视图,就像您将它推入 NavigationView 或者如果你出示它。

然而,这是@user3441734 的评论出现的地方。你真正需要做的是在你的 View 之外的某个地方保留 model 的所有权,因为在 #1 中有解释。

如果您的 @ObservedObject 被多次初始化,那是因为对象的所有者在每次状态更改时都会被刷新和重新创建。如果您的应用是 iOS 14 及更高版本,请尝试使用 @StateObject。它可以防止在刷新视图时重新创建对象。 https://developer.apple.com/documentation/swiftui/stateobject

When a view creates its own @ObservedObject instance it is recreated every time a view is discarded and redrawn. On the contrary a @State variable will keep its value when a view is redrawn. A @StateObject is a combination of @ObservedObject and @State - the instance of the ViewModel will be kept and reused even after a view is discarded and redrawn