SwiftUI Combine 的 PassthroughSubject
PassthroughSubject,本质是把一个数据,变为Publisher
以一个数组publisher为例
let fibonacciPublisher = [0,1,1,2,3,5].publisher
查看fibonacciPublisher(option+鼠标点击),他是Publishers.Sequence<[Int], Never>的类型,是将Sequence转换后的publisher,我们可以用sink的方法,订阅并接收他发布的数据
fibonacciPublisher
.sink(receiveCompletion: { completion in
switch(completion) {
case .finished:
print("finished")
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { value in
print(value)
})
打印结果
0
1
1
2
3
5
finished
结果表明了两个信息:
1、Sequence的数据逐个被发布并接收
2、数据全部发布完毕后,结束了这次订阅
实际应用中,我们很少会用到这种Publisher,我们用到比较多的是PassthroughtSubject 和 CurrentValueSubject
PassthroughSubject
我们用PassthroughSubject发布一个同样的数组
let pubFibonacci = PassthroughSubject<[Int], Never>()
pubFibonacci
.sink(receiveCompletion: { completion in
switch(completion) {
case .finished:
print("finished")
case .failure(let error):
print(error.localizedDescription)
}
}, receiveValue: { value in
print(value)
})
PassthroughtSubject包含两个参数,第一个是发布的数据类型,这里我们要发布整型数组,所以填写[Int],第二个是错误类型,我们默认他永远不会发布错误,所以填写 Never
运行观察打印,发现没有任何输出信息。因为PassthroughSubject并没有发布任何信息,他和Sequence的publisher不同,PassthrougthSubject需要手动发布数据,这个特性也使得他可以灵活的运用到我们的App工程中。
加入发送代码
pubFibonacci.send([0,1,1,2,3,5])
这时候打印信息如下:
[0, 1, 1, 2, 3, 5]
可以看到和Sequence不同,PassthroughSubject一次性发布了所有数据,同时,没有结束订阅过程。
再次调用send发布另一个数组数据
pubFibonacci.send([7,9,13,18,21])
打印信息如下:
[7, 9, 13, 18, 21]
订阅的过程仍在继续。
如果想要订阅结束,我们需要手动发布一个结束的指令
pubFibonacci.send(completion: .finished)
打印信息如下:
finished
此后再用send发布数据,也不会有打印了,因为订阅已经结束了。
注意,我们开始声明的PassthroughSubject的错误类型是Never,默认不会发布错误。实际应用中,大多数情况下会有错误的情况,比如网络请求的错误等,我们将其改为Error```
// MARK: - 声明一个符合Error协议的自定义Error
enum Error: Swift.Error {
case someError
}
let pubFibonacci = PassthroughSubject<[Int], MyError>()
pubFibonacci
.sink(receiveCompletion: { completion in
switch(completion) {
case .finished:
print("finished")
case .failure(let error):
print("Received error: \(error)")
}
}, receiveValue: { value in
print(value)
})
// 正常发布
pubFibonacci.send([0,1,1,2,3,5])
// 发布错误
pubFibonacci.send(completion: .failure(.someError))
// 正常发布
pubFibonacci.send([7,9,13,18,21])
这时我们的打印信息
[0, 1, 1, 2, 3, 5]
Received error: someError
订阅者接收到了错误,订阅终止,后续发布的数据也无法被接收到了。
实战应用
上面我们介绍了PassthroughSubject的基本概念和功能,我们来看实际项目中如何使用。
在实际项目中,PassthroughSubject,或者可以说所有的Publisher,大多数情况,都是用来
将变量,转化为publisher,
这点非常重要。转化为publisher后,我们可以接收,或者说监听变量的变化情况,来进行即时的处理,也就是我们常说的异步。
我们新建一个主View,显示水果列表和导航栏的右侧按钮
import SwiftUI
import Combine
struct PassthroughSubjectView: View {
@StateObject var vm = ViewModel()
@State var isSheet: Bool = false
@State var newFruit: String = ""
var body: some View {
NavigationView {
VStack {
List(vm.listText, id: \.self) { t in
Text(t)
}
}
.navigationBarTitle("我喜欢的水果")
.navigationBarItems(trailing: Button(action: {
self.isSheet.toggle()
}, label: {
Image(systemName: "plus.circle")
}))
.sheet(isPresented: $isSheet, content: {
SheetView()
})
}
}
}
extension PassthroughSubjectView {
class ViewModel: ObservableObject {
@Published var searchText: String = ""
@Published var listText: [String] = [
"Banana", "Apple", "Cherry", "Watermelon"
]
}
}
再建立一个子View,用来底部弹出显示,子View中包含一个水果输入框和一个按钮,点击按钮我们想要将新输入的水果,显示到我们的主View中。
extension PassthroughSubjectView {
func SheetView() -> some View {
VStack {
TextField("Add fruit...", text: $newFruit)
.padding()
.background(Color.gray.opacity(0.2))
.padding()
Button(action: {
isSheet.toggle()
}, label: {
Text("Add")
})
}
}
}
现在我们用Combine的PassthroughSubject实现这一功能
在ViewModel中,定义一个publisher
var addFruit = PassthroughSubject<String, Never>()
在初始化时订阅这个publisher
var cancellable: AnyCancellable?
init() {
cancellable = addFruit
.print("_add fruit_")
.sink { [weak self] fruit in
self?.listText.append(fruit)
}
}
在我们子View的按钮“Add”被点击时,我们将用户新输入的内容,发布给订阅者。
Button(action: {
// 发布订阅值
vm.addFruit.send(newFruit)
isSheet.toggle()
}, label: {
Text("Add")
})
此时,ViewModel中的addFruit.sink开始接收数据,并将数据增加到listText数组中

控制台调试信息如下
_add fruit_: receive subscription: (PassthroughSubject)
_add fruit_: request unlimited
_add fruit_: receive value: (Grace)
这就是一个PassthroughSubject在实际应用中的一个简单操作。
我们将子View中输入框内容的字符串,包装成,或者说转化为publisher,让Combine可以工作,可以在ViewModel中接收他发布的内容,然后进行处理。
这是一个异步过程,也是响应式编程的思想。
