CurrentValueSubject 和 @Published 之间的区别
Posted
技术标签:
【中文标题】CurrentValueSubject 和 @Published 之间的区别【英文标题】:Difference between CurrentValueSubject and @Published 【发布时间】:2020-02-28 18:07:58 【问题描述】:所以我正在研究 combine 并提出了这个问题。
使用CurrentValueSubject
(并使用currentValueSubject.value
设置其值)或使用@Published var
并使用$
访问其发布者之间有什么真正的区别吗?我的意思是我知道有人返回 Subject
而不是 Publisher
,但我能发现的唯一真正区别是 CurrentValueSubject
更有用,因为您可以在协议上声明它。
我真的不明白如果我们可以使用PassthroughSubject
,@Published
会有什么用处,我在这里遗漏了什么吗?
请注意,这是使用 UIKit,它可能对 SwiftUI 有其他用途。
谢谢。
【问题讨论】:
【参考方案1】:CurrentValueSubject
是一个值,一个发布者和一个订阅者合而为一。
遗憾的是,在 ObservableObject 中使用时它不会触发 objectWillChange.send()
。
您可以指定错误类型。
@Published
是一个属性包装器,因此:
@Published
在 ObservableObject 中使用时会自动触发 objectWillChange.send()
。
如果您尝试从后台队列发布到 @Published
包装属性,Xcode 将发出警告。可能是因为必须从主线程调用objectWillChange.send()
。
其发布者的错误类型为Never
我对@987654329@ 最大的不满是它不能充当订阅者,并且与当前值主题相比,设置组合管道需要额外的管道。
我们可以在协议中声明@Published
属性。不是很漂亮...
protocol TestProtocol
var isEnabled: Bool get
var isEnabledPublished: Published<Bool> get
var isEnabledPublisher: Published<Bool>.Publisher get
class Test: ObservableObject, TestProtocol
@Published var isEnabled: Bool = false
var isEnabledPublished: Published<Bool> _isEnabled
var isEnabledPublisher: Published<Bool>.Publisher $isEnabled
【讨论】:
你不必使用@Published 你可以重新定义 var objectWillChange 成为你喜欢的任何发布者。【参考方案2】:@Published 只是一种更简洁地使用 CurrentValueSubject 的快速方法。当我调试我的一个应用程序并查看 $paramName 返回的类型时,它实际上只是一个 CurrentValueSubject:
po self.$books
▿ Publisher
▿ subject : <CurrentValueSubject<Array<Book>, Never>: 0x6000034b8910>
我想使用 CurrentValueSubject 而不是 @Published 的一个好处可能是允许您使用错误类型?
注意:尽管我是 CurrentValueSubject现在我永远不会依赖这个假设。
【讨论】:
谢谢!另一个好处是能够在协议上声明它:) @Mykod 有趣,我没有考虑过协议,是的,像@Published
这样的包装器是不允许的:D
这是可能的,但它很丑……看我的回答。【参考方案3】:
我发现自己又回到了这篇文章,所以我想对@Published
和CurrentValueSubject
之间的区别添加一些额外的见解。
可以在@Published
的文档中找到一个主要区别:
当属性发生变化时,会在属性的 willSet 块中进行发布,这意味着订阅者会在新值被实际设置在属性上之前收到它。
此外,Swift Forums 上的对话请注意,@Published
旨在与 SwiftUI 一起使用。
关于@Published
在其属性的willSet
块中发布,请考虑以下示例:
class PublishedModel
@Published var number: Int = 0
let pModel = PublishedModel()
pModel.$number.sink number in
print("Closure: \(number)")
print("Object: \(pModel.number) [read via closure]")
pModel.number = 1
print("Object: \(pModel.number) [read after assignment]")
这会产生以下输出:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 0 [read via closure]
Object: 1 [read after assignment]
将此与另一个示例进行对比,在该示例中,除了将 @Published
替换为 CurrentValueSubject
之外,我们保持相同:
class CurrentValueSubjectModel
var number: CurrentValueSubject<Int, Never> = .init(0)
let cvsModel = CurrentValueSubjectModel()
cvsModel.number.sink number in
print("Closure: \(number)")
print("Object: \(cvsModel.number.value) [read via closure]")
cvsModel.number.send(1)
print("Object: \(cvsModel.number.value) [read after assignment]")
输出:
Closure: 0
Object: 0 [read via closure]
Closure: 1
Object: 1 [read via closure] // <— Here is the difference
Object: 1 [read after assignment]
将number
更新为1 后,读取对象的CurrentValueSubject
的值属性在 闭包打印新值而不是旧值,就像@Published
一样强>。
总之,在您的ObservableObjects
中使用@Published
来获取您的SwiftUI 视图。如果您希望创建某种模型对象,其实例属性包含当前值并且也在设置后发布它的更改,请使用CurrentValueSubject
。
【讨论】:
【参考方案4】:@Published
的一个优点是它可以充当私有可变、公共不可变的 CurrrentValueSubject。
比较:
@Published private(set) var text = "someText"
与:
let text = CurrentValueSubject<String, Never>("someText")
在设计 API 时,您通常希望允许客户端读取当前值并订阅更新,但阻止它们直接设置值。
【讨论】:
以上是关于CurrentValueSubject 和 @Published 之间的区别的主要内容,如果未能解决你的问题,请参考以下文章
从另一个 CurrentValueSubject 创建一个 CurrentValueSubject
Combine - 测试 CurrentValueSubject 是不是有订阅者
如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?
带有 CurrentValueSubject 绑定的 TextField 上的“Binding<String> 操作尝试每帧更新多次”