在上一篇概览 中说过,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编程