Skip to content

Remove outdated thread-safety docs #3701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 0 additions & 55 deletions Sources/ComposableArchitecture/Store.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,55 +83,6 @@ import SwiftUI
/// }
/// ```
///
/// ### Thread safety
///
/// The `Store` class is not thread-safe, and so all interactions with an instance of ``Store``
/// (including all of its child stores) must be done on the same thread the store was created on.
/// Further, if the store is powering a SwiftUI or UIKit view, as is customary, then all
/// interactions must be done on the _main_ thread.
///
/// The reason stores are not thread-safe is due to the fact that when an action is sent to a store,
/// a reducer is run on the current state, and this process cannot be done from multiple threads.
/// It is possible to make this process thread-safe by introducing locks or queues, but this
/// introduces new complications:
///
/// * If done simply with `DispatchQueue.main.async` you will incur a thread hop even when you are
/// already on the main thread. This can lead to unexpected behavior in UIKit and SwiftUI, where
/// sometimes you are required to do work synchronously, such as in animation blocks.
///
/// * It is possible to create a scheduler that performs its work immediately when on the main
/// thread and otherwise uses `DispatchQueue.main.async` (_e.g._, see Combine Schedulers'
/// [UIScheduler][uischeduler]).
///
/// This introduces a lot more complexity, and should probably not be adopted without having a very
/// good reason.
///
/// This is why we require all actions be sent from the same thread. This requirement is in the same
/// spirit of how `URLSession` and other Apple APIs are designed. Those APIs tend to deliver their
/// outputs on whatever thread is most convenient for them, and then it is your responsibility to
/// dispatch back to the main queue if that's what you need. The Composable Architecture makes you
/// responsible for making sure to send actions on the main thread. If you are using an effect that
/// may deliver its output on a non-main thread, you must explicitly perform `.receive(on:)` in
/// order to force it back on the main thread.
///
/// This approach makes the fewest number of assumptions about how effects are created and
/// transformed, and prevents unnecessary thread hops and re-dispatching. It also provides some
/// testing benefits. If your effects are not responsible for their own scheduling, then in tests
/// all of the effects would run synchronously and immediately. You would not be able to test how
/// multiple in-flight effects interleave with each other and affect the state of your application.
/// However, by leaving scheduling out of the ``Store`` we get to test these aspects of our effects
/// if we so desire, or we can ignore if we prefer. We have that flexibility.
///
/// [uischeduler]: https://github.com/pointfreeco/combine-schedulers/blob/main/Sources/CombineSchedulers/UIScheduler.swift
///
/// #### Thread safety checks
///
/// The store performs some basic thread safety checks in order to help catch mistakes. Stores
/// constructed via the initializer ``init(initialState:reducer:withDependencies:)`` are assumed
/// to run only on the main thread, and so a check is executed immediately to make sure that is the
/// case. Further, all actions sent to the store and all scopes (see ``scope(state:action:)-90255``)
/// of the store are also checked to make sure that work is performed on the main thread.
///
/// ### ObservableObject conformance
///
/// The store conforms to `ObservableObject` but is _not_ observable via the `@ObservedObject`
Expand Down Expand Up @@ -222,12 +173,6 @@ public final class Store<State, Action> {
/// .task { await store.send(.task).finish() }
/// ```
///
/// > Important: The ``Store`` is not thread safe and you should only send actions to it from the
/// > main thread. If you want to send actions on background threads due to the fact that the
/// > reducer is performing computationally expensive work, then a better way to handle this is to
/// > wrap that work in an ``Effect`` that is performed on a background thread so that the
/// > result can be fed back into the store.
///
/// - Parameter action: An action.
/// - Returns: A ``StoreTask`` that represents the lifecycle of the effect executed when
/// sending the action.
Expand Down
5 changes: 0 additions & 5 deletions Sources/ComposableArchitecture/ViewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,6 @@ import SwiftUI
/// self.viewStore.send(.incrementButtonTapped)
/// }
/// ```
///
/// > Important: The `ViewStore` class is not thread-safe, and all interactions with it (and the
/// > store it was derived from) must happen on the same thread. Further, for SwiftUI applications,
/// > all interactions must happen on the _main_ thread. See the documentation of the ``Store``
/// > class for more information as to why this decision was made.
@available(
iOS,
deprecated: 9999,
Expand Down
Loading