As mentioned in the previous overview , Combine has three core concepts: Publisher, Operator and Subscriber. Publisher and Subscriber represent event publishers and subscribers, respectively. Operator has the characteristics of both. It follows the Subscriber and Publisher protocols and is used to operate upstream data.
As long as you understand these three core concepts, you can use Combine very well, so from this perspective, we can simply understand Combine as the following form:
Combine = Publishers + Operators + Subscribers
Publisher
definition:
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
}
复制代码
After the publisher is subscribed, it will provide data according to the subscriber's request. A publisher without any subscriber will not send any data.
Publisher can publish three kinds of events:
- Output: the new value appearing in the event stream
- Finished: All elements in the event flow have been released, and the event flow has completed its mission and terminated
- Failure: An error occurred in the event flow, and the event flow ends here
Finished and Failure events are defined in Subscribers.Completion
extension Subscribers {
@frozen public enum Completion<Failure> where Failure : Error {
case finished
case failure(Failure)
}
}
复制代码
None of the three events are required. A Publisher may or may not emit one or more output values; it may never stop, or it may signal termination by emitting a failure or finished event .
A stream of events that will eventually terminate is called a finite stream of events, while a stream of events that does not emit failure or finished is called an infinite stream of events. For example, a network request is a finite event stream, and a button click event stream is an infinite event stream.
Subject
Subject is also a 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的介绍,在概览中已经做了相对详细的介绍。
Operators can be used as input to upstream Publishers, and they can also become new Publishers that output processed data to downstream. We can combine different operators to form a processing chain: when the Publisher at the top of the chain publishes events or data, the Operators in the chain will process these data and events step by step, and finally reach the result specified by the subscriber.
Regarding the commonly used operators, this article is very comprehensive and can be used as a reference.