【问题标题】:SwiftUI not releasing model instancesSwiftUI 不发布模型实例
【发布时间】:2020-08-26 16:36:25
【问题描述】:

拥有一个由 Xcode 生成的 SwiftUI 项目,并添加一个带有 MyViewModel 的自定义 MyViewContentView 只是渲染 MyView

问题:

  1. ContentView 重新加载(重新加载按钮改变其状态)时,MyViewModelMyView 断开连接(当单击按钮时,MyView 计数器在 UI 中停止递增),但控制台日志显示增量工作。
  2. 如果模型订阅了发布者,它不会因为实例未发布而被取消订阅。因此,实例仍会处理传入消息并更改应用的数据和状态。

在控制台中查看实例计数器和内存地址:

  1. 每次刷新ContentView 时,都会创建一个新的MyViewMyViewModel 实例。但是,计数器增量使用最初创建的模型实例。
  2. 部分模型实例未发布。

编辑: 每次重新创建 MyView 时都需要重新创建模型。

import SwiftUI

struct ContentView: View {
    @State
    private var reloadCounter = 0

    var body: some View {
        VStack {
            Button(action: { self.reloadCounter += 1 },
                   label: { Text("Reload view") })

            Text("Reload counter: \(reloadCounter)")

            MyView().environmentObject(MyViewModel())
        }
    }
}
import SwiftUI

struct MyView: View {
    @EnvironmentObject
    private var model: MyViewModel

    var body: some View {
        VStack {
            Button(action: { self.model.counter += 1 },
                   label:  { Text("Increment counter") })

            Text("Counter value: \(model.counter)")
        }
        .frame(width: 480, height: 300)
    }

    init() { withUnsafePointer(to: self) { print("Initialising MyView struct instance \(String(format: "%p", $0))") }}
}
import Combine

class MyViewModel: ObservableObject {
    private static var instanceCount: Int = 0 { didSet {
        print("SettingsViewModel: \(instanceCount) instances")
    }}

    @Published
    var counter: Int = 0 { didSet {
        print("Model counter: \(counter), self: \(Unmanaged.passUnretained(self).toOpaque())")
    }}

    init() { print("Initialising MyViewModel class instance \(Unmanaged.passUnretained(self).toOpaque())"); Self.instanceCount += 1 }
    deinit { print("Deinitialising MyViewModel class instance \(Unmanaged.passUnretained(self).toOpaque())"); Self.instanceCount -= 1 }
}

知道我做错了什么吗?

下图描绘了执行日志中的所有操作后应用的 UI。

【问题讨论】:

    标签: swiftui combine


    【解决方案1】:

    每次刷新都会创建新的视图模型,所以只需将其移出body,就像

    struct ContentView: View {
        @State
        private var reloadCounter = 0
    
        private let vm = MyViewModel()    // << here !!
    
        var body: some View {
            VStack {
                Button(action: { self.reloadCounter += 1 },
                       label: { Text("Reload view") })
    
                Text("Reload counter: \(reloadCounter)")
    
                MyView().environmentObject(vm)   // << reference only !!
            }
        }
    }
    

    注意:如果您在子视图层次结构中不需要它,请考虑使用 @ObservedObject 而不是 @EnvironmentObject 并通过构造函数传递引用(因为环境对象存储在外部某处并且您拥有的更少控制其生命周期)

    【讨论】:

    • 问题是每次创建 MainView 时我都需要一个新模型 - 它必须是新鲜的,以便将 MyView 设置为模型的默认值。提供的示例有点简化。实际上,层次结构更深一些(这就是我使用 EnvironmentObject 的原因)。
    猜你喜欢
    • 2013-02-28
    • 2011-04-16
    • 1970-01-01
    • 2020-11-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-28
    相关资源
    最近更新 更多