Skip to content
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

Add migration guide for 1.16. #3491

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -532,13 +532,14 @@ advanced usages.
The documentation for releases and `main` are available here:

* [`main`](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/)
* [1.15.0](https://pointfreeco.github.io/swift-composable-architecture/1.15.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.15))
* [1.16.0](https://pointfreeco.github.io/swift-composable-architecture/1.16.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.16))

<details>
<summary>
Other versions
</summary>

* [1.15.0](https://pointfreeco.github.io/swift-composable-architecture/1.15.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.15))
stephencelis marked this conversation as resolved.
Show resolved Hide resolved
* [1.14.0](https://pointfreeco.github.io/swift-composable-architecture/1.14.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.14))
* [1.13.0](https://pointfreeco.github.io/swift-composable-architecture/1.13.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.13))
* [1.12.0](https://pointfreeco.github.io/swift-composable-architecture/1.12.0/documentation/composablearchitecture/) ([migration guide](https://pointfreeco.github.io/swift-composable-architecture/main/documentation/composablearchitecture/migratingto1.12))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ APIs, and these guides contain tips to do so.

## Topics

- <doc:MigratingTo1.16>
- <doc:MigratingTo1.15>
- <doc:MigratingTo1.14>
- <doc:MigratingTo1.13>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Migrating to 1.16

The `.appStorage` strategy used with `@Shared` now uses key-value observing instead of
`NotificationCenter` when possible. Learn how this may affect your code.

## Overview

There are no steps needed to migrate to 1.16 of the Composable Architecture, but there has been
a change to the underlying behavior of `.appStorage` that one should be aware of. When using
`.appStorage` with `@Shared`, if your key does not contain the characters "." or "@", then changes
to that key in `UserDefaults` will be observed using key-value observing (KVO).
Otherwise, `NotificationCenter` will be used to observe changes.

KVO is a far more efficient way of observing changes to `UserDefaults` and it works cross-process,
such as from widgets and app extensions. However, KVO does not work when the keys contain "."
or "@", and so in those cases we must use the cruder tool of `NotificationCenter`. That is not
as efficient, and it forces us to perform a thread-hop when the notification is posted before
we can update the `@Shared` value. For this reason it is not possible to animate changes that are
made directly to `UserDefaults`:

```swift
withAnimation {
// ⚠️ This will not animate any SwiftUI views using '@Shared(.appStorage("co.pointfree.count"))'
UserDefaults.standard.set(0, forKey: "co.pointfree.count")
}
```

In general, we recommend using other delimeters for your keys, such as "/", ":", "-", etc.:

```swift
@Shared(.appStorage("co:pointfree:count")) var count = 0
```
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ enum Action {
```

And in the reducer, instead of invoking
``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-3dw7i`` with a case path using the
``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-6zye8`` with a case path using the
`/` prefix operator:

```swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ func viewDidLoad() {
}
```

This can now be done more simply using the ``ObjectiveC/NSObject/observe(_:)`` method defined on
This can now be done more simply using the ``ObjectiveC/NSObject/observe(_:)-94oxy`` method defined on
all `NSObject`s:

```swift
Expand All @@ -920,7 +920,7 @@ func viewDidLoad() {
}
```

Be sure to read the documentation for ``ObjectiveC/NSObject/observe(_:)`` to learn how to best
Be sure to read the documentation for ``ObjectiveC/NSObject/observe(_:)-94oxy`` to learn how to best
wield this tool.

### Replacing Store.ifLet
Expand All @@ -940,7 +940,7 @@ store
```

This can now be done more simply using the `observe` method and
``Store/scope(state:action:fileID:filePath:line:column:)-2ck1n``:
``Store/scope(state:action:fileID:filePath:line:column:)-3yvuf``:

```swift
observe {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ should take a plain, non-`Shared` value and you construct the `Shared` value in
* You are using a persistence strategy with shared state (_e.g._
``PersistenceReaderKey/appStorage(_:)-4l5b``, ``PersistenceReaderKey/fileStorage(_:decoder:encoder:)``, _etc._),
then the initializer should take a plain, non-`Shared` value and you construct the `Shared` value in
the initializer using ``Shared/init(wrappedValue:_:fileID:line:)-512rh`` which takes a
the initializer using ``Shared/init(wrappedValue:_:fileID:line:)-9kfmy`` which takes a
``PersistenceKey`` as the second argument:

```swift
Expand All @@ -338,7 +338,7 @@ the initializer using ``Shared/init(wrappedValue:_:fileID:line:)-512rh`` which t

> Important: The value passed to this initializer is only used if the external storage does not
> already have a value. If a value exists in the storage then it is not used. In fact, the
> `wrappedValue` argument of ``Shared/init(wrappedValue:_:fileID:line:)-512rh`` is an
> `wrappedValue` argument of ``Shared/init(wrappedValue:_:fileID:line:)-9kfmy`` is an
> `@autoclosure` so that it is not even evaluated if not used. For that reason you
> may prefer to make the argument to the initializer an `@autoclosure` so that it too is evaluated
> only if actually used:
Expand Down Expand Up @@ -436,7 +436,7 @@ responsible for persisting and deriving shared state to pass to the child.
If your shared state is a collection, and in particular an `IdentifiedArray`, then we have another
tool for deriving shared state to a particular element of the array. You can subscript into a
``Shared`` collection with the `[id:]` subscript, and that will give a piece of optional shared
state (thanks to a dynamic member overload ``Shared/subscript(dynamicMember:)-7ibhr``), which you
state (thanks to a dynamic member overload ``Shared/subscript(dynamicMember:)-9xw64``), which you
can then unwrap to turn into honest shared state:

```swift
Expand Down Expand Up @@ -1071,7 +1071,7 @@ own implementations of `encode(to:)` and `init(from:)` that do the appropriate t
For example, if the data type is sharing state with a persistence strategy, you can decode by
delegating to the memberwise initializer that implicitly loads the shared value from the property
wrapper's persistence strategy, or you can explicitly initialize a shared value via
``Shared/init(wrappedValue:_:fileID:line:)-512rh``. And for encoding you can often skip encoding
``Shared/init(wrappedValue:_:fileID:line:)-9kfmy``. And for encoding you can often skip encoding
the shared value:

```swift
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,29 @@

### Storing a value

- ``PersistenceReaderKey/appStorage(_:)-4l5b``
- ``PersistenceReaderKey/appStorage(_:)-6d47p``
- ``PersistenceReaderKey/appStorage(_:)-6tsph``
- ``PersistenceReaderKey/appStorage(_:)-69h4r``
- ``PersistenceReaderKey/appStorage(_:)-xphy``
- ``PersistenceReaderKey/appStorage(_:)-617ld``
- ``PersistenceReaderKey/appStorage(_:)-6lnxu``
- ``PersistenceReaderKey/appStorage(_:)-ibg0``
- ``PersistenceReaderKey/appStorage(_:)-4l5b`` <!-- Bool -->
- ``PersistenceReaderKey/appStorage(_:)-6d47p`` <!-- Data -->
- ``PersistenceReaderKey/appStorage(_:)-6tsph`` <!-- Double -->
- ``PersistenceReaderKey/appStorage(_:)-69h4r`` <!-- Integer -->
- ``PersistenceReaderKey/appStorage(_:)-xphy`` <!-- String -->
- ``PersistenceReaderKey/appStorage(_:)-617ld`` <!-- URL -->
- ``PersistenceReaderKey/appStorage(_:)-6k27r`` <!-- RawRepresentable<Int> -->
- ``PersistenceReaderKey/appStorage(_:)-m54v`` <!-- RawRepresentable<String> -->

### Storing an optional value

- ``PersistenceReaderKey/appStorage(_:)-4s3s5``
- ``PersistenceReaderKey/appStorage(_:)-2dfnh``
- ``PersistenceReaderKey/appStorage(_:)-5wv8g``
- ``PersistenceReaderKey/appStorage(_:)-40e42``
- ``PersistenceReaderKey/appStorage(_:)-4veqp``
- ``PersistenceReaderKey/appStorage(_:)-7rox5``
- ``PersistenceReaderKey/appStorage(_:)-2keyn``
- ``PersistenceReaderKey/appStorage(_:)-7u49u``
- ``PersistenceReaderKey/appStorage(_:)-4s3s5`` <!-- Bool -->
- ``PersistenceReaderKey/appStorage(_:)-2dfnh`` <!-- Data -->
- ``PersistenceReaderKey/appStorage(_:)-5wv8g`` <!-- Double -->
- ``PersistenceReaderKey/appStorage(_:)-40e42`` <!-- Integer -->
- ``PersistenceReaderKey/appStorage(_:)-4veqp`` <!-- String -->
- ``PersistenceReaderKey/appStorage(_:)-7rox5`` <!-- URL -->
- ``PersistenceReaderKey/appStorage(_:)-2cfq9`` <!-- RawRepresentable<Int> -->
- ``PersistenceReaderKey/appStorage(_:)-9j150`` <!-- RawRepresentable<String> -->

### Key-path access

- ``PersistenceReaderKey/appStorage(_:)-5jsie``
- ``PersistenceReaderKey/appStorage(_:)-69h4r``
- ``AppStorageKeyPathKey``

### Overriding app storage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ instead.
- ``Reducer/ifLet(_:action:destination:fileID:filePath:line:column:)-5y8z4``
- ``Reducer/ifLet(_:action:fileID:filePath:line:column:)-12kry``
- ``Reducer/ifCaseLet(_:action:then:fileID:filePath:line:column:)-403y9``
- ``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-1oguc``
- ``Reducer/forEach(_:action:element:fileID:filePath:line:column:)-o1gn``
- ``Reducer/forEach(_:action:destination:fileID:filePath:line:column:)-74erx``
- ``Reducer/onChange(of:removeDuplicates:_:)``

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,7 +581,7 @@ xcodebuild -skipMacroValidation …
- ``Scope``
- ``ifLet(_:action:then:fileID:filePath:line:column:)-2r2pn``
- ``ifCaseLet(_:action:then:fileID:filePath:line:column:)-7sg8d``
- ``forEach(_:action:element:fileID:filePath:line:column:)-3dw7i``
- ``forEach(_:action:element:fileID:filePath:line:column:)-6zye8``
- <doc:Navigation>

### Sharing state
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ``ComposableArchitecture/Reducer/forEach(_:action:element:fileID:filePath:line:column:)-3dw7i``
# ``ComposableArchitecture/Reducer/forEach(_:action:element:fileID:filePath:line:column:)-6zye8``

## Topics

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,22 @@

### Creating a persisted value

- ``init(wrappedValue:_:fileID:line:)-512rh``
- ``init(wrappedValue:_:fileID:line:)-7a80y``
- ``init(wrappedValue:_:fileID:line:)-9kfmy``
- ``init(wrappedValue:_:fileID:line:)-7ndwc``
- ``init(_:fileID:line:)-8zcy1``
- ``init(_:fileID:line:)-8jqg5``
- ``init(_:fileID:line:)-gluj``
- ``init(_:fileID:line:)-9d3q``
- ``init(_:fileID:line:)-1q6ev``

### Accessing the value

- ``wrappedValue``
- ``projectedValue``
- ``reader``
- ``subscript(dynamicMember:)-6kmzm``
- ``subscript(dynamicMember:)-22ga9``
- ``subscript(dynamicMember:)-6dq81``
- ``subscript(dynamicMember:)-7n9xc``
- ``subscript(dynamicMember:)-6f2x``
- ``subscript(dynamicMember:)-9xw64``

### Isolating the value

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,18 @@

### Creating a persisted value

- ``init(wrappedValue:_:fileID:line:)-7q52``
- ``init(wrappedValue:_:fileID:line:)-6asu2``
- ``init(wrappedValue:_:fileID:line:)-7f68o``
- ``init(wrappedValue:_:fileID:line:)-galu``
- ``init(_:fileID:line:)-41rb8``
- ``init(_:fileID:line:)-3lxyf``
- ``init(_:fileID:line:)-hzp``
- ``init(_:fileID:line:)-5bxk6``

### Getting the value

- ``wrappedValue``
- ``projectedValue``
- ``subscript(dynamicMember:)-34wfb``
- ``subscript(dynamicMember:)-pigs``
- ``subscript(dynamicMember:)-2barb``

### SwiftUI integration

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

### Writable, bindable state

- ``Store/state-3ppqv``
- ``Store/state-7k27v``
- ``Store/state-20w4g``
- ``Store/state-2wgiw``
- ``Store/state-1qxwl``
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

### Configuring exhaustivity

- ``withDependencies(_:operation:)-3x2vc``
- ``withDependencies(_:operation:)-988rh``
- ``withDependencies(_:operation:)-61in2``
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
### Configuring exhaustivity

- ``Exhaustivity``
- ``withExhaustivity(_:operation:)-9psu7``
- ``withExhaustivity(_:operation:)-3fqeg``
- ``withExhaustivity(_:operation:)-1mhu4``
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ integrate into application code written in UIKit.

### Subscribing to state changes

- ``ObjectiveC/NSObject/observe(_:)``
- ``ObjectiveC/NSObject/observe(_:)-94oxy``
- ``ObservationToken``

### Presenting alerts and action sheets
Expand Down
6 changes: 5 additions & 1 deletion Sources/ComposableArchitecture/Effect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,12 @@ extension Effect {
/// - priority: Priority of the underlying task. If `nil`, the priority will come from
/// `Task.currentPriority`.
/// - operation: The operation to execute.
/// - catch: An error handler, invoked if the operation throws an error other than
/// - handler: An error handler, invoked if the operation throws an error other than
/// `CancellationError`.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
/// - Returns: An effect wrapping the given asynchronous work.
public static func run(
priority: TaskPriority? = nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ extension Store where State: ObservableState {
/// - Parameters:
/// - state: A key path to an identified array of child state.
/// - action: A case key path to an identified child action.
/// - column: The column.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - Returns: An collection of stores of child state.
@_disfavoredOverload
public func scope<ElementID, ElementState, ElementAction>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ extension NavigationLink where Destination == Never {
/// - state: An optional value to present. When the user selects the link, SwiftUI stores a
/// copy of the value. Pass a `nil` value to disable the link.
/// - label: A label that describes the view that this link presents.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
#if compiler(>=6)
@MainActor
#endif
Expand Down Expand Up @@ -269,6 +273,8 @@ extension NavigationLink where Destination == Never {
/// presents.
/// - state: An optional value to present. When the user selects the link, SwiftUI stores a
/// copy of the value. Pass a `nil` value to disable the link.
/// - fileID: The fileID.
/// - line: The line.
#if compiler(>=6)
@MainActor
#endif
Expand All @@ -292,6 +298,8 @@ extension NavigationLink where Destination == Never {
/// - title: A string that describes the view that this link presents.
/// - state: An optional value to present. When the user selects the link, SwiftUI stores a
/// copy of the value. Pass a `nil` value to disable the link.
/// - fileID: The fileID.
/// - line: The line.
#if compiler(>=6)
@MainActor
#endif
Expand Down
16 changes: 16 additions & 0 deletions Sources/ComposableArchitecture/Observation/Store+Observation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ extension Store where State: ObservableState {
/// - Parameters:
/// - state: A key path to optional child state.
/// - action: A case key path to child actions.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
/// - Returns: An optional store of non-optional child state and actions.
public func scope<ChildState, ChildAction>(
state: KeyPath<State, ChildState?>,
Expand Down Expand Up @@ -153,6 +157,10 @@ extension Binding {
/// - Parameters:
/// - state: A key path to optional child state.
/// - action: A case key path to presentation child actions.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
/// - Returns: A binding of an optional child store.
#if swift(>=5.10)
@preconcurrency@MainActor
Expand Down Expand Up @@ -228,6 +236,10 @@ extension SwiftUI.Bindable {
/// - Parameters:
/// - state: A key path to optional child state.
/// - action: A case key path to presentation child actions.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
/// - Returns: A binding of an optional child store.
#if swift(>=5.10)
@preconcurrency@MainActor
Expand Down Expand Up @@ -306,6 +318,10 @@ extension Perception.Bindable {
/// - Parameters:
/// - state: A key path to optional child state.
/// - action: A case key path to presentation child actions.
/// - fileID: The fileID.
/// - filePath: The filePath.
/// - line: The line.
/// - column: The column.
/// - Returns: A binding of an optional child store.
public func scope<State: ObservableState, Action, ChildState, ChildAction>(
state: KeyPath<State, ChildState?>,
Expand Down
Loading
Loading