在上一篇概览 中说过,Combine有三个中心概念:Publisher、Operator和Subscriber。Publisher 和 Subscriber 分别代表事情的发布者和订阅者,Operator兼具两者的特性,它一起遵循Subscriber和 Publisher协议,用来对上游数据进行操作。

只需理解了这三个中心概念,你就能够很好的运用Combine,所以从这个视点来说,咱们能够将Combine简略的理解为下面的方式:

Combine = Publishers + Operators + Subscribers

Publisher

界说:

public protocol Publisher<Output, Failure> {
    associatedtype Output
    associatedtype Failure : Error
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

publisher被订阅后,会根据subscriber的请求供给数据,一个没有任何subscriber的publisher不会宣布任何数据。

Publisher能够发布三种事情:

  • Output:事情流中出现的新值
  • Finished: 事情流中所有元素发布完毕,事情流完成使命并完结
  • Failure: 事情流中产生了错误,事情流到此完结

Finished和Failure事情被界说在Subscribers.Completion中

extension Subscribers {
    @frozen public enum Completion<Failure> where Failure : Error {
        case finished
        case failure(Failure)
    }
}

三种事情都不是必须的。Publisher 或许会宣布一个或多个 output 值,也或许不宣布任何值;它或许永久不会停止,也或许会通过宣布failure 或 finished 事情来表明完结

终究将会终止的事情流被称为有限事情流,而不会宣布 failure 或许 finished 的事情流则被称为无限事情流。比如,一次网络请求就是一个有限事情流,而某个按钮的点击事情流就是无限事情流。

Subject

Subject也是一个Publisher

public protocol Subject : AnyObject, Publisher {
	func send(_ value: Self.Output)
	func send(completion: Subscribers.Completion<Self.Failure>)
}

Subject 暴露了两个 send 方法,外部调用者能够通过这两个方法来主动地发布 output 值、failure 事情或 finished 事情。Subject能够将传统的指令式编程中的异步事情和信号转换到呼应式的国际中去

Combine内置了两种Subject类型:

  • PassthroughSubject

    简略地将 send 接收到的事情转发给下流的其他 Publisher 或 Subscriber,不会持有最新的output;如果在订阅前履行send操作,是无效的。

let publisher1 = PassthroughSubject<Int, Never>()
print("开端订阅")
publisher1.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
    })
publisher1.send(1)
publisher1.send(2)
publisher1.send(completion: .finished)
// 输出:
// 开端订阅
// 1
// 2
// finished

调整一下 sink 订阅的机遇,将它延后到 publisher.send(1) 之后,那么订阅者将会从 2 的事情开端进行呼应:

let publisher2 = PassthroughSubject<Int, Never>()
publisher2.send(1)
print("开端订阅")
publisher2.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
              })
publisher2.send(2)
publisher2.send(completion: .finished)
// 输出:
// 开端订阅
// 2
// finished
  • CurrentValueSubject

    会包装和持有一个值,并在设置该值时发送事情并保留新的值。在订阅产生的瞬间,会把当前保存的值发送给订阅者;接下来对值的每次设置都将触发订阅呼应。

let publisher3 = CurrentValueSubject<Int, Never>(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

Subscriber

界说:

public protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible {
    associatedtype Input
    associatedtype Failure : Error
    func receive(subscription: Subscription)
    func receive(_ input: Self.Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

想要订阅某个 Publisher,Subscriber 中的这两个类型必须与 Publisher 的 Output 和 Failure 一致。

Combine 中也界说了几个比较常见的 Subscriber,能够供咱们直接运用。

sink

sink的完整函数签名为

func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable

receiveCompletion 用来接收 failure 或许 finished 事情,receiveValue 用来接收 output 值。

let just = Just("Hello word!")
    _ = just.sink(receiveCompletion: {
        print("Received completion", $0)
    }, receiveValue: {
        print("Received value", $0)
    })

如果说Subject供给了一条从指令式异步编程通向呼应式国际的路途的话,那么sink就补全了别的一侧。sink能够作为呼应式代码和根据闭包的指令式代码之间的桥梁,让你能够通过它从呼应式的国际中回到指令式的国际。由于receiveValue闭包会将值带给你,想要对它做什么就随你愿意了。

assign

func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root,Self.Output>, on object:Root) -> AnyCancellable

assign 承受一个 class 目标以及目标类型上的某个键途径 (key path)。每当 output 事情到来时,其中包括的值就将被设置到对应的特点上去。

界说一个MyObject类

class MyObject {
	var value: String = "123" {
		didSet {
			print(value)
		}
	}
}

运用 assign(to:on:)修改MyObject实例目标特点的值

let obj = MyObject()
let _ = ["456"].publisher.assign(to: \.value, on: obj)

assign 还有一个变体, assign(to:) 可将 Publisher 宣布的值用于 @Published 特点包装器包装过的特点

class MyObject {
	@Published var value = 0
}
let objc = MyObject()
objc.$value.sink {		
	print($0)
}
(0 ..< 5).publisher.assign(to: &objc.$value)

value 特点用 @Published包装,除了可作为常规特点访问之外,它还为特点创立了一个 Publisher。运用 @Published 特点上的 $ 前缀来访问其底层 Publisher,订阅该 Publisher,并打印出收到的每个值。终究,咱们创立一个 0..<5 的 Int Publisher 并将它宣布的每个值 assign 给 object 的 value Publisher。 运用 & 来表明对特点的 inout 引证,这儿的 inout 来源于函数签名:

func assign(to published: inout Published<Self.Output>.Publisher)

这儿有一个值得注意的地方,如果运用 assign(to: .value, on: self)存储生成的 AnyCancellable,或许会引起引证循环:MyObject 类实例持有生成的 AnyCancellable,而生成的 AnyCancellable 同样坚持对 MyObject 类实例的引证。因而,推荐运用 assign(to:) 来替代 assign(to:on:) ,以避免此问题的产生,由于assign(to:) ::不返回 AnyCancellable,在内部完成了生命周期的办理,在 @Published 特点释放时会撤销订阅。::

Operator

关于Operator的介绍,在概览中已经做了相对具体的介绍。

Operator 能够作为上游 Publisher 的输入,一起它们也能够成为新的 Publisher,输出处理过的数据给下流。咱们能够把不同的操作符组合起来形成一个处理链:当链条最上端的 Publisher 发布事情或数据时,链条内的 Operator 会对这些数据和事情一步一步地进行处理,终究到达 subscriber 指定的成果。

关于常用操作符,这篇文章 介绍的十分全面,可做参阅。

参阅

Combine: Asynchronous Programming with Swift

SwiftUI和Combine编程