Skip to content

Commit ce17215

Browse files
committed
Fix docs for mutating shared state.
1 parent 3395a96 commit ce17215

File tree

1 file changed

+35
-32
lines changed

1 file changed

+35
-32
lines changed

Sources/ComposableArchitecture/Documentation.docc/Articles/SharingState.md

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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 }
446446
todo // 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

451484
Shared 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

9991002
There are a few gotchas to be aware of when using shared state in the Composable Architecture.

0 commit comments

Comments
 (0)