SwiftUI - @Binding 到访问 ObservableObject 属性内的值的计算属性会复制变量吗?

Posted

技术标签:

【中文标题】SwiftUI - @Binding 到访问 ObservableObject 属性内的值的计算属性会复制变量吗?【英文标题】:SwiftUI - @Binding to a computed property which accesses value inside ObservableObject property duplicates the variable? 【发布时间】:2020-07-16 13:34:16 【问题描述】:

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

ViewA - 显示存储在 ObservableObject ViewModel 中的值; ViewB - 显示相同的值并具有一个更改该值的滑块,该滑块使用绑定传递给视图。

在 ViewModelA 内部,我有一个计算属性,它既可以避免视图直接访问模型,又可以在模型(正在显示的那个)内部的值发生更改时执行一些其他操作。 我还使用 Binding 将该计算值传递给 ViewModelB,它充当 ViewB 的 StateObject。但是,当拖动 Slider 以更改该值时,ViewA 上的值会更改,但 ViewB 上的值不会更改,并且滑块本身不会滑动。正如预期的那样,在调试时,Binding 中的 WrappedValue 没有改变。 但是变化是如何向上传播的(我想是通过绑定的设置器)而不是向下传播回 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

【问题讨论】:

您通常不会在视图模型中使用绑定。绑定用于视图的值类型属性。 是的,我从未见过有人在 ViewModels 中使用 Binding,但如果我坚持使用 mvvm,其中 View 和 Model 之间的连接应该通过 ViewModel 进行,所以如果我想更改一个值存储在模型中,其中包含在整个应用程序中使用的信息,例如滑块,我看到的唯一选项是每个模型仅使用一个 ViewModel,或者必须通过绑定在 ViewModel 之间传递模型(或其属性)(并且我不明白为什么不)。还是我错过了什么? 绑定是为了别的东西,所以你没有正确使用它。最佳实践是将视图的角色减少到单个作业。如果您需要在多个地方更改单个模型,可以使用@EnvironmentObject 将其“注入”,也可以使用其视图模型实例化视图。 【参考方案1】:

如果你只看你不能去的地方,你可能会错过下面的财富

打破单一事实来源,以及通过绑定共享 @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 开发人员要做的第一件事是为每个视图引入共享的可变引用,并通过绑定传递引用...

现在回答你的问题:

我看到的唯一选择是每个模型只使用一个 ViewModel,或者必须通过绑定在 ViewModel 之间传递模型(或其属性)

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

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

例如;

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

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

            ViewB(value: viewModelA)
        
    

顺便说一句,“不要直接从视图访问模型”有什么意义?

当你的模型是值类型时,它是零意义的。

尤其是当您在聚会中传递视图模型引用时,例如 cookie,以便每个人都可以拥有它。

【讨论】:

是的,我同意你的看法。它只是你在网上寻找 SwiftUI 的应用程序架构/设计模式的所有地方,每个人似乎都认为 mvvm 是要走的路,但在搞砸了几个月后,我开始相信它实际上并不适合应用程序比教程/文章中的示例复杂一点。从设计的角度考虑,应用程序的 UI(尤其是移动设备)应该尽可能简单,这有点道理,但正如你所说,由于 Swift 的值类型,当应用程序有点时移动信息变得很复杂更复杂。 您的 EnvState 是 ViewModel 的一个示例,您刚刚更改了层次结构和注入 VM 的方式。所以不确定“MVVM 废话”是什么【参考方案2】:

真的,它看起来像破碎的单一来源或真实概念。相反,以下只是工作(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)
        
    

【讨论】:

是的,绝对打破了单一的事实来源,我只是不知道为什么。是的,我从未见过有人在 ViewModels 中使用 Binding,但如果我坚持使用 mvvm,其中 View 和 Model 之间的连接应该通过 ViewModel 进行,所以如果我想使用使用的信息更改存储在 Model 中的值在整个应用程序中,例如,一个滑块,我看到的唯一选项是每个模型只使用一个 ViewModel,或者必须通过绑定在 ViewModel 之间传递模型(或其属性)(我不明白为什么不这样做) .还是我错过了什么?

以上是关于SwiftUI - @Binding 到访问 ObservableObject 属性内的值的计算属性会复制变量吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Binding 确定 SwiftUI 中的字符串更改

SwiftUI 中的可选绑定

SwiftUI 中的 @Binding 和 ForEach

SwiftUI Xcode 11 beta 7 @Binding for collections 正在打破预览

带有 @Binding 控件的 SwiftUI 动态列表

如何在 SwiftUI 中使用 inout 而不是 Binding?