本文主要介绍一下Subject
,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
方法,外部调用者可以通过这些方法来主动地发布 output
值、failure
事件、 finished
事件以及subscription
。
Combine
内置提供了两种常用的 Subject
类型,分别是 PassthroughSubject
和 CurrentValueSubject
,下面来分别看一下。
PassthroughSubject
PassthroughSubject
是Combine
框架中的一种Subject
具体类型,它不持有任何值,将自己接收到的任何值简单的传递给下游的Subscriber
。
当我们要创建一个PassthroughSubject
时,需要指定要发送的值的类型,然后使用send
方法发送,任何的Subscriber
都会收到这个值。因为它本身不持有值,所以如果下游没有Subscriber
,那么这个值将废弃了。
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
事件,sink
方法中对应的输出都正常。但是在添加sink
方法之前发送的send("1")
没有任何输出,就像上面说的,发送的时候还没有任何订阅者,发送的值就直接抛弃了。
输出结果:
---> value is: 2
---> Finished
作为Subject
的具体实现,PassthroughSubject
提供了一种方便的方法,使现有的命令式代码适应Combine
模型。
CurrentValueSubject
Currentvaluessubject
是Combine
框架中的一种Subject
具体类型。它可以保存单个值,并在设置新值时向任何订阅者发布新值。
Currentvaluessubject
在初始化的时候需要设置一个初始值。
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
中实例化了一个CurrentValueSubject
,并添加了subscriber
,在SwiftUI
界面添加了三个按钮,用来发送数据。
在发送数据的时候,可以通过send
方法,也可以通过直接设置value
的方法,效果都是一样的。
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
后(不管是finished
还是error
),Subject
都不会再发送任何新值。
就上面的CurrentValueSubject
为例,在发送一个value
之后,就发送finished
,然后在发送value
就无效了。
PassthroughSubject
亦是如此。所以当使用PassthroughSubject
或CurrentValueSubject
时,重要的是要考虑生命周期,并在明显没有任何值发送时关闭Subject
。
PassthroughSubject与CurrentValueSubject区别
首先这两个都是Subject
的具体实现,都可以根据需要异步地无限地发出事件。这两个Subject
的用法都比较简单,都作为Publisher
发布数据,不过却别还是有的。
PassthroughSubject
没有初始值,也不需要持有最近一次发布的值。
CurrentValueSubject
可以为Publisher
提供初始值,并通过更新 value
属性自动发出事件。
网上有一个较为恰当的比喻:
PassthroughSubject
就像一个门铃按钮。当有人按门铃时,只有当你在家时才会通知你。
CurrentValueSubject
就像一个电灯开关。当你不在的时候灯是开着的,当你回家的时候你仍然会注意到它是开着的。
写在最后
本文主要介绍了PassthroughSubject
与CurrentValueSubject
的概念、使用以及一些区别,希望大家通过本文能对这两个Subject有个初步的了解和使用,文中如果有不对的地方,还望大家指正。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。