Como se mencionó en la descripción anterior , Combine tiene tres conceptos básicos: publicador, operador y suscriptor. Publisher y Subscriber representan publicadores y suscriptores de eventos, respectivamente. Operator tiene las características de ambos. Sigue los protocolos Subscriber y Publisher y se utiliza para operar datos upstream.
Siempre que comprenda estos tres conceptos básicos, puede usar Combine muy bien, por lo que desde esta perspectiva, podemos simplemente entender Combine de la siguiente forma:
Combinar = Editores + Operadores + Suscriptores
Editor
definición:
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
}
复制代码
Una vez que el editor se haya suscrito, proporcionará datos de acuerdo con la solicitud del suscriptor. Un editor sin ningún suscriptor no enviará ningún dato.
El editor puede publicar tres tipos de eventos:
- Salida: el nuevo valor que aparece en el flujo de eventos
- Terminado: todos los elementos del flujo de eventos se han liberado y el flujo de eventos ha completado su misión y ha finalizado.
- Error: se produjo un error en el flujo de eventos y el flujo de eventos termina aquí
Los eventos Finalizados y Fallidos se definen en Subscribers.Completion
extension Subscribers {
@frozen public enum Completion<Failure> where Failure : Error {
case finished
case failure(Failure)
}
}
复制代码
No se requiere ninguno de los tres eventos. Un publicador puede o no emitir uno o más valores de salida; puede que nunca se detenga, o puede señalar la finalización emitiendo un evento de falla o finalización .
Una secuencia de eventos que finalmente terminará se denomina secuencia finita de eventos, mientras que una secuencia de eventos que no falla ni finaliza se denomina secuencia infinita de eventos. Por ejemplo, una solicitud de red es un flujo de eventos finito y un flujo de eventos de clic de botón es un flujo de eventos infinito.
Sujeto
El sujeto también es un editor
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的介绍,在概览中已经做了相对详细的介绍。
Los operadores se pueden usar como entrada para los publicadores ascendentes y también pueden convertirse en nuevos publicadores que envían datos procesados a los descendentes. Podemos combinar diferentes operadores para formar una cadena de procesamiento: cuando el Publicador en la parte superior de la cadena publica eventos o datos, los Operadores de la cadena procesarán estos datos y eventos paso a paso, y finalmente alcanzarán el resultado especificado por el suscriptor.
Con respecto a los operadores de uso común, este artículo es muy completo y puede usarse como referencia.