视图更新后保持对视图/数据模型的引用 [英] Keep reference on view/data model after View update

查看:30
本文介绍了视图更新后保持对视图/数据模型的引用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我们有一个 RootView 和一个 DetailView.DetailView 有它自己的 BindableObject,我们称之为 DetailViewModel 并且我们有场景:

Consider we have a RootView and a DetailView. DetailView has it's own BindableObject, let's call it DetailViewModel and we have scenario:

  1. RootView 可能会被某种全局事件更新,例如错过了互联网连接或通过它自己的数据/视图模型
  2. RootView 处理事件时内容被更新,这导致 DetailView 的新结构被创建
  3. 如果 DetailViewModel 在初始化时由 DetailView 创建,将会有 DetailViewModel 的另一个引用,它的状态(例如选定的对象)将被错过
  1. RootView may be updated by some kind of global event e.g. missed internet connection or by it's own data/view model
  2. When RootView handling event it's content is updated and this is causes new struct of DetailView to be created
  3. If DetailViewModel is created by DetailView on init, there would be another reference of DetailViewModel and it's state (e.g. selected object) will be missed

我们怎样才能避免这种情况?

How can we avoid this situation?

  1. 将所有 ViewModel 存储为 EnvironmentObjects,这基本上是一个单例池.这种方法导致在不使用时将不需要的对象存储在内存中
  2. 将所有 ViewModel 从 RootView 扔给它的孩子和孩子的孩子(有上面的缺点 + 痛苦的依赖)
  3. 将视图独立的数据对象(又名工作人员)存储为环境对象.在那种情况下,我们在哪里存储对应于模型的视图依赖状态?如果我们将它存储在 View 中,它将最终导致我们交叉更改 SwiftUI 禁止的@States
  4. 更好的方法?

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

Sorry me for not providing any code. This question is on architecture concept of Swift UI where we trying to combine declarative structs and reference objects with data.

目前,我认为没有一种方法可以仅保留与适当视图相对应的引用,并且不要将它们永远保持在当前状态的内存/环境中.

For now I don't see da way to keep references that corresponds to appropriate view only and don't keep them in memory/environment forever in their current states.

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

Lets add some code to see whats happening if VM is created by it's View

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:

看来,如果我们将 ObservableObject 作为 @State 存储在视图中(而不是作为 ObservedObject),则视图会在 VM 上保持引用

Update 2:

It seems that if we store ObservableObject as @State in view (not as ObservedObject) View keeps reference on VM

@State var viewModel = ContentDetailViewModel()

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

Any negative effects? Can we use it like this?

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

It seems that if ViewModel kept in View's @State:

  1. 并且 ViewModel 被具有强引用的闭包保留 - deinit 将永远不会被执行 -> 内存泄漏
  2. 并且 ViewModel 由具有弱引用的闭包保留 - 每次视图更新时 deinit 都会调用,所有 subs 将被重置,但属性将相同
  1. and ViewModel is retained by closure with strong reference - deinit will never be executed -> memory leak
  2. and ViewModel is retained by closure with weak reference - deinit invokes every time on view update, all subs will be reseted, but properties will be the same

嗯……

这种方法还允许你在绑定闭包中保持强引用

This approach also allows you to keep strong references in bindings closures

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 说我们应该对存在于 SwiftUI 之外的数据使用 ObservableObject.这意味着您必须自己管理数据源.

Apple says that we should use ObservableObject for the data that lives outside of SwiftUI. It means you have to manage your data source yourself.

看起来单状态容器最适合 SwiftUI 架构.

It looks like a single state container fits best for SwiftUI architecture.

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 应用程序的环境中,它将在所有视图中可用,并将存储您的应用程序状态而不会丢失数据.

You can pass the instance of the store into the environment of your SwiftUI app and it will be available in all views and will store your app state without data losses.

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

I wrote a blog post about this approach, take a look at it for more information https://swiftwithmajid.com/2019/09/18/redux-like-state-container-in-swiftui/

这篇关于视图更新后保持对视图/数据模型的引用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆