|
| 1 | +// |
| 2 | +// WithUnretained.swift |
| 3 | +// CombineExt |
| 4 | +// |
| 5 | +// Created by Robert on 01/09/2021. |
| 6 | +// Copyright © 2020 Combine Community. All rights reserved. |
| 7 | +// |
| 8 | + |
| 9 | +#if canImport(Combine) |
| 10 | +import Combine |
| 11 | + |
| 12 | +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) |
| 13 | +public extension Publisher { |
| 14 | + /** |
| 15 | + Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events published by the publisher. |
| 16 | + |
| 17 | + In the case the provided object cannot be retained successfully, the publisher will complete. |
| 18 | + |
| 19 | + - parameter obj: The object to provide an unretained reference on. |
| 20 | + - parameter resultSelector: A function to combine the unretained referenced on `obj` and the value of the observable sequence. |
| 21 | + - returns: A publisher that contains the result of `resultSelector` being called with an unretained reference on `obj` and the values of the upstream. |
| 22 | + */ |
| 23 | + func withUnretained<UnretainedObject: AnyObject, Output>(_ obj: UnretainedObject, resultSelector: @escaping (UnretainedObject, Self.Output) -> Output) -> Publishers.WithUnretained<UnretainedObject, Self, Output> { |
| 24 | + Publishers.WithUnretained(unretainedObject: obj, upstream: self, resultSelector: resultSelector) |
| 25 | + } |
| 26 | + |
| 27 | + /** |
| 28 | + Provides an unretained, safe to use (i.e. not implicitly unwrapped), reference to an object along with the events published by the publisher. |
| 29 | + |
| 30 | + In the case the provided object cannot be retained successfully, the publisher will complete. |
| 31 | + |
| 32 | + - parameter obj: The object to provide an unretained reference on. |
| 33 | + - returns: A publisher that publishes a sequence of tuples that contains both an unretained reference on `obj` and the values of the upstream. |
| 34 | + */ |
| 35 | + func withUnretained<UnretainedObject: AnyObject>(_ obj: UnretainedObject) -> Publishers.WithUnretained<UnretainedObject, Self, (UnretainedObject, Output)> { |
| 36 | + Publishers.WithUnretained(unretainedObject: obj, upstream: self) { ($0, $1) } |
| 37 | + } |
| 38 | + |
| 39 | + /// Attaches a subscriber with closure-based behavior. |
| 40 | + /// |
| 41 | + /// Use ``Publisher/sink(unretainedObject:receiveCompletion:receiveValue:)`` to observe values received by the publisher and process them using a closure you specify. |
| 42 | + /// This method creates the subscriber and immediately requests an unlimited number of values, prior to returning the subscriber. |
| 43 | + /// The return value should be held, otherwise the stream will be canceled. |
| 44 | + /// |
| 45 | + /// - parameter obj: The object to provide an unretained reference on. |
| 46 | + /// - parameter receiveComplete: The closure to execute on completion. |
| 47 | + /// - parameter receiveValue: The closure to execute on receipt of a value. |
| 48 | + /// - Returns: A cancellable instance, which you use when you end assignment of the received value. Deallocation of the result will tear down the subscription stream. |
| 49 | + func sink<UnretainedObject: AnyObject>(unretainedObject obj: UnretainedObject, receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((UnretainedObject, Self.Output) -> Void)) -> AnyCancellable { |
| 50 | + withUnretained(obj) |
| 51 | + .sink(receiveCompletion: receiveCompletion, receiveValue: receiveValue) |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +// MARK: - Publisher |
| 56 | +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) |
| 57 | +public extension Publishers { |
| 58 | + struct WithUnretained<UnretainedObject: AnyObject, Upstream: Publisher, Output>: Publisher { |
| 59 | + public typealias Failure = Upstream.Failure |
| 60 | + |
| 61 | + private weak var unretainedObject: UnretainedObject? |
| 62 | + private let upstream: Upstream |
| 63 | + private let resultSelector: (UnretainedObject, Upstream.Output) -> Output |
| 64 | + |
| 65 | + public init(unretainedObject: UnretainedObject, upstream: Upstream, resultSelector: @escaping (UnretainedObject, Upstream.Output) -> Output) { |
| 66 | + self.unretainedObject = unretainedObject |
| 67 | + self.upstream = upstream |
| 68 | + self.resultSelector = resultSelector |
| 69 | + } |
| 70 | + |
| 71 | + public func receive<S: Combine.Subscriber>(subscriber: S) where Failure == S.Failure, Output == S.Input { |
| 72 | + upstream.subscribe(Subscriber(unretainedObject: unretainedObject, downstream: subscriber, resultSelector: resultSelector)) |
| 73 | + } |
| 74 | + } |
| 75 | +} |
| 76 | + |
| 77 | +// MARK: - Subscriber |
| 78 | +@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *) |
| 79 | +private extension Publishers.WithUnretained { |
| 80 | + class Subscriber<Downstream: Combine.Subscriber>: Combine.Subscriber where Downstream.Input == Output, Downstream.Failure == Failure { |
| 81 | + typealias Input = Upstream.Output |
| 82 | + typealias Failure = Downstream.Failure |
| 83 | + |
| 84 | + private weak var unretainedObject: UnretainedObject? |
| 85 | + private let downstream: Downstream |
| 86 | + private let resultSelector: (UnretainedObject, Input) -> Output |
| 87 | + |
| 88 | + init(unretainedObject: UnretainedObject?, downstream: Downstream, resultSelector: @escaping (UnretainedObject, Input) -> Output) { |
| 89 | + self.unretainedObject = unretainedObject |
| 90 | + self.downstream = downstream |
| 91 | + self.resultSelector = resultSelector |
| 92 | + } |
| 93 | + |
| 94 | + func receive(subscription: Subscription) { |
| 95 | + if unretainedObject == nil { return } |
| 96 | + downstream.receive(subscription: subscription) |
| 97 | + } |
| 98 | + |
| 99 | + func receive(_ input: Input) -> Subscribers.Demand { |
| 100 | + guard let unretainedObject = unretainedObject else { return .none } |
| 101 | + return downstream.receive(resultSelector(unretainedObject, input)) |
| 102 | + } |
| 103 | + |
| 104 | + func receive(completion: Subscribers.Completion<Failure>) { |
| 105 | + if unretainedObject == nil { |
| 106 | + return downstream.receive(completion: .finished) |
| 107 | + } |
| 108 | + downstream.receive(completion: completion) |
| 109 | + } |
| 110 | + } |
| 111 | +} |
| 112 | +#endif |
0 commit comments