Subscriber & Subscribers(五)

65 阅读7分钟

发布者协议区分和总结

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 但不需要实际功能的场景。

综合实例

下面是一个综合实例,展示了 SubscriberAnySubscriberSubscription 的使用。

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)

常用订阅者

  1. Subscribe

subscribe 操作符是 Combine 框架中的一个基础操作符,它用于将订阅者与发布者连接。实际上,在Combine 中我们很少直接使用 subscribe 操作符,而是通过其他操作符(如 sinkassign)来间接实现订阅。

虽然在 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)
  1. 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!"
  1. 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

参考文档

  1. developer.apple.com/documentati…