// // ReplaySubject.swift // CombineExt // // Created by Jasdev Singh on 13/04/2020. // Copyright © 2020 Combine Community. All rights reserved. // #if canImport(Combine) import Combine /// A `ReplaySubject` is a subject that can buffer one or more values. It stores value events, up to its `bufferSize` in a /// first-in-first-out manner and then replays it to /// future subscribers and also forwards completion events. /// /// The implementation borrows heavily from [Entwine’s](https://github.com/tcldr/Entwine/blob/b839c9fcc7466878d6a823677ce608da998b95b9/Sources/Entwine/Operators/ReplaySubject.swift). @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) public final class ReplaySubject: Subject { public typealias Output = Output public typealias Failure = Failure private let bufferSize: Int private var buffer = [Output]() // Keeping track of all live subscriptions, so `send` events can be forwarded to them. private var subscriptions = [Subscription>]() private var completion: Subscribers.Completion? private var isActive: Bool { completion == nil } /// Create a `ReplaySubject`, buffering up to `bufferSize` values and replaying them to new subscribers /// - Parameter bufferSize: The maximum number of value events to buffer and replay to all future subscribers. public init(bufferSize: Int) { self.bufferSize = bufferSize } public func send(_ value: Output) { guard isActive else { return } buffer.append(value) if buffer.count > bufferSize { buffer.removeFirst() } subscriptions.forEach { $0.forwardValueToBuffer(value) } } public func send(completion: Subscribers.Completion) { guard isActive else { return } self.completion = completion subscriptions.forEach { $0.forwardCompletionToBuffer(completion) } } public func send(subscription: Combine.Subscription) { subscription.request(.unlimited) } public func receive(subscriber: Subscriber) where Failure == Subscriber.Failure, Output == Subscriber.Input { let subscriberIdentifier = subscriber.combineIdentifier let subscription = Subscription(downstream: AnySubscriber(subscriber)) { [weak self] in guard let self = self, let subscriptionIndex = self.subscriptions .firstIndex(where: { $0.innerSubscriberIdentifier == subscriberIdentifier }) else { return } self.subscriptions.remove(at: subscriptionIndex) } subscriptions.append(subscription) subscriber.receive(subscription: subscription) subscription.replay(buffer, completion: completion) } } @available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) extension ReplaySubject { final class Subscription: Combine.Subscription where Output == Downstream.Input, Failure == Downstream.Failure { private var demandBuffer: DemandBuffer? private var cancellationHandler: (() -> Void)? fileprivate let innerSubscriberIdentifier: CombineIdentifier init(downstream: Downstream, cancellationHandler: (() -> Void)?) { self.demandBuffer = DemandBuffer(subscriber: downstream) self.innerSubscriberIdentifier = downstream.combineIdentifier self.cancellationHandler = cancellationHandler } func replay(_ buffer: [Output], completion: Subscribers.Completion?) { buffer.forEach(forwardValueToBuffer) if let completion = completion { forwardCompletionToBuffer(completion) } } func forwardValueToBuffer(_ value: Output) { _ = demandBuffer?.buffer(value: value) } func forwardCompletionToBuffer(_ completion: Subscribers.Completion) { demandBuffer?.complete(completion: completion) } func request(_ demand: Subscribers.Demand) { _ = demandBuffer?.demand(demand) } func cancel() { cancellationHandler?() cancellationHandler = nil demandBuffer = nil } } } #endif