发布者协议区分和总结
在 Combine
框架中,数据流的核心是发布者(Publisher)和订阅者(Subscriber)。发布者发送值和完成事件,订阅者接收这些值和事件。
Subscriber
Subscriber
是一个协议,定义了接收发布者发布的输入值和完成事件的接口。任何符合该协议的对象都可以订阅发布者并处理其发布的值。
Subscribers
Subscribers
是一个命名空间,包含一些具体的订阅者实现,这些订阅者实现了 Subscriber
协议。如sink
,assign 等
AnySubscriber
AnySubscriber
是一种类型擦除的订阅者,用于将任何具体的订阅者封装为一个通用的订阅者。它隐藏了具体的订阅者类型,只暴露 Subscriber
协议定义的接口。
Subscription
Subscription
是一个协议,表示发布者和订阅者之间的连接。它负责管理订阅的生命周期,包括请求值和取消订阅。
属性和方法
func request(_ demand: Subscribers.Demand)
:请求发布者发送值的数量。func cancel()
:取消订阅。
Subscriptions
Subscriptions
是一个命名空间,包含一些与 Subscription
相关的实用工具和默认实现。
Subscriptions.empty
:表示一个空的订阅,用于需要返回一个Subscription
但不需要实际功能的场景。
综合实例
下面是一个综合实例,展示了 Subscriber
、AnySubscriber
和 Subscription
的使用。
import Combine
// 自定义订阅者
class CustomSubscriber: Subscriber {
typealias Input = String
typealias Failure = Never
func receive(subscription: Subscription) {
print("Received subscription")
subscription.request(.max(3)) // 请求最多 3 个值
}
func receive(_ input: String) -> Subscribers.Demand {
print("Received value: (input)")
return .none // 不再请求更多值
}
func receive(completion: Subscribers.Completion<Never>) {
print("Received completion: (completion)")
}
}
// 创建一个发布者
let publisher = ["One", "Two", "Three", "Four"].publisher
// 使用自定义订阅者订阅发布者
let customSubscriber = CustomSubscriber()
publisher.subscribe(customSubscriber)
// 使用 AnySubscriber 进行类型擦除
let anySubscriber = AnySubscriber<String, Never>(
receiveSubscription: { subscription in
subscription.request(.unlimited)
},
receiveValue: { value in
print("AnySubscriber received value: (value)")
return .none
},
receiveCompletion: { completion in
print("AnySubscriber received completion: (completion)")
}
)
publisher.subscribe(anySubscriber)
常用订阅者
-
Subscribe
subscribe
操作符是 Combine
框架中的一个基础操作符,它用于将订阅者与发布者连接。实际上,在Combine
中我们很少直接使用 subscribe
操作符,而是通过其他操作符(如 sink
和 assign
)来间接实现订阅。
虽然在 Combine
中较少直接使用 subscribe
操作符,但它仍然有其独特的使用场景,尤其是在需要自定义订阅者时。
import Combine
let publisher = Just("Hello, Combine!")
let subscriber = Subscribers.Sink<String, Never>(
receiveCompletion: { completion in
switch completion {
case .finished:
print("Finished")
case .failure(let error):
print("Error: (error)")
}
},
receiveValue: { value in
print("Received value: (value)")
}
)
publisher.subscribe(subscriber)
-
Assign
assign
操作符用于将发布者的输出直接分配给一个对象的属性。它常用于将 UI 控件的属性与发布者的输出绑定。
import Combine
import SwiftUI
class ViewModel: ObservableObject {
@Published var text: String = ""
}
let viewModel = ViewModel()
let publisher = Just("Hello, Combine!")
let subscription = publisher
.assign(to: .text, on: viewModel)
print(viewModel.text) // Output: "Hello, Combine!"
-
Sink
sink
操作符是最常用的订阅操作符之一。它允许你处理发布者发布的每个值,并可选地处理完成或错误事件。
import Combine
let publisher = Just("Hello, Combine!")
let subscription = publisher.sink(receiveCompletion: { completion in
switch completion {
case .finished:
print("Completed")
case .failure(let error):
print("Error: (error)")
}
}, receiveValue: { value in
print("Received value: (value)")
})
自定义订阅者
场景1:
这里我们自定义一个订阅者来控制接收数据的速率,通过实现 Subscriber
接口的 request(_:)
方法来动态地管理订阅者对数据的需求量。这种方式可以实现背压(Backpressure),即订阅者告诉发布者它能够处理多少个数据项。
class CustomSubscriber: Subscriber, Cancellable {
typealias Input = String
typealias Failure = Never
private var subscription: Subscription?
private let maxDemand: Int
private var demand = Subscribers.Demand.none
private var processedCount = 0
private var totalProcessedCount = 0
private let processingQueue = DispatchQueue(label: "CustomSubscriberQueue")
private let dateFormatter: DateFormatter
init(maxDemand: Int) {
self.maxDemand = maxDemand
self.dateFormatter = DateFormatter()
self.dateFormatter.dateFormat = "HH:mm"
}
func receive(subscription: Subscription) {
self.subscription = subscription
print("Received subscription")
subscription.request(.max(maxDemand)) // 请求初始需求量
}
func receive(_ input: String) -> Subscribers.Demand {
print("Received value: (input)")
processedCount += 1
totalProcessedCount += 1
// 模拟处理数据的异步耗时操作
if processedCount == maxDemand {
// 达到最大需求量时,进行耗时操作
processingQueue.async {
print("Processing (self.maxDemand) items...")
// 模拟耗时操作
Thread.sleep(forTimeInterval: 2.0)
print("Processing completed.")
// 继续请求更多数据
DispatchQueue.main.async {
if self.totalProcessedCount < 10 {
self.subscription?.request(.max(self.maxDemand))
}
self.processedCount = 0 // 重置每次批量处理的计数器
}
}
return .none // 暂停请求数据
} else if totalProcessedCount >= 10 {
// 处理到 30 个数据时,取消订阅
subscription?.cancel()
receive(completion: .finished)
print("Cancelled subscription after processing 10 items")
return .none // 暂停请求数据
} else {
return .max(1) // 继续请求下一个数据
}
}
func receive(completion: Subscribers.Completion<Never>) {
print("Received completion: (completion)")
subscription = nil
}
func cancel() {
subscription?.cancel()
subscription = nil
}
}
// 创建一个定时器发布者,每秒发布一个递增的整数
let timerPublisher = Timer.publish(every: 1.0, on: .main, in: .default)
.autoconnect()
.map { $0.timeIntervalSince1970 } // 映射为时间戳
.map { Date(timeIntervalSince1970: $0) } // 转换为 Date 对象
.map {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm:ss"
return dateFormatter.string(from: $0) // 格式化为 "HH:mm" 格式
}
.eraseToAnyPublisher()
// 创建并订阅自定义订阅者
let customSubscriber = CustomSubscriber(maxDemand: 3)
timerPublisher.subscribe(customSubscriber)
// 输出结果
// Received subscription // Received value: 09:29:46 // Received value: 09:29:47 // Received value: 09:29:48 // Processing 3 items... // Received value: 09:29:49 // Received value: 09:29:50 // Processing completed. // Received value: 09:29:51 // Received value: 09:29:52 // Received value: 09:29:53 // Processing 3 items... // Received value: 09:29:54 // Received value: 09:29:55 // Processing completed. // Received completion: finished // Cancelled subscription after processing 10 items
场景2:
假设有一个上游发布者以非常不规则的速率发送值 - 有时可能几秒或几分钟过去而没有任何值,然后一连串值可能一下子全部传过来。我想创建一个自定义发布者,它订阅上游值,缓冲它们并在它们进入时以常规的已知节奏发出它们,但如果它们全部用尽,则不发布任何内容。
import Combine
import Foundation
extension Publisher {
func pace<Context: Scheduler, MySubject: Subject>(
_ pace: Context.SchedulerTimeType.Stride, scheduler: Context, subject: MySubject)
-> AnyCancellable
where MySubject.Output == Output, MySubject.Failure == Failure
{
return step {
switch $0 {
case .input(let input, let promise):
// Send the input from upstream now.
subject.send(input)
// Wait for the pace interval to elapse before requesting the
// next input from upstream.
scheduler.schedule(after: scheduler.now.advanced(by: pace)) {
promise(.more)
}
case .completion(let completion):
subject.send(completion: completion)
}
}
}
}
extension Publisher {
func step(with stepper: @escaping (SteppingSubscriber<Output, Failure>.Event) -> ()) -> AnyCancellable {
let subscriber = SteppingSubscriber<Output, Failure>(stepper: stepper)
self.subscribe(subscriber)
return .init(subscriber)
}
}
public class SteppingSubscriber<Input, Failure: Error> {
public init(stepper: @escaping Stepper) {
l_state = .subscribing(stepper)
}
public typealias Stepper = (Event) -> ()
public enum Event {
case input(Input, Promise)
case completion(Completion)
}
public typealias Promise = (Request) -> ()
public enum Request {
case more
case cancel
}
public typealias Completion = Subscribers.Completion<Failure>
private let lock = NSLock()
// The l_ prefix means it must only be accessed while holding the lock.
private var l_state: State
private var l_nextPromiseId: PromiseId = 1
private typealias PromiseId = Int
private var noPromiseId: PromiseId { 0 }
}
extension SteppingSubscriber {
private enum State {
// Completed or cancelled.
case dead
// Waiting for Subscription from upstream.
case subscribing(Stepper)
// Waiting for a signal from upstream or for the latest promise to be completed.
case subscribed(Subscribed)
// Calling out to the stopper.
case stepping(Stepping)
var subscription: Subscription? {
switch self {
case .dead: return nil
case .subscribing(_): return nil
case .subscribed(let subscribed): return subscribed.subscription
case .stepping(let stepping): return stepping.subscribed.subscription
}
}
struct Subscribed {
var stepper: Stepper
var subscription: Subscription
var validPromiseId: PromiseId
}
struct Stepping {
var subscribed: Subscribed
// If the stepper completes the current promise synchronously with .more,
// I set this to true.
var shouldRequestMore: Bool
}
}
}
extension SteppingSubscriber: Cancellable {
public func cancel() {
let sub: Subscription? = lock.sync {
defer { l_state = .dead }
return l_state.subscription
}
sub?.cancel()
}
}
extension SteppingSubscriber: Subscriber {
public func receive(subscription: Subscription) {
let action: () -> () = lock.sync {
guard case .subscribing(let stepper) = l_state else {
return { subscription.cancel() }
}
l_state = .subscribed(.init(stepper: stepper, subscription: subscription, validPromiseId: noPromiseId))
return { subscription.request(.max(1)) }
}
action()
}
public func receive(completion: Subscribers.Completion<Failure>) {
let action: (() -> ())? = lock.sync {
// The only state in which I have to handle this call is .subscribed:
// - If I'm .dead, either upstream already completed (and shouldn't call this again),
// or I've been cancelled.
// - If I'm .subscribing, upstream must send me a Subscription before sending me a completion.
// - If I'm .stepping, upstream is currently signalling me and isn't allowed to signal
// me again concurrently.
guard case .subscribed(let subscribed) = l_state else {
return nil
}
l_state = .dead
return { [stepper = subscribed.stepper] in
stepper(.completion(completion))
}
}
action?()
}
public func receive(_ input: Input) -> Subscribers.Demand {
let action: (() -> Subscribers.Demand)? = lock.sync {
// The only state in which I have to handle this call is .subscribed:
// - If I'm .dead, either upstream completed and shouldn't call this,
// or I've been cancelled.
// - If I'm .subscribing, upstream must send me a Subscription before sending me Input.
// - If I'm .stepping, upstream is currently signalling me and isn't allowed to
// signal me again concurrently.
guard case .subscribed(var subscribed) = l_state else {
return nil
}
let promiseId = l_nextPromiseId
l_nextPromiseId += 1
let promise: Promise = { request in
self.completePromise(id: promiseId, request: request)
}
subscribed.validPromiseId = promiseId
l_state = .stepping(.init(subscribed: subscribed, shouldRequestMore: false))
return { [stepper = subscribed.stepper] in
stepper(.input(input, promise))
let demand: Subscribers.Demand = self.lock.sync {
// The only possible states now are .stepping and .dead.
guard case .stepping(let stepping) = self.l_state else {
return .none
}
self.l_state = .subscribed(stepping.subscribed)
return stepping.shouldRequestMore ? .max(1) : .none
}
return demand
}
}
return action?() ?? .none
}
} // end of extension SteppingSubscriber: Publisher
extension SteppingSubscriber {
private func completePromise(id: PromiseId, request: Request) {
let action: (() -> ())? = lock.sync {
switch l_state {
case .dead, .subscribing(_): return nil
case .subscribed(var subscribed) where subscribed.validPromiseId == id && request == .more:
subscribed.validPromiseId = noPromiseId
l_state = .subscribed(subscribed)
return { [sub = subscribed.subscription] in
sub.request(.max(1))
}
case .subscribed(let subscribed) where subscribed.validPromiseId == id && request == .cancel:
l_state = .dead
return { [sub = subscribed.subscription] in
sub.cancel()
}
case .subscribed(_):
// Multiple completion or stale promise.
return nil
case .stepping(var stepping) where stepping.subscribed.validPromiseId == id && request == .more:
stepping.subscribed.validPromiseId = noPromiseId
stepping.shouldRequestMore = true
l_state = .stepping(stepping)
return nil
case .stepping(let stepping) where stepping.subscribed.validPromiseId == id && request == .cancel:
l_state = .dead
return { [sub = stepping.subscribed.subscription] in
sub.cancel()
}
case .stepping(_):
// Multiple completion or stale promise.
return nil
}
}
action?()
}
}
fileprivate extension NSLock {
@inline(__always)
func sync<Answer>(_ body: () -> Answer) -> Answer {
lock()
defer { unlock() }
return body()
}
}
// Demo
var cans: [AnyCancellable] = []
let erratic = Just("A").delay(for: 0.0, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher()
.merge(with: Just("B").delay(for: 0.3, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher())
.merge(with: Just("C").delay(for: 0.6, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher())
.merge(with: Just("D").delay(for: 5.0, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher())
.merge(with: Just("E").delay(for: 5.3, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher())
.merge(with: Just("F").delay(for: 5.6, tolerance: 0.001, scheduler: DispatchQueue.main).eraseToAnyPublisher())
.handleEvents(
receiveOutput: { print("erratic: (Date().hhmmss()) ($0)") },
receiveCompletion: { print("erratic completion: (Date().hhmmss()) ($0)") }
)
.makeConnectable()
let subject = PassthroughSubject<String, Never>()
cans += [erratic
.buffer(size: 1000, prefetch: .byRequest, whenFull: .dropOldest)
.pace(.seconds(1), scheduler: DispatchQueue.main, subject: subject)]
cans += [subject.sink(
receiveCompletion: { print("paced: (Date().hhmmss()) ($0)") },
receiveValue: { print("paced completion: (Date().hhmmss()) ($0)") }
)]
let c = erratic.connect()
cans += [AnyCancellable { c.cancel() }]
// 打印结果
// erratic: 15:18:28 A
// paced: 15:18:28 A
// erratic: 15:18:28 B
// erratic: 15:18:28 C
// paced: 15:18:29 B
// paced: 15:18:30 C
// erratic: 15:18:33 D
// paced: 15:18:33 D
// erratic: 15:18:33 E
// erratic: 15:18:33 F
// erratic completion: 15:18:33 finished
// paced: 15:18:34 E
// paced: 15:18:35 F
// paced completion: 15:18:36 finished