这是this question的抽象版本。在应用程序中,这些嵌套的Observable Objects实际上是在视图中使用的(因此,我宁愿使用Observable Objects而不是直接的Publishers)。但是,我希望能够简单地订阅视图模型以对其进行测试。该协议在那里,所以我可以在测试中模拟嵌套。
这是基本设置:
protocol NestedProtocol: AnyObject {
var string: String { get set }
}
class Nested: ObservableObject, NestedProtocol {
@Published var string = ""
}
class Parent: ObservableObject {
@Published var nested: NestedProtocol
init(nested: NestedProtocol) {
self.nested = nested
}
}
var sinkHole = Set<AnyCancellable>()
let nested = Nested()
let parent = Parent(nested: nested)
parent.$nested.sink { newValue in
print("NEW VALUE \(newValue.string)")
}.store(in: &sinkHole)
然后此命令
nested.string = "foo1"
输出“ NEW VALUE”,它应作为nested
的初始值。我希望它输出“ NEW VALUE foo1”。 (TIL发布的变量似乎是当前值的发布者。)
nested.string = "foo1"
parent.nested = nested
我会得到“ NEW VALUE foo1”,但这太臭了。
protocol NestedProtocol: ObservableObject {
var string: String { get set }
}
class Nested<T>: ObservableObject where T: NestedProtocol {
...
但是在现实生活中,我想嵌套声明一些静态常量,这在泛型类型中是不允许的。所以那行不通。
Parent
init() {
nested.objectWillChange.sink { [weak self] (_) in
self?.objectWillChange.send()
}.store(in: sinkHole)
}
Nested
init() {
string.sink { [weak self] (_) in
self?.objectWillChange.send()
}.store(in: sinkHole)
}
没有骰子。这些方法被调用了,但外层接收器仍只是返回“ NEW VALUE”
parent.nested.string = "foo1"
所以现在我要修改父级,这应该可以,对吗?不对。
答案 0 :(得分:0)
这花了我几个小时,在尝试以各种方式修改协议时偶然发现了它。
第1课:
protocol NestedProtocol: AnyObject {
var string: String { get set }
}
应该是
protocol NestedProtocol {
var string: String { get set }
}
为什么?我不确定。显然,如果父级不能假定已发布的对象是一个类,那么它会更密切地监视对其的修改?我的直觉告诉了我完全相反的一面,但是它表明了我有多信任我的直觉。
第2课: 确实,我的第四个想法是正确的,您需要在嵌套对象修改中命名父对象:
nested.string = "foo1"
应该是
parent.nested.string = "foo1"
同样,它们都是类,所以这与我的理解有些冲突,但是我不知道@Published下发生的所有魔术。
最终的完整版本如下:
protocol NestedProtocol {
var string: String { get set }
}
class Nested: ObservableObject, NestedProtocol {
@Published var string = ""
}
class Parent: ObservableObject {
@Published var nested: NestedProtocol
init(nested: NestedProtocol) {
self.nested = nested
}
}
var sinkHole = Set<AnyCancellable>()
let nested = Nested()
let parent = Parent(nested: nested)
parent.$nested.sink { newValue in
print("NEW VALUE \(newValue.string)")
}.store(in: &sinkHole)
和
nested.string = "foo1"
parent.nested.string = "foo2"
返回 “新值” “新值foo2”
答案 1 :(得分:0)
这里有很多东西可以打开。
首先,您可能会知道,如果属性是值类型,例如struct
或String
,然后将其标记为@Published
即可:
class Outer {
@Published var str: String = "default"
}
let outer = Outer()
outer.$str.sink { print($0) }
outer.str = "changed"
将输出:
default
changed
但是,您的问题是关于嵌套的可观察对象(它是引用类型)。因此,以上内容不适用于引用类型。
但是在您的示例中,您使用的是协议作为存在的协议(即代替最终实例),并且正如您指出的那样,它没有从AnyObject
继承而来,实际上它的行为就像一个值类型:
protocol InnerProtocol {
var str: String { get set }
}
class Inner: InnerProtocol {
@Published var str: String = "default"
}
class Outer {
@Published var inner: InnerProtocol
init(_ inner: InnerProtocol) { self.inner = inner }
}
let inner = Inner()
let outer = Outer(inner)
outer.$inner.sink { print($0.str) }
outer.inner.str = "changed"
这还将输出:
default
changed
看起来像您想要的,但是实际上它并没有真正“观察”嵌套对象中的任何更改。当您执行outer.inner.str
时,它具有值类型的语义,因此就像您重新分配了.inner
属性一样。但是,如果您真的有兴趣观察对象本身的变化,那么这种方法将根本行不通。例如:
nested.str = "inner changed"
不会导致输出。如果inner
对象更改了自己的属性,例如:
init() {
DisplatchQueue.main.asyncAfter(.now() + 1) {
self.str = "async changed"
}
}
因此,目前尚不清楚您到底要达到什么目标。如果要观察引用类型属性,则需要直接观察它。
class Inner: ObservableObject {
@Published var str: String
//...
}
class Outer: ObservableObject {
var inner: Inner
//...
}
//...
outer.inner.$str.sink { ... }
// or
outer.inner.objectWillChange.sink { ... }
如果您坚持的话,也可以使用协议来实现这一点:
protocol InnerProtocol: ObservableObject {
var str: String { get set }
}
class Inner: InnerProtocol {
@Published var str: String = "default"
}
class Outer<T: InnerProtocol>: ObservableObject {
var inner: T
init(_ inner: T) { self.inner = inner }
}
let inner = Inner()
let outer = Outer(inner)
outer.inner.$str.sink { ... }
inner.str = "changed"