2个ObservableObjects之间的数据通信
Data communication between 2 ObservableObjects
我有 2 个独立的 ObservableObject
,分别叫做 ViewModel1
和 ViewModel2
。
ViewModel2
有一个字符串数组:
@Published var strings: [String] = []
.
每当修改该数组时,我希望 ViewModel1
得到通知。
实现此目标的推荐方法是什么?
我遇到了同样的问题,我发现这种方法效果很好,只是使用引用类型的想法并利用 class 就像使用共享类型一样!
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() }
}
}
显然,有许多可能的解决方案,例如前面提到的 NotificationCenter
和单例想法。
对我来说,这似乎是 Combine 非常有用的场景:
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
的子迭代创建一个新连接。
为了让这个场景起作用,你总是必须引用 ViewModel1
和 ViewModel2
,或者至少引用 connect
方法,正如我在ChildView
。您可以通过依赖注入甚至通过 EnvironmentObject
轻松传递它
ViewModel1
也可以更改为而不是有 1 个连接,通过使 cancellable
成为 Set<AnyCancellable>
并在每次需要一个 -> 多个时添加一个连接来获得多个连接场景。
使用 AnyPublisher
解耦了等式两边具有特定类型的想法,因此将 ViewModel4
连接到 ViewModel1
等也很容易
我有 2 个独立的 ObservableObject
,分别叫做 ViewModel1
和 ViewModel2
。
ViewModel2
有一个字符串数组:
@Published var strings: [String] = []
.
每当修改该数组时,我希望 ViewModel1
得到通知。
实现此目标的推荐方法是什么?
我遇到了同样的问题,我发现这种方法效果很好,只是使用引用类型的想法并利用 class 就像使用共享类型一样!
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() }
}
}
显然,有许多可能的解决方案,例如前面提到的 NotificationCenter
和单例想法。
对我来说,这似乎是 Combine 非常有用的场景:
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
的子迭代创建一个新连接。
为了让这个场景起作用,你总是必须引用 ViewModel1
和 ViewModel2
,或者至少引用 connect
方法,正如我在ChildView
。您可以通过依赖注入甚至通过 EnvironmentObject
ViewModel1
也可以更改为而不是有 1 个连接,通过使 cancellable
成为 Set<AnyCancellable>
并在每次需要一个 -> 多个时添加一个连接来获得多个连接场景。
使用 AnyPublisher
解耦了等式两边具有特定类型的想法,因此将 ViewModel4
连接到 ViewModel1
等也很容易