【问题标题】:Data communication between 2 ObservableObjects2 个 ObservableObjects 之间的数据通信
【发布时间】:2023-09-13 22:06:01
【问题描述】:

我有 2 个独立的 ObservableObjects,分别称为 ViewModel1ViewModel2

ViewModel2 有一个字符串数组:

@Published var strings: [String] = [].

每当修改该数组时,我都希望通知ViewModel1

实现这一目标的推荐方法是什么?

【问题讨论】:

  • ViewModel1 可以订阅 strings 中的更改,例如viewModel2.$strings.sink{...}
  • 可能有很多,您能否更具体地了解您的上下文?
  • @Asperi 不确定我可以在不详细说明的情况下说得更具体。就像我写的那样,它们是两个独立的类,负责处理非常不同的职责。但我需要在第二堂课发生变化时通知第一堂课。
  • 如果你想让它们保持独立,请使用 NotificationCenter
  • @Asperi 我认为 NotificationCenter 不太合适,因为只有 ViewModel1 对更改感兴趣。我所说的独立的意思是类有不同的职责。

标签: swift swiftui combine observableobject data-communication


【解决方案1】:

显然,对此有许多潜在的解决方案,例如前面提到的NotificationCenter 和单例想法。

对我来说,这似乎是一个组合相当有用的场景:

import SwiftUI
import Combine

class ViewModel1 : ObservableObject {
    var cancellable : AnyCancellable?
    
    func connect(_ publisher: AnyPublisher<[String],Never>) {
        cancellable = publisher.sink(receiveValue: { (newStrings) in
            print(newStrings)
        })
    }
}

class ViewModel2 : ObservableObject {
    @Published var strings: [String] = []
}


struct ContentView : View {
    @ObservedObject private var vm1 = ViewModel1()
    @ObservedObject private var vm2 = ViewModel2()
    
    var body: some View {
        VStack {
            Button("add item") {
                vm2.strings.append("\(UUID().uuidString)")
            }
            
            ChildView(connect: vm1.connect)
            
        }.onAppear {
            vm1.connect(vm2.$strings.eraseToAnyPublisher())
        }
    }
}

struct ChildView : View {
    var connect : (AnyPublisher<[String],Never>) -> Void
    @ObservedObject private var vm2 = ViewModel2()
    
    var body: some View {
        Button("Connect child publisher") {
            connect(vm2.$strings.eraseToAnyPublisher())
            vm2.strings = ["Other strings","From child view"]
        }
    }
}

要对此进行测试,首先尝试按下“添加项目”按钮 - 您将在控制台中看到 ViewModel1 接收到新值。

然后,尝试Connect child publisher 按钮——现在,初始连接被取消,并为ViewModel2 的子迭代创建一个新连接。

为了使这个场景起作用,你总是必须引用ViewModel1ViewModel2,或者至少引用connect 方法,正如我在ChildView 中演示的那样。你可以很容易地通过依赖注入甚至通过EnvironmentObject

ViewModel1 也可以更改为,而不是拥有 1 个连接,通过将 cancellable 设置为 Set&lt;AnyCancellable&gt; 并在每次需要一个 -> 多个场景时添加一个连接来拥有多个。

使用AnyPublisher 可以将等式两边都有特定类型的想法解耦,因此将ViewModel4 连接到ViewModel1 等同样容易。

【讨论】:

  • 谢谢。这真的很简洁,我一直在寻找一种合理的现代方法来连接两个 @StateObject 对象以传递特定的状态片段,这就是事实。我发现@Published 充当CurrentValueSubject 的一个巧妙之处,因此当您创建连接时,您还将获得初始状态(这是我需要的),以及任何未来的更改。
【解决方案2】:

我遇到了同样的问题,我发现这种方法效果很好,只是使用了引用类型的思想,并像使用共享类型一样利用类!

import SwiftUI

struct ContentView: View {
    
    @StateObject var viewModel2: ViewModel2 = ViewModel2.shared
    @State var index: Int = Int()
    
    var body: some View {

        Button("update strings array of ViewModel2") {

            viewModel2.strings.append("Hello" + index.description)
            index += 1
        }

        
    }
}


class ViewModel1: ObservableObject {
    
    static let shared: ViewModel1 = ViewModel1()
    
    @Published var onReceiveViewModel2: Bool = Bool() {
        
        didSet {
        
            print("strings array of ViewModel2 got an update!")
            print("new update is:", ViewModel2.shared.strings)

        }
        
    }
    
}


class ViewModel2: ObservableObject {
    
    static let shared: ViewModel2 = ViewModel2()
    
    @Published var strings: [String] = [String]() {
        
        didSet { ViewModel1.shared.onReceiveViewModel2.toggle() }
        
    }
    
}

【讨论】: