You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -385,7 +385,7 @@ The Composable Architecture comes with a number of tools to aid in debugging.
385
385
386
386
1. 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.
387
387
388
-
2. 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 ReactiveSwift's [`UIScheduler`](https://github.com/ReactiveCocoa/ReactiveSwift/blob/f97db218c0236b0c6ef74d32adb3d578792969c0/Sources/Scheduler.swift)). This introduces a lot more complexity, and should probably not be adopted without having a very good reason.
388
+
2. 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 [CombineScheduler](https://github.com/pointfreeco/combine-schedulers)'s [`UIScheduler`](https://github.com/pointfreeco/combine-schedulers/blob/main/Sources/CombineSchedulers/UIScheduler.swift)). This introduces a lot more complexity, and should probably not be adopted without having a very good reason.
389
389
390
390
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 `.observe(on:)` in order to force it back on the main thread.
/// The `Store` class is not thread-safe, and so all interactions with an instance of ``Store``
79
+
/// (including all of its scopes and derived ``ViewStore``s) must be done on the same thread.
80
+
/// Further, if the store is powering a SwiftUI or UIKit view, as is customary, then all
81
+
/// interactions must be done on the _main_ thread.
82
+
///
83
+
/// The reason stores are not thread-safe is due to the fact that when an action is sent to a store,
84
+
/// a reducer is run on the current state, and this process cannot be done from multiple threads.
85
+
/// It is possible to make this process thread-safe by introducing locks or queues, but this
86
+
/// introduces new complications:
87
+
///
88
+
/// * If done simply with `DispatchQueue.main.async` you will incur a thread hop even when you are
89
+
/// already on the main thread. This can lead to unexpected behavior in UIKit and SwiftUI, where
90
+
/// sometimes you are required to do work synchronously, such as in animation blocks.
91
+
///
92
+
/// * It is possible to create a scheduler that performs its work immediately when on the main
93
+
/// thread and otherwise uses `DispatchQueue.main.async` (e.g. see CombineScheduler's [UIScheduler](https://github.com/pointfreeco/combine-schedulers/blob/main/Sources/CombineSchedulers/UIScheduler.swift)). This introduces a lot more complexity, and should probably not be adopted without having a very
94
+
/// good reason.
95
+
///
96
+
/// This is why we require all actions be sent from the same thread. This requirement is in the same
97
+
/// spirit of how `URLSession` and other Apple APIs are designed. Those APIs tend to deliver their
98
+
/// outputs on whatever thread is most convenient for them, and then it is your responsibility to
99
+
/// dispatch back to the main queue if that's what you need. The Composable Architecture makes you
100
+
/// responsible for making sure to send actions on the main thread. If you are using an effect that
101
+
/// may deliver its output on a non-main thread, you must explicitly perform `.receive(on:)` in
102
+
/// order to force it back on the main thread.
103
+
///
104
+
/// This approach makes the fewest number of assumptions about how effects are created and
105
+
/// transformed, and prevents unnecessary thread hops and re-dispatching. It also provides some
106
+
/// testing benefits. If your effects are not responsible for their own scheduling, then in tests
107
+
/// all of the effects would run synchronously and immediately. You would not be able to test how
108
+
/// multiple in-flight effects interleave with each other and affect the state of your application.
109
+
/// However, by leaving scheduling out of the ``Store`` we get to test these aspects of our effects
110
+
/// if we so desire, or we can ignore if we prefer. We have that flexibility.
111
+
///
112
+
/// See also: ``ViewStore`` to understand how one observes changes to the state in a ``Store`` and
113
+
/// sends user actions.
9
114
publicfinalclassStore<State, Action>{
10
115
@MutableProperty
11
116
private(set)varstate:State
@@ -36,26 +141,28 @@ public final class Store<State, Action> {
36
141
///
37
142
/// This can be useful for deriving new stores to hand to child views in an application. For
38
143
/// example:
39
-
/// ```swift
40
-
/// // Application state made from local states.
41
-
/// struct AppState { var login: LoginState, ... }
42
-
/// struct AppAction { case login(LoginAction), ... }
43
144
///
44
-
/// // A store that runs the entire application.
45
-
/// let store = Store(
46
-
/// initialState: AppState(),
47
-
/// reducer: appReducer,
48
-
/// environment: AppEnvironment()
49
-
/// )
145
+
/// ```swift
146
+
/// // Application state made from local states.
147
+
/// struct AppState { var login: LoginState, ... }
148
+
/// struct AppAction { case login(LoginAction), ... }
149
+
///
150
+
/// // A store that runs the entire application.
151
+
/// let store = Store(
152
+
/// initialState: AppState(),
153
+
/// reducer: appReducer,
154
+
/// environment: AppEnvironment()
155
+
/// )
156
+
///
157
+
/// // Construct a login view by scoping the store to one that works with only login domain.
158
+
/// LoginView(
159
+
/// store: store.scope(
160
+
/// state: \.login,
161
+
/// action: AppAction.login
162
+
/// )
163
+
/// )
164
+
/// ```
50
165
///
51
-
/// // Construct a login view by scoping the store to one that works with only login domain.
52
-
/// LoginView(
53
-
/// store: store.scope(
54
-
/// state: { $0.login },
55
-
/// action: { AppAction.login($0) }
56
-
/// )
57
-
/// )
58
-
/// ```
59
166
/// Scoping in this fashion allows you to better modularize your application. In this case,
60
167
/// `LoginView` could be extracted to a module that has no access to `AppState` or `AppAction`.
61
168
///
@@ -65,29 +172,32 @@ public final class Store<State, Action> {
65
172
/// For example, the above login domain could model a two screen login flow: a login form followed
66
173
/// by a two-factor authentication screen. The second screen's domain might be nested in the
67
174
/// first:
68
-
/// ```swift
69
-
/// struct LoginState: Equatable {
70
-
/// var email = ""
71
-
/// var password = ""
72
-
/// var twoFactorAuth: TwoFactorAuthState?
73
-
/// }
74
175
///
75
-
/// enum LoginAction: Equatable {
76
-
/// case emailChanged(String)
77
-
/// case loginButtonTapped
78
-
/// case loginResponse(Result<TwoFactorAuthState, LoginError>)
79
-
/// case passwordChanged(String)
80
-
/// case twoFactorAuth(TwoFactorAuthAction)
81
-
/// }
82
-
/// ```
176
+
/// ```swift
177
+
/// struct LoginState: Equatable {
178
+
/// var email = ""
179
+
/// var password = ""
180
+
/// var twoFactorAuth: TwoFactorAuthState?
181
+
/// }
182
+
///
183
+
/// enum LoginAction: Equatable {
184
+
/// case emailChanged(String)
185
+
/// case loginButtonTapped
186
+
/// case loginResponse(Result<TwoFactorAuthState, LoginError>)
187
+
/// case passwordChanged(String)
188
+
/// case twoFactorAuth(TwoFactorAuthAction)
189
+
/// }
190
+
/// ```
191
+
///
83
192
/// The login view holds onto a store of this domain:
84
-
/// ```swift
85
-
/// struct LoginView: View {
86
-
/// let store: Store<LoginState, LoginAction>
193
+
/// ```swift
194
+
/// struct LoginView: View {
195
+
/// let store: Store<LoginState, LoginAction>
196
+
///
197
+
/// var body: some View { ... }
198
+
/// }
199
+
/// ```
87
200
///
88
-
/// var body: some View { ... }
89
-
/// }
90
-
/// ```
91
201
/// If its body were to use a view store of the same domain, this would introduce a number of
92
202
/// problems:
93
203
///
@@ -106,54 +216,59 @@ public final class Store<State, Action> {
106
216
///
107
217
/// To avoid these issues, one can introduce a view-specific domain that slices off the subset of
108
218
/// state and actions that a view cares about:
109
-
/// ```swift
110
-
/// extension LoginView {
111
-
/// struct State: Equatable {
112
-
/// var email: String
113
-
/// var password: String
114
-
/// }
115
219
///
116
-
/// enum Action: Equatable {
117
-
/// case emailChanged(String)
118
-
/// case loginButtonTapped
119
-
/// case passwordChanged(String)
120
-
/// }
121
-
/// }
122
-
/// ```
220
+
/// ```swift
221
+
/// extension LoginView {
222
+
/// struct State: Equatable {
223
+
/// var email: String
224
+
/// var password: String
225
+
/// }
226
+
///
227
+
/// enum Action: Equatable {
228
+
/// case emailChanged(String)
229
+
/// case loginButtonTapped
230
+
/// case passwordChanged(String)
231
+
/// }
232
+
/// }
233
+
/// ```
234
+
///
123
235
/// One can also introduce a couple helpers that transform feature state into view state and
0 commit comments