Combine的角色
- Publisher;负责发布事件
- Subscriber:负责订阅事件
- Operator:负责转换事件和数据
- Publisher发布事件经过Operator处理中转,最终由Subscriber订阅接收
Publisher
事件发布
- Combine 中包括 Publisher 在内的一系列角色都使用协议来进行定义
public protocol Publisher {
associatedtype Output
associatedtype Failure : Error
func receive(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
- Publisher 最主要的工作: 发布新的事件及其数据,准备好被 Subscriber 订阅。
- Publisher 可以发布三种事件:
-
- 类型为 Output 的新值:这代表事件流中出现了新的值。
-
- 类型为 Failure 的错误:这代表事件流中发生了问题,事件流到此终止。
-
- 完成事件:表示事件流中所有的元素都已经发布结束,事件流到此终止。
-
- 对于第一种事件,Publisher 会直接将新的值发布出来。后两种事件在 Combine 中 则使用Subscribers.Completion 来描述,Completion 是一个含有两个成员的 enum,其中成员类型为 .failure(Failure) 以及 .finished。
有限事件流和无限事件流
- 最终会终结的事件流称为有限事件流,比如一个网络请求
- 将不会发出 failure 或者 finished 的事件流 称为无限事件流,比如按钮的点击
如何创建Publisher(Publisher从哪里来)
- Publishers.Sequence。顾名思义,Publishers.Sequence 接受一个 Sequence:它可以是一个数组,也可以是一个 Range。在被订阅时,Sequence 中的元素被逐个发 送出来:
check("Sequence") {
Publishers.Sequence<[Int], Never>(sequence: [1, 2, 3])
}
check("Array") {
[1, 2, 3].publisher
}
// 输出:
//----- Sequence -----
// receive subscription: ([1, 2, 3])
// request unlimited
// receive value: (1)
// receive value: (2)
// receive value: (3)
// receive finished
Subject
- Subject 本身也是一个 Publisher:
public protocol Subject : AnyObject, Publisher {
func send(_ value: Self.Output)
func send(completion: Subscribers.Completion)
}
- Combine 内置提供了两种常用的 Subject 类型,分别是和CurrentValueSubject。
- PassthroughSubject 简单地将 send 接收到的事件转发给下游的其他 Publisher 或 Subscriber:
let publisher2 = PassthroughSubject()
publisher2.send(1)
print("开始订阅")
publisher2.sink(
receiveCompletion: { complete in print(complete)
},
receiveValue: { value in
print(value)
}
)
let s1 = check("Subject") { () -> PassthroughSubject in
let subject = PassthroughSubject() delay(1) {
subject.send(1)
delay(1) {
subject.send(2)
delay(1) {
subject.send(completion: .finished)
}
} }
return subject
}
- CurrentValueSubject 则会包装和持有一个值,并在 设置该值时发送事件并保留新的值。在订阅发生的瞬间,CurrentValueSubject 会把 当前保存的值发送给订阅者
let publisher3 = CurrentValueSubject(0)
print("开始订阅")
publisher3.sink(
receiveCompletion: { complete in print(complete)
},
receiveValue: { value in
print(value)
}
)
publisher3.value = 1
publisher3.value = 2
publisher3.send(completion: .finished)
// 输出: !" 开始订阅 !"0
// 1
// 2
// finished
- Just:订阅和值的发布其实是同步过程,它往往被 用来将某个值 “包装” 成一个 Publisher 来提供给各类 Publisher 的合并操作
- Future: 订阅操作和值的发布是异步行为,不在同一时间发生的话,可以使用 Future。Future 提供了一种方式,可以让我们创建一个接受未来的事件的 Publisher。
func testFuture() {
Future<(Data, URLResponse), Error> { promise in
self.loadPage(url: URL(string: "sdf")!) { data, response, error in
if let data = data, let response = response {
promise(.success((data, response)))
} else {
promise(.failure(error!))
}
}
}
}
- 使用 Subject,相对于单次事件的网络请求,可以发布多次事件的操作在日常开发中更加常见:
func testSubject() {
let subject = PassthroughSubject()
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
subject.send(Date())
}
}
Foundation 中的 Publisher
- URLSession Publisher
func test() {
URLSession.shared
.dataTaskPublisher(for: URL(string: "https:!"httpbin.org/get?foo=bar")!)
.map{data,_indata}
.decode(type: Response.self, decoder: JSONDecoder()) .compactMap { $0.args!"foo }
}
- Timer Publisher: Foundation 中的 Timer 类型也提供了一个方法,来创建一个按照一定间隔发送事件 的 Publisher
func test() {
let temp = check("Timer") {
Timer.publish(every: 1, on: .main, in: .default)
}
}
- Notification Publisher
extension NotificationCenter {
public func publisher(
for name: Notification.Name,
object: AnyObject? = nil
) -> NotificationCenter.Publisher
}
func testNotificationPublisher() -> AnyPublisher {
NotificationCenter.default
.publisher(for: UIApplication.didBecomeActiveNotification, object: nil)
.map { $0.object as! Response}
.eraseToAnyPublisher()
}
- @Published: Combine 中存在 @Published 封装,用来把一个 class 的属性值转变为 Publisher。它同时提供了值的存储和对外的 Publisher (通过投影符号 $ 获取)。在被 订阅时,当前值也会被发送给订阅者,它的底层其实就是一个 CurrentValueSubject
class Wrapper {
@Published var text: String = "hoho"
}
var wrapper = Wrapper()
check("Published") {
wrapper.$text
}
wrapper.text = "123"
wrapper.text = "abc"
Operator
- Operator使用上游 Publisher 所发布的数据作为输入, 以此产生的新的数据,然后自身成为新的 Publisher,并将这些新的数据作为输出,发布给下游
- 通过一系列组合,可以得到一个响应式的 Publisher 链条: 当链条最上端的 Publisher 发布某个事件后,链条中的各个 Operator 对事件和数据进行处理。在链条的末端我们希望最终能得到可以直接驱动 UI 状态的事件和数据。这样, 终端的消费者可以直接使用这些准备好的数据,而这个消费者的角色由 Subscriber 来担任。
- Publisher的所有操作符
常用的Operator
- print: publisher 发送事件时,将具体事件的内容打印到控制台
public func check(_ title: String, publisher: () -> P) -> AnyCancellable {
print("----- \(title) -----")
defer { print("") }
return publisher()
.print()
.sink(
receiveCompletion: { _ in},
receiveValue: { value in print(value) }
)
}
- empty: 只在被订阅的时候发布一个完成事件 (receive finished)。这个 publisher 不会输出任何的 output 值,只能用于表示某个事件已经 发生。
Empty()
.print()
.sink(
receiveCompletion: { _ in},
receiveValue: { value in print(value) }
)
// 输出:
// ----- Empty -----
//receive subscription: (Empty) !" request unlimited
//receive finished
- map: 对Publisher的 output 的元素 进行变形
check("Map") {
// 注意是在 `Publisher` 上调用了 `map`
[1,2,3] .publisher .map{$0*2}
}
- reduce 对Publisher中的 output 值,按照某种规则进行合并,当序列中值耗尽时,它将发布 finished事件,才会将最终的reduce后的结果发布出来。
reduce
check("Reduce") {
[1,2,3,4,5].publisher.reduce(0, +)
}
// 输出:
// ----- Reduce -----
// receive subscription: (Once) !" request unlimited
// receive value: (15)
// receive finished
- scan: 把运算的每一步结果,保存并发布出来
extension Sequence {
public func scan(
_ initial: ResultElement,
_ nextPartialResult: (ResultElement, Element) -> ResultElement
) -> [ResultElement] {
var result: [ResultElement] = []
for x in self {
result.append(nextPartialResult(result.last ?? initial, x))
}
return result
}
}
func testScan() {
check("Scan") {
[1,2,3,4,5].publisher.scan(0, +)
}
// ----- Scan -----
// receive subscription: ([1, 3, 6, 10, 15])
// request unlimited
// receive value: (1)
// receive value: (3)
// receive value: (6)
// receive value: (10)
// receive value: (15)
// receive finished
}
scan
- compactMap: 将 map 结果中那些 nil 的元素去除掉,这个操作通常会 “压缩” 结果,让其中的元素数减少
func testCompactMap() {
check("Compact Map") {
["1", "2", "3", "cat", "5"]
.publisher
.compactMap { Int($0) }
}
// 输出:
// ----- Compact Map -----
// receive subscription: ([1, 2, 3, 5])
}
- flatMap flatMap 将外层 Publisher 发出的事件中的值传递给内层 Publisher,然后汇总内层 Publisher 给出的事件输出,作为最终变形后的结果。其核心目的是为了 "降维"
func testFlatMap() {
check("Flat Map 1") {
[[1, 2, 3], ["a", "b", "c"]]
.publisher
.flatMap {
$0.publisher
}
}
check("Flat Map 2") {
["A", "B", "C"]
.publisher
.flatMap { letter in
[1, 2, 3]
.publisher
.map { "\(letter)\($0)" }
}
}
// 输出:
// ----- Flat Map 2 -----
// receive subscription: (FlatMap) !" request unlimited
// receive value: (A1)
// receive value: (A2)
// receive value: (A3)
// receive value: (B1)
// receive value: (B2)
// receive value: (B3)
// receive value: (C1)
// receive value: (C2)
// receive value: (C3)
// receive finished
}
flatMap
- removeDuplicates: 过滤掉这些重复的事件,进而避免无谓的额外开销
func test() {
check("Remove Duplicates") {
["S", "Sw", "Sw", "Sw", "Swi","Swif", "Swift", "Swift", "Swif"]
.publisher
.removeDuplicates()
}
// 输出:
// ----- Remove Duplicates -----
// receive subscription: (["S", "Sw", "Swi", "Swif", "Swift", "Swif"]) !" request unlimited
// receive value: (S)
// receive value: (Sw)
// receive value: (Swi)
// receive value: (Swif)
// receive value: (Swift)
// receive value: (Swif)
// receive finished
}
-
merge:它将两个事件流进行合并,在对应的时间完整保留两个事件流的全部事件:
mapError: Publisher 的 Failure 转换成 Subscriber 所需要的 Failure 类型:
check("Map Error") {
Fail(error: .sampleError)
.mapError { _ in MyError.myError }
}
- tryMap: 处理数据时发生的错误转变为标志事件流失败的结束事件
func testTryMap() {
check("Throw") {
["1", "2", "Swift", "4"].publisher
.tryMap { s -> Int in
guard let value = Int(s) else {
throw MyError.myError
}
return value
}
}
// 输出:
// ----- Throw -----
// receive subscription: (TryMap) !" request unlimited
// receive value: (1)
// receive value: (2)
// receive error: (myError)
}
- replaceError: 从错误中恢复, replaceError 会将 Publisher 的 Failure 类型抹为 Never,比如使用 assign 来将 Publisher 绑定到 UI 上时所需要的 Failure 类型为Never。我们可以用 replaceError 来提供这样一个在出现错误时应该显示的默认值。
check("Replace Error") {
["1", "Swift", "4"]
.publisher
.tryMap { s -> Int in
guard let value = Int(s) else {
throw MyError.myError
}
return value
}
.replaceError(with: -1)
}
replaceError
- catch: 上游 Publisher 发生错误时,catch 操作会使用新的 Publisher 来把原来的 Publisher 替换掉, 从实例代码中看上去输出和上面的 replaceError 没有区别,但是在 catch 的闭包中,返 回的是 Just(-1) 这个 Publisher,而不仅仅只是 Int 的 -1。
catch
check("Catch with Just Error") {
["1", "Swift", "4"]
.publisher
.tryMap { s -> Int in
guard let value = Int(s) else {
throw MyError.myError
}
return value
}
.catch({_ in [-1, -2, -3].publisher })
// .catch({ _ in Just(-1) })
}
- Catch and Continue, 比如 如果我们将 (由 ["1", "2", "Swift", "4"] 构成的) 原 Publisher 看作是用户输入,将结 果的 Int 看作是最后输出,那么像上面那样的方式使用 replaceError 或者 catch 的 话,一旦用户输入了不能转为 Int 的非法值 (如 “Swift”),整个结果将永远停在我们给定的默认恢复值上,接下来的任意用户输入都将被完全忽略。这往往不是我们想要的结果,一般情况下,我们会想要后续的用户输入也能继续驱动输出,这时候我们 可以靠组合一些 Operator 来完成所需的逻辑
check("Catch and Continue") {
["1", "2", "Swift", "4"]
.publisher
.print("[ Original ]")
.flatMap { s in
return Just(s)
.tryMap { s -> Int in
guard let value = Int(s) else {
throw MyError.myError
}
return value
}
.print("[ TryMap ]")
.catch { _ in
Just(-1)
.print("[ Just ]")
}
.print("[ Catch ]")
}
}
- zip: 它会把两个 (或多个) Publisher 事 件序列中在同一 index 位置上的值进行合并,也就是说,Publisher1 中的第一个事 件和 Publisher2 中的第一个事件结对合并,Publisher1 中的第二个事件和 Publisher2 中的第二个事件合并,以此类推, zip 在时序语义上更接近于 “当...且...”,当 Publisher1 发布值,且 Publisher2 发布 值时,将两个值合并,作为新的事件发布出去。在实践中,zip 经常被用在合并多个 异步事件的结果,比如同时发出了多个网络请求,希望在它们全部完成的时候把结 果合并在一起
func testZip() {
let subject1 = PassthroughSubject()
let subject2 = PassthroughSubject()
check("testZip") {
subject1.zip(subject2)
}
subject1.send(1)
subject2.send("A")
subject1.send(2)
subject2.send("B")
subject2.send("C")
subject2.send("D")
subject1.send(3)
subject1.send(4)
subject1.send(5)
}
// 输出:
// ----- Zip -----
// receive subscription: (Zip) !" request unlimited
// receive value: ((1, "A"))
// receive value: ((2, "B"))
// receive value: ((3, "C"))
// receive value: ((4, "D"))
- combineLatest: 它的语义接近于 “当...或...”,当 Publisher1 发布 值,或者 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。在实践中,combineLatest 被用来处理多个可变状态,在其中某一个状态发生变化 时,获取这些全部状态的最新值。
// combineLatest
func testCombineLatest() {
// 它的语义接近于 “当...或...”,当 Publisher1 发布 值,或者 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。
let subject1 = PassthroughSubject()
let subject2 = PassthroughSubject()
check("combineLatest") {
subject1.combineLatest(subject2)
}
subject1.send(1)
subject2.send("A") // 1A
subject1.send(2) // 2A
subject2.send("B") //2B
subject2.send("C")
subject2.send("D")
subject1.send(3)
subject1.send(4)
subject1.send(5)
}
类型抹消 - eraseToAnyPublisher
- 类型转换作用,抹除了publisher在通过Operator产生的一些中间类型,直接将最后的类型作为输出
extension Publisher {
@inlinable
public func eraseToAnyPublisher() -> AnyPublisher
let p1 = [[1, 2, 3], [4, 5, 6]] .publisher
.flatMap { $0.publisher }
// p1 : Publishers.FlatMap<
// Publishers.Sequence<[Int], Never>,
// Publishers.Sequence<[[Int]], Never>
// >
let p2=p1.map{$0*2}
// p2 : Publishers.Map,
// Publishers.Sequence<[[Int]], Never>
// >,
// Int >
let p3 = p2.eraseToAnyPublisher()
// let p3: AnyPublisher.Output, Never>, Publishers.Sequence<[[Int]], Never>>, Int>.Failure>
// p3: AnyPublisher
let p1_ = Publishers.FlatMap(
upstream: [[1, 2, 3], [4, 5, 6]].publisher, maxPublishers: .unlimited)
{
$0.publisher
}
let p2_ = Publishers.Map(upstream: p1_) { $0 * 2 }
public struct FlatMap: Publisher
where Child.Failure == Upstream.Failure
{
public typealias Output = Child.Output
public typealias Failure = Upstream.Failure
public let upstream: Upstream
public let maxPublishers: Subscribers.Demand
public let transform: (Upstream.Output) -> Child
public init(upstream: Upstream, maxPublishers: Subscribers.Demand,
transform: @escaping (Upstream.Output) -> Child) {
self.upstream = upstream
self.maxPublishers = maxPublishers
self.transform = transform
}
public func receive(subscriber: Downstream)
where Child.Output == Downstream.Input, Upstream.Failure == Downstream.Failure
{
let outer = Outer(downstream: subscriber,
maxPublishers: maxPublishers,
map: transform)
subscriber.receive(subscription: outer)
upstream.subscribe(outer)
}
}
public struct Map: Publisher {
public typealias Failure = Upstream.Failure
/// The publisher from which this publisher receives elements.
public let upstream: Upstream
/// The closure that transforms elements from the upstream publisher.
public let transform: (Upstream.Output) -> Output
public init(upstream: Upstream,
transform: @escaping (Upstream.Output) -> Output) {
self.upstream = upstream
self.transform = transform
}
public func receive(subscriber: Downstream)
where Output == Downstream.Input, Downstream.Failure == Upstream.Failure
{
upstream.subscribe(Inner(downstream: subscriber, map: transform))
}
}
Subcriber - 订阅和绑定
public protocol Subscriber {
associatedtype Input
associatedtype Failure : Error
func receive(subscription: Subscription)
func receive(_ input: Self.Input) -> Subscribers.Demand
func receive(completion: Subscribers.Completion)
}
通过 sink 订阅 Publisher 事件
- sink订阅Publisher事件中的value
let buttonClicked: AnyPublisher buttonClicked
.scan(0) { value, _ in value + 1 }
.map { String($0) }
.sink { print("Button pressed count: \($0)") }
通过 assign 绑定 Publisher 值
- assign 所接 受的第一个参数的类型为 ReferenceWritableKeyPath,也就是说,只有 class 上用 var 声明的属性可以通过 assign 来直接赋值。
- assign 的另一个 “限制” 是,上游 Publisher 的 Failure 的类型必须是 Never。如果 上游 Publisher 可能会发生错误,必须先对它进行处理,比如使用 replaceError 或者 catch 来把错误在绑定之前就 “消化” 掉。
let foo = Foo()
let buttonClicked: AnyPublisher buttonClicked
.scan(0) { value, _ in value + 1 } .map { String($0) }
.assign(to: \.bar, on: foo)
- 问题:
- 下面代码在combine内部的执行流程(见流程图或者源码)
-
store
方法为什么传入的参数是一个inout
类型
subject.eraseToAnyPublisher()
.sink { value in
print(value)
}
.store(in: &bags)
- 大致做了这些事情
- 转换输出类型
- 创建订阅者
- 创建下游 Conduit (导管)
- 设置订阅状态
- 将AnyCancellable对象添加到输入bags中。AnyCancellable的生命周期由于外部输入Set
bags决定.
Publisher 的引用共享 - share
- 一个subject给多个下游subscribers发送元素
- 上游Publisher只执行一次,下游被多个subscribers监听
- 比如:一个网络请求被多个subcriber监听
public init(upstream: Upstream) {
self.inner = upstream.multicast(subject: .init()).autoconnect()
self.upstream = upstream
}
/// Provides a subject to deliver elements to multiple subscribers.
///
/// Use a multicast publisher when you have multiple downstream subscribers, but you
/// want upstream publishers to only process one `receive(_:)` call per event.
/// This is useful when upstream publishers are doing expensive work you don’t want
/// to duplicate, like performing network requests.
///
public func multicast(
subject: SubjectType
) -> Publishers.Multicast
where Failure == SubjectType.Failure, Output == SubjectType.Output
{
return multicast { subject }
}
public func multicast(
subject: SubjectType
) -> Publishers.Multicast
where Failure == SubjectType.Failure, Output == SubjectType.Output
{
return multicast { subject }
}
public final class Multicast
: ConnectablePublisher
where Upstream.Failure == SubjectType.Failure,
Upstream.Output == SubjectType.Output
{
private var lazySubject: SubjectType {
lock.lock()
if let subject = subject {
lock.unlock()
return subject
}
let subject = createSubject()
self.subject = subject
lock.unlock()
return subject
}
public init(upstream: Upstream, createSubject: @escaping () -> SubjectType) {
self.upstream = upstream
self.createSubject = createSubject
}
public func receive(subscriber: Downstream)
where SubjectType.Failure == Downstream.Failure,
SubjectType.Output == Downstream.Input
{
lazySubject.subscribe(Inner(parent: self, downstream: subscriber))
}
}
func test() {
let dataTaskPublisher = URLSession.shared
.dataTaskPublisher(for: URL(string: "https://httpbin.org/get?foo=bar")!)
.share()
let isSuccess = dataTaskPublisher.map { data, response -> Bool in
guard let httpRes = response as? HTTPURLResponse else { return false }
return httpRes.statusCode // 200
}
.replaceError(with: false)
let latestText = dataTaskPublisher
.map{ data,_ in data}
.decode(type: Response.self, decoder: JSONDecoder()) .compactMap { $0.args!"foo
}
.replaceError(with: "")
}
Cancellable, AnyCancellable 和内存管理
public final class AnyCancellable: Cancellable, Hashable {
private var _cancel: (() -> Void)?
public init(_ cancel: @escaping () -> Void) {
_cancel = cancel
}
public init(_ canceller: OtherCancellable) {
_cancel = canceller.cancel
}
public func cancel() {
_cancel?()
_cancel = nil
}
public static func == (lhs: AnyCancellable, rhs: AnyCancellable) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
deinit {
_cancel?()
}
}
Scheduler
- A protocol that defines when and how to execute a closure.
- Scheduler 所要解决的就 是两个问题:在什么地方 (where),以及在什么时候 (when) 来发布事件和执行代码。
- Debounce
- Delay
- MeasureInterval
- SubscribeOn
- Throttle
- Timeout
- ImmediateScheduler
- DispatchQueue
- OperationQueue
- RunLoop
- Timer
func test() {
URLSession.shared
.dataTaskPublisher(for: URL(string: "https:!"example.com")!)
.compactMap { String(data: $0.data, encoding: .utf8) }
.receive(on: RunLoop.main)
.sink(receiveCompletion: { _ in
}, receiveValue: {
textView.text = $0
})
}
- 在什么地方 (where): RunLoop 就是一个实现了 Scheduler 协议的类型,它知道要如何执行后续的订阅任 务
-
receive(on: RunLoop.main)
决定下游数据在主线程执行
pub.sink {
DispatchQueue.main.async {
// Do something.
}
}
/// Use this pattern instead:
pub.receive(on: DispatchQueue.main).sink {
// Do something.
}
-
subscribe(on: RunLoop.main)
决定上游数据在主线程执行
let ioPerformingPublisher == // Some publisher.
let uiUpdatingSubscriber == // Some subscriber that updates the UI.
ioPerformingPublisher
.subscribe(on: backgroundQueue) // 决定 ioPerformingPublisher 在后台线程中运行
.receive(on: RunLoop.main) // 决定 uiUpdatingSubscriber 在RunLoop.main运行
.subscribe(uiUpdatingSubscriber)
- 在什么时候 (when): 比较常见的两种操作是 delay 和 debounce。delay 简单地将所有事件按照一定事件 延后。debounce 则是设置了一个计时器,在事件第一次到来时,计时器启动。在计 时器有效期间,每次接收到新值,则将计时器时间重置。当且仅当计时窗口中没有新 的值到来时,最后一次事件的值才会被当作新的事件发送出去。