Swift Combine — Subject Publishers(PassthroughSubject & CurrentValueSubject)
本文具体介绍一下Subject。其中涉及到的主体也具有 publisher 的属性。其定义如下:
public protocol Subject<Output, Failure> : AnyObject, Publisher {
func send(_ value: Self.Output)
func send(completion: Subscribers.Completion<Self.Failure>)
func send(subscription: any Subscription)
}
根据定义可知,在 Subject 中暴露了三个 send 方法。这些方法被用来触发输出值的发布,并且同时也会触发失败事件和完成事件。订阅机制也会被激活。
Combine内建支持两个典型主体类型:通过式主体和当前值主体。接下来将逐一分析这两种类型。
PassthroughSubject
在Combine框架中定义了一种特殊的Subject类型——PassthroughSubject。这种类型的实例不会携带任何数据,并直接将接收的所有信息传递给其下游的Subscriber。
为了创建一个passthroughsubject, 我们需明确所传递值的数据类型, 然后通过调用send方法将该数据发送出去, 所有订阅该主题的对象都会接收到该数据, 由于自身不携带该数据, 若接收端没有相应的订阅者介入, 则该数据将被弃用。
func testPassthroughSubjectPublisher() {
let publish2 = PassthroughSubject<String, Error>()
publish2.send("1")
publish2
.sink { completion in
switch completion {
case .finished:
print("---> Finished")
case .failure(let error):
print("---> Error: \(error.localizedDescription)")
}
} receiveValue: { value in
print("---> value is: \(value)")
}
.store(in: &cancellable)
publish2.send("2")
publish2.send(completion: .finished)
}
在上述代码中,我们首先对PassthroughSubject类进行了实例化操作,并将其命名为publish2对象。随后借助sink方法将该对象与接收方建立了订阅关系。在完成订阅操作后不久,我们便调用了send方法来触发了一次值为2的事件以及一个finished事件。然而,在我们设置订阅者之前的一次send操作中输入的值"1"并未产生任何响应效果——因为在这一时刻尚未有任何接收方接收到该消息内容就被直接丢弃掉了
输出结果:
---> value is: 2
---> Finished
基于Subject的功能实现,PassthroughSubject被采用为一种便捷的方式,使得现有的命令式编程模式能够有效应对复杂的组合需求.
CurrentValueSubject
Currentvaluessubject属于Combine框架中的一种特定的Subject类型。它用于存储单个的值,并且在其状态发生变化时会通知所有订阅者。
在初始化过程中,默认情况下必须指定一个初始值。
let publisher = CurrentValueSubject<String, Error>("one")
在订阅者进行订阅时会立即发送这个值。下面代码中在初始化CurrentValueSubjectViewModel时则会直接输出“—> value is: one”。
class CurrentValueSubjectViewModel: ObservableObject {
init() {
setUpPublisher()
}
func setUpPublisher() {
let publisher = CurrentValueSubject<String, Error>("one")
let cancelable = publisher
.sink { completion in
switch completion {
case .finished:
print("---> Finished")
case .failure(let error):
print("---> Error: \(error.localizedDescription)")
}
} receiveValue: { value in
print("---> value is: \(value)")
}
}
}
在viewModel生命周期中创建了一个当前值主题实例,并成功地注册了一个订阅器。随后又在SwiftUI界面上布置了三个按钮来实现数据发送功能。
当发送数据时, 可以通过采用 send 方法来实现目标; 或者可以直接设置其属性值为所需的数据以达到相同的效果.
class CurrentValueSubjectViewModel: ObservableObject {
private var cancellable = Set<AnyCancellable>()
let publisher = CurrentValueSubject<String, Error>("one")
init() {
setUpPublisher()
}
func setUpPublisher() {
publisher
.sink { completion in
switch completion {
case .finished:
print("---> Finished")
case .failure(let error):
print("---> Error: \(error.localizedDescription)")
}
} receiveValue: { value in
print("---> value is: \(value)")
}
.store(in: &cancellable)
}
func sendMessage() {
publisher.send("Hello World")
publisher.value = "Swift Combine"
}
func sendError() {
publisher.send(completion: .failure(NetworkError.invalidURL))
}
func sendFinished() {
publisher.send(completion: .finished)
}
}
struct CurrentValueSubjectDemo: View {
@StateObject private var viewModel = CurrentValueSubjectViewModel()
var body: some View {
VStack {
Button("Send Message") {
viewModel.sendMessage()
}
Button("Send Finished") {
viewModel.sendFinished()
}
Button("Send Error") {
viewModel.sendError()
}
}
.buttonStyle(BorderedProminentButtonStyle())
}
}
当点击按钮时,输出如下:

关于Subject生命周期
Subject对象具有生命周期属性,在收到completion响应后(无论其状态是已结束还是出现错误),该Subject对象将不再发送新的数据值。
就上述CurrentValueSubject的情况而言,在此之前依次发出一个值,在此之后发出完成标记,在此之后再次发出值将不再有效

PassthroughSubject同样也是这种情况。因此,在使用PassthroughSubject或CurrentValueSubject时,请务必考虑其生命周期,并确保在没有任何值发送时关闭Subject。
PassthroughSubject与CurrentValueSubject区别
首先这两个都是Subject的具体实例它们都能够根据需求进行异步且无止境的事件发布这两种Subject的操作相对简单并都通过作为Publisher来发布数据
对于 PassthroughSubject, 它无需初始值, 并不需要保持最新发布.
对于 CurrentValueSubject, 它能够为 Publisher赋初值, 并在每次 `value$ attribute发生变化时触发事件.
网上有一个合适的类比:
PassthroughSubject相当于一个门铃按钮。当有人按下门铃时,在您在家的时候就会发出提示音。
CurrentValueSubject相当于一个电灯开关。而当你不在家的时候灯一直是开着的状态,在您回到家的时候仍然会注意到它处于开启状态。
写在最后
本文旨在阐述PassthroughSubject与CurrentValueSubject的基本概念、应用场景及其主要区别。对于初学开发者而言,掌握基本用法是理解这两种Subject的关键所在。我们期待初学开发者在阅读本文后能够对这两个Subject有清晰的认识,并在此基础上熟练运用它们于实际项目中。若有任何疑问或指出不足之处,请随时提出建议以助于我们进一步完善相关内容。
最终希望能为有需求的人提供帮助。如果您觉得有所帮助,请点赞麻烦您点个关注。笔者也会持续努力未来会带来更多优质的内容。
