-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Fix docs for mutating shared state. #3580
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
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
ce17215
Fix docs for mutating shared state.
mbrandonw 3c0d9a4
A few more fixes
mbrandonw e24c094
Add FAQ to table of contents.
mbrandonw a219f04
Update Sources/ComposableArchitecture/Documentation.docc/Articles/Sha…
stephencelis 073d24b
Merge remote-tracking branch 'origin/main' into fix-sharing-docs
mbrandonw File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,6 +33,7 @@ system, such as SQLite. | |
| * [Observing changes to shared state](#Observing-changes-to-shared-state) | ||
| * [Initialization rules](#Initialization-rules) | ||
| * [Deriving shared state](#Deriving-shared-state) | ||
| * [Concurrent mutations to shared state](#Concurrent-mutations-to-shared-state) | ||
| * [Testing shared state](#Testing-shared-state) | ||
| * [Testing when using persistence](#Testing-when-using-persistence) | ||
| * [Testing when using custom persistence strategies](#Testing-when-using-custom-persistence-strategies) | ||
|
|
@@ -41,7 +42,6 @@ system, such as SQLite. | |
| * [Testing tips](#Testing-tips) | ||
| * [Read-only shared state](#Read-only-shared-state) | ||
| * [Type-safe keys](#Type-safe-keys) | ||
| * [Concurrent mutations to shared state](#Concurrent-mutations-to-shared-state) | ||
| * [Shared state in pre-observation apps](#Shared-state-in-pre-observation-apps) | ||
| * [Gotchas of @Shared](#Gotchas-of-Shared) | ||
|
|
||
|
|
@@ -446,6 +446,39 @@ else { return } | |
| todo // Shared<Todo> | ||
| ``` | ||
|
|
||
| ## Concurrent mutations to shared state | ||
|
|
||
| [mutating-shared-state-article]: https://swiftpackageindex.com/pointfreeco/swift-sharing/main/documentation/sharing/mutatingsharedstate | ||
|
|
||
| While the [`@Shared`](<doc:Shared>) property wrapper makes it possible to treat shared state | ||
stephencelis marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| _mostly_ like regular state, you do have to perform some extra steps to mutate shared state. | ||
| This is because shared state is technically a reference deep down, even | ||
| though we take extra steps to make it appear value-like. And this means it's possible to mutate the | ||
| same piece of shared state from multiple threads, and hence race conditions are possible. See | ||
| [Mutating Shared State][mutating-shared-state-article] for a more in-depth explanation. | ||
|
Comment on lines
+457
to
+458
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And I'm linking out to the dedicated "Mutating Shared State" article over in the Sharing repo. |
||
|
|
||
| To mutate a piece of shared state in an isolated fashion, use the `withLock` method | ||
| defined on the `@Shared` projected value: | ||
|
|
||
| ```swift | ||
| state.$count.withLock { $0 += 1 } | ||
| ``` | ||
|
|
||
| That locks the entire unit of work of reading the current count, incrementing it, and storing it | ||
| back in the reference. | ||
|
|
||
| Technically it is still possible to write code that has race conditions, such as this silly example: | ||
|
|
||
| ```swift | ||
| let currentCount = state.count | ||
| state.$count.withLock { $0 = currentCount + 1 } | ||
| ``` | ||
|
|
||
| But there is no way to 100% prevent race conditions in code. Even actors are susceptible to | ||
| problems due to re-entrancy. To avoid problems like the above we recommend wrapping as many | ||
| mutations of the shared state as possible in a single `withLock`. That will make | ||
| sure that the full unit of work is guarded by a lock. | ||
|
|
||
| ## Testing shared state | ||
|
|
||
| Shared state behaves quite a bit different from the regular state held in Composable Architecture | ||
|
|
@@ -476,7 +509,7 @@ struct Feature { | |
| Reduce { state, action in | ||
| switch action { | ||
| case .incrementButtonTapped: | ||
| state.count += 1 | ||
| state.$count.withLock { $0 += 1 } | ||
| return .none | ||
| } | ||
| } | ||
|
|
@@ -964,36 +997,6 @@ struct FeatureView: View { | |
| } | ||
| ``` | ||
|
|
||
| ## Concurrent mutations to shared state | ||
|
|
||
| While the [`@Shared`](<doc:Shared>) property wrapper makes it possible to treat shared state | ||
| _mostly_ like regular state, you do have to perform some extra steps to mutate shared state. | ||
| This is because shared state is technically a reference deep down, even | ||
| though we take extra steps to make it appear value-like. And this means it's possible to mutate the | ||
| same piece of shared state from multiple threads, and hence race conditions are possible. | ||
|
|
||
| To mutate a piece of shared state in an isolated fashion, use the `withLock` method | ||
| defined on the `@Shared` projected value: | ||
|
|
||
| ```swift | ||
| state.$count.withLock { $0 += 1 } | ||
| ``` | ||
|
|
||
| That locks the entire unit of work of reading the current count, incrementing it, and storing it | ||
| back in the reference. | ||
|
|
||
| Technically it is still possible to write code that has race conditions, such as this silly example: | ||
|
|
||
| ```swift | ||
| let currentCount = state.count | ||
| state.$count.withLock { $0 = currentCount + 1 } | ||
| ``` | ||
|
|
||
| But there is no way to 100% prevent race conditions in code. Even actors are susceptible to | ||
| problems due to re-entrancy. To avoid problems like the above we recommend wrapping as many | ||
| mutations of the shared state as possible in a single `withLock`. That will make | ||
| sure that the full unit of work is guarded by a lock. | ||
|
|
||
| ## Gotchas of @Shared | ||
|
|
||
| There are a few gotchas to be aware of when using shared state in the Composable Architecture. | ||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm moving this up higher because it's important to know this before the testing material.