这部分内容主要是API的实战,也作为实际开发过程中查阅使用
一: 常用类实战演练
常用两个发布者和两个订阅者例子
订阅者1: Sink
/// sink -- 订阅
/// 一个用于订阅请求不限数量的简单订阅者
func skinDemo() {
publisher.sink { value in
print(value)
}.store(in: &cancellables)
}
订阅者2: Assign
主要是通过KeyPath的方式把 发布者发过来的数据,赋值到KeyPath对应的属性中, KeyPath相关的知识这里不再赘述
func assignDemo() {
class Student{
var name: String = ""
}
let student = Student()
up.assign(to: \.name, on: student).store(in: &cancellable)
up.send("王金山")
print(student.name)
}
两个常用发布者:
发布者1 PassthroughSubject
PassthroughSubject 类似于冷信号, 只有先进行了订阅才会收到这个消息 PassthroughSubject 初始化方法中不需要传参数,说明不会收到第一次的信号
class PassthroughDemo {
var cancellables = [AnyCancellable]()
let subject = PassthroughSubject<Int, Never>()
func send1() {
// 还没有订阅者,此时发送了一条消息
subject.send(1)
}
func sink() {
// 此时订阅了消息,但上面发送的消息不会收到,这就是与CurrenValueSubject不一样的地方
subject.sink(receiveCompletion: { state in
switch state {
case .finished:
print("完成")
case .failure(_):
print("失败")
}
}, receiveValue: { value in
print("recevie: \(value)")
})
.store(in: &cancellables)
}
func send2() {
// 此时再次发送了一条消息,这时上面的订阅者将收到一条消息
subject.send(2)
// 当我们发送finish消息后,上面的订阅者将收到一条finish的消息,此后发布者再次发送消息,该订阅者将不再收到任何消息
subject.send(completion: .finished)
// 此时订阅者将不会收到消息3
subject.send(3)
}
}
发布者2: CurrentValueSubject
CurrentValueSubject 只保留最新的值, 相当与热信号, 可以在没有订阅之前发送消息, 调用subject.send(10), 这个方法向publish发送消息,改变publish的初始值,就是 subject.value = 10, 同时后面的所有订阅者都会收到这个第一次发出的初始值
同时如果不调用 subject.send(10), 则后面的所有订阅者都会收到 CurrentValueSubject<Int, Never>(1) 这个1, CurrentValueSubject此类不管你调用send或者value方法 第一次都会收到初始值
class CurrentSubjectDemo {
var cancellables = [AnyCancellable]()
let subject = CurrentValueSubject<Int, Never>(1)
func demo() {
// subject在每次被订阅的时候,都会将当前的value发送给订阅者
subject.sink(receiveCompletion: { state in
switch state {
case .finished:
print("完成")
case .failure(_):
print("失败")
}
}, receiveValue: { value in
print("recevie 第一次: \(value)")
})
.store(in: &cancellables)
// 订阅第二次
subject.sink { temp in
print(temp)
}.store(in: &cancellables)
subject.filter { temp in
return temp == 3
}.sink { temp in
print(temp)
}.store(in: &cancellables)
}
func send() {
// 当value发生改变时,subject也会将当前变更的value值发送给订阅者
subject.value = 2
// 实现Subject协议的发布者,提供以下方法向发布者主动发送消息
subject.send(3)
// 此时上面的send方法同时改变了subject的value属性值
print("current.value: \(subject.value)")
// 你也可以向订阅者发送一个完成消息,可以是finish,也可以是failure,此后,发布者将不会像订阅者发送任何消息
subject.send(completion: .finished)
// 此处,虽然改变了value的值,但是由于发布者发送了finish,订阅者将不会收到任何消息
subject.value = 4
// 这里同样如此,订阅者将不再收到消息,需要注意的是,此时的value将不会改变
subject.send(5)
// 虽然调用了send(5),但由于订阅已经完成,send调用无效,value值也就不会更新
print("current.value: \(subject.value)")
}
}
二: 内置发布者
Deferred
// 注意:这里的cancellables必须被有效持有,因为下面的Future是异步的,所以在Future发送消息之前,AnyCancellable必须保持不被销毁状态,这里我定义成了一个全局变量
// 这里我们指定Deferred闭包返回的是Future类型的发布者,这里常被用于接口调用
// Deferred 有点类似于command , 注意传参方式
// MARK: - Deferred
let publisherDeferred = Deferred<Future<String, Error>> {
// 在这个闭包中,创建一个Future的发布者,并返回
return Future<String, Error>{ promise in
// 这里处理一个延迟的异步请求
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
// 返回数据
promise(.success("{'result': 0, 'data': {...}}"))
// 或者返回一个错误
// promise(.failure(.HandlerError("请求失败")))
}
}
}
extension CombineAllDemo {
func deferredDemo() {
//这里开始第一个订阅者订阅Deferred发布者,此时Deferred会调用自己初始化时的闭包,创建一个发布者
publisherDeferred.sink { _ in
} receiveValue: { str in
print("返回数据: \(str)")
}.store(in: &cancellables)
// 这里开始第二个订阅者订阅Deferred发布者,此时Deferred会调用自己初始化时的闭包,创建一个发布者
publisherDeferred.sink { state in
print(state)
} receiveValue: { str in
print("返回数据: \(str)")
}.store(in: &cancellables)
}
// MARK: - 模拟网络请求
func deferredHttp(ip: String) -> Deferred<Future<String, Error>> {
return Deferred<Future<String, Error>> {
// 在这个闭包中,创建一个Future的发布者,并返回
return Future<String, Error>{ promise in
// 这里处理一个延迟的异步请求
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
// 返回数据
promise(.success("返回传入的参数\(ip)"))
// 或者返回一个错误
// promise(.failure(.HandlerError("请求失败")))
}
}
}
}
func bindDeferredDemo() {
deferredHttp(ip: "http://192.168.0.1").sink { _ in
} receiveValue: { res in
print(res)
}.store(in: &cancellables)
}
}
Future
最终只会生成单个值,然后发送完成或失败的发布者。即:该发布者只能发送一次消息
let publisherFuture = Future<String, Error>{ promise in
// 发送了第一条消息
promise(.success("同志们好!"))
// 发送了第二条消息(由于该发布者只能发送一次消息,所以这个消息不会有人收到)
promise(.success("同志们辛苦了!"))
}
func futureDemo() {
// 第一个订阅者订阅了消息
publisherFuture.sink { state in
} receiveValue: { msg in
print("receive1: \(msg)")
}
.store(in: &cancellables)
DispatchQueue.global().asyncAfter(deadline: .now()+3) {
// 3秒后再次订阅了该发布者,发布者会将之前发布的消息再次发给这个订阅者
self.publisherFuture.sink { state in
} receiveValue: { msg in
print("receive2: \(msg)")
}
.store(in: &self.cancellables)
}
}
Just
仅仅向每个订阅者发出一次消息,然后发送完成的发布者,这一点与Future很想,但他们的区别在于,Future可以决定何时向订阅者发布消息,而Just实例化时就决定了要发送的值,并且这个值不能改变
func justDemo() {
let publisher = Just(1)
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}
.store(in: &cancellables)
// 如果Just的值实现了Equatable协议,那么两个Just可以通过 == 进行比较
let publisher1 = Just(1)
let publisher2 = Just(1)
if publisher1 == publisher2 {
print("publisher1 == publisher2")
}else{
print("publisher1 != publisher2")
}
// 高阶函数
let publisherG = Just("a,b,c,d")
// 使用map高阶函数将Just的值转成字符串数组
publisherG.map { $0.components(separatedBy: ",")}
.sink { list in
print("list: \(list)")
}
.store(in: &cancellables)
}
Empty
从不发布任何值并可选择立即完成的发布者,这种发布者一般用于没有值可发送,只发送完成的情况,或者不想发布任何消息的情况
func emptyDemo() {
// 这里我们初始化一个立即完成的Empty发布者
let publisher1 = Empty<String, Error>(completeImmediately: true)
publisher1.sink { state in
switch state {
case .finished:
print("完成1") // 这里只会收到完成
case .failure(_):
print("失败1") // Empty不会发送failure消息,所以这里不会收到
}
} receiveValue: { str in
// 这里不会收到任何信息,因为发布者是Empty,它不会发布任何消息,只会发布完成
print("str1: \(str)")
}
.store(in: &cancellables)
publisher1.sink { state in
switch state {
case .finished:
print("完成3") // 这里只会收到完成
case .failure(_):
print("失败1") // Empty不会发送failure消息,所以这里不会收到
}
} receiveValue: { str in
// 这里不会收到任何信息,因为发布者是Empty,它不会发布任何消息,只会发布完成
print("str1: \(str)")
}
.store(in: &cancellables)
// 这里我们初始化一个不会发送完成的Empty发布者,下面的订阅者将永远不会收到任何消息
let publisher2 = Empty<String, Error>(completeImmediately: false)
publisher2.sink { state in
switch state {
case .finished:
print("完成1") // 这里只会收到完成
case .failure(_):
print("失败1") // Empty不会发送failure消息,所以这里不会收到
}
} receiveValue: { str in
// 这里不会收到任何信息,因为发布者是Empty,它不会发布任何消息,只会发布完成
print("str1: \(str)")
}
.store(in: &cancellables)
}
Fail
立即以指定错误而终止的发布者,即:该发布者只会发布一条错误消息,然后终止
func failDemo() {
// 这里初始化一个指定错误信息的Fail发布者,该发布者只会发布一个错误消息
let publisher = Fail<String, DataError>(error: .otherError("读取数据失败"))
publisher.sink { state in
switch state {
case .finished:
print("完成") // 由于是Fail发布者,所以这里不会收到
case .failure(let err): // 这里会收到初始化指定的错误消息
switch err {
case .netError:
print("报错:netError")
case .otherError(let msg):
print("报错:\(msg)")
}
}
} receiveValue: { str in
// 这里将不会收到具体值的消息
print("receive: \(str)")
}
.store(in: &cancellables)
}
Record
该发布者允许记录一系列输入和完成,以便稍后回放给每个订阅者
func recordDemo() {
// 初始化一个指定录制消息列表,并以finished结束的发布者
let output = ["消息1", "消息2", "消息3", "消息4"]
let publisher = Record<String, DataError>(output: output, completion: .finished)
publisher.sink { _ in
} receiveValue: { str in
print("receive: \(str)")
}
.store(in: &cancellables)
// 初始化一个带有闭包的录制发布者
let publisher2 = Record<String, Never> { recording in
// 这里录制发布的消息
recording.receive("消息1")
recording.receive("消息2")
// 以finished结束
recording.receive(completion: .finished)
}
publisher2.sink { _ in
} receiveValue: { str in
print("receive2: \(str)")
}
.store(in: &cancellables)
}
ObservableObjectPublisher
从可观察对象中发布更改的发布者。即:只会发布一条空消息的发布者,并且该发布者不会完成,可以一直发送消息。这个发布者常用于SwifUI中,结合实现ObservableObject协议的类使用,用于观察类的属性值的变化。
func observableObjectPublisherDemo() {
// 初始化很简单
let publisher = ObservableObjectPublisher()
// 订阅
publisher.sink { state in
switch state {
case .finished:
print("完成")
}
} receiveValue: {
print("收到消息")
}
.store(in: &cancellables)
// 发送消息,此时订阅者会收到消息,但不会收到完成的消息
publisher.send()
}
三:符号(本质属于发布者)
AllSatisfy
Publishers.AllSatisfy:这个发布者会发送一个Bool类型的值,并完成。这个Bool值表示所接收到的发布者的消息中,全部满足条件。它会订阅一个发布者,并将这个发布者所有的消息传递给一个闭包函数,这个闭包函数返回一个Bool,当闭包中返回false时,发布者其余发布的消息将忽略,此时会直接发送一条消息,其Bool为false,并执行finished
func allSatisfyDemo() {
let ps = PassthroughSubject<String, Never>()
// 初始化AllSatisfy发布者,该发布者接收一个PassthroughSubject发布者,以及一个闭包。AllSatisfy会订阅PassthroughSubject发布者的消息,当PassthroughSubject发布者发送finished或failure的消息,或 AllSatisfy闭包返回false,AllSatisfy才会向订阅者发送消息,具体值取决于AllSatisfy的闭包返回的值。
let publisher = Publishers.AllSatisfy<PassthroughSubject<String, Never>>(upstream: ps) { str in
print("收到消息:\(str)")
return str.hasPrefix("A") // 这里判断收到的消息字符串都是以A开头,当收到的消息不是以A开头的,AllSatisfy将发送消息给订阅者,其值为false,并且终止发送
}
// 这里订阅AllSatisfy发布者的消息
publisher.sink { _ in
} receiveValue: { res in
print("receive: \(res)") // res 是bool 类型 ,res = true 表示收到的所有的消息都满足条件, false 则有不满足的消息例如,下面的 ps.send("no satisfy")
}
.store(in: &cancellables)
// ps是AllSatisfy发布者订阅的发布者,我们向AllSatisfy发送以下消息
ps.send("Asd")
ps.send("Addd")
ps.send("Adddewe")
ps.send("no satisfy")
// 以上消息字符串都是以”A“开头的,并且ps尚未发送finished或failure消息,所以AllSatisfy暂不发送消息
// 这里做一个异步延迟操作
DispatchQueue.global().asyncAfter(deadline: .now()+2) {
// 这里再次发送一条消息,仍然是以”A“开头。
ps.send("Addddd")
}
// 这里再次做一个异步延迟操作
DispatchQueue.global().asyncAfter(deadline: .now()+4) {
// 这里发送一条finished消息,此时AllSatisfy发布者会立即发送消息,并完成。由于以上消息中,全部满足AllSatisfy闭包中的条件,AllSatisfy的订阅者收到的值为true
ps.send(completion: .finished)
}
// 倘若ps尚未发送finished消息,而是发送了一条”Bddd“消息,由于AllSatisfy闭包中返回false,此时AllSatisfy会立即发送一条false消息,并终止。之后ps再次发送消息,AllSatisfy也将不再处理
}
AssertNoFailure
断言发布者,该发布者会判断与之关联的发布者不会发送failure消息,一旦发送了failure消息,断言发布者将调用fatalError终止程序。(注意:这里不论是Debug还是Release都会调用fatalError导致程序终止)
func assertNoFailureDemo() {
// 初始化一个用于发布消息的发布者
let ps = PassthroughSubject<Int, DataError>()
// 初始化一个断言发布者,这个断言发布者会判断其订阅的发布者不会发布错误消息,一旦发布了错误消息,这个断言发布者将会调用fatalError(prefix),导致程序中断,这个断言发布者是不区分Release和Debug的。这里初始化的时候,会传入一些参数,prefix:应该会作为fatalError函数的参数;file:#file当前文件路径; line:当前行
// 需要注意的是,这里不论是Debug还是Release都会调用fatalError导致程序终止
let publisher = Publishers.AssertNoFailure<PassthroughSubject<Int, DataError>>(upstream: ps, prefix: "ps发布了一条错误消息", file: #file, line: #line)
publisher.sink { state in
switch state {
case .finished: // 当ps发布finished时,会收到
print("完成")
}
} receiveValue: { value in
// 当ps发送value时,这里会收到
print("receive: \(value)")
}
.store(in: &cancellables)
// 发送一条消息1,此时断言发布者将该条消息转发给其订阅者
ps.send(1)
// ps.send(completion: .failure(.netError))
ps.send(completion: .finished)
// 这里ps发送了一个failure消息,此时断言发布者会调用fatalError函数,程序终止
ps.send(100)
}
MakeConnectable
根据指定Publisher生成一个实现ConnectablePublisher协议的发布者,该协议的发布者提供如下两个方法:connect() -> Cancellable和autoconnect() -> Autoconnect()。该发布者被订阅后,不会立即收到消息,仅当调用connect()方法后,订阅者才与当前发布者链接到一起,此时订阅者才能收到发布者发布的消息。注意: 只会收到关联之后的数据, 之前的数据被丢弃了, MakeConnectable 发布者可以决定我们何时开始接收,上游的数据
func makeConnectableDemo() {
let ps = PassthroughSubject<Int, Never>()
// 我们通过MakConnectable创建了一个实现ConnectablePublisher协议的发布者,该发布者接收另一个发布者,用于与其订阅者进行连接
let publisher = Publishers.MakeConnectable(upstream: ps)
// 此时我们订阅MakConnectable发布者,这里实际上是间接的订阅PassthroughSubject发布者。
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}
.store(in: &cancellables)
// 此时我们的PassthroughSubject发布者发送了一条消息,但由于我们订阅的是MakConnectable发布者,并没有调用connect()方法,此时订阅者并没有与PassthroughSubject连接。所以这里发送消息,订阅者不会收到消息。
ps.send(1)
// 下面我们调用了connect()方法,将订阅者与PassthroughSubject发布者连接
publisher.connect().store(in: &cancellables)
// 再次发送消息,此时订阅者将收到消息
ps.send(2)
}
Autoconnect
该发布者初始化接收一个实现ConnectablePublisher协议的发布者,实现订阅时自动调用connect()方法
func autoconnectDemo() {
let ps = PassthroughSubject<Int, Never>()
// 我们通过MakConnectable创建了一个实现ConnectablePublisher协议的发布者
let connectPublisher = Publishers.MakeConnectable(upstream: ps)
// 这里我们初始化一个Autoconnect的发布者
let publisher = Publishers.Autoconnect(upstream: connectPublisher)
ps.send(1999) // 不会收到这个消息
// 此时订阅Autoconnect发布者,该发布者此时会为我们自动调用connect()使订阅者与上游发布者进行连接,即:与MakeConnectable发布者进行连接
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}
.store(in: &cancellables)
// 由于Autoconnect在我们订阅的时候,自动完成了connect操作,所以下面发送一条消息,订阅者会立即收到
ps.send(1)
}
Breakpoint
用于实现断点调试的发布者,该发布者接收三个可选闭包,每个闭包返回Bool,当其中任意一个闭包返回true时,程序中断。
func breakpointDemo() {
let ps = PassthroughSubject<Int, Never>()
// 创建一个BreakPoint发布者,该发布者用于实现断点调试,该发布者接收三个可选的闭包参数,用于对上游发布者每个环节做断点,每个闭包返回一个Bool,当任意一个闭包返回true,该发布者会发布一个`SIGTRAP` 信号,来终止当前进程。
let publisher = Publishers.Breakpoint(upstream: ps) { subscription in
// 当订阅动作发生时的回调
print("subscription")
return false
} receiveOutput: { val in
// 当收到订阅消息时的回调
print("receiveOutput: \(val)")
// 我们这里增加一条判断,当收到的值>3时,终止程序
return val > 3
} receiveCompletion: { state in
// 当发布者发送完成或失败的时候的回调
print("receiveCompletion")
return false
}
// 开始订阅,此时Breakpoin的第一个闭包将被调用
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}
.store(in: &cancellables)
// 上游发布者开始发送消息
ps.send(1)
ps.send(2)
ps.send(3)
// 至此以上消息不会产长断点,接下来发送4,此时的断点闭包返回true,程序将中断。
ps.send(4)
}
Catch
func catchDemo() {
let ps = PassthroughSubject<String, DataError>()
// 初始化一个Catch发布者,接收一个上游发布者,以及处理上游发布者发布错误消息的闭包,该闭包返回一个新的发布者
let publisher = Publishers.Catch(upstream: ps) { error -> Just<String> in
// 捕获到上游发布者发出失败信号,使用一个新的发布者发送的值,发送给订阅者
Just("{'data': {}, 'state': 'fail'}")
}
// 订阅Catch发布者,该发布者会捕获上游发布者发出的failure
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}
.store(in: &cancellables)
// 上游发布者正常发送消息
ps.send("{'data': {'name': 'drbox'}, 'state': 'success'}")
// 上游发布者发送错误消息,此时Catch发布者会捕获到这个错误,并做处理
ps.send(completion: .failure(.otherError("处理异常")))
}
Collect
一个将上游发布者发送的元素,存储到集合中,等到上游发布者发送完成后,该发布者将其集合中的全部元素一起发送给订阅者的发布者
func collectDemo() {
// 上游发布者
let ps = PassthroughSubject<String, DataError>()
// 一个将上游发布者发送的元素,存储到集合中,等到上游发布者发送完成后,该发布者将其集合中的全部元素一起发送给订阅者
let publisher = Publishers.Collect(upstream: ps)
publisher.sink { _ in
} receiveValue: { list in
// Collect发布者发送的是一个集合,集合元素类型依赖于上游发布者,因此这里的集合类型为:[String]
print("receive: \(list)")
}.store(in: &cancellables)
ps.send("a")
ps.send("b")
ps.send("c")
ps.send("d")
// 在上游发布者发送finished之前,订阅者不会收到任何消息,直到上游发布者发送了finished消息
ps.send(completion: .finished)
// 当上游发布者发送了failure时,订阅者将收到failure消息
}
// 这里的缓存是针对订阅者缓存的,即:当有订阅者订阅时,此后发送的消息,将会针对该订阅者进行缓存
func collectDemo2() {
// 上游发布者
let ps = PassthroughSubject<String, DataError>()
// 一个将上游发布者发送的元素,存储到集合中,并指定集合的最大容量
let publisher = Publishers.Collect(upstream: ps)
// 无订阅者订阅之前,发送'p'
ps.send("p")
publisher.sink { _ in
} receiveValue: { list in
print("receive1: \(list)")
}
.store(in: &cancellables)
ps.send("a")
// 订阅者1订阅之后,发送'a'
publisher.sink { _ in
} receiveValue: { list in
print("receive2: \(list)")
}
.store(in: &cancellables)
// 订阅者2订阅之后,发送'b','c'
ps.send("b")
ps.send("c")
ps.send(completion: .finished)
// 上游发送完成,此时订阅者1收到:['a','b','c'],完成1
// 此时订阅者2收到:['b','c'],完成2
}
CollectByCount
类似于Collect发布者,该发布者可以指定最大缓存个数,当缓存个数达到指定大小,该发布者将会把缓存的数据一起发送给订阅者。倘若上游发布者发送了完成或失败的消息后,即使缓存数量尚未到达上限,也会一起发送给订阅者,并发送完成或失败(失败的时候,不会发送任何值)
func collectByCountDemo() {
// 上游发布者
let ps = PassthroughSubject<String, DataError>()
// 一个将上游发布者发送的元素,存储到集合中,并指定集合的最大容量
let publisher = Publishers.CollectByCount(upstream: ps, count: 2)
publisher.sink { state in
print(state)
} receiveValue: { list in
// CollectByCount发布者发送的是一个集合,集合元素类型依赖于上游发布者,因此这里的集合类型为:[String]
print("receive: \(list)")
}
.store(in: &cancellables)
//由于CollectByCount发布者指定的最大集合容量为2,因此,只有满足以下条件,才会向发布者发送消息
// 1、当集合中的元素个数达到最大容量
// 2、当上游发布者发送了完成或失败消息
ps.send("a")
// 由于上面只发送了一个‘a’,尚未达到最大容量2,因此不会向订阅者发送任何消息,此时再次发送‘b’,订阅者将立即收到['a', 'b']
ps.send("b")
ps.send("c") // 此时再次发送'c',由于没达到上限,不会发送
// 上游发布者发送了finished消息,此时订阅者会收到['c'], finish
ps.send(completion: .finished)
// 当上游发布者发送了failure时,订阅者将只会收到failure消息
}
CollectByTime
该发布者比CollectByCount发布者更加灵活,允许我们指定向下游发布者的发布策略,例如:按每多长时间发布,或者达到多少条后发送
func collectByTimeDemo() {
// 上游发布者
let ps = PassthroughSubject<String, DataError>()
// 指定发布策略,每5秒钟,采用主线程调度,向订阅者发送(有缓存的消息才去发送)
// .byTime(DispatchQueue.main, .seconds(5))
let publisher = Publishers.CollectByTime(upstream: ps, strategy: .byTimeOrCount(DispatchQueue.main, .seconds(5), 2), options: .none)
publisher.sink { _ in
} receiveValue: { list in
print("receive: \(list)")
print("thread: \(Thread.current)")
}
.store(in: &systemCancellables)
ps.send("a")
ps.send("b")
ps.send("c")
ps.send("d")
// 上游发布者发送完以上消息,CollectByTime发布者并不会立即向订阅者发送,直到从订阅开始,每个5秒钟,才会发送一次
// 但是,如果此时上游发布者发送了完成,或失败,CollectByTime发布者也会立即向订阅者发送消息
// ps.send(completion: .finished)
DispatchQueue.global().asyncAfter(deadline: .now() + 9) {
// 此时在子线程中,延迟9秒后再次发送消息
print("send thread: \(Thread.current)")
ps.send("1")
ps.send("2")
// 发送完成后,下一秒到达了发布者策略定义的周期,发布者会立即向订阅者发送["1","2"],并且这里发送是在子线程,但是订阅者收到的是在策略定义中的主线程
}
}
CombineLatest
合并两个上游发布者的发布者,它将会把两个发布者的消息合并到一起,发送给订阅者
func combineLatestDemo() {
// 上游发布者1
let ps1 = PassthroughSubject<String, DataError>()
// 上游发布者2
let ps2 = PassthroughSubject<String, DataError>()
// 初始化一个合并发布者,将两个上游发布者的消息进行合并
let publisher = Publishers.CombineLatest(ps1, ps2)
publisher.sink { _ in
} receiveValue: { val in
// 合并后的结果以元祖的结构返回(ps1, ps2)
// print("receive ps1: \(val.0), ps2: \(val.1)")
}.store(in: &systemCancellables)
// 案例1:
// ps1.send("a") // 第一个上游发布者发送了一条"a"
// ps2.send("b") // 等到第二个发布者也发送了一条"b",CombineLatest才会将两个上游发布的消息合并一起发送给订阅者,订阅者此时收到:("a", "b")
// 案例2:
// ps1.send("a")
// ps1.send("b") // 第一个上游发布者连续发送了两个消息"a", "b",由于第二个发布者尚未发出消息,此时下游订阅者不会收到任何消息
//
// ps2.send("c") // 此时第二个上游发布者发送了"c",此时CombineLatest开始合并两个上游发出的消息,这里会将第一个发布者最后一条消息与之合并后发送给订阅者,("b", "c") ,第一个发布者发出的"a"被忽略。
// ps2.send("d") // 此时发布者会将第一个发布者上一次发送的消息"b"与之合并,("b", "d")
// 案例3:
// ps1.send("a")
// ps2.send(completion: .finished) // 此时第二个发布者发送了完成,由于第二个没有值,所以无法合并
// ps1.send(completion: .finished) // 当第一个再次发送完成后,发布者才会向下游订阅者发送完成
// 案例4:
// ps1.send("a")
// ps2.send(completion: .failure(.netError)) // 两个上游任意一个发出了失败消息,都将终止CombineLatest发布者,并向下游发出错误消息
ps1.send("a")
ps1.send(completion: .finished) // ps2 还没有发送消息, 此时 a将被忽略
ps2.send(completion: .failure(.netError)) // 与ps1 的发送类型不一样, 所以无法收到订阅消息
}
CompactMap
该发布者会将上游发送的全部消息,都交给其闭包进行过滤,一旦闭包返回nil,该消息将不会发送给订阅者
func compactMapDemo() {
let ps = PassthroughSubject<String?, DataError>()
// 该发布者会将上游发送的全部消息,都交给其闭包进行过滤,一旦闭包返回nil,该消息将不会发送给订阅者
let publisher = Publishers.CompactMap<PassthroughSubject<String?, DataError>, String>(upstream: ps) { val in
guard val != "nil" && val != "null" else {
return nil // 返回nil的消息将不会发送给订阅者
}
return val
}
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in
print("receive: \(val)")
})
.store(in: &systemCancellables)
ps.send("a")
ps.send("nil")
ps.send("null")
ps.send("b")
// 由于"nil"与"null"会被CompactMap发布者过滤掉,所以,订阅者只会收到"a","b"
}
Comparison
该发布者会对上游发送的消息进行排序后,保留其中一条消息,当上游发布者发送finished后,该发布者会将其保留的值发送给下游订阅者
func comparisonDemo() {
let ps = PassthroughSubject<String, DataError>()
// 该发布者会对上游发布的消息进行重新排列,但最终只会将最后一次的结果,发送给(仅当上游发布者发送完成时)下游订阅者。该订阅者会将上游发送的消息转给闭包,然后通过闭包返回值进行排序,最终只保留一条记录,当上游发布者发送finished时,会将这条记录发送给下游订阅者。
let publisher = Publishers.Comparison(upstream: ps) { val1, val2 in
// 第一个参数:上游发出的最新消息
// 第二个参数:之前缓存的消息
print("val1: \(val1), val2: \(val2)")
// 返回false,发布者会保留第一个参数的值
// 返回true,发布者会保留第二个参数的值
return false
}
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in
print("receive: \(val)")
})
.store(in: &systemCancellables)
ps.send("a") // 上游发送了一条"a",此时由于仅有一条,无法比较,所以不会执行闭包,仅对该条消息进行缓存
ps.send("b") // 再次发送了一条,发布者会将缓存的记录与这条记录发送给闭包,进行比较,发布者会根据规则,更新缓存的值
ps.send(completion: .finished) // 此时发布者会将其保留的最后一条消息发送给订阅者
}
Concatenate
一个将两个上游发布者链接到一起的发布者,该发布者会先将prefix发出的消息转发给订阅者,当prefix终止发送消息后,开始将suffix发布者最新发出的消息转发给订阅者。当prefix发布者发送failure后,Concatenate发布者同时将错误发送给订阅者,并终止发送,此后suffix发送的任何消息都将不会被转发
func concatenateDemo() {
let ps1 = PassthroughSubject<String, DataError>()
let ps2 = PassthroughSubject<String, DataError>()
// 一个将两个上游发布者链接到一起的发布者,该发布者会先将prefix发出的消息转发给订阅者,当prefix终止发送消息后,开始将suffix发布者最新发出的消息转发给订阅者。当prefix发布者发送failure后,Concatenate发布者同时将错误发送给订阅者,并终止发送,此后suffix发送的任何消息都将不会被转发。
let publisher = Publishers.Concatenate(prefix: ps1, suffix: ps2)
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in
print("receive: \(val)")
})
.store(in: &systemCancellables)
ps1.send("ps1-a")
ps1.send("ps1-b")
// 由于ps1尚未发出completion事件,因此ps2发送任何消息都将被忽略
ps2.send("ps2-a")
ps2.send("ps2-b")
ps1.send(completion: .finished) // 倘若这里发送了failure,Concatenate也将终止,下面ps发送的任何消息,都将无效
// 由于ps1已经终止发送消息,下面ps2最新发出的消息,将被转发给订阅者
ps2.send("ps2-c")
ps2.send("ps2-d")
ps2.send(completion: .finished) // ps2终止发送了消息,此时Concatenate发布者也将终止。
}
Contains
一个判断上游发布者发送的消息中是否包含指定值的发布者。下面这个发布者,判断上游发布者发送的消息中,是否包含"b",如果包含,Contains发布者会立即发送下游订阅者,并终止,消息值为:true,倘若不包含,Contains将不会发出任何消息,直到上游发布者终止了发送,订阅者将收到值为false的消息
func containsDemo() {
let ps = PassthroughSubject<String, DataError>()
// 一个判断上游发布者发送的消息中是否包含指定值的发布者。下面这个发布者,判断上游发布者发送的消息中,是否包含"b",如果包含,Contains发布者会立即发送下游订阅者,并终止,消息值为:true,倘若不包含,Contains将不会发出任何消息,直到上游发布者终止了发送,订阅者将收到值为false的消息。
// let publisher = Publishers.Contains(upstream: ps, output: "b")
let publisher = Publishers.ContainsWhere(upstream: ps) { value in
return value == "b"
}
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in // val是Bool类型值
print("receive: \(val ? "true" : "false")")
})
.store(in: &systemCancellables)
ps.send("a")
ps.send("c")
// 以上发送的消息中,都不值"b",因此订阅者不会收到任何消息,倘若此时ps终止发送,Contains将发送给订阅者false
// ps.send(completion: .finished)
ps.send("b") // 由于此次发送的消息为"b",使得Contains条件成立,Contains发布者会立即向订阅者发送true,并终止
ps.send("b") // 信号已经结束, 不会收到任何消息
}
Count
该发布者用于统计上游发布者在终止发送后,发送的消息总数
func countDemo() {
let ps = PassthroughSubject<String, DataError>()
// 该发布者用于统计上游发布者在终止发送后,发送的消息总数
let publisher = Publishers.Count(upstream: ps)
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { count in
print("receive: \(count)")
})
.store(in: &systemCancellables)
ps.send("a")
ps.send("b")
ps.send("c")
ps.send(completion: .finished) // 此时向订阅者发送一个数子3
}
Debounce
按照一定频率转发上游消息的发布者,每次接收到新数据,都会重新开启一个新的时间窗口,同时取消之前的时间窗口。该发布者指定一个发送的时间间隔dueTime,以及一个发送任务的调度者scheduler。这里指定每隔2秒钟,在主线程中,向订阅者发送上游发送的消息,倘若此时上游未发送任何消息,Debounce将不会做任何转发。常用于输入框输入文字,进行联想搜索,避免频繁发起请求
func debounceDemo() {
let ps = PassthroughSubject<String, DataError>()
// 按照一定频率转发上游消息的发布者。该发布者指定一个发送的时间间隔dueTime,以及一个发送任务的调度者scheduler。这里指定每隔2秒钟,在主线程中,向订阅者发送上游发送的消息,倘若此时上游未发送任何消息,Debounce将不会做任何转发。
let publisher = Publishers.Debounce(upstream: ps, dueTime: .seconds(2), scheduler: DispatchQueue.main, options: nil)
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in
print("receive: \(val)")
})
.store(in: &systemCancellables)
ps.send("a")
ps.send("b")
ps.send("c")
ps.send("d")
// 以上连续发送四条消息,待2秒间隔到了,订阅者会收到消息"d"
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
// 第3秒后,再次发送两条消息
ps.send("e")
ps.send("f") // 待2秒间隔到了,订阅者会收到消息"f"
}
DispatchQueue.main.asyncAfter(deadline: .now()+7) {
// 第7秒后,再次发送两条消息
ps.send("g")
ps.send("h") // 待7秒间隔到了,订阅者会收到消息"h"
}
}
Throttle
一个按照固定时间间隔发送消息的发布者,它可以指定在一段时间间隔内收到的消息中,选择第一条还是最后一条数据发送给订阅者。(当publisher发送的数据刚好在时间窗口边缘的时候,结果是不确定的)。与Debounce对比:Throttle按照固定的顺序排列时间窗口,在时间窗口的结尾处发送数据,而Debounce每次接收到新数据,都会重新开启一个新的时间窗口,同时取消之前的时间窗口。例如:如果我在2秒内疯狂点击按钮,时间间隔时长设置为0.5秒,那么throttle可以发送4次数据,而debounce不会发送数据,只有当我停止点击0.5秒后,才会发送一次数据
func throttleDemo() {
let ps = PassthroughSubject<Int, Never>()
// 设置间隔时间为3秒,指定在3秒时间段内将最后一条消息,发送给订阅者
let publisher = Publishers.Throttle(upstream: ps,
interval: .seconds(3),
scheduler: DispatchQueue.main,
latest: true) // true:指定最后一条;false:指定第一条
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { val in
print("receive: \(val)")
})
.store(in: &systemCancellables)
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
ps.send(1)
ps.send(2)
ps.send(3)
}
DispatchQueue.main.asyncAfter(deadline: .now()+5) {
ps.send(4)
ps.send(5)
}
DispatchQueue.main.asyncAfter(deadline: .now()+7) {
ps.send(6)
ps.send(7)
}
}
Delay
该发布者会将上游发布者发送的消息延迟指定时间后发送给下游订阅者。例如,Delay规定延迟1秒发送,上游发布者第一次发送消息是在0.2秒时发送,那么订阅者会在1.2秒后收到
func delayDemo() {
// 一个每隔1秒发送一条消息的上游发布者
let timer = Timer.publish(every: 1, on: .main, in: .default, options: nil).autoconnect()
// 当timer调用.autoconnect会立即发布消息,如果我们想实现5秒后在开始接收,那么我们可以使用Delay发布者
// 延迟消息发布者,该发布者用于将上游发布者的消息(包括完成)进行延迟发送。这里指定延迟5秒后发送
let publisher = Publishers.Delay(upstream: timer,
interval: .seconds(5),
tolerance: .seconds(1),
scheduler: DispatchQueue.global(),
options: nil)
publisher.sink(receiveCompletion: { _ in
}, receiveValue: { date in
print("receive: \(date),当前时间: \(Date())")
})
.store(in: &systemCancellables)
print("当前时间:\(Date())")
}
Timeout
用于实现超时任务的发布者
func timeoutDemo() {
enum CustomError: Error {
case timeout
case other
}
let up = PassthroughSubject<Int, CustomError>()
// 定义一个超时的发布者,规定1秒后发出一个failure消息
// let publisher = Publishers.Timeout(upstream: up, interval: .seconds(1), scheduler: DispatchQueue.main, options: nil) {
// .timeout
// }
// 定义一个超时的发布者,规定1秒后发出一个finish消息
let publisher = Publishers.Timeout(upstream: up, interval: .seconds(1), scheduler: DispatchQueue.main, options: nil, customError: nil)
publisher.sink { state in
switch state {
case .finished:
print("完成")
case .failure(let error):
switch error {
case .other:
print("报错了:其他错误")
case .timeout:
print("报错了:超时")
}
}
} receiveValue: { val in
print("recive: \(val)")
}.store(in: &systemCancellables)
up.send(1999)
DispatchQueue.main.asyncAfter(deadline: .now()+2) {
print("发送消息")
up.send(1) // 如果超时了,这里发出的消息,订阅者将无法收到
};
}
Decode Encode
一个解码发布者者,用于将上游发布者消息,通过指定的解码器进行解码,转成指定类型的消息,发送给订阅者,一个编码发布者,用于将上游发布者消息,通过指定的编码器进行编码,转成指定类型的消息,发送给订阅者
func decodeDemo() {
struct UserInfo: Codable {
var name: String?
var age: Int?
}
let ps = PassthroughSubject<Data, DataError>()
// 一个解码器发布者,用于将上游发布者消息,通过指定的解码器进行解码,转成指定类型的消息,发送给订阅者。这里指定一个JSONDecoder解码器,解码后的对象指定为UserInfo
let publisher = Publishers.Decode<PassthroughSubject<Data, DataError>, UserInfo, JSONDecoder>(upstream: ps, decoder: JSONDecoder())
publisher.sink(receiveCompletion: { state in
switch state {
case .finished:
print("完成")
case .failure(let err):
print("报错:\(err)") // 因为解码器解码过程会抛出异常,倘若抛出异常,这里会收到错误
}
}, receiveValue: { userInfo in // 这里收到的消息是UserInfo类型
print("receive: name: \(userInfo.name ?? "空"), age: \(userInfo.age ?? 0)")
})
.store(in: &systemCancellables)
let data = """
{"name": "drbox", "age": 25}
""".data(using: .utf8)
ps.send(data!) // 上游发布者,发送一个json数据。
}
func encodeDemo() {
struct UserInfo: Codable {
var name: String?
var age: Int?
}
let ps = PassthroughSubject<UserInfo, DataError>()
// 一个编码器发布者,用于将上游发布者消息,通过指定的编码器进行编码,转成指定类型的消息,发送给订阅者。这里指定一个JSONEncoder编码器
let publisher = Publishers.Encode<PassthroughSubject<UserInfo, DataError>, JSONEncoder>(upstream: ps, encoder: JSONEncoder())
publisher.sink(receiveCompletion: { state in
switch state {
case .finished:
print("完成")
case .failure(let err):
print("报错:\(err)") // 因为编码器编码过程会抛出异常,倘若抛出异常,这里会收到错误
}
}, receiveValue: { data in // 这里收到的消息是Data类型
let jsonStr = String(data: data, encoding: .utf8)
print("receive: \(jsonStr ?? "空")")
})
.store(in: &systemCancellables)
let info = UserInfo(name: "drbox", age: 25)
ps.send(info) // 上游发布者,发送一条消息
// 订阅者会收到:receive: {"name":"drbox","age":25}
}
Drop
在发布元素之前忽略指定数量的元素的发布者, 相当与 skip
func dropDemo() {
let up = PassthroughSubject<Int, Never>()
// 该发布者会将上游发布者发出指定数量的消息过滤掉,并在之后的发送中向下游订阅者转发消息
let publisher = Publishers.Drop(upstream: up, count: 4)
publisher.sink { state in
switch state {
case .finished:
print("完成")
}
} receiveValue: { val in
print("recive: \(val)")
}.store(in: &systemCancellables)
up.send(1)
up.send(2)
up.send(3)
up.send(4) // 上面指定发出4次之后的消息,将被转发,即:从这里为止,之后再次发送的消息,订阅者将会收到
up.send(5)
up.send(6)
up.send(7)
}
DropUntilOutput
该发布者忽略来自上游发布者的元素,直到接收到来自第二个发布者的元素
func dropUntilOutputDemo() {
let up = PassthroughSubject<Int, Never>()
let other = PassthroughSubject<Int, Never>()
let publisher = Publishers.DropUntilOutput(upstream: up, other: other)
publisher.sink { _ in
} receiveValue: { value in
print(value)
}.store(in: &systemCancellables)
up.send(1)
up.send(2)
up.send(3)
// 在没有收到 other 信号之前的 所有的up 的信号值都会被忽略
other.send(4)
up.send(5)
up.send(6)
up.send(7)
}
DropWhile
func dropWhileDemo() {
let up = PassthroughSubject<Int, DataError>()
// 该发布者,它忽略来自上游发布者的元素,直到闭包返回false
// 这个是忽略条件, 意思是忽略掉满足条件的值, 得到大于3的值 4 5 6
// let publisher = Publishers.DropWhile(upstream: up) { $0 < 3 }
// 筛选出满足条件的值, 会得到满足小于3的值 12
let publisher = Publishers.Filter(upstream: up, isIncluded: { $0 < 3 })
// 只接收第一条数据
// let publisher = Publishers.First(upstream: up)
// 只接收满足条件的第一个信号
// let publisher = Publishers.FirstWhere(upstream: up, predicate: { $0 < 2 })
// 该发布者,会将上游发布者发送的元素,交给闭包,该闭包返回一个新的发布者,下游订阅者会收到该新的发布者发送的元素
// 这个函数相当于数据转化 把原来的值 转成 jush里面的值
// let publisher = Publishers.FlatMap(upstream: up, maxPublishers: .max(2)) { output in
// return Just("返回值 \(output)")
// }
// 和上面的类似
// let publisher = Publishers.Map(upstream: up) { output in
// return output * output
// }
// let publisher = Publishers.MapError<PassthroughSubject<Int, DataError>, DataError>(upstream: up, transform: { error in
// return .netError
// })
publisher.sink { _ in
} receiveValue: { val in
print("recive: \(val)")
}.store(in: &systemCancellables)
up.send(1)
up.send(2) // 闭包判断,当上游发布者发送的元素值<3时,都将被过滤掉,因此:1、2不会被收到
up.send(3) // 以下元素值>3,将会发送给订阅者
up.send(4)
up.send(5)
}
HandleEvents
该发布者,用于hook上游发布者的订阅、请求、send、取消相关操作
func handleEventsDemo() {
var cancelables: [AnyCancellable] = []
let up = PassthroughSubject<Int, Never>()
let publisher = Publishers.HandleEvents(upstream: up) { sub in
print("接收到订阅者的订阅:\(sub)") // 这里的sub为上游发布者:PassthroughSubject<Int, Never>
} receiveOutput: { val in
print("接收到上游发布者发送的元素:\(val)")
} receiveCompletion: { state in
switch state {
case .finished:
print("接收到发布者发出的finish消息")
case .failure(_):
print("接收到发布者发出的failure消息")
}
} receiveCancel: {
print("订阅者取消了订阅")
} receiveRequest: { demand in
switch demand {
case .none:
print("接收到订阅者的请求,请求元素个数为:none");
case .unlimited:
print("接收到订阅者的请求,请求元素个数为:不限");
case let max:
print("接收到订阅者的请求,请求元素个数为:\(max)");
}
}
let cancelable = publisher.sink { state in
switch state {
case .finished:
print("完成")
}
} receiveValue: { val in
print("recive: \(val)")
}
cancelables.append(cancelable)
up.send(1)
up.send(2)
// up.send(completion: .finished)
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
cancelables.first?.cancel()
}
}
IgnoreOutput
该发布者会忽略上游发布者发出的任何元素,而对发出的completion事件不会被忽略
func ignoreOutputDemo() {
let up = PassthroughSubject<Int, Never>()
let publisher = Publishers.IgnoreOutput(upstream: up)
publisher.sink { _ in
} receiveValue: { val in
// 注意:IgnoreOutput.Output为Never,所以这里的回调将不会被调用
}.store(in: &systemCancellables)
// 该发布者会阻止上游发出的元素,不让订阅者收到
up.send(1)
up.send(2)
// 订阅者只能收到,上游发布者发出completion事件
up.send(completion: .finished)
// up.send(completion: .failure(.other))
}
LastWhere
func lastDemo() {
let up = PassthroughSubject<Int, Never>()
// 该发布者只会将上游发布者发出completion事件后,之前发出的最后一个元素,发送给下游订阅者
// let publisher = Publishers.Last(upstream: up)
// 该发布者只会将上游发布者发出completion事件后,之前发出的最后一次满足闭包条件的元素
let publisher = Publishers.LastWhere(upstream: up) { output in
return output < 2
}
publisher.sink { state in
switch state {
case .finished:
print("完成")
}
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(1)
up.send(2)
up.send(3)
up.send(4)
// 此时上面发出的元素,订阅者不会收到,直到下面发出completion事件后,订阅者将会收到4
up.send(completion: .finished)
}
MapKeyPath
MapKeyPath、Publishers.MapKeyPath2、Publishers.MapKeyPath3:这三个发布者无可用的构造函数,也就是说我们无法对其直接初始化。所以我们在使用这三个发布者的时候,只能通过Apple为我们提供的Publisher扩展函数map函数来使用 可以理解成通过 keypath 把所有满足的消息的 keypath的值筛选出来
func keyPathDemo() {
struct User {
var name: String
var age: Int
var ID: String
}
let up = PassthroughSubject<User, Never>()
// MapKeyPath
let publisher = up.map(\.name)
publisher.sink { _ in
} receiveValue: { val in
print("receive User.name: \(val)")
}.store(in: &systemCancellables)
// MapKeyPath2
let publisher1 = up.map(\.name, \.age)
publisher1.sink { val in
// val是上面publisher1指定的两个keyPath值的元祖
print("receive User.name: \(val.0), User.age: \(val.1)")
}.store(in: &systemCancellables)
// MapKeyPath3
let publisher2 = up.map(\.name, \.age, \.ID)
publisher2.sink { val in
// val是上面publisher2指定的三个keyPath值的元祖
print("receive User.name: \(val.0), User.age: \(val.1), User.ID: \(val.2)")
}.store(in: &systemCancellables)
up.send(User(name: "drbox", age: 25, ID: "1110001100110011"))
}
MeasureInterval
一个用于测量上游发布者发送每个元素的间隔时间的发布者
func measureIntervalDemo() {
let up = PassthroughSubject<String, Never>()
// 一个用于测量上游发布者发送每个元素的间隔时间的发布者
let publisher = Publishers.MeasureInterval(upstream: up, scheduler: DispatchQueue.main)
publisher.sink { interval in
print("receive: \(interval.magnitude)")
}.store(in: &systemCancellables)
DispatchQueue.main.asyncAfter(deadline: .now()+1) {
up.send("msg1")
}
DispatchQueue.main.asyncAfter(deadline: .now()+4) {
up.send("msg2")
}
DispatchQueue.main.asyncAfter(deadline: .now()+7) {
up.send("msg3")
}
}
MergeMany
一个将两个上游发布者合并的发布者,使得下游订阅者可以收到两个上游发布者的消息。以下的发布者与之功能类似 Publishers.Merge3、Publishers.Merge4、Publishers.Merge5、Publishers.Merge6......Publishers.Merge8以及Publishers.MergeMany(该构造函数接收一个可变参数,用于支持合并更多的上游发布者)
func margeDemo() {
let up1 = PassthroughSubject<Int, Never>()
let up2 = PassthroughSubject<Int, Never>()
// 一个将两个上游发布者合并的发布者,使得下游订阅者可以收到两个上游发布者的消息
// let publisher = Publishers.Merge(up1, up2)
let publisher = Publishers.MergeMany(up1, up2)
publisher.sink { val in
print("receive: \(val)") // 这里既可以收到up1的消息,也可以收到up2的消息
}.store(in: &systemCancellables)
up1.send(1)
up2.send(2)
up2.send(3)
up1.send(4)
}
Multicast
该发布者会通过闭包创建的Subject,向多个订阅者发送消息,注意:该发布者实现了ConnectablePublisher协议,所以订阅者若要能够在上游发出消息后,收到该条消息,则必须在发出该条消息前调用connect()
func multicastDemo() {
let up = Deferred<Future<Int, Never>> {
print("开始请求接口")
return Future<Int, Never>{ promise in
// 这里一般用于请求接口,返回数据时,通过promise回调
promise(.success(1))
}
}
// 上面介绍过Deferred这个发布者,该发布者会在订阅者发生订阅时,触发闭包,创建一个发布者(并且是每次订阅都会创建)
// up.sink { val in
// print("接口返回数据1: \(val)")
// }.store(in: &cancelables)
//
// up.sink { val in
// print("接口返回数据2: \(val)")
// }.store(in: &cancelables)
// 以上两个订阅者发生了订阅,此时接口请求会发生两次,如果更多的订阅者订阅时,则会发生更多的接口请求,而我们只需要请求一次接口就可以了,不需要重复的发送请求
// 解决以上问题,可以通过下面的发布者来完成
// 该发布者会通过闭包创建的Subject,向多个订阅者发送消息,注意:该发布者实现了ConnectablePublisher协议,所以订阅者若要能够在上游发出消息后,收到该条消息,则必须在发出该条消息前调用connect()
let publisher = Publishers.Multicast(upstream: up) { () -> PassthroughSubject<Int, Never> in
print("create a subject") // 当有订阅者订阅publisher时,该闭包会被调用,而且仅被调用一次(即:多个订阅者订阅publisher,这里只被调用一次)
return PassthroughSubject<Int, Never>() // 之后上游发布者每次发送一个元素,都会用这里返回的Subject,进行转发
}
// 当第一个订阅者发生订阅时,publisher的闭包会被执行
publisher.sink { val in
print("receive1: \(val)")
}.store(in: &systemCancellables)
// 再次发生订阅时,publisher的闭包将不被调用
publisher.sink { val in
print("receive2: \(val)")
}.store(in: &systemCancellables)
publisher.connect().store(in: &systemCancellables) // 这里的作用是让publisher订阅上游发布者up,此时会执行上游发布者的闭包,也就是请求接口操作。这个调用放在这里的目的,是为了确保上面两个订阅者都能够收到上游发出的消息
// 这样一来,我们就确保上面两个订阅者订阅时,只发生一次接口调用
}
Output
该发布者指定一个范围,规定只将上游发布者发出的元素序列,或次序范围内的元素,发送给订阅者
func outputDemo() {
let up = PassthroughSubject<String, Never>()
// 该发布者指定一个范围,规定只将上游发布者发出的元素序列,或次序范围内的元素,发送给订阅者
let publisher = Publishers.Output(upstream: up, range: 0..<4)
publisher.sink { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
// 由于publisher指定的范围是[0, 4),所以订阅者只能收到发出的第1个元素到第4个元素,即:A、B、C、D
up.send("A")
up.send("B")
up.send("C")
up.send("D")
up.send("E")
up.send("F")
}
PrefixUntilOutput
该发布者会将第一个上游发布者发出的元素,发送给下游订阅者,直到第二个发布者发出元素时,该发布者终止发送
func prefixUntilOutputDemo() {
let up1 = PassthroughSubject<Int, Never>()
let up2 = PassthroughSubject<Int, Never>()
// 该发布者会将第一个上游发布者发出的元素,发送给下游订阅者,直到第二个发布者发出元素时,该发布者终止发送
let publisher = Publishers.PrefixUntilOutput(upstream: up1, other: up2)
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up1.send(1)
up1.send(2)
// 以上订阅者会收到,1、2
up2.send(3) // 直到第二个发布者发出元素,此时publisher会发出finish,终止发送。订阅者并不会收到3
// up2.send(completion: .finished) // 如果此时第二个发布者发出completion消息,并不会终止publisher发送消息
up1.send(4)
}
PrefixWhile
该发布者会将上游发出的每个元素,交给一个闭包函数,闭包返回true,则该元素会发送给订阅者,如果返回false,将终止发送
func prefixWhileDemo() {
let up = PassthroughSubject<Int, Never>()
// 该发布者会将上游发出的每个元素,交给一个闭包函数,闭包返回true,则该元素会发送给订阅者,如果返回false,将终止发送
let publisher = Publishers.PrefixWhile(upstream: up) { $0 < 3 }
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(1) // 1<3,满足闭包条件,订阅者会收到1
up.send(2) // 2<3,满足闭包条件,订阅者会收到2
up.send(3) // 3==3,不满足闭包条件,publisher将终止发送,订阅者不会收到3
up.send(4)
}
该发布者会打印上游发布者所有的行为日志,你可以指定一个输出日志的前缀,用于快速定位日志,你还可以指定一个用于输出日志的对象
func printDemo() {
struct ASCIIPrint: TextOutputStream {
mutating func write(_ string: String) {
let str = string.unicodeScalars
.lazy
.map {
$0 == "\n" ? "" : $0.escaped(asASCII: true)
}
.joined(separator: "")
print("\(str)")
}
}
let up = PassthroughSubject<String, Never>()
// 该发布者会打印上游发布者所有的行为日志,你可以指定一个输出日志的前缀,用于快速定位日志,你还可以指定一个用于输出日志的对象
let publisher = Publishers.Print(upstream: up, prefix: "[log]", to: ASCIIPrint())
// let publisher = Publishers.Print(upstream: up, prefix: "[log]", to: nil)
publisher.sink { state in
switch state {
case .finished:
print("完成")
}
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send("😁")
up.send("😺")
up.send("🥰")
}
ReceiveOn
该发布者指定上游发布者,在指定调度队列中执行发送任务,这里指定主队列
func receiveOn() {
let up = PassthroughSubject<Int, Never>()
// 该发布者指定上游发布者,在指定调度队列中执行发送任务,这里指定主队列
let publisher = Publishers.ReceiveOn(upstream: up, scheduler: DispatchQueue.main, options: nil)
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val),thread:\(Thread.current)") // 上游发布者无论在主队列还是在全局队列中调用send发送元素,这里始终都在主队列中回调
}.store(in: &systemCancellables)
up.send(1)
DispatchQueue.global().async {
print("curr thread: \(Thread.current)")
up.send(2)
}
}
Reduce
func reduceDemo() { let up = PassthroughSubject<Int, Never>()
// 一个归纳发布者,它会将上游发布者发送的全部元素,经过一个闭包,该闭包的返回值作为下一次闭包调用的第一个参数,直到上游发布者发出completion事件后,将最终的结果发送给下游订阅者
let publisher = Publishers.Reduce(upstream: up, initial: 0) { initital, val in // initial参数作为闭包函数的第一个参数的初始值
print("initital: \(initital), val: \(val)")
return initital + val // 这里对上游发送的值进行累加运算
}
}
RemoveDuplicates
func removeDuplicatesDemo() {
let up = PassthroughSubject<Int, Never>()
// 该发布者用于对上游发布者两次发出的值进行去重,上游发布者第一次发出第一个元素时,不会执行闭包。当第二次及以上每发出一个元素都会经过闭包。该闭包第一个参数为上游发布者最后一次发出的元素,第二个参数为最新发出的元素。该闭包返回一个bool,返回true,表示上游最新发出的元素不会发送给下游订阅者。反之,false会发送给下游订阅者。
let publisher = Publishers.RemoveDuplicates(upstream: up) { val1, val2 in
print("val1: \(val1), val2: \(val2)")
return val1 == val2
}
}
ReplaceEmpty
该发布者会在上游发布者未发出过任何元素时,而发出completion事件时,向下游发布者发出一个默认值:output,并终止
func replaceEmpty() {
let up = PassthroughSubject<Int, Never>()
// 该发布者会在上游发布者未发出过任何元素时,而发出finish事件时,向下游订阅者发出一个默认值:output,并终止
let publisher = Publishers.ReplaceEmpty(upstream: up, output: 0)
up.send(completion: .finished) // 在这之前未发出过任何元素,publisher将会向下游发布者发出一个指定的默认值:0
}
ReplaceError
该发布者会在上游发布者发出failure事件时,向下游订阅者发出一个默认值:output,并终止
func replaceErrorDemo() {
let up = PassthroughSubject<Int, DataError>()
// 该发布者会在上游发布者发出failure事件时,向下游订阅者发出一个默认值:output,并终止
let publisher = Publishers.ReplaceError(upstream: up, output: 0)
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(1)
up.send(completion: .failure(.netError)) // 上游发出了failure事件,此时publisher会向下游订阅者发出一个默认值:0,并终止
}
Retry
一个重试发布者,它会在上游发布者发出failure事件时,再次重新尝试订阅上游发布者,这里指定重试次数为:3(一般用于接口调用失败后重试)
func retryDemo() {
enum CustomError: Error {
case fail
}
var retryCount = 0
let up = Deferred<Future<Int, CustomError>> {
retryCount += 1;
print("发送接口请求")
return Future<Int, CustomError>{ promise in
if retryCount >= 3 {
promise(.success(1))
}else{
promise(.failure(.fail))
}
}
}
// 一个重试发布者,它会在上游发布者发出failure事件时,再次重新尝试订阅上游发布者,这里指定重试次数为:3(一般用于接口调用失败后重试)
let publisher = Publishers.Retry(upstream: up, retries: 3)
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
}
Scan
一个扫描发布者,它会对上游发布者发出的每个元素,经过一个闭包扫描,该闭包返回值,会立即发送给下游订阅者。闭包的第一个参数为:上游发布者最后一次发出的元素,第二个参数为:最新发出的元素。该发布者类似于Publishers.Reduce,区别就是Scan会将上游发布者每个发出的元素经过闭包后,发送给订阅者;而Reduce虽然也是每次都会经过闭包,但仅当上游发布者发出finish事件后,将闭包最后一次运算的结果发给订阅者。简单点就是:Scan:订阅者会收到多次消息;Reduce:订阅者仅仅会收到一次消息。
func scanDemo() {
let up = PassthroughSubject<Int, Never>()
// 一个扫描发布者,它会对上游发布者发出的每个元素,经过一个闭包扫描,该闭包返回值,会立即发送给下游订阅者。闭包的第一个参数为:上游发布者最后一次发出的元素,第二个参数为:最新发出的元素。
let publisher = Publishers.Scan(upstream: up, initialResult: 0) { initResult, val in // initialResult:闭包第一个参数的初始值
print("initResult: \(initResult), val: \(val)")
return initResult+val
}
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(1) // 闭包返回:0+1,订阅者收到:1
up.send(2) // 闭包返回:1+2,订阅者收到:3
up.send(3) // 闭包返回:3+3,订阅者收到:6
}
Sequence
一个将元素序列逐一发送给订阅者的发布者 Sequence同样实现了序列相关的操作,并将操作结果转成一个发布者的形式,包括:allSatisfy、append、contains、count、map等
func sequenceDemo() {
// 一个将元素序列逐一发送给订阅者的发布者
let publisher = Publishers.Sequence<[Int], Never>(sequence: [1, 2, 3, 4, 5, 6])
publisher.sink { _ in
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
publisher.allSatisfy { val in // 遍历所有元素,直到返回false或遍历结束,终止遍历,并向下游订阅者发送一个元素,元素类型为该闭包返回的bool类型
print("val: \(val)")
return val < 5
}.sink { satisfy in
// allSatisfy发布者判断序列中是否全部满足闭包表达式的条件,由于序列的最后一个元素为6,不满足条件,因此satisfy结果为:false
print("结果:\(satisfy ? "满足条件" : "不满足条件")")
}.store(in: &systemCancellables)
let sequence = Publishers.Sequence<[Int], Never>(sequence: [7, 8, 9])
publisher.append(sequence).sink { val in // 将上一个序列发布者的所有序列元素,追加到当前序列发布者的序列元素中
print("append after val: \(val)") // 最终结果为:1、2、3、4、5、6、7、8、9
}.store(in: &systemCancellables)
publisher.append(7, 8, 9).sink { val in // 还可以利用可变参数方式,向当前序列发布者的序列中追加元素
print("可变参数append after val: \(val)")
}.store(in: &systemCancellables)
publisher.append([100, 99, 88]).sink { val in // 仍然可以将一个序列的元素全部追加到当前序列发布者的序列中
print("序列参数append after val: \(val)")
}.store(in: &systemCancellables)
publisher.collect().sink { list in // 将当前序列发布者中的元素,以序列的形式一次性发送给订阅者
for val in list {
print("list val: \(val)")
}
}.store(in: &systemCancellables)
publisher.prepend(7, 8, 9).sink { val in // 将可变参数中的值,添加到当前序列发布者的序列之前
print("prepend val: \(val)") // 最终序列为:7、8、9、1、2、3、4、5、6
}.store(in: &systemCancellables)
publisher.output(at: 0).sink { val in // 取出当前序列发布者的序列中的第0个下标的元素
print("element at 0: \(val)")
}.store(in: &systemCancellables)
publisher.contains(2).sink { has in
print("contains \(has ? "包含" : "不包含") 2")
}.store(in: &systemCancellables)
publisher.count().sink { count in
print("当前序列发布者的序列元素个数:\(count)")
}.store(in: &systemCancellables)
}
SetFailureType
该发布者用于修改上游发布者的Failure类型,被修改的上游发布者的原Failure类型必须为Never类型
func failureTypeDemo() {
let up1 = PassthroughSubject<Int, Never>()
let up2 = PassthroughSubject<String, DataError>()
// 该发布者用于修改上游发布者的Failure类型,被修改的上游发布者的原Failure类型必须为Never类型
let publisher = Publishers.SetFailureType<PassthroughSubject<Int, Never>, DataError>(upstream: up1)
// 下面的操作符函数告诉了我们,为什么需要SetFailureType发布者,我们知道combineLatest的作用是将两个发布者发送的元素进行合并后统一发送给下游订阅者,这个操作有个前提,那就是两个发布者的Failure类型要相同。
// 因此我们才有了上面SetFailureType发布者,它将up1的Failure类型从Never改成了DataError(注意:这里实际上并不是真正的修改其Failure类型,而是通过SetFailureType将上游包装了起来)
publisher.combineLatest(up2).sink { _ in
} receiveValue: { tupleVal in
print("up1: \(tupleVal.0), up2: \(tupleVal.1)")
}.store(in: &systemCancellables)
up1.send(1) // 由于up2尚未发送过元素,因此需要等待up2发送元素后,才会将这两个元素合并成一个元祖,一起发送给订阅者
up2.send("One") // 此时订阅者收到消息:(1, "One")
up1.send(2)
up2.send(completion: .failure(.netError))
}
Share
一个共享订阅信息的发布者,它的作用与Multicast是一样的,实际上Share就是Multicast与PassthroughSubject结合autoconnect()操作的一个组合(目的也是为了实现一个发布者被多个订阅者订阅,保证这个订阅操作只执行一次)
func shareDemo() {
let up = Deferred<Future<Int, Never>> {
print("发送接口请求")
return Future<Int, Never>{ promise in
DispatchQueue.main.asyncAfter(deadline: .now()+3) {
promise(.success(1))
}
}
}
// 一个共享订阅信息的发布者,它的作用与Multicast是一样的,实际上Share就是Multicast与PassthroughSubject结合autoconnect()操作的一个组合(目的也是为了实现一个发布者被多个订阅者订阅,保证这个订阅操作只执行一次)
let publisher = Publishers.Share(upstream: up)
publisher.sink { val in
print("receive1: \(val)")
}.store(in: &systemCancellables)
publisher.sink { val in
print("receive2: \(val)")
}.store(in: &systemCancellables)
publisher.sink { val in
print("receive3: \(val)")
}.store(in: &systemCancellables)
// 以上有3个订阅者订阅了publisher,但是对up发布者的订阅操作仅发生一次
}
SubscribeOn
该订阅者用于指定对上游发布者的订阅操作,在指定的线程队列中执行,类似于ReceiveOn(是指定向下游订阅者发送元素的操作,在哪个队列中进行的)
func subscribeOnDemo() {
// Deferred之前我们已经讲过了,它会在订阅者对其产生订阅操作时,通过执行一个闭包,返回一个发布者,因此我们可以在这个闭包中确认SubscribeOn的作用
let up = Deferred<Just<Int>> {
// 产生订阅的闭包体
print("subscribe on thread: \(Thread.current)") // SubscribeOn可以修改这里所运行的线程环境
return Just(1)
}
// 该订阅者用于指定对上游发布者的订阅操作,在指定的线程队列中执行,类似于ReceiveOn(是指定向下游订阅者发送元素的操作,在哪个队列中进行的)
let publisher = Publishers.SubscribeOn(upstream: up, scheduler: DispatchQueue.global(), options: nil)
publisher.receive(on: DispatchQueue.main).sink { val in
print("receive: \(val), thread: \(Thread.current)") // ReceiveOn可以修改这里所运行的线程环境
}.store(in: &systemCancellables)
}
SwitchToLatest
该发布者用于【订阅】上游发布者所发送的【发布者】,并将【发布者】发送的元素,发送给下游订阅者。当上游发布者重新发布了一个新的【新发布者】,SwitchToLatest会取消上一个【发布者】的订阅,并重新订阅这个【新发布者】
func switchToLatestDemo() {
let up = PassthroughSubject<PassthroughSubject<Int, Never>, Never>()
// 该发布者用于【订阅】上游发布者所发送的【发布者】,并将【发布者】发送的元素,发送给下游订阅者。当上游发布者重新发布了一个新的【新发布者】,SwitchToLatest会取消上一个【发布者】的订阅,并重新订阅这个【新发布者】
let publisher = Publishers.SwitchToLatest(upstream: up)
publisher.sink { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
let sub1 = PassthroughSubject<Int, Never>()
up.send(sub1) // 向publisher发送一个发布者,publisher会订阅该发布者
sub1.send(1) // 此时下游订阅者收到:1
sub1.send(2) // 此时下游订阅者收到:2
let sub2 = PassthroughSubject<Int, Never>()
up.send(sub2) // 向publisher重新发送了一个新的发布者,publisher会取消对sub1的订阅,并会订阅sub2的发布者
sub1.send(3) // 由于sub1的订阅已经取消了,因此下游发布者无法收到任何消息
sub2.send(4) // 此时下游订阅者收到:4
sub2.send(5)
}
Zip
该发布者用于将两个上游发布者合并,两个上游发出的元素会一对一对的发出,下游接收的元素类型为一个元祖。该发布者与Merge、CombineLatest和Concatenate发布者功能类似,但是在订阅者接收的时候存很大差异。同时还提供了Zip3、Zip4,用于合并3个或4个上游发布者
func zipDemo() {
let up1 = PassthroughSubject<Int, Never>()
let up2 = PassthroughSubject<Int, Never>()
// 该发布者用于将两个上游发布者合并,两个上游发出的元素会一对一对的发出,下游接收的元素类型为一个元祖。该发布者与Merge、CombineLatest和Concatenate发布者功能类似,但是在订阅者接收的时候存很大差异。
let publisher = Publishers.Zip(up1, up2)
publisher.sink { tupleVal in
print("receive up1: \(tupleVal.0), up2: \(tupleVal.1)")
}.store(in: &systemCancellables)
up1.send(1)
up1.send(2) // up2尚未发出元素,订阅者不会收到任何元素
up2.send(3) // 此时publisher会将up1第一次发出的元素:1与up2当前发出的元素:3一起发送给订阅者,订阅者收到(1, 3)
up2.send(4) // 此时publisher会将up1第二次发出的元素:2与up2当前发出的元素:4一起发送给订阅者,订阅者收到(2, 4)
}
ZIP Merge CombineLatest Concatenate 区别
ZIP: 两个一一对应(返回是个元组),不对应的值就会被忽略 / 任意一个失败就算失败,订阅就结束 / 任意一个结束就算结束
Merge: 返回是个value, 两个publisher按照发送顺序接收 / 任意一个失败就算失败,订阅就结束 / 两个都finish才算结束
CombineLatest: 返回元组, 各取最后一个发送的value组成一个元组 / 任意一个失败就算失败,订阅就结束 / 两个都finish才算结束
Concatenate: 按照顺序, 先接收 publisher, 等publisher1 发送finish, publisher2的数据才算开始 / 任意一个失败就算失败,订阅就结束
TryCatch
该发布者会将上游发出的failure事件,转成一个新的发布者,你可以在这个闭包中根据上游的具体的错误信息,返回不同的发布者,该闭包是一个可抛出异常的函数
func tryCatchDemo() {
let up = PassthroughSubject<Int, CustomError>()
// 该发布者会将上游发出的failure事件,转成一个新的发布者,你可以在这个闭包中根据上游的具体的错误信息,返回不同的发布者,该闭包是一个可抛出异常的函数
let publisher = Publishers.TryCatch<PassthroughSubject<Int, CustomError>, Just<Int>>(upstream: up) { err in
switch err {
case .fail:
throw CustomError.fail // 当上游的错误为fail时,抛出一个异常错误,该错误会发送给下游订阅者,并终止
case .timeout:
return Just(-1) // 当上游的错误为timeout时,返回一个Just(-1)的发布者,该发布者的元素-1,将发送给下游订阅者
case .empty:
return Just(0) // 当上游的错误为empty时,返回一个Just(0)的发布者,该发布者的元素0,将发送给下游订阅者
}
}
publisher.sink { state in
switch state {
case .finished:
print("完成")
case .failure(_):
print("失败")
}
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(3) // 订阅者收到:3
up.send(2) // 订阅者收到:2
up.send(completion: .failure(.fail)) // 此时publisher的闭包将被调用,并抛出一个错误,该错误会发送给订阅者,并终止
// up.send(completion: .failure(.timeout)) // 此时publisher的闭包将被调用,并返回一个Just(-1)的发布者,订阅者将收到:-1
// up.send(completion: .failure(.empty)) // 此时publisher的闭包将被调用,并返回一个Just(0)的发布者,订阅者将收到:0
}
TryMap
另外Apple还提供了一些实现Try前缀的发布者,这些发布者都存在一个闭包,这些闭包都是可抛出异常的函数,用于向订阅者发送Failure的消息
func mapTryDemo() {
let up = PassthroughSubject<Int, Never>()
// 该发布者是Map发布者的另一种实现方式,它的闭包是一个可抛出异常的函数,抛出的错误会发送给下游订阅者,并终止
let publisher = Publishers.TryMap(upstream: up) { val -> String in
if val == 0 { throw CustomError.empty }
return "value: \(val)"
}
publisher.sink { state in
switch state {
case .finished:
print("完成")
case .failure(let err):
switch err {
case CustomError.empty:
print("空错误")
case CustomError.timeout:
print("超时错误")
case CustomError.fail:
print("失败")
default:
print("其他错误")
}
}
} receiveValue: { val in
print("receive: \(val)")
}.store(in: &systemCancellables)
up.send(1)
up.send(0) // 此时闭包中会抛出CustomError.empty错误
}