Skip to content

Instantly share code, notes, and snippets.

@robertmryan
Created June 1, 2024 17:50
Show Gist options
  • Save robertmryan/3ac998cf72afda940aed0187f4ca37ec to your computer and use it in GitHub Desktop.
Save robertmryan/3ac998cf72afda940aed0187f4ca37ec to your computer and use it in GitHub Desktop.
@discardableResult
public func send(_ action: Action) async throws -> State {
try Task.checkCancellation()
let effect = await reducer.reduce(into: &viewState, action: action)
if let nextAction = await effect.run() {
return try await send(nextAction)
}
return viewState
}
func testSend() async throws {
let state = try await store.send(.increment)
XCTAssertEqual(viewState.counter, 1)
}
@robertmryan
Copy link
Author

The idea is:

  • remain within structured concurrency; avoid introducing unstructured concurrency with Task {…}.
  • provide mechanism to know when the asynchronous process is done (we could have done it with an optional completion handler, but with Swift concurrency we would just make the testXxx function an async throws function);
  • return the state so that the test doesn’t have to reach in and fetch the internal state; make it a @discardableResult if you don’t need this functionality within your app; and
  • periodically check to see if the task was canceled … your app might not need it right now, but it is best practice to always support cancelation because if you later need cancelation support, you don’t want to have to go back and refactor everything; support cancelation from the “get go”.

@robertmryan
Copy link
Author

Note, I’ve removed the MainActor.run {…}. If it needs to run on the main actor, isolate the Reducer, itself, to the main actor:

@MainActor
final class Reducer {  }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment