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

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

问题描述

考虑一下,我们有一个RootViewDetailView. 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

如何避免这种情况?

  1. 将所有ViewModels存储为基本上是单例池的EnvironmentObjects.这种方法导致不使用不需要的对象时将其存储在内存中
  2. 将RootView的所有ViewModels传递给它的孩子和孩子的孩子(具有上述缺点+完全依赖)
  3. 将独立于视图的DataObject(也称为worker)存储为EnvironmentObject.在那种情况下,我们在哪里存储与模型相对应的视图相关状态?如果我们将其存储在View中,它将最终发生在我们交叉交换@States的情况下,SwiftUI禁止的内容
  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.

让我们添加一些代码来查看如果虚拟机是通过其视图创建的,会发生什么情况

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

Mehhh ...

Mehhh...

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

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天全站免登陆