SwiftUI - @Binding to a computed 属性 which accesses value inside ObservableObject 属性 复制变量?

SwiftUI - @Binding to a computed property which accesses value inside ObservableObject property duplicates the variable?

在下面的代码(项目中某些代码的精简版)中,我使用了具有两个视图的 MVVM 模式:

在 ViewModelA 内部,我有一个计算 属性,它既可以避免视图直接访问模型,也可以在模型(显示的那个)内部的值发生变化时执行一些其他操作。

我还使用 Binding 将该计算值传递给 ViewModelB,Binding 充当 ViewB 的 StateObject。但是,当拖动 Slider 更改该值时,ViewA 上的值会发生变化,但 ViewB 上的值不会发生变化,并且滑块本身不会滑动。正如预期的那样,在调试时,Binding 中的 wrappedValue 没有发生变化。

但是变化是如何向上传播的(我想是通过 Binding 的设置器)而不是向下传播回 ViewB?

我想只有当变量在某处被复制并且只在一个地方发生变化时才会发生这种情况,但我似乎无法理解实际发生的情况或是否发生了这种情况。

提前致谢!

观看次数:

import SwiftUI

struct ContentView: View {
    @StateObject var viewModelA = ViewModelA()
    
    var body: some View {
        VStack{
            ViewA(value: viewModelA.value)
            
            ViewB(value: $viewModelA.value)
        }
    }
}

struct ViewA: View {
    let value: Double
    
    var body: some View {
        Text("\(value)").padding()
    }
}

struct ViewB: View {
    @StateObject var viewModelB: ViewModelB
    
    init(value: Binding<Double>){
        _viewModelB = StateObject(wrappedValue: ViewModelB(value: value))
    }
    
    var body: some View {
        VStack{
            Text("\(viewModelB.value)")
            
            Slider(value: $viewModelB.value, in: 0...1)
        }
    }
}

视图模型:

class ViewModelA: ObservableObject {
    @Published var model = Model()
    
    var value: Double {
        get {
            model.value
        }
        set {
            model.value = newValue
            // perform other checks and operations
        }
    }
}

class ViewModelB: ObservableObject {
    @Binding var value: Double
    
    init(value: Binding<Double>){
        self._value = value
    }
}

型号:

struct Model {
    var value: Double = 0
}

真的,它看起来像是打破了 单一来源或真相 的概念。相反,以下只是工作(ViewModelB 可能需要某些东西,但不是这种情况)

测试 Xcode 12 / iOS 14

仅修改部分:

struct ContentView: View {
    @StateObject var viewModelA = ViewModelA()

    var body: some View {
        VStack{
            ViewA(value: viewModelA.value)

            ViewB(value: $viewModelA.model.value)
        }
    }
}

struct ViewB: View {
    @Binding var value: Double

    var body: some View {
        VStack{
            Text("\(value)")

            Slider(value: $value, in: 0...1)
        }
    }
}

If you only look where you can't go, you might just miss the riches below

通过绑定共享来破坏单一事实来源和破坏 @StateObject 的本地(私有)属性 是两个你不能去的地方。

@EnvironmentObject 或更一般地说,视图之间的“共享对象”概念是下面的财富。

这是一个没有 MVVM 废话的例子:

import SwiftUI

final class EnvState: ObservableObject {@Published var value: Double = 0 }

struct ContentView: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        VStack{
            ViewA()
    
            ViewB()
        }
    }
}

struct ViewA: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        Text("\(eos.value)").padding()
    }
}

struct ViewB: View {
    @EnvironmentObject var eos: EnvState 

    var body: some View {
        VStack{
            Text("\(eos.value)")
        
            Slider(value: $eos.value, in: 0...1)
        }
    }
}

这不是更容易阅读、更清晰、更不容易出错、开销更少,并且不会严重违反基本编码原则吗?

MVVM 不考虑值类型。 Swift 引入值类型的原因是你不会传递共享可变引用并产生各种错误。

然而,MVVM 开发人员做的第一件事是为每个视图引入共享可变引用并通过绑定传递引用...

现在回答你的问题:

the only options I see are either using only one ViewModel per Model, or having to pass the Model (or it's properties) between ViewModels through Binding

另一种选择是放弃 MVVM,摆脱所有视图模型,并改用 @EnvironmentObject

或者如果您不想删除 MVVM,请传递 @ObservedObject(您的视图模型是引用类型)而不是 @Binding

例如;

struct ContentView: View {
    @ObservedObject var viewModelA = ViewModelA()

    var body: some View {
        VStack{
            ViewA(value: viewModelA)

            ViewB(value: viewModelA)
        }
    }
}

附带说明一下,“不要直接从视图访问模型”有什么意义?

当你的模型是值类型时,它没有任何意义。

尤其是当您像聚会中的 cookie 一样传递视图模型引用以便每个人都可以拥有它时。