视图更新后保留对视图/数据模型的引用



考虑我们有一个RootView和一个DetailViewDetailView有自己的 BindableObject,我们称之为DetailViewModel我们有场景:

  1. RootView可能会被某种全球事件更新,例如错过 互联网连接或通过它自己的数据/视图模型
  2. RootView处理事件时,它是 内容已更新,这会导致DetailView的新结构 被创建
  3. 如果DetailViewModel是由DetailView在 init 上创建的, 会有另一个DetailViewModel引用,它的状态(例如选定的对象(将被遗漏

我们如何避免这种情况?

  1. 将所有视图模型存储为环境对象,该对象基本上是一个单一实例池。此方法会导致在不使用时将不需要的对象存储在内存中
  2. 将所有视图模型从 RootView 传递给它的子项和子项的子项(具有上述缺点 + 痛苦的依赖项(
  3. 将视图独立数据对象(也称为工作线程(存储为环境对象。在这种情况下,我们在哪里存储与模型对应的视图依赖状态?如果我们将其存储在视图中,它最终将出现交叉更改@States SwiftUI 禁止的内容的情况
  4. 更好的方法?

很抱歉我没有提供任何代码。这个问题是关于 Swift UI 的架构概念的,我们试图将声明性结构引用对象与数据结合起来。

目前,我没有看到仅保留与适当视图对应的引用的方法,并且不要将它们永远保留在当前状态的内存/环境中。

更新:

让我们添加一些代码,看看如果 VM 是由它的视图创建的,会发生什么

import SwiftUI
import Combine
let trigger = Timer.publish(every: 2.0, on: .main, in: .default)
struct ContentView: View {
@State var state: Date = Date()
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: ContentDetailView(), label: {
Text("Navigation push")
.padding()
.background(Color.orange)
})
Text("(state)")
.padding()
.background(Color.green)
ContentDetailView()
}
}
.onAppear {
_ = trigger.connect()
}
.onReceive(trigger) { (date) in
self.state = date
}
}
}
struct ContentDetailView: View {
@ObservedObject var viewModel = ContentDetailViewModel()
@State var once = false
var body: some View {
let vmdesc = "View model uuid:n(viewModel.uuid)"
print("State of once: (once)")
print(vmdesc)
return Text(vmdesc)
.multilineTextAlignment(.center)
.padding()
.background(Color.blue)
.onAppear {
self.once = true
}
}
}
class ContentDetailViewModel: ObservableObject, Identifiable {
let uuid = UUID()
}

更新 2:

似乎如果我们将可观察对象存储为视图中的@State(而不是作为观察对象(,视图会在 VM 上保留引用

@State var viewModel = ContentDetailViewModel()

有什么负面影响吗?我们可以这样使用它吗?

更新 3:

看来,如果ViewModel保留在View的@State中:

  1. 并且 ViewModel 通过闭包保留,并具有强引用 - deinit 永远不会被执行 ->内存泄漏
  2. 并且 ViewModel 通过弱引用的闭包保留 - deinit 每次在视图更新时调用,所有subs都将重置,但属性将相同

呵呵......

更新 4:

此方法还允许您在绑定闭包中保留强引用

import Foundation
import Combine
import SwiftUI
/**
static func instanceInView() -> UIViewController {
let vm = ContentViewModel()
let vc = UIHostingController(rootView: ContentView(viewModel: vm))
vm.bind(uiViewController: vc)
return vc
}
*/
public protocol ViewModelProtocol: class {
static func instanceInView() -> UIViewController
var bindings: Set<AnyCancellable> { get set }
func onAppear()
func onDisappear()
}
extension ViewModelProtocol {
func bind(uiViewController: UIViewController) {
uiViewController.publisher(for: .parent)
.sink(receiveValue: { [weak self] (parent) in
if parent == nil {
self?.bindings.cancel()
}
})
.store(in: &bindings)
}
}
struct ModelView<ViewModel: ViewModelProtocol>: UIViewControllerRepresentable {
func makeUIViewController(context: UIViewControllerRepresentableContext<ModelView>) -> UIViewController {
return ViewModel.instanceInView()
}
func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<ModelView>) {
//
}
}
struct RootView: View {
var body: some View {
ModelView<ParkingViewModel>()
.edgesIgnoringSafeArea(.vertical)
}
}

Apple 表示,我们应该将 ObservableObject 用于 SwiftUI 之外的数据。这意味着您必须自己管理数据源。

它看起来像一个单一状态容器最适合 SwiftUI 架构。

typealias Reducer<State, Action> = (inout State, Action) -> Void
final class Store<State, Action>: ObservableObject {
@Published private(set) var state: State
private let reducer: Reducer<State, Action>
init(initialState: State, reducer: @escaping Reducer<State, Action>) {
self.state = initialState
self.reducer = reducer
}
func send(_ action: Action) {
reducer(&state, action)
}
}

您可以将存储的实例传递到 SwiftUI 应用程序的环境中,它将在所有视图中可用,并将存储应用程序状态而不会丢失数据。

我写了一篇关于这种方法的博客文章,看看它以获取更多信息 https://swiftwithmajid.com/2019/09/18/redux-like-state-container-in-swiftui/

最新更新