考虑我们有一个RootView
and a DetailView
. DetailView
有它自己的 BindableObject,我们称之为它DetailViewModel
我们有这样的场景:
-
RootView
可能会被某种全局事件更新,例如错过了
互联网连接或通过它自己的数据/视图模型
- When
RootView
处理事件是
内容已更新,这会导致新的结构DetailView
到
被创造
- If
DetailViewModel
是由创建的DetailView
在初始化时,
会有另一个参考DetailViewModel
并且它的状态(例如选定的对象)将被错过
我们怎样才能避免这种情况呢?
- 将所有 ViewModel 存储为环境对象,这基本上是一个单例池。这种方法会导致在不使用不需要的对象时将它们存储在内存中
- 将所有 ViewModel 从根视图传递到其子视图和子视图的子视图(具有上述缺点 + 痛苦的依赖关系)
- 将 View 独立数据对象(也称为工作人员)存储为环境对象。在这种情况下,我们在哪里存储与模型相对应的视图相关状态?如果我们将它存储在 View 中,最终会出现交叉更改 @States 的情况,这是 SwiftUI 所禁止的
- 更好的方法?
抱歉,我没有提供任何代码。这个问题是关于 Swift UI 的架构概念,我们试图将其结合起来声明性结构 and 引用带有数据的对象.
目前,我没有看到一种方法来保留仅对应于适当视图的引用,并且不要将它们永远保留在当前状态的内存/环境中。
Update:
让我们添加一些代码来看看如果虚拟机是由它的视图创建的话会发生什么
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),那么 View 会在 VM 上保留引用
@State var viewModel = ContentDetailViewModel()
有什么负面影响吗?我们可以这样使用它吗?
更新3:
看来如果ViewModel保留在View的@State中:
- 并且 ViewModel 通过具有强引用的闭包保留 - deinit 永远不会被执行 -> 内存泄漏
- 并且 ViewModel 通过弱引用闭包保留 - deinit 每次视图更新时都会调用,所有
subs
将被重置,但属性将相同
Mehhh...
更新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)
}
}