Combine框架简介与使用
Combine 是 Apple 对函数响应式编程库的实现,类似于RxSwift,它实现了大部分的RxSwift中的功能,但也存在一部分没有实现的功能,这里有一个Combine与RxSwift功能对照表,点击查看。Combine是SwiftUI的核心库,SwiftUI中的大部分属性包装器,例如@State、@StateObject、@ObservedObject等等,都是采用Combine实现的。下面是Combine库的整体结构:
Comine库中最核心的就是Publisher(发布者)、Subscriber(订阅者)、Subscription(订阅契约),其中Subscription定义了订阅者需要发布者的多少条消息,并且可以通过Subscription取消订阅。
下面就是Publisher与Subscriber之间的交互时序图:
1、订阅者订阅发布者的消息:通过调用发布者的receiver(Subscriber)方法,告诉发布者有订阅者订阅消息。
2、发布者给订阅者一份订阅契约(Demand),订阅者可以通过这份契约向发布者索要消息,订阅者通过锲约的request(Demand)方法,向发布者索要消息,Demand可以指定索要多少条的消息,它的取值范围包括:unlimited(不限数量)、none(0条消息)、max(Int)(指定数量)。如果此时订阅者没有调用锲约的request(Demand)方法,或调用request时传入的Demand为none,发布者将不会发送消息给订阅者。
3、订阅者通过锲约request(Demand)向发布者索要消息
4、发布者根据锲约中Demand定义的消息数量,向订阅者发布消息,此时订阅者收到消息后,还可以修改契约,具体逻辑如下:
func request(_ demand: Subscribers.Demand){ currDemand = demand while currDemand > 0 { currDemand -= 1 currDemand += receive(1) // 在请求的条数基础上,增加订阅者返回的条数 } print("发送结束") } func receive(_ input: Int) -> Subscribers.Demand{ print("receive value: \(input)") return .none }
因为Demand实现了Comparable协议,并实现了加减等运算符,所以以上可以直接进行运算和比较
5、当发布者没有消息可以发送给订阅者 或 发送过程出错了,通过调用Subscriber.receive(Completion),通知订阅者。Completion是枚举,定义了枚举值包括:finished,failure(Failure),所以可以告诉订阅者,本次request是完成,还是失败,同时发布者会将契约取消(Subscription.cancel())。
接下来我们通过自己实现一个订阅者和发布者,来对发布与订阅之间的关系做个深刻的认识。
首先我们先来定义一个发布者,确保该发布者实现了Publisher协议,发布者拟定了一份契约,具体代码如下:
class MyPublisher: Publisher{ typealias Failure = Never typealias Output = String // 这是我们要发布的消息列表 var messageList: [String] deinit { Swift.print("MyPublisher deinit") } init(msgList: [String]) { messageList = msgList } // 接收订阅者的订阅方法 func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, String == S.Input { // 签署契约,同时向订阅者发送契约 let subscription = MySubscription(publisher: self, subscriber: subscriber) subscriber.receive(subscription: subscription) } // 定义一个契约 class MySubscription<S: Subscriber>: Subscription where S.Input == String{ var publisher: MyPublisher? var subscriber: S? private var currDemand = Subscribers.Demand.unlimited // 默认不限 deinit { cancel() Swift.print("MySubscription deinit") } // 锲约的初始化,需要发布者与订阅者两个人 init(publisher: MyPublisher, subscriber: S){ self.publisher = publisher self.subscriber = subscriber } // 订阅者通过该方法,向发布者索要消息 func request(_ demand: Subscribers.Demand) { currDemand = demand guard let _publisher = publisher, let _subscriber = subscriber else { return } var list = _publisher.messageList while currDemand > 0 { currDemand -= 1 if let msg = list.first { // 发送给订阅者消息 currDemand += _subscriber.receive(msg) list.removeFirst() }else{ // 消息已发送完毕 _subscriber.receive(completion: .finished) cancel() // 取消契约 break } } } // 解除契约 func cancel() { self.publisher = nil self.subscriber = nil } // 契约的唯一标示 var combineIdentifier = CombineIdentifier() } }
然后我们在实现订阅者,确保我们的订阅者实现Subscriber协议,因为订阅者也可以取消协议,所以我们的订阅者同时需要实现Cancellable协议,具体代码如下:
class MySubscriber: Subscriber, Cancellable{ typealias Input = String typealias Failure = Never typealias Receive = (String) -> Void typealias Finish = (Subscribers.Completion<Never>) -> Void private var receiveCallback: Receive // 接收消息回调 private var finishCallback: Finish // 完成回调 private var subscription: Subscription? deinit { print("MySubscriber deinit") } init(receive: @escaping Receive, finish: @escaping Finish) { receiveCallback = receive finishCallback = finish } // 接收发布者的契约 func receive(subscription: Subscription) { self.subscription = subscription // 向发布者索要消息 subscription.request(.unlimited) } // 发布者发送完毕 func receive(completion: Subscribers.Completion<Never>) { finishCallback(completion) } // 接收发布者的消息 func receive(_ input: String) -> Subscribers.Demand { receiveCallback(input) return .none } // 取消契约,订阅者有权取消订阅 func cancel() { subscription?.cancel() } }
下面我们实例化一个发布者,两个订阅者,这两个订阅者同时订阅同一个发布者,代码如下:
let msgList = ["第一条消息","第二条消息","第三条消息"] let publisher = MyPublisher(msgList: msgList) let subscriber1 = MySubscriber { msg in print("subscriber1 接收消息:\(msg)") } finish: { state in switch state { case .finished: print("subscriber1 接收完毕") } } let subscriber2 = MySubscriber { msg in print("subscriber2 接收消息:\(msg)") } finish: { state in switch state { case .finished: print("subscriber2 接收完毕") } } // subscriber1订阅发布者publisher publisher.receive(subscriber: subscriber1) // subscriber2订阅发布者publisher publisher.receive(subscriber: subscriber2)
run 输入如下:
subscriber1 接收消息:第一条消息
subscriber1 接收消息:第二条消息
subscriber1 接收消息:第三条消息
subscriber1 接收完毕
subscriber2 接收消息:第一条消息
subscriber2 接收消息:第二条消息
subscriber2 接收消息:第三条消息
subscriber2 接收完毕
MySubscriber deinit
MySubscription deinit
MySubscriber deinit
MySubscription deinit
MyPublisher deinit
从Combine库的整体结构来看,苹果为我们提供了一些内置的发布者和订阅者,接下来我逐一介绍一下这些发布者与订阅者的使用。
首先介绍订阅者,目前苹果为我们只提供了两个订阅者,分别是Sink、Assign、AnySubscriber。
AnySubscriber:是一个类型擦除的结构体,一般用于包装不想公开其详细信息的现有订阅者,当然你也可以通过继承该结构图,实现自定义的订阅者。
Sink:苹果提供一个用于订阅请求不限数量的简单订阅者,使用也很简单,如下:
var cancellables = [AnyCancellable]() let publisher = ["a", "b", "c"].publisher publisher.sink { str in print("receive: \(str)") } .store(in: &cancellables) // 或者 publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { str in print("receive: \(str)") } .store(in: &cancellables)
sink方法实际上是对Publisher协议扩展的默认实现。其内部实例化Sink对象,并进行订阅receive。该方法返回一个AnyCancellable实例,用于维护订阅者与发布者两者的生命周期,通过调用其cancel()来释放资源,AnyCancellable在deinit的时候会自动调用cancel()。
同时AnyCancellable提供了store方法,用于将AnyCancellable自己存储在指定的集合对象中。
Assign:一个简单的订阅者,它将接收到的元素分配给由键路径指示的属性。事例如下:
struct Book { var name: String } class Student{ var book: Book init(book: Book) { self.book = book } } let publisher = Just("《时间简史》") let book = Book(name: "《大卫》") let student = Student(book: book) _ = publisher.assign(to: \.book.name, on: student) print("book.name: \(student.book.name)")
这里需要注意一点,assign参数中的keyPath是ReferenceWritableKeyPath<Root, Input>类型的,这意味着在第二个参数on中的类型必须是class类型
接下来我们来介绍发布者,苹果实现了一些发布者,例如:AnyPublisher、CurrentValueSubject、PassthroughSubject等,让我们来一一介绍:
Publisher:分别是Published(属性包装器:@Published)、Optional、Result的内部类,他们分别是对这三种类型实现发布功能的默认实现。
AnyPublisher:类似于AnySubscriber,Publisher可以通过调用eraseToAnyPublisher方法,将自定义的发布者的类型进行擦除,该方法返回一个AnyPublisher类型的对象。
CurrentValueSubject:包装单个值并在值发生变化时发布新元素的发布者。案例如下:
var cancellables = [AnyCancellable]() let subject = CurrentValueSubject<Int, Never>(1) // subject在每次被订阅的时候,都会将当前的value发送给订阅者 subject.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } }, receiveValue: { value in print("recevie: \(value)") }) .store(in: &cancellables) // 当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)")
PassthroughSubject:用于向订阅者发布消息的发布者,该发布者与CurrentValueSubject不同之处在于,PassthroughSubject不会存储当前的消息,因此订阅者在订阅的时候,不会收到它之前发送的消息。案例如下:
var cancellables = [AnyCancellable]() let subject = PassthroughSubject<Int, Never>() // 还没有订阅者,此时发送了一条消息 subject.send(1) // 此时订阅了消息,但上面发送的消息不会收到,这就是与CurrenValueSubject不一样的地方 subject.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } }, receiveValue: { value in print("recevie: \(value)") }) .store(in: &cancellables) // 此时再次发送了一条消息,这时上面的订阅者将收到一条消息 subject.send(2) // 当我们发送finish消息后,上面的订阅者将收到一条finish的消息,此后发布者再次发送消息,该订阅者将不再收到任何消息 subject.send(completion: .finished) // 此时订阅者将不会收到消息3 subject.send(3)
Deferred:用于延迟创建发布者的发布者,即:在每次订阅者订阅该发布者时,Deferred都会调用其初始化时传入的闭包函数,该闭包函数返回一个发布者,而订阅者实际上是变相的订阅了这个闭包返回的发布者。案例如下:
// 注意:这里的cancellables必须被有效持有,因为下面的Future是异步的,所以在Future发送消息之前,AnyCancellable必须保持不被销毁状态,这里我定义成了一个全局变量 var cancellables = [AnyCancellable]() // 这里我们指定Deferred闭包返回的是Future类型的发布者,这里常被用于接口调用 let publisher = Deferred<Future<String, DataError>>{ // 在这个闭包中,创建一个Future的发布者,并返回 print("创建了一个Future发布者") return Future<String, DataError>{ promise in // 这里处理一个延迟的异步请求 DispatchQueue.global().asyncAfter(deadline: .now()+2) { // 返回数据 promise(.success("{'result': 0, 'data': {...}}")) // 或者返回一个错误 // promise(.failure(.HandlerError("请求失败"))) } } } // 这里开始第一个订阅者订阅Deferred发布者,此时Deferred会调用自己初始化时的闭包,创建一个发布者 publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): switch err { case .HandlerError(let msg): print("报错:\(msg)") } } } receiveValue: { str in print("返回数据: \(str)") } .store(in: &cancellables) // 这里开始第二个订阅者订阅Deferred发布者,此时Deferred会调用自己初始化时的闭包,创建一个发布者 publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): switch err { case .HandlerError(let msg): print("报错:\(msg)") } } } receiveValue: { str in print("返回数据: \(str)") } .store(in: &cancellables)
Future:最终只会生成单个值,然后发送完成或失败的发布者。即:该发布者只能发送一次消息。案例如下:
var cancellables = [AnyCancellable]() let publisher = Future<String, Error>{ promise in // 发送了第一条消息 promise(.success("同志们好!")) // 发送了第二条消息(由于该发布者只能发送一次消息,所以这个消息不会有人收到) promise(.success("同志们辛苦了!")) } // 第一个订阅者订阅了消息 publisher.sink { state in switch state { case .finished: print("完成1") case .failure(_): print("失败1") } } receiveValue: { msg in print("receive1: \(msg)") } .store(in: &cancellables) DispatchQueue.global().asyncAfter(deadline: .now()+3) { // 3秒后再次订阅了该发布者,发布者会将之前发布的消息再次发给这个订阅者 publisher.sink { state in switch state { case .finished: print("完成2") case .failure(_): print("失败2") } } receiveValue: { msg in print("receive2: \(msg)") } .store(in: &cancellables) }
Just:仅仅向每个订阅者发出一次消息,然后发送完成的发布者,这一点与Future很想,但他们的区别在于,Future可以决定何时向订阅者发布消息,而Just实例化时就决定了要发送的值,并且这个值不能改变。案例如下:
var cancellables = [AnyCancellable]() let publisher = Just(1) publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") } .store(in: &cancellables)
Just看似简单,其实也不简单,苹果为我们实现了很多有用的功能。如果Just的值实现了Equatable协议,那么两个Just可以通过 == 进行比较。
let publisher1 = Just(1) let publisher2 = Just(1) if publisher1 == publisher2 { print("publisher1 == publisher2") }else{ print("publisher1 != publisher2") }
Just同时为我们实现了很多高阶函数,让我们就像处理数组、字符串,等数据一样,处理Just,案例如下:
var cancellables = [AnyCancellable]() let publisher = Just("a,b,c,d") // 使用map高阶函数将Just的值转成字符串数组 publisher.map { $0.components(separatedBy: ",")} .sink { list in print("list: \(list)") } .store(in: &cancellables)
Empty:从不发布任何值并可选择立即完成的发布者,这种发布者一般用于没有值可发送,只发送完成的情况,或者不想发布任何消息的情况。案例如下:
var cancellables = [AnyCancellable]() // 这里我们初始化一个立即完成的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) // 这里我们初始化一个不会发送完成的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:立即以指定错误而终止的发布者,即:该发布者只会发布一条错误消息,然后终止。案例如下:
enum DataError: Error { case netError case otherError(String) } var cancellables = [AnyCancellable]() // 这里初始化一个指定错误信息的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:该发布者允许记录一系列输入和完成,以便稍后回放给每个订阅者。
var cancellables = [AnyCancellable]() // 初始化一个指定录制消息列表,并以finished结束的发布者 let output = ["消息1", "消息2", "消息3", "消息4"] let publisher = Record<String, DataError>(output: output, completion: .finished) publisher.sink { state in switch state { case .finished: print("完成") case .failure(let error): switch error { case .netError: print("报错:netError") case .otherError(let msg): print("报错:\(msg)") } } } 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 { state in switch state { case .finished: print("完成2") } } receiveValue: { str in print("receive2: \(str)") } .store(in: &cancellables)
ObservableObjectPublisher:从可观察对象中发布更改的发布者。即:只会发布一条空消息的发布者,并且该发布者不会完成,可以一直发送消息。这个发布者常用于SwifUI中,结合实现ObservableObject协议的类使用,用于观察类的属性值的变化。
var cancellables = [AnyCancellable]() // 初始化很简单 let publisher = ObservableObjectPublisher() // 订阅 publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { print("收到消息") } .store(in: &cancellables) // 发送消息,此时订阅者会收到消息,但不会收到完成的消息 publisher.send()
结合ObservableObject协议来使用,用过SwiftUI的同学肯定非常熟悉这个,我们看一下这个例子。
class People: ObservableObject{ var say: String? { didSet{ // 当say改变了,我们发布一个通知 self.objectWillChange.send() } } // @Published var say: String? } var cancellables = [AnyCancellable]() let people = People() people.objectWillChange.sink { print("people.say: \(people.say ?? "")") } .store(in: &cancellables) people.say = "同志们好!" people.say = "同志们辛苦了!"
以上当people实例属性say的值发生改变后,即:didSet中,调用了发布者objectWillChange.send()方法通知外部订阅者,这个objectWillChange就是ObservableObjectPublisher这个发布者。
在SwiftUI中我们也会集合属性包装器@Published来包装实现ObservableObject协议类的属性,这个属性包装器会在被修饰的属性值willSet后调用objectWillChange.send(),因此,在上面的案例中,如果你采用People类中的注释部分实现的话,订阅者收到的消息将会是:"","同志们好!"。
Published.Publisher:常用于观察class属性值发生变化的发布者,是属性包装器@Published下的发布者。案例如下:
class People { @Published var say: String? } let people = People() people.$say.sink { state in switch state { case .finished: print("完成") } } receiveValue: { say in print("receive: \(say ?? "")") } .store(in: &cancellables) people.say = "同志们好!" people.say = "同志们辛苦了!"
people.$say即Published下的Publisher,这里我们第一次订阅时,会立即收到当前people属性say的当前值的消息。之后每次say的值发生改变是,订阅者都会收到通知。
Publishers.AllSatisfy:这个发布者会发送一个Bool类型的值,并完成。这个Bool值表示所接收到的发布者的消息中,全部满足条件。它会订阅一个发布者,并将这个发布者所有的消息传递给一个闭包函数,这个闭包函数返回一个Bool,当闭包中返回false时,发布者其余发布的消息将忽略,此时会直接发送一条消息,其Bool为false,并执行finished。案例如下:
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 { state in switch state { case .finished: print("完成") } } receiveValue: { res in print("receive: \(res ? "true" : "false")") } .store(in: &cancellables) // ps是AllSatisfy发布者订阅的发布者,我们向AllSatisfy发送以下消息 ps.send("Asd") ps.send("Addd") ps.send("Adddewe") // 以上消息字符串都是以”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也将不再处理
Publishers.AssertNoFailure:断言发布者,该发布者会判断与之关联的发布者不会发送failure消息,一旦发送了failure消息,断言发布者将调用fatalError终止程序。(注意:这里不论是Debug还是Release都会调用fatalError导致程序终止)
// 初始化一个用于发布消息的发布者 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发送了一个failure消息,此时断言发布者会调用fatalError函数,程序终止 ps.send(completion: .failure(.netError)) ps.send(completion: .finished)
Publishers.MakeConnectable:根据指定Publisher生成一个实现ConnectablePublisher协议的发布者,该协议的发布者提供如下两个方法:connect() -> Cancellable和autoconnect() -> Autoconnect()。该发布者被订阅后,不会立即收到消息,仅当调用connect()方法后,订阅者才与当前发布者链接到一起,此时订阅者才能收到发布者发布的消息。案例如下:
let ps = PassthroughSubject<Int, Never>() // 我们通过MakConnectable创建了一个实现ConnectablePublisher协议的发布者,该发布者接收另一个发布者,用于与其订阅者进行连接 let publisher = Publishers.MakeConnectable(upstream: ps) // 此时我们订阅MakConnectable发布者,这里实际上是间接的订阅PassthroughSubject发布者。 publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } 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)
ConnectablePublisher类型的发布者,允许我们决定何时开始接收上游(upstream)发布者的消息。
Publishers.Autoconnect:该发布者初始化接收一个实现ConnectablePublisher协议的发布者,实现订阅时自动调用connect()方法。案例如下:
let ps = PassthroughSubject<Int, Never>() // 我们通过MakConnectable创建了一个实现ConnectablePublisher协议的发布者 let connectPublisher = Publishers.MakeConnectable(upstream: ps) // 这里我们初始化一个Autoconnect的发布者 let publisher = Publishers.Autoconnect(upstream: connectPublisher) // 此时订阅Autoconnect发布者,该发布者此时会为我们自动调用connect()使订阅者与上游发布者进行连接,即:与MakeConnectable发布者进行连接 publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") } .store(in: &cancellables) // 由于Autoconnect在我们订阅的时候,自动完成了connect操作,所以下面发送一条消息,订阅者会立即收到 ps.send(1)
Publishers.Breakpoint:用于实现断点调试的发布者,该发布者接收三个可选闭包,每个闭包返回Bool,当其中任意一个闭包返回true时,程序中断。案例如下:
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 { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") } .store(in: &cancellables) // 上游发布者开始发送消息 ps.send(1) ps.send(2) ps.send(3) // 至此以上消息不会产长断点,接下来发送4,此时的断点闭包返回true,程序将中断。 ps.send(4)
Publishers.Catch:用于捕获上游发布者发出的错误消息的发布者,并对上游发出的错误进行处理,返回一个正常的发布数据,作为异常处理。案例如下:
let ps = PassthroughSubject<String, DataError>() // 初始化一个Catch发布者,接收一个上游发布者,以及处理上游发布者发布错误消息的闭包,该闭包返回一个新的发布者 let publisher = Publishers.Catch(upstream: ps) { error -> Just<String> in // 捕获到上游发布者发出失败信号,使用一个新的发布者发送的值,发送给订阅者 Just("{'data': {}, 'state': 'fail'}") } // 订阅Catch发布者,该发布者会捕获上游发布者发出的failure publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") } .store(in: &cancellables) // 上游发布者正常发送消息 ps.send("{'data': {'name': 'drbox'}, 'state': 'success'}") // 上游发布者发送错误消息,此时Catch发布者会捕获到这个错误,并做处理 ps.send(completion: .failure(.other("处理异常")))
Publishers.Collect:一个将上游发布者发送的元素,存储到集合中,等到上游发布者发送完成后,该发布者将其集合中的全部元素一起发送给订阅者的发布者。案例如下:
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 一个将上游发布者发送的元素,存储到集合中,等到上游发布者发送完成后,该发布者将其集合中的全部元素一起发送给订阅者 let publisher = Publishers.Collect(upstream: ps) publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): switch err { case .netError: print("netError") case .other(let msg): print("出错:\(msg)") } } } 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消息
这里的缓存是针对订阅者缓存的,即:当有订阅者订阅时,此后发送的消息,将会针对该订阅者进行缓存,举个例子理解:
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 一个将上游发布者发送的元素,存储到集合中,并指定集合的最大容量 let publisher = Publishers.Collect(upstream: ps) // 无订阅者订阅之前,发送'p' ps.send("p") publisher.sink { state in switch state { case .finished: print("完成1") case .failure(let err): switch err { case .netError: print("netError1") case .other(let msg): print("出错1:\(msg)") } } } receiveValue: { list in print("receive1: \(list)") } .store(in: &cancellables) ps.send("a") // 订阅者1订阅之后,发送'a' publisher.sink { state in switch state { case .finished: print("完成2") case .failure(let err): switch err { case .netError: print("netError2") case .other(let msg): print("出错2:\(msg)") } } } 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
Publishers.CollectByCount:类似于Collect发布者,该发布者可以指定最大缓存个数,当缓存个数达到指定大小,该发布者将会把缓存的数据一起发送给订阅者。倘若上游发布者发送了完成或失败的消息后,即使缓存数量尚未到达上限,也会一起发送给订阅者,并发送完成或失败(失败的时候,不会发送任何值)。案例如下:
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 一个将上游发布者发送的元素,存储到集合中,并指定集合的最大容量 let publisher = Publishers.CollectByCount(upstream: ps, count: 2) publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): switch err { case .netError: print("netError") case .other(let msg): print("出错:\(msg)") } } } 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消息
这里的缓存最大上限,指的是订阅者收到消息的缓存最大上限,举个例子理解一下:
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 一个将上游发布者发送的元素,存储到集合中,并指定集合的最大容量 let publisher = Publishers.CollectByCount(upstream: ps, count: 2) publisher.sink { state in switch state { case .finished: print("完成1") case .failure(let err): switch err { case .netError: print("netError1") case .other(let msg): print("出错1:\(msg)") } } } receiveValue: { list in print("receive1: \(list)") } .store(in: &cancellables) ps.send("a") // 此时订阅者1缓存了一条'a' publisher.sink { state in switch state { case .finished: print("完成2") case .failure(let err): switch err { case .netError: print("netError2") case .other(let msg): print("出错2:\(msg)") } } } receiveValue: { list in print("receive2: \(list)") } .store(in: &cancellables) ps.send("b") // 此时订阅者1缓存中的数据为:['a', 'b'],由于达到上限,立即向订阅者1发送 // 因为'a'是在订阅者2订阅之前发送的,因此订阅者2缓存数据为:['b'],未达上限,不发送 ps.send("c") // 此时订阅者1缓存为:['c'] // 此时订阅者2缓存为:['b', 'c'],达上限,立即向订阅者2发送 // 因此,这个指定的上限,是针对订阅者的,指的是每个订阅者缓存的最大上限。
Publishers.CollectByTime:该发布者比CollectByCount发布者更加灵活,允许我们指定向下游发布者的发布策略,例如:按每多长时间发布,或者达到多少条后发送。案例如下:
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 指定发布策略,每5秒钟,采用主线程调度,向订阅者发送(有缓存的消息才去发送) let publisher = Publishers.CollectByTime(upstream: ps, strategy: .byTime(DispatchQueue.main, .seconds(5)), options: nil) publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } } receiveValue: { list in print("receive: \(list)") print("thread: \(Thread.current)") } .store(in: &cancellables) 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"],并且这里发送是在子线程,但是订阅者收到的是在策略定义中的主线程 }
我们还可以指定另一种策略,即:每个5秒钟发送一次,或缓存数达到2个发送一次,两者任意一个满足,都会触发发送。
// 上游发布者 let ps = PassthroughSubject<String, DataError>() // 指定发布策略,每5秒钟,采用主线程调度,或缓存数达到2个,向订阅者发送(有缓存的消息才去发送) let publisher = Publishers.CollectByTime(upstream: ps, strategy: .byTimeOrCount(DispatchQueue.main, .seconds(5), 2), options: nil) publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } } receiveValue: { list in print("receive: \(list)") } .store(in: &cancellables) ps.send("a") ps.send("b") ps.send("c") // 连续发送完了3条消息,根据发布者定义的策略,达到缓存上限2条后,立即触发发送给订阅者,订阅者将收到:["a", "b"] // "c"被留给下次发送,当发布周期到了,或满足了2条上限,"c"会被一起发送给订阅者
Publishers.CombineLatest:合并两个上游发布者的发布者,它将会把两个发布者的消息合并到一起,发送给订阅者。案例如下:
// 上游发布者1 let ps1 = PassthroughSubject<String, DataError>() // 上游发布者2 let ps2 = PassthroughSubject<String, DataError>() // 初始化一个合并发布者,将两个上游发布者的消息进行合并 let publisher = Publishers.CombineLatest(ps1, ps2) publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } } receiveValue: { val in // 合并后的结果以元祖的结构返回(ps1, ps2) print("receive ps1: \(val.0), ps2: \(val.1)") } .store(in: &cancellables) // 案例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发布者,并向下游发出错误消息
此外还包括另外两个与其功能一样的发布者,CombineLatest3(合并3个发布者),CombineLatest4(合并4个发布者)
Publishers.CompactMap:该发布者会将上游发送的全部消息,都交给其闭包进行过滤,一旦闭包返回nil,该消息将不会发送给订阅者。案例如下:
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: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in print("receive: \(val)") }) .store(in: &cancellables) ps.send("a") ps.send("nil") ps.send("null") ps.send("b") // 由于"nil"与"null"会被CompactMap发布者过滤掉,所以,订阅者只会收到"a","b"
Publishers.Comparison:该发布者会对上游发送的消息进行排序后,保留其中一条消息,当上游发布者发送finished后,该发布者会将其保留的值发送给下游订阅者。案例如下:
let ps = PassthroughSubject<String, DataError>() // 该发布者会对上游发布的消息进行重新排列,但最终只会将最后一次的结果,发送给(仅当上游发布者发送完成时)下游订阅者。该订阅者会将上游发送的消息转给闭包,然后通过闭包返回值进行排序,最终只保留一条记录,当上游发布者发送finished时,会将这条记录发送给下游订阅者。 let publisher = Publishers.Comparison(upstream: ps) { val1, val2 in // 第一个参数:上游发出的最新消息 // 第二个参数:之前缓存的消息 print("val1: \(val1), val2: \(val2)") // 返回true,发布者会保留第一个参数的值 // 返回false,发布者会保留第二个参数的值 return true } publisher.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in print("receive: \(val)") }) .store(in: &cancellables) ps.send("a") // 上游发送了一条"a",此时由于仅有一条,无法比较,所以不会执行闭包,仅对该条消息进行缓存 ps.send("b") // 再次发送了一条,发布者会将缓存的记录与这条记录发送给闭包,进行比较,发布者会根据规则,更新缓存的值 ps.send(completion: .finished) // 此时发布者会将其保留的最后一条消息发送给订阅者
Publishers.Concatenate:一个将两个上游发布者链接到一起的发布者,该发布者会先将prefix发出的消息转发给订阅者,当prefix终止发送消息后,开始将suffix发布者最新发出的消息转发给订阅者。当prefix发布者发送failure后,Concatenate发布者同时将错误发送给订阅者,并终止发送,此后suffix发送的任何消息都将不会被转发。案例如下:
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: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in print("receive: \(val)") }) .store(in: &cancellables) 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发布者也将终止。
Publishers.Contains:一个判断上游发布者发送的消息中是否包含指定值的发布者。下面这个发布者,判断上游发布者发送的消息中,是否包含"b",如果包含,Contains发布者会立即发送下游订阅者,并终止,消息值为:true,倘若不包含,Contains将不会发出任何消息,直到上游发布者终止了发送,订阅者将收到值为false的消息。案例如下:
let ps = PassthroughSubject<String, DataError>() // 一个判断上游发布者发送的消息中是否包含指定值的发布者。下面这个发布者,判断上游发布者发送的消息中,是否包含"b",如果包含,Contains发布者会立即发送下游订阅者,并终止,消息值为:true,倘若不包含,Contains将不会发出任何消息,直到上游发布者终止了发送,订阅者将收到值为false的消息。 let publisher = Publishers.Contains(upstream: ps, output: "b") publisher.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in // val是Bool类型值 print("receive: \(val ? "true" : "false")") }) .store(in: &cancellables) ps.send("a") ps.send("c") // 以上发送的消息中,都不值"b",因此订阅者不会收到任何消息,倘若此时ps终止发送,Contains将发送给订阅者false // ps.send(completion: .finished) ps.send("b") // 由于此次发送的消息为"b",使得Contains条件成立,Contains发布者会立即向订阅者发送true,并终止
Publishers.ContainsWhere:该发布者与Contains发布者功能一样,是对Contains的补充,是否包含的条件由一个闭包来决定。案例如下:
let ps = PassthroughSubject<String, DataError>() let publisher = Publishers.ContainsWhere(upstream: ps) { val in val == "b" } publisher.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { res in // val是Bool类型值 print("receive: \(res ? "true" : "false")") }) .store(in: &cancellables) ps.send("a") ps.send("c") // 以上发送的消息中,都不值"b",因此订阅者不会收到任何消息,倘若此时ps终止发送,Contains将发送给订阅者false // ps.send(completion: .finished) ps.send("b") // 由于此次发送的消息为"b",使得Contains条件成立,Contains发布者会立即向订阅者发送true,并终止
Publisher.Count:该发布者用于统计上游发布者在终止发送后,发送的消息总数。案例如下:
let ps = PassthroughSubject<String, DataError>() // 该发布者用于统计上游发布者在终止发送后,发送的消息总数 let publisher = Publishers.Count(upstream: ps) publisher.sink(receiveCompletion: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { count in print("receive: \(count)") }) .store(in: &cancellables) ps.send("a") ps.send("b") ps.send("c") ps.send(completion: .finished) // 此时向订阅者发送一个数子3
Publishers.Debounce:按照一定频率转发上游消息的发布者,每次接收到新数据,都会重新开启一个新的时间窗口,同时取消之前的时间窗口。该发布者指定一个发送的时间间隔dueTime,以及一个发送任务的调度者scheduler。这里指定每隔2秒钟,在主线程中,向订阅者发送上游发送的消息,倘若此时上游未发送任何消息,Debounce将不会做任何转发。常用于输入框输入文字,进行联想搜索,避免频繁发起请求。案例如下:
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: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in print("receive: \(val)") }) .store(in: &cancellables) 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" }
Publishers.Throttle:一个按照固定时间间隔发送消息的发布者,它可以指定在一段时间间隔内收到的消息中,选择第一条还是最后一条数据发送给订阅者。(当publisher发送的数据刚好在时间窗口边缘的时候,结果是不确定的)。与Debounce对比:Throttle按照固定的顺序排列时间窗口,在时间窗口的结尾处发送数据,而Debounce每次接收到新数据,都会重新开启一个新的时间窗口,同时取消之前的时间窗口。例如:如果我在2秒内疯狂点击按钮,时间间隔时长设置为0.5秒,那么throttle
可以发送4次数据,而debounce
不会发送数据,只有当我停止点击0.5秒后,才会发送一次数据。案例如下:
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: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { val in print("receive: \(val)") }) .store(in: &cancellables) 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) }
以上打印日志如下:(latest如果设置为false,输出将会是:1、4、6)
receive: 3 receive: 5 receive: 7
Publisher.Delay:该发布者会将上游发布者发送的消息延迟指定时间后发送给下游订阅者。例如,Delay规定延迟1秒发送,上游发布者第一次发送消息是在0.2秒时发送,那么订阅者会在1.2秒后收到。案例如下:
// 一个每隔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: { state in switch state { case .finished: print("完成") case .failure(_): print("报错") } }, receiveValue: { date in print("receive: \(date),当前时间: \(Date())") }) .store(in: &cancellables) print("当前时间:\(Date())")
输入日志如下:
当前时间:2021-07-16 03:19:59 +0000 receive: 2021-07-16 03:20:00 +0000,当前时间: 2021-07-16 03:20:05 +0000 receive: 2021-07-16 03:20:01 +0000,当前时间: 2021-07-16 03:20:06 +0000 receive: 2021-07-16 03:20:02 +0000,当前时间: 2021-07-16 03:20:07 +0000 receive: 2021-07-16 03:20:03 +0000,当前时间: 2021-07-16 03:20:08 +0000 receive: 2021-07-16 03:20:04 +0000,当前时间: 2021-07-16 03:20:09 +0000 receive: 2021-07-16 03:20:05 +0000,当前时间: 2021-07-16 03:20:10 +0000
Publishers.Timeout:用于实现超时任务的发布者。
enum CustomError: Error { case timeout case other } var cancelables = [AnyCancellable](); 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) let cancelable = 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)") } cancelables.append(cancelable) DispatchQueue.main.asyncAfter(deadline: .now()+2) { print("发送消息") up.send(1) // 如果超时了,这里发出的消息,订阅者将无法收到 };
Publishers.Decode:一个解码发布者者,用于将上游发布者消息,通过指定的解码器进行解码,转成指定类型的消息,发送给订阅者。案例如下:
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: &cancellables) let data = """ {"name": "drbox", "age": 25} """.data(using: .utf8) ps.send(data!) // 上游发布者,发送一个json数据。
Publishers.Encode:一个编码发布者,用于将上游发布者消息,通过指定的编码器进行编码,转成指定类型的消息,发送给订阅者。案例如下:
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: &cancellables) let info = UserInfo(name: "drbox", age: 25) ps.send(info) // 上游发布者,发送一条消息 // 订阅者会收到:receive: {"name":"drbox","age":25}
Publishers.Drop:在发布元素之前忽略指定数量的元素的发布者。
let up = PassthroughSubject<Int, Never>(); // 该发布者会将上游发布者发出指定数量的消息过滤掉,并在之后的发送中向下游订阅者转发消息 let publisher = Publishers.Drop(upstream: up, count: 4); 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(3) up.send(4) // 上面指定发出4次之后的消息,将被转发,即:从这里为止,之后再次发送的消息,订阅者将会收到 up.send(5) up.send(6) up.send(7)
Publishers.DropUntilOutput:该发布者忽略来自上游发布者的元素,直到接收到来自第二个发布者的元素。
let up = PassthroughSubject<Int, Never>() let other = PassthroughSubject<Int, Never>() // 该发布者,它忽略来自上游发布者的元素,直到接收到来自第二个发布者的元素。 let publisher = Publishers.DropUntilOutput(upstream: up, other: other) 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(3) // 忽略上游发布者发出的1、2、3 other.send(4) // 直到other发布者发出元素(其发出的元素,不会被订阅者收到),注意:这里如果发送的是finish或failure消息,那么publisher将被终止发送 // 下面的上游发布者发出的5、6、7将被订阅者收到 up.send(5) up.send(6) up.send(7)
Publishers.DropWhile:该发布者,它忽略来自上游发布者的元素,直到闭包返回false,只要出现一次false,后面无论是否出现true,发出的元素都不会忽略
let up = PassthroughSubject<Int, Never>() // 该发布者,它忽略来自上游发布者的元素,直到闭包返回false let publisher = Publishers.DropWhile(upstream: up) { val in val < 3 } 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) // 闭包判断,当上游发布者发送的元素值<3时,都将被过滤掉,因此:1、2不会被收到 up.send(3) // 以下元素值>3,将会发送给订阅者 up.send(4) up.send(5)
Publishers.Filter:该发布者,将通过一个闭包来过滤上游发布者发送的元素,当闭包返回true时,才会发送给订阅者。
let up = PassthroughSubject<Int, Never>() // 该发布者,将通过一个闭包来过滤上游发布者发送的元素,当闭包返回true时,才会发送给订阅者 let publisher = Publishers.Filter(upstream: up) { $0 >= 3 } 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) // 闭包判断,当上游发布者发送的元素值>=3时,才会向订阅者发布数据,因此,1、2不会发送给订阅者 up.send(3) // 以下元素值>=3,将会发送给订阅者 up.send(4) up.send(5)
Publishers.First:该发布者,会在上游发出第一个元素后,立即终止发送。
let up = PassthroughSubject<Int, Never>() // 该发布者,会在上游发出第一个元素后,立即终止发送 let publisher = Publishers.First(upstream: up) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("recive: \(val)") } cancelables.append(cancelable) up.send(1) // 当上游发布者,发出1后,随后publisher会发出finish消息,此后上游发出的元素,订阅者将无法收到 up.send(2) up.send(3)
Publishers.FirstWhere:该发布者,会在上游发出的元素中,第一个满足闭包条件的元素发送给订阅者,并终止。
let up = PassthroughSubject<Int, Never>() // 该发布者,会在上游发出的元素中,第一个满足闭包条件的元素发送给订阅者,并终止 let publisher = Publishers.FirstWhere(upstream: up) { $0 >= 3 } 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(3) // 此时,满足闭包条件,订阅者会收到3,publisher同时会发出finish消息,此后上游发出的消息,都不会发送给订阅者。 up.send(4) up.send(5)
Publishers.FlatMap:该发布者,会将上游发布者发送的元素,交给闭包,该闭包返回一个新的发布者,下游订阅者会收到该新的发布者发送的元素(一般用于将接口返回的data,转成一个Model)
let up = PassthroughSubject<Int, Never>() // 该发布者,会将上游发布者发送的元素,交给闭包,该闭包返回一个新的发布者,下游订阅者会收到该新的发布者发送的元素 let publisher = Publishers.FlatMap(upstream: up, maxPublishers: .max(1)) { val in return Just("value: \(val)") // 这里返回一个Just的发布者,该发布者将上游发出的Int类型的元素,转成一个String类型,发送给下游订阅者 } let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("recive: \(val)") // 此时订阅者收到的val为:"value: 1"和"value: 2" } cancelables.append(cancelable) up.send(1) up.send(2)
Publishers.HandleEvents:该发布者,用于hook上游发布者的订阅、请求、send、取消相关操作
let up = PassthroughSubject<Int, Never>() // 该发布者,用于hook上游发布者的订阅、请求、send、取消相关操作 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() }
Publishers.IgnoreOutput:该发布者会忽略上游发布者发出的任何元素,而对发出的completion事件不会被忽略
let up = PassthroughSubject<Int, CustomError>() // 该发布者会忽略上游发布者发出的任何元素,而对发出的completion事件不会被忽略 let publisher = Publishers.IgnoreOutput(upstream: up) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): print("报错:\(err)") } } receiveValue: { val in // 注意:IgnoreOutput.Output为Never,所以这里的回调将不会被调用 } cancelables.append(cancelable) // 该发布者会阻止上游发出的元素,不让订阅者收到 up.send(1) up.send(2) // 订阅者只能收到,上游发布者发出completion事件 up.send(completion: .finished) // up.send(completion: .failure(.other))
Publishers.Last:该发布者只会将上游发布者发出completion事件后,之前发出的最后一个元素,发送给下游订阅者
let up = PassthroughSubject<Int, Never>() // 该发布者只会将上游发布者发出completion事件后,之前发出的最后一个元素,发送给下游订阅者 let publisher = Publishers.Last(upstream: up) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") } cancelables.append(cancelable) up.send(1) up.send(2) up.send(3) up.send(4) // 此时上面发出的元素,订阅者不会收到,直到下面发出completion事件后,订阅者将会收到4 up.send(completion: .finished)
Publishers.LastWhere:该发布者只会将上游发布者发出completion事件后,之前发出的最后一次满足闭包条件的元素,发送给下游订阅者
let up = PassthroughSubject<Int, Never>() // 该发布者只会将上游发布者发出completion事件后,之前发出的最后一次满足闭包条件的元素,发送给下游订阅者 let publisher = Publishers.LastWhere(upstream: up) { val in print("执行闭包:\(val)") return val < 4 } let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") } cancelables.append(cancelable) up.send(1) // 1 < 4,满足闭包条件 up.send(2) // 2 < 4,满足闭包条件 up.send(3) // 3 < 4,满足闭包条件(最后一个满足条件的元素) up.send(4) // 不满足闭包条件 // 此时上面发出的元素,订阅者不会收到,直到下面发出completion事件后,订阅者将会收到3 up.send(completion: .finished)
Publishers.MakeConnectable:该发布者是一个满足ConnectablePublisher协议的发布者,他会连接上游发布者,当下游订阅者订阅该发布者时,若订阅者想收到上游发出的元素时,你需要调用该发布者的connect()方法来连接上游发布者。
let up = PassthroughSubject<Int, Never>() // 该发布者是一个满足ConnectablePublisher协议的发布者,他会连接上游发布者,当下游订阅者订阅该发布者时,若订阅者想收到上游发出的元素时,你需要调用该发布者的connect()方法来连接上游发布者。 let publisher = Publishers.MakeConnectable(upstream: up) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") } cancelables.append(cancelable) up.send(1) up.send(2) // 在没有调用连接上游方法时,1、2将被忽略 let cancel = publisher.connect() // 此后上游发出的元素,将被订阅者收到 up.send(3) up.send(4)
Publishers.Map:该发布者与FlatMap类似,同样会将上游发布者发出的每个元素,经过一个闭包,将该元素转换成其他类型
let up = PassthroughSubject<Int, Never>() // 该发布者与FlatMap类似,同样会将上游发布者发出的每个元素,经过一个闭包,将该元素转换成其他类型 let publisher = Publishers.Map(upstream: up) { "{value: \($0)}" } let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") // 此时订阅者收到的元素类型为:String } cancelables.append(cancelable) up.send(1) up.send(2) up.send(3) up.send(4)
Publishers.MapError:该发布者与FlatMap、Map功能类似,区别在于该发布者只会对上游发出的failure进行转换,它会将上游发出的failure交给一个闭包做转换,该闭包可以返回一个新的类型的failure
let up = PassthroughSubject<Int, Custom1Error>() // 该发布者与FlatMap、Map功能类似,区别在于该发布者只会对上游发出的failure进行转换,它会将上游发出的failure交给一个闭包做转换,该闭包可以返回一个新的类型的failure let publisher = Publishers.MapError(upstream: up, { (err: Custom1Error) -> Custom2Error in switch err { case let .fail(msg): print("上游发布者报错了:\(msg)") } return .fail // 对于下游订阅者来说,它不想知道错误具体的内容,因此,这里我们返回给下游订阅者一个Custom2Error类型的错误 }) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") case .failure(let err): // 这里的err类型不再是上游发布者的failure类型Custom1Error,而是闭包所转换后的Custom2Error类型 switch err { case .fail: print("失败") } } } receiveValue: { val in print("receive: \(val)") } cancelables.append(cancelable) up.send(1) up.send(2) up.send(3) up.send(completion: .failure(.fail("因为某些原因,我这里不能再发送元素了")))
Publishers.MapKeyPath、Publishers.MapKeyPath2、Publishers.MapKeyPath3:这三个发布者无可用的构造函数,也就是说我们无法对其直接初始化。所以我们在使用这三个发布者的时候,只能通过Apple为我们提供的Publisher扩展函数map函数来使用。
struct User { var name: String var age: Int var ID: String } let up = PassthroughSubject<User, Never>() // MapKeyPath let publisher = up.map(\.name) let cancelable = publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive User.name: \(val)") } cancelables.append(cancelable) // MapKeyPath2 let publisher1 = up.map(\.name, \.age) let cancelable1 = publisher1.sink { val in // val是上面publisher1指定的两个keyPath值的元祖 print("receive User.name: \(val.0), User.age: \(val.1)") } cancelables.append(cancelable1) // MapKeyPath3 let publisher2 = up.map(\.name, \.age, \.ID) let cancelable2 = publisher2.sink { val in // val是上面publisher2指定的三个keyPath值的元祖 print("receive User.name: \(val.0), User.age: \(val.1), User.ID: \(val.2)") } cancelables.append(cancelable2) up.send(User(name: "drbox", age: 25, ID: "1110001100110011"))
Publishers.MeasureInterval:一个用于测量上游发布者发送每个元素的间隔时间的发布者
let up = PassthroughSubject<String, Never>() // 一个用于测量上游发布者发送每个元素的间隔时间的发布者 let publisher = Publishers.MeasureInterval(upstream: up, scheduler: DispatchQueue.main) let cancelable = publisher.sink { interval in print("receive: \(interval.magnitude)") } cancelables.append(cancelable) 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") }
打印结果:
receive: 1094784843 receive: 2905751820 receive: 2999943350
Publisher.Merge:一个将两个上游发布者合并的发布者,使得下游订阅者可以收到两个上游发布者的消息。以下的发布者与之功能类似
Publishers.Merge3、Publishers.Merge4、Publishers.Merge5、Publishers.Merge6......Publishers.Merge8以及Publishers.MergeMany(该构造函数接收一个可变参数,用于支持合并更多的上游发布者)
let up1 = PassthroughSubject<Int, Never>() let up2 = PassthroughSubject<Int, Never>() // 一个将两个上游发布者合并的发布者,使得下游订阅者可以收到两个上游发布者的消息 let publisher = Publishers.Merge(up1, up2) let cancelable = publisher.sink { val in print("receive: \(val)") // 这里既可以收到up1的消息,也可以收到up2的消息 } cancelables.append(cancelable) up1.send(1) up2.send(2) up2.send(3) up1.send(4)
Publishers.Multicast:该发布者会通过闭包创建的Subject,向多个订阅者发送消息,注意:该发布者实现了ConnectablePublisher协议,所以订阅者若要能够在上游发出消息后,收到该条消息,则必须在发出该条消息前调用connect()
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: &cancelables) // 再次发生订阅时,publisher的闭包将不被调用 publisher.sink { val in print("receive2: \(val)") }.store(in: &cancelables) let cancel = publisher.connect() // 这里的作用是让publisher订阅上游发布者up,此时会执行上游发布者的闭包,也就是请求接口操作。这个调用放在这里的目的,是为了确保上面两个订阅者都能够收到上游发出的消息 // 这样一来,我们就确保上面两个订阅者订阅时,只发生一次接口调用
Publishers.Output:该发布者指定一个范围,规定只将上游发布者发出的元素序列,或次序范围内的元素,发送给订阅者
let up = PassthroughSubject<String, Never>() // 该发布者指定一个范围,规定只将上游发布者发出的元素序列,或次序范围内的元素,发送给订阅者 let publisher = Publishers.Output(upstream: up, range: 0..<4) publisher.sink { val in print("receive: \(val)") }.store(in: &cancelables) // 由于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")
Publishers.PrefixUntilOutput:该发布者会将第一个上游发布者发出的元素,发送给下游订阅者,直到第二个发布者发出元素时,该发布者终止发送
let up1 = PassthroughSubject<Int, Never>() let up2 = PassthroughSubject<Int, Never>() // 该发布者会将第一个上游发布者发出的元素,发送给下游订阅者,直到第二个发布者发出元素时,该发布者终止发送 let publisher = Publishers.PrefixUntilOutput(upstream: up1, other: up2) publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up1.send(1) up1.send(2) // 以上订阅者会收到,1、2 up2.send(3) // 直到第二个发布者发出元素,此时publisher会发出finish,终止发送。订阅者并不会收到3 // up2.send(completion: .finished) // 如果此时第二个发布者发出completion消息,并不会终止publisher发送消息 up1.send(4)
Publishers.PrefixWhile:该发布者会将上游发出的每个元素,交给一个闭包函数,闭包返回true,则该元素会发送给订阅者,如果返回false,将终止发送
let up = PassthroughSubject<Int, Never>() // 该发布者会将上游发出的每个元素,交给一个闭包函数,闭包返回true,则该元素会发送给订阅者,如果返回false,将终止发送 let publisher = Publishers.PrefixWhile(upstream: up) { $0 < 3 } publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) // 1<3,满足闭包条件,订阅者会收到1 up.send(2) // 2<3,满足闭包条件,订阅者会收到2 up.send(3) // 3==3,不满足闭包条件,publisher将终止发送,订阅者不会收到3 up.send(4)
Publishers.Print:该发布者会打印上游发布者所有的行为日志,你可以指定一个输出日志的前缀,用于快速定位日志,你还可以指定一个用于输出日志的对象
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: &cancelables) up.send("😁") up.send("😺") up.send("🥰")
指定ASCIIPrint对象的日志打印情况如下:
[log]: receive subscription: (PassthroughSubject)
[log]: request unlimited
[log]: receive value: (\u{0001F601})
receive: 😁
[log]: receive value: (\u{0001F63A})
receive: 😺
[log]: receive value: (\u{0001F970})
receive: 🥰
未指定ASCIIPrint对象的日志打印情况如下:
[log]: receive subscription: (PassthroughSubject)
[log]: request unlimited
[log]: receive value: (😁)
receive: 😁
[log]: receive value: (😺)
receive: 😺
[log]: receive value: (🥰)
receive: 🥰
Publishers.ReceiveOn:该发布者指定上游发布者,在指定调度队列中执行发送任务,这里指定主队列
let up = PassthroughSubject<Int, Never>() // 该发布者指定上游发布者,在指定调度队列中执行发送任务,这里指定主队列 let publisher = Publishers.ReceiveOn(upstream: up, scheduler: DispatchQueue.main, options: nil) publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val),thread:\(Thread.current)") // 上游发布者无论在主队列还是在全局队列中调用send发送元素,这里始终都在主队列中回调 }.store(in: &cancelables) up.send(1) DispatchQueue.global().async { print("curr thread: \(Thread.current)") up.send(2) }
Publishers.Reduce:一个归纳发布者,它会将上游发布者发送的全部元素,经过一个闭包,该闭包的返回值作为下一次闭包调用的第一个参数,直到上游发布者发出completion事件后,将最终的结果发送给下游订阅者
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 // 这里对上游发送的值进行累加运算 } publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) // 闭包返回: 0+1 up.send(2) // 闭包返回: 1+2 up.send(3) // 闭包返回: 3+3 up.send(completion: .finished) // publisher发送最终的累加元素值:3+3
Publishers.RemoveDuplicates:该发布者用于对上游发布者两次发出的值进行去重,上游发布者第一次发出第一个元素时,不会执行闭包。当第二次及以上每发出一个元素都会经过闭包。该闭包第一个参数为上游发布者最后一次发出的元素,第二个参数为最新发出的元素。该闭包返回一个bool,返回true,表示上游最新发出的元素不会发送给下游订阅者。反之,false会发送给下游订阅者。
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 } publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) // 第一次发出元素,不经过闭包,订阅者收到1 up.send(2) // 第二次发出元素,闭包第一个参数为最后一次发给下游订阅者的值:1,第二个参数为最新元素值:2,由于两个值导致闭包返回false,订阅者收到:2 up.send(2) // 第三次发出元素,闭包第一个参数为:2,第二个参数为:2,由于两个值导致闭包返回true,不发送给下游订阅者 up.send(2) // 不发送 up.send(3) // 第五次发出元素,闭包第一个参数为:2,第二个参数为:3,由于两个值导致闭包返回false,订阅者收到:3 up.send(4)
Publishers.ReplaceEmpty:该发布者会在上游发布者未发出过任何元素时,而发出completion事件时,向下游发布者发出一个默认值:output,并终止
let up = PassthroughSubject<Int, Never>() // 该发布者会在上游发布者未发出过任何元素时,而发出finish事件时,向下游订阅者发出一个默认值:output,并终止 let publisher = Publishers.ReplaceEmpty(upstream: up, output: 0) publisher.sink { state in switch state { case .finished: print("完成") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) // up.send(1) up.send(completion: .finished) // 在这之前未发出过任何元素,publisher将会向下游发布者发出一个指定的默认值:0
Publishers.ReplaceError:该发布者会在上游发布者发出failure事件时,向下游订阅者发出一个默认值:output,并终止
let up = PassthroughSubject<Int, CustomError>() // 该发布者会在上游发布者发出failure事件时,向下游订阅者发出一个默认值:output,并终止 let publisher = Publishers.ReplaceError(upstream: up, output: 0) publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) up.send(completion: .failure(.fail)) // 上游发出了failure事件,此时publisher会向下游订阅者发出一个默认值:0,并终止
Publishers.Retry:一个重试发布者,它会在上游发布者发出failure事件时,再次重新尝试订阅上游发布者,这里指定重试次数为:3(一般用于接口调用失败后重试)
enum CustomError: Error { case fail } var cancelables = [AnyCancellable]() 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 { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables)
执行结果日志:
发送接口请求 发送接口请求 发送接口请求 receive: 1 完成
Publishers.Scan:一个扫描发布者,它会对上游发布者发出的每个元素,经过一个闭包扫描,该闭包返回值,会立即发送给下游订阅者。闭包的第一个参数为:上游发布者最后一次发出的元素,第二个参数为:最新发出的元素。
该发布者类似于Publishers.Reduce,区别就是Scan会将上游发布者每个发出的元素经过闭包后,发送给订阅者;而Reduce虽然也是每次都会经过闭包,但仅当上游发布者发出finish事件后,将闭包最后一次运算的结果发给订阅者。简单点就是:Scan:订阅者会收到多次消息;Reduce:订阅者仅仅会收到一次消息。
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 { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) // 闭包返回:0+1,订阅者收到:1 up.send(2) // 闭包返回:1+2,订阅者收到:3 up.send(3) // 闭包返回:3+3,订阅者收到:6
Publishers.Sequence:一个将元素序列逐一发送给订阅者的发布者
// 一个将元素序列逐一发送给订阅者的发布者 let publisher = Publishers.Sequence<[Int], Never>(sequence: [1, 2, 3, 4, 5, 6]) publisher.sink { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { val in print("receive: \(val)") }.store(in: &cancelables)
执行结果:
receive: 1 receive: 2 receive: 3 receive: 4 receive: 5 receive: 6 完成
Sequence同样实现了序列相关的操作,并将操作结果转成一个发布者的形式,包括:allSatisfy、append、contains、count、map等
// 一个将元素序列逐一发送给订阅者的发布者 let publisher = Publishers.Sequence<[Int], Never>(sequence: [1, 2, 3, 4, 5, 6]) publisher.allSatisfy { val in // 遍历所有元素,直到返回false或遍历结束,终止遍历,并向下游订阅者发送一个元素,元素类型为该闭包返回的bool类型 print("val: \(val)") return val < 5 }.sink { satisfy in // allSatisfy发布者判断序列中是否全部满足闭包表达式的条件,由于序列的最后一个元素为6,不满足条件,因此satisfy结果为:false print("结果:\(satisfy ? "满足条件" : "不满足条件")") }.store(in: &cancelables) 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: &cancelables) publisher.append(7, 8, 9).sink { val in // 还可以利用可变参数方式,向当前序列发布者的序列中追加元素 print("可变参数append after val: \(val)") }.store(in: &cancelables) publisher.append([7, 8, 9]).sink { val in // 仍然可以将一个序列的元素全部追加到当前序列发布者的序列中 print("序列参数append after val: \(val)") }.store(in: &cancelables) publisher.collect().sink { list in // 将当前序列发布者中的元素,以序列的形式一次性发送给订阅者 for val in list { print("list val: \(val)") } }.store(in: &cancelables) publisher.prepend(7, 8, 9).sink { val in // 将可变参数中的值,添加到当前序列发布者的序列之前 print("prepend val: \(val)") // 最终序列为:7、8、9、1、2、3、4、5、6 }.store(in: &cancelables) publisher.output(at: 0).sink { val in // 取出当前序列发布者的序列中的第0个下标的元素 print("element at 0: \(val)") }.store(in: &cancelables) publisher.contains(2).sink { has in print("contains \(has ? "包含" : "不包含") 2") }.store(in: &cancelables) publisher.count().sink { count in print("当前序列发布者的序列元素个数:\(count)") }.store(in: &cancelables)
Publishers.SetFailureType:该发布者用于修改上游发布者的Failure类型,被修改的上游发布者的原Failure类型必须为Never类型
let up1 = PassthroughSubject<Int, Never>() let up2 = PassthroughSubject<String, CustomError>() // 该发布者用于修改上游发布者的Failure类型,被修改的上游发布者的原Failure类型必须为Never类型 let publisher = Publishers.SetFailureType<PassthroughSubject<Int, Never>, CustomError>(upstream: up1) // 下面的操作符函数告诉了我们,为什么需要SetFailureType发布者,我们知道combineLatest的作用是将两个发布者发送的元素进行合并后统一发送给下游订阅者,这个操作有个前提,那就是两个发布者的Failure类型要相同。 // 因此我们才有了上面SetFailureType发布者,它将up1的Failure类型从Never改成了CustomError(注意:这里实际上并不是真正的修改其Failure类型,而是通过SetFailureType将上游包装了起来) publisher.combineLatest(up2).sink { state in switch state { case .finished: print("完成") case .failure(_): print("失败") } } receiveValue: { tupleVal in print("up1: \(tupleVal.0), up2: \(tupleVal.1)") }.store(in: &cancelables) up1.send(1) // 由于up2尚未发送过元素,因此需要等待up2发送元素后,才会将这两个元素合并成一个元祖,一起发送给订阅者 up2.send("One") // 此时订阅者收到消息:(1, "One") up1.send(2) up2.send(completion: .failure(.fail))
输出执行结果:
up1: 1, up2: One up1: 2, up2: One 失败
Publishers.Share:一个共享订阅信息的发布者,它的作用与Multicast是一样的,实际上Share就是Multicast与PassthroughSubject结合autoconnect()操作的一个组合(目的也是为了实现一个发布者被多个订阅者订阅,保证这个订阅操作只执行一次)
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: &cancelables) publisher.sink { val in print("receive2: \(val)") }.store(in: &cancelables) publisher.sink { val in print("receive3: \(val)") }.store(in: &cancelables) // 以上有3个订阅者订阅了publisher,但是对up发布者的订阅操作仅发生一次
执行结果:
发送接口请求 receive2: 1 receive1: 1 receive3: 1
Publishers.SubscribeOn:该订阅者用于指定对上游发布者的订阅操作,在指定的线程队列中执行,类似于ReceiveOn(是指定向下游订阅者发送元素的操作,在哪个队列中进行的)
// 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: &cancelables)
Publishers.SwitchToLatest:该发布者用于【订阅】上游发布者所发送的【发布者】,并将【发布者】发送的元素,发送给下游订阅者。当上游发布者重新发布了一个新的【新发布者】,SwitchToLatest会取消上一个【发布者】的订阅,并重新订阅这个【新发布者】
let up = PassthroughSubject<PassthroughSubject<Int, Never>, Never>() // 该发布者用于【订阅】上游发布者所发送的【发布者】,并将【发布者】发送的元素,发送给下游订阅者。当上游发布者重新发布了一个新的【新发布者】,SwitchToLatest会取消上一个【发布者】的订阅,并重新订阅这个【新发布者】 let publisher = Publishers.SwitchToLatest(upstream: up) publisher.sink { val in print("receive: \(val)") }.store(in: &cancelables) 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)
Publishers.Zip:该发布者用于将两个上游发布者合并,两个上游发出的元素会一对一对的发出,下游接收的元素类型为一个元祖。该发布者与Merge、CombineLatest和Concatenate发布者功能类似,但是在订阅者接收的时候存很大差异。
同时还提供了Zip3、Zip4,用于合并3个或4个上游发布者
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: &cancelables) 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)
与Merge、CombineLatest、Concatenate的区别如下:
Publishers.TryCatch:该发布者会将上游发出的failure事件,转成一个新的发布者,你可以在这个闭包中根据上游的具体的错误信息,返回不同的发布者,该闭包是一个可抛出异常的函数
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: &cancelables) 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
另外Apple还提供了一些实现Try前缀的发布者,这些发布者都存在一个闭包,这些闭包都是可抛出异常的函数,用于向订阅者发送Failure的消息,例如:TryMap
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: &cancelables) up.send(1) up.send(0) // 此时闭包中会抛出CustomError.empty错误
以上发布者在实际开发中可以不用以上的方式通过初始化一个发布者来使用,Apple为我们提供了相关的函数封装,方便我们使用,例如:Map
let up = PassthroughSubject<Int, Never>() up.map { "value: \($0)" }.sink { val in print("receive: \(val)") }.store(in: &cancelables) up.send(1) up.send(2)