本文主要介绍一下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 inswitch completion {case .finished:print("---> Finished")case .failure(let error):print("---> Error: \(error.localizedDescription)")}} receiveValue: { value inprint("---> 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 inswitch completion {case .finished:print("---> Finished")case .failure(let error):print("---> Error: \(error.localizedDescription)")}} receiveValue: { value inprint("---> 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 inswitch completion {case .finished:print("---> Finished")case .failure(let error):print("---> Error: \(error.localizedDescription)")}} receiveValue: { value inprint("---> 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有个初步的了解和使用,文中如果有不对的地方,还望大家指正。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。