From db0c44598820b53f9f10091865dfbf4a06bc5f6e Mon Sep 17 00:00:00 2001 From: Jefferson Setiawan Date: Wed, 15 Feb 2023 21:31:25 +0700 Subject: [PATCH] Fix Stack overflow on Old Reducer style (#74) remove deprecate, rearrange send --- .../Internal/Deprecated.swift | 11 -- Sources/RxComposableArchitecture/Store.swift | 131 +++++++++--------- .../StoreOldScopeTest.swift | 4 +- 3 files changed, 69 insertions(+), 77 deletions(-) diff --git a/Sources/RxComposableArchitecture/Internal/Deprecated.swift b/Sources/RxComposableArchitecture/Internal/Deprecated.swift index 0ffb1b5..e9f901a 100644 --- a/Sources/RxComposableArchitecture/Internal/Deprecated.swift +++ b/Sources/RxComposableArchitecture/Internal/Deprecated.swift @@ -13,17 +13,6 @@ import Darwin /// Read for more information. /// /// A type alias to ``AnyReducer`` for source compatibility. This alias will be removed. -@available( - *, - deprecated, - renamed: "AnyReducer", - message: - """ - 'Reducer' has been deprecated in favor of 'ReducerProtocol'. - - See the migration guide for more information: https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/reducerprotocol - """ -) public typealias Reducer = AnyReducer diff --git a/Sources/RxComposableArchitecture/Store.swift b/Sources/RxComposableArchitecture/Store.swift index cdc8daa..2c77e9f 100644 --- a/Sources/RxComposableArchitecture/Store.swift +++ b/Sources/RxComposableArchitecture/Store.swift @@ -254,14 +254,74 @@ public final class Store { } @discardableResult - public func send( + public func send(_ action: Action, originatingFrom originatingAction: Action? = nil) -> Task? { + if useNewScope { + return newSend(action, originatingFrom: originatingAction) + } + self.threadCheck(status: .send(action, originatingAction: originatingAction)) + if !isSending { + synchronousActionsToSend.append(action) + } else { + bufferedActions.append(action) + return nil + } + + while !synchronousActionsToSend.isEmpty || !bufferedActions.isEmpty { + let action = !synchronousActionsToSend.isEmpty + ? synchronousActionsToSend.removeFirst() + : bufferedActions.removeFirst() + + isSending = true + #if swift(>=5.7) + let effect = self.reducer.reduce(into: &state, action: action) + #else + let effect = self.reducer(&state, action) + #endif + isSending = false + + var didComplete = false + var isProcessingEffects = true + var disposeKey: CompositeDisposable.DisposeKey? + + switch effect.operation { + case .none: break + case .run: + assertionFailure("old style reducer cannot use `run`") + case let .observable(observable): + let effectDisposable = observable.subscribe( + onNext: { [weak self] effectAction in + if isProcessingEffects { + self?.synchronousActionsToSend.append(effectAction) + } else { + self?.send(effectAction, originatingFrom: action) + } + }, + onError: { err in + assertionFailure("Error during effect handling: \(err.localizedDescription)") + }, + onCompleted: { [weak self] in + didComplete = true + if let disposeKey = disposeKey { + self?.effectDisposables.remove(for: disposeKey) + } + } + ) + isProcessingEffects = false + + if !didComplete { + disposeKey = effectDisposables.insert(effectDisposable) + } + } + } + return nil + } + + @inline(__always) + @discardableResult + public func newSend( _ action: Action, originatingFrom originatingAction: Action? = nil ) -> Task? { - guard useNewScope else { - oldSend(action, originatingFrom: originatingAction) - return nil - } self.threadCheck(status: .send(action, originatingAction: originatingAction)) self.bufferedActions.append(action) @@ -481,63 +541,6 @@ public final class Store { ) -> Effect { return relay.map(toLocalState).distinctUntilChanged().eraseToEffect() } - - @inline(__always) - private func oldSend(_ action: Action, originatingFrom originatingAction: Action? = nil) { - self.threadCheck(status: .send(action, originatingAction: originatingAction)) - if !isSending { - synchronousActionsToSend.append(action) - } else { - bufferedActions.append(action) - return - } - - while !synchronousActionsToSend.isEmpty || !bufferedActions.isEmpty { - let action = !synchronousActionsToSend.isEmpty - ? synchronousActionsToSend.removeFirst() - : bufferedActions.removeFirst() - - isSending = true - #if swift(>=5.7) - let effect = self.reducer.reduce(into: &state, action: action) - #else - let effect = self.reducer(&state, action) - #endif - isSending = false - - var didComplete = false - var isProcessingEffects = true - var disposeKey: CompositeDisposable.DisposeKey? - - switch effect.operation { - case .none, .run: break - case let .observable(observable): - let effectDisposable = observable.subscribe( - onNext: { [weak self] effectAction in - if isProcessingEffects { - self?.synchronousActionsToSend.append(effectAction) - } else { - self?.send(effectAction, originatingFrom: action) - } - }, - onError: { err in - assertionFailure("Error during effect handling: \(err.localizedDescription)") - }, - onCompleted: { [weak self] in - didComplete = true - if let disposeKey = disposeKey { - self?.effectDisposables.remove(for: disposeKey) - } - } - ) - isProcessingEffects = false - - if !didComplete { - disposeKey = effectDisposables.insert(effectDisposable) - } - } - } - } } extension Store { @@ -632,7 +635,7 @@ extension Store where State: Collection, State.Element: HashDiffable, State: Equ guard let element = toLocalState(identifier, state) else { return nil } let localStore = Store( initialState: element, - reducer: Reducer { localState, localAction, _ in + reducer: AnyReducer { localState, localAction, _ in isSending = true defer { isSending = false } self.send(fromLocalAction(localAction)) @@ -662,7 +665,7 @@ extension Store where State: Collection, State.Element: HashDiffable, State: Equ let localStore = Store( initialState: element, - reducer: Reducer { localState, localAction, _ in + reducer: AnyReducer { localState, localAction, _ in self.send(fromLocalAction(localAction)) guard let finalState = toLocalState(identifier, self.state) else { return .none diff --git a/Tests/RxComposableArchitectureTests/StoreOldScopeTest.swift b/Tests/RxComposableArchitectureTests/StoreOldScopeTest.swift index c20dced..b4606cf 100644 --- a/Tests/RxComposableArchitectureTests/StoreOldScopeTest.swift +++ b/Tests/RxComposableArchitectureTests/StoreOldScopeTest.swift @@ -220,12 +220,12 @@ internal final class StoreOldScopeTest: XCTestCase { _ = store.send((1, .didTap)) XCTAssertEqual(numCalls1, 4) - XCTAssertEqual(numCalls2, 4) + XCTAssertEqual(numCalls2, 5) XCTAssertEqual(store.state.qty, 2) _ = store.send((1, .didTap)) XCTAssertEqual(numCalls1, 6) - XCTAssertEqual(numCalls2, 6) + XCTAssertEqual(numCalls2, 8) XCTAssertEqual(store.state.qty, 3) }