Skip to content

Commit

Permalink
Fix Stack overflow on Old Reducer style (#74)
Browse files Browse the repository at this point in the history
remove deprecate, rearrange send
  • Loading branch information
jeffersonsetiawan authored Feb 15, 2023
1 parent 71f5c11 commit db0c445
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 77 deletions.
11 changes: 0 additions & 11 deletions Sources/RxComposableArchitecture/Internal/Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@ import Darwin
/// Read <doc:MigratingToTheReducerProtocol> 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


Expand Down
131 changes: 67 additions & 64 deletions Sources/RxComposableArchitecture/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -254,14 +254,74 @@ public final class Store<State, Action> {
}

@discardableResult
public func send(
public func send(_ action: Action, originatingFrom originatingAction: Action? = nil) -> Task<Void, Never>? {
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<Void, Never>? {
guard useNewScope else {
oldSend(action, originatingFrom: originatingAction)
return nil
}
self.threadCheck(status: .send(action, originatingAction: originatingAction))

self.bufferedActions.append(action)
Expand Down Expand Up @@ -481,63 +541,6 @@ public final class Store<State, Action> {
) -> Effect<LocalState> {
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 {
Expand Down Expand Up @@ -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<State.Element, LocalAction>(
initialState: element,
reducer: Reducer { localState, localAction, _ in
reducer: AnyReducer { localState, localAction, _ in
isSending = true
defer { isSending = false }
self.send(fromLocalAction(localAction))
Expand Down Expand Up @@ -662,7 +665,7 @@ extension Store where State: Collection, State.Element: HashDiffable, State: Equ

let localStore = Store<State.Element, LocalAction>(
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
Expand Down
4 changes: 2 additions & 2 deletions Tests/RxComposableArchitectureTests/StoreOldScopeTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down

0 comments on commit db0c445

Please sign in to comment.