Announcement: Concurrency Beta #1186
Replies: 9 comments 29 replies
-
|
In my testing it appears that the default timeout of one millisecond isn't enough for things like |
Beta Was this translation helpful? Give feedback.
-
|
The following Gist demonstrates that the phenomenon can be spotted by a So the idea is to discuss whether the problem lies in the Reducer { state, action, environment in
func updateDescription(_ inout State) {…}
switch action {
case .binding:
updateDescription(&state)
return .none
case .updateDescription:
updateDescription(&state)
return .none
}
}Hopefully, reducers as protocol could help formalizing this kind of lego construction, where we could imagine ultra-specialized Reducers components of larger Reducers, processing only one kind of information/action. Another standard example of action forwarding is for dismissing some presented modal whenever the user selects a value: you can either set the state's modal properties/flag to This issue wasn't really expressing until now, but if one starts to use more and more It also poses the question of the necessity or not to have synchronous |
Beta Was this translation helpful? Give feedback.
-
|
Are yall open to Linux support in the beta? Will it be possible to get away with no reliance on Combine, or not quite yet? My use case is that I'm working on an iOS/macOS app, but I'd be able to run our unit test CI on Linux boxes if we no longer needed to use Combine, which would be 10x cheaper. |
Beta Was this translation helpful? Give feedback.
-
|
Hey everyone, we just opened a draft PR (#1189) of our ongoing work bringing concurrency to the library. We will be making smaller, more focused PR's against that branch so that it is clear how we are evolving things for the next few weeks. |
Beta Was this translation helpful? Give feedback.
-
|
In the current |
Beta Was this translation helpful? Give feedback.
-
|
In some reducer, I'm updating some CoreData store using a Should I can understand why it could matter in terms of future-proofing in any case Swift gets typed throws and the library goes back to |
Beta Was this translation helpful? Give feedback.
-
|
As I'm converting my project, I'm encountering many WithViewStore(store) { viewStore in
Text("Hello \(viewStore.name)!")
.task { await viewStore.send(.task).finish() }
}As this pattern happens a lot, I'm wondering if we could improve the situation with a little sugar. For example, as we know we'll need a WithViewStore(store) { viewStore in
Text("Hello \(viewStore.name)!")
}.task(action: .task)I've also experimented with WithViewStore(store, task: .task) { viewStore in
Text("Hello \(viewStore.name)!")
}Another approach could directly use VStack {
DetailView(store.scope(…))
}.task(store: store, action: .task)Thus, we could call If the " What do you think about this? |
Beta Was this translation helpful? Give feedback.
-
|
Does Without it, adding a (Very possible I'm doing something wrong, in which case, disregard!) |
Beta Was this translation helpful? Give feedback.
-
|
Hello everyone, today we have officially released 0.39.0 of the library which makes all of these async tools available in the library, and ends the beta period of this feature. Thanks to everyone that provided feedback and helped catch bugs! |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hey everyone! As we kick off our series of episodes on "Async Composable Architecture," we'd like to invite users of the library to test our upcoming release more formally. We’ve been working on these tools for many, many, many months, and are excited that they are finally coming together! We hope to gather community feedback to make them even better for release.
The episodes will dive deep into the motivation and basic implementations of many new async features, but we'll do our best to give an overview of the additions and changes below. Most of these changes should be fully backwards-compatible, with a few minor exceptions. All deprecations are "soft" for now and should not produce noisy warnings.
Changes to reducer effects
The
Effecttype has been tightly coupled to the Combine framework for a long time now: it conforms toPublisherand we have expected library users to interact with it as such. While Combine suited us when TCA first launched, it is a complex framework that SwiftUI mostly hides behind the scenes, and is a learning hurdle for library users. It is also closed-source, which prevents TCA from being used on non-Apple platforms.In our upcoming release we will begin to loosen our dependence on Combine by nudging library users toward a smaller collection of async/await endpoints on the
Effecttype. We believe that it is possible to define all of your application's effects in terms of these endpoints, and you can even completely avoid interacting withEffectas a Combine publisher. In the future, we hope to deprecate our dependency on Combine entirely and instead just offer the following surface area:Effect.noneThis endpoint is not new, and will remain the effect that does nothing.
Effect.taskThis endpoint allows you to spin up an async context that will feed an action back into a reducer as some later date.
While
Effect.taskwas introduced awhile ago, we’ve made a few changes.Previously, we documented that you should not use
Effect.taskdirectly in your reducers, as the async context made testing precarious. This is no longer the case, as we have improved our scheduling and testing tools to accommodate async/await (see "Changes to the test store" and "Changes to Combine Schedulers").Effect.taskwas also originally overloaded with throwing and non-throwing versions:We have consolidated this into a single endpoint that returns a non-failing
Effect<_, Never>that uses an optional "catch" block for handling errors:operationthrows aCancellationError, the effect will be ignored and no action will be fed back into the system.operationthrows anything else,catchwill be invoked to feed an error handling action back into the system.catchis not provided, a runtime warning / test failure will be generated.For more on error-handling ergonomics and
Effect.task, see "Task results".The version of
Effect.taskthat returnsEffect<_, Error>has been soft-deprecated, as it requires you to perform additional work with Combine operators to catch errors.Effect.runThis is a new endpoint (not to be confused with the existing, Combine-friendly API). Like
Effect.task, it allows you to spin up an async context, but instead of returning a single action to be fed back into the reducer, it can feed multiple actions back into the reducer over time via asendcontinuation.The
sendparameter, likeViewStore.send, accepts an optionalanimationparameter for performing SwiftUI animations:Error handling is done the same way as
Effect.task: throwing aCancellationErrorwill terminate the async work, while throwing anything else must be handled in acatchclosure to avoid runtime warnings and test failures.Effect.fireAndForgetThis endpoint allows you to spin up an async context that will never feed an action back into the reducer.
While a synchronous version of this endpoint has existed for a long time, the async-friendly version was introduced just recently.
Unlike
Effect.taskandEffect.run, errors are ignored, and the context will terminate as soon as one is thrown.Effect cancellation
Effect cancellation remains mostly the same, with a few improvements.
First,
ViewStore.sendnow returns a task that can be awaited/cancelled directly, this can help eliminate extra work done in the reducer to manage cancellation. (See "Changes to the view store" for more.)Second, there are new async-friendly
Task.cancel(id:)andwithTaskCancellation(id:)helpers for introducing more fine-grained cancellation to an effect:Task results
We’ve introduced a new type,
TaskResultto aid in performing failable work in the Composable Architecture. It can be used to wrap the output of an effect:So why use a
TaskResultinstead of aResult?Swift error handling is currently untyped, which means an error thrown from an async effect is an
any Error. TCA’s testing tools require a feature’s state and actions be equatable for assertions to be made, so modeling an effect response withResultis incompatible with these tools.It has been common to dance around this by replacing errors with custom equatable error types, instead, but this requires a lot of boilerplate.
TaskResulteliminates that extra work.If you attempt to assert against a task result failure with a non-equatable error, the test will fail. Simply conform the error type you test to
Equatableto get a passing test.Changes to the view store
ViewStore.sendnow returns aViewStoreTask, which is a handle to the lifecycle of any effects the reducer returns for the action.This task can be awaited or cancelled, which means a SwiftUI view can use a
taskview modifier to automatically cancel and tear down long-living effects spun up by the reducer:This makes it much easier for child features to clean up their long-living effects automatically, and will help avoid runtime warnings commonly encountered when using
optionalandforEachreducers.Changes to the test store
In order to test a reducer that uses async effects, the test store must be made more async-aware. There are two main changes to be aware of:
Async
TestStore.receiveTestStore.receivehas a new async overload, which will await the action to be received from an effect to give the concurrency runtime enough time to deliver the action.The non-async version of
receivehas been soft-deprecated.TestStoreTaskWhere
ViewStore.sendreturns aViewStoreTaskthat can be cancelled/awaited,TestStore.sendnow returns aTestStoreTaskthat can do the same. This means if you write a test that spins up a long-living effect for a view’s task modifier, you can test its cancellation:You can also wait for a test store task to finish before making an assertion:
Changes to Combine Schedulers
TCA has heavily relied on our Combine Scheduler library to make time-based features more testable. While Swift 5.7 introduces the
Clockprotocol and paves a way towards writing time-based features without Combine, we will still benefit from improved tooling in the meantime.As such, we’ve introduced the following changes to Combine Schedulers:
Async
TestScheduler.advanceTestScheduler.advancehas a new async overload for async contexts. It automatically suspends its task as it advances and schedules work to be performed.Scheduler.sleepandScheduler.timerThere are new async endpoints on
Schedulerfor sleeping a task or spinning up a timer. Use these endpoints insideEffect.taskandEffect.runto schedule work and remain synchronously testable:These methods can be thought of as replacements for the
Effect.delayandEffect.timerendpoints.Trying the beta
To give the beta a shot, update your SPM dependencies to point to the
concurrency-betabranch:This branch also includes updated demo applications using these APIs, so check them out if you're curious!
We hope these new tools make TCA more fun and easier to use than ever. If you take things for a spin, please let us know if you have any questions, comments, concerns, or suggestions!
Beta Was this translation helpful? Give feedback.
All reactions