@@ -33,6 +33,7 @@ system, such as SQLite.
3333* [ Observing changes to shared state] ( #Observing-changes-to-shared-state )
3434* [ Initialization rules] ( #Initialization-rules )
3535* [ Deriving shared state] ( #Deriving-shared-state )
36+ * [ Concurrent mutations to shared state] ( #Concurrent-mutations-to-shared-state )
3637* [ Testing shared state] ( #Testing-shared-state )
3738 * [ Testing when using persistence] ( #Testing-when-using-persistence )
3839 * [ Testing when using custom persistence strategies] ( #Testing-when-using-custom-persistence-strategies )
@@ -41,7 +42,6 @@ system, such as SQLite.
4142 * [ Testing tips] ( #Testing-tips )
4243* [ Read-only shared state] ( #Read-only-shared-state )
4344* [ Type-safe keys] ( #Type-safe-keys )
44- * [ Concurrent mutations to shared state] ( #Concurrent-mutations-to-shared-state )
4545* [ Shared state in pre-observation apps] ( #Shared-state-in-pre-observation-apps )
4646* [ Gotchas of @Shared ] ( #Gotchas-of-Shared )
4747
@@ -446,6 +446,39 @@ else { return }
446446todo // Shared<Todo>
447447```
448448
449+ ## Concurrent mutations to shared state
450+
451+ [mutating - shared- state- article]: https: // swiftpackageindex.com/pointfreeco/swift-sharing/main/documentation/sharing/mutatingsharedstate
452+
453+ While the [`@Shared `](< doc: Shared> ) property wrapper makes it possible to treat shared state
454+ _mostly_ like regular state, you do have to perform some extra steps to mutate shared state.
455+ This is because shared state is technically a reference deep down, even
456+ though we take extra steps to make it appear value- like. And this means it's possible to mutate the
457+ same piece of shared state from multiple threads, and hence race conditions are possible. See
458+ [Mutating Shared State][mutating - shared- state- article] for a more in - depth explanation.
459+
460+ To mutate a piece of shared state in an isolated fashion, use the `withLock` method
461+ defined on the `@Shared ` projected value:
462+
463+ ```swift
464+ state.$count.withLock { $0 += 1 }
465+ ```
466+
467+ That locks the entire unit of work of reading the current count, incrementing it, and storing it
468+ back in the reference.
469+
470+ Technically it is still possible to write code that has race conditions, such as this silly example:
471+
472+ ```swift
473+ let currentCount = state.count
474+ state.$count.withLock { $0 = currentCount + 1 }
475+ ```
476+
477+ But there is no way to 100 % prevent race conditions in code. Even actors are susceptible to
478+ problems due to re- entrancy. To avoid problems like the above we recommend wrapping as many
479+ mutations of the shared state as possible in a single `withLock`. That will make
480+ sure that the full unit of work is guarded by a lock.
481+
449482## Testing shared state
450483
451484Shared state behaves quite a bit different from the regular state held in Composable Architecture
@@ -476,7 +509,7 @@ struct Feature {
476509 Reduce { state, action in
477510 switch action {
478511 case .incrementButtonTapped :
479- state.count += 1
512+ state.$ count. withLock { $0 += 1 }
480513 return .none
481514 }
482515 }
@@ -964,36 +997,6 @@ struct FeatureView: View {
964997}
965998```
966999
967- ## Concurrent mutations to shared state
968-
969- While the [`@Shared `](< doc: Shared> ) property wrapper makes it possible to treat shared state
970- _mostly_ like regular state, you do have to perform some extra steps to mutate shared state.
971- This is because shared state is technically a reference deep down, even
972- though we take extra steps to make it appear value- like. And this means it's possible to mutate the
973- same piece of shared state from multiple threads, and hence race conditions are possible.
974-
975- To mutate a piece of shared state in an isolated fashion, use the `withLock` method
976- defined on the `@Shared ` projected value:
977-
978- ```swift
979- state.$count.withLock { $0 += 1 }
980- ```
981-
982- That locks the entire unit of work of reading the current count, incrementing it, and storing it
983- back in the reference.
984-
985- Technically it is still possible to write code that has race conditions, such as this silly example:
986-
987- ```swift
988- let currentCount = state.count
989- state.$count.withLock { $0 = currentCount + 1 }
990- ```
991-
992- But there is no way to 100 % prevent race conditions in code. Even actors are susceptible to
993- problems due to re- entrancy. To avoid problems like the above we recommend wrapping as many
994- mutations of the shared state as possible in a single `withLock`. That will make
995- sure that the full unit of work is guarded by a lock.
996-
9971000## Gotchas of @Shared
9981001
9991002There are a few gotchas to be aware of when using shared state in the Composable Architecture.
0 commit comments