From 1460c11cf1bada567598051e6212230c1fcd9aef Mon Sep 17 00:00:00 2001 From: Pat Brown Date: Sun, 29 Aug 2021 11:17:30 +1000 Subject: [PATCH 1/2] Replaced CurrentValueSubject with CurrentValueRelay --- .../Internal/Create.swift | 2 +- .../Internal/CurrentValueRelay.swift | 58 +++++++++++++++++++ .../ComposableArchitecture/ViewStore.swift | 4 +- .../ViewStoreTests.swift | 31 ++++++++++ 4 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 Sources/ComposableArchitecture/Internal/CurrentValueRelay.swift diff --git a/Sources/ComposableArchitecture/Internal/Create.swift b/Sources/ComposableArchitecture/Internal/Create.swift index 69d63b7f678b..af2b697c4c98 100644 --- a/Sources/ComposableArchitecture/Internal/Create.swift +++ b/Sources/ComposableArchitecture/Internal/Create.swift @@ -23,7 +23,7 @@ import Combine import Darwin -private class DemandBuffer { +class DemandBuffer { private var buffer = [S.Input]() private let subscriber: S private var completion: Subscribers.Completion? diff --git a/Sources/ComposableArchitecture/Internal/CurrentValueRelay.swift b/Sources/ComposableArchitecture/Internal/CurrentValueRelay.swift new file mode 100644 index 000000000000..9a51db2cd47e --- /dev/null +++ b/Sources/ComposableArchitecture/Internal/CurrentValueRelay.swift @@ -0,0 +1,58 @@ +import Combine +import Foundation + +class CurrentValueRelay: Publisher { + typealias Failure = Never + + private var currentValue: Output + private var subscriptions: [Subscription>] = [] + + var value: Output { + get { self.currentValue } + set { self.send(newValue) } + } + + init(_ value: Output) { + self.currentValue = value + } + + func receive(subscriber: S) + where S: Subscriber, Never == S.Failure, Output == S.Input + { + let subscription = Subscription(downstream: AnySubscriber(subscriber)) + self.subscriptions.append(subscription) + subscriber.receive(subscription: subscription) + subscription.forwardValueToBuffer(self.currentValue) + } + + func send(_ value: Output) { + self.currentValue = value + for subscription in subscriptions { + subscription.forwardValueToBuffer(value) + } + } +} + +extension CurrentValueRelay { + class Subscription: Combine.Subscription + where Output == Downstream.Input, Failure == Downstream.Failure + { + private var demandBuffer: DemandBuffer? + + init(downstream: Downstream) { + self.demandBuffer = DemandBuffer(subscriber: downstream) + } + + func forwardValueToBuffer(_ value: Output) { + _ = demandBuffer?.buffer(value: value) + } + + func request(_ demand: Subscribers.Demand) { + _ = demandBuffer?.demand(demand) + } + + func cancel() { + demandBuffer = nil + } + } +} diff --git a/Sources/ComposableArchitecture/ViewStore.swift b/Sources/ComposableArchitecture/ViewStore.swift index 83e689a42097..c6764122f8be 100644 --- a/Sources/ComposableArchitecture/ViewStore.swift +++ b/Sources/ComposableArchitecture/ViewStore.swift @@ -58,7 +58,7 @@ public final class ViewStore: ObservableObject { public private(set) lazy var objectWillChange = ObservableObjectPublisher() private let _send: (Action) -> Void - fileprivate let _state: CurrentValueSubject + fileprivate let _state: CurrentValueRelay private var viewCancellable: AnyCancellable? /// Initializes a view store from a store. @@ -72,7 +72,7 @@ public final class ViewStore: ObservableObject { removeDuplicates isDuplicate: @escaping (State, State) -> Bool ) { self._send = store.send - self._state = CurrentValueSubject(store.state.value) + self._state = CurrentValueRelay(store.state.value) self.viewCancellable = store.state .removeDuplicates(by: isDuplicate) diff --git a/Tests/ComposableArchitectureTests/ViewStoreTests.swift b/Tests/ComposableArchitectureTests/ViewStoreTests.swift index 334929219432..c2ded621629d 100644 --- a/Tests/ComposableArchitectureTests/ViewStoreTests.swift +++ b/Tests/ComposableArchitectureTests/ViewStoreTests.swift @@ -137,6 +137,37 @@ final class ViewStoreTests: XCTestCase { XCTAssertNoDifference(results, [0, 1]) } + func testStorePublisherSubscriptionOrder() { + let reducer = Reducer { count, _, _ in + count += 1 + return .none + } + let store = Store(initialState: 0, reducer: reducer, environment: ()) + let viewStore = ViewStore(store) + + var results: [Int] = [] + + viewStore.publisher + .sink { _ in results.append(0) } + .store(in: &self.cancellables) + + viewStore.publisher + .sink { _ in results.append(1) } + .store(in: &self.cancellables) + + viewStore.publisher + .sink { _ in results.append(2) } + .store(in: &self.cancellables) + + XCTAssertNoDifference(results, [0, 1, 2]) + + for _ in 0 ..< 9 { + viewStore.send(()) + } + + XCTAssertNoDifference(results, Array(repeating: [0, 1, 2], count: 10).flatMap { $0 }) + } + #if compiler(>=5.5) func testSendWhile() { guard #available(iOS 15, macOS 12, tvOS 15, watchOS 8, *) else { return } From 284edae1676db76fc21c4e52071bd26e3d9f6b09 Mon Sep 17 00:00:00 2001 From: Pat Brown Date: Tue, 31 Aug 2021 07:32:40 +1000 Subject: [PATCH 2/2] Added final to DemandBuffer --- Sources/ComposableArchitecture/Internal/Create.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComposableArchitecture/Internal/Create.swift b/Sources/ComposableArchitecture/Internal/Create.swift index af2b697c4c98..b0c925c7985f 100644 --- a/Sources/ComposableArchitecture/Internal/Create.swift +++ b/Sources/ComposableArchitecture/Internal/Create.swift @@ -23,7 +23,7 @@ import Combine import Darwin -class DemandBuffer { +final class DemandBuffer { private var buffer = [S.Input]() private let subscriber: S private var completion: Subscribers.Completion?