* bring back view store performance
* wip
* Allow chaining of store bindings
* wip
* Localize ignoring bindings to text field resignation/dismissal
* wip
* fix DiagnosticsError message (#2597)
* store collection
* wip
* wip
* update migration guide
* Add `@Presents` macro for observable presentation
While it would be nice for the `@PresentationState` property wrapper to
"just work" with TCA's upcoming observable tools, sadly that does not
seem to be the case. Adding observation directly to
`@PresentationState`, as we have done with the beta so far, can break
existing projects due to the additional observation. This primarily
manifests itself in projects that present navigation stacks, where the
`@PresentationState` observation can cause the navigation hierarchy to
recompute and trigger SwiftUI bugs.
The best we've come up with so far is introducing a brand new macro that
automatically wraps a property with `@PresentationState` _and_
instruments it with observation.
We're open to other ideas, and we do have future plans to eliminate the
need for a property wrapper or macro at all, but till then this offers a
non-breaking upgrade path!
* fixes
* Observe child store changes
* wip
* wip
* wip
* Fix typo in MigratingTo1.6.md (#2608)
* Rename bindingViewStore argument to store in MigratingTo1.6.md (#2611)
* wip
* Revert "wip"
This reverts commit f221ed0e1a.
* Add `@Presents` macro for observable presentation (#2604)
* Add `@Presents` macro for observable presentation
While it would be nice for the `@PresentationState` property wrapper to
"just work" with TCA's upcoming observable tools, sadly that does not
seem to be the case. Adding observation directly to
`@PresentationState`, as we have done with the beta so far, can break
existing projects due to the additional observation. This primarily
manifests itself in projects that present navigation stacks, where the
`@PresentationState` observation can cause the navigation hierarchy to
recompute and trigger SwiftUI bugs.
The best we've come up with so far is introducing a brand new macro that
automatically wraps a property with `@PresentationState` _and_
instruments it with observation.
We're open to other ideas, and we do have future plans to eliminate the
need for a property wrapper or macro at all, but till then this offers a
non-breaking upgrade path!
* wip
* Fix perception bindings (#2609)
* Fix runtime warning when binding accesses perceptible state.
* Fix runtime warning in SwiftUI bindings.
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* wip
* wip
* fix
* wip
* wip
* wip
* Check observable state identity for presentation state.
* Add willSset/didSet to registrar types.
* clean up @Presents
* clean up
* fix
* Emit observation warnings in escaping contexts like `ForEach` and `sheet` (#2613)
* Fix perception warning in ForEach.
* fix
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
* Introduce @ViewAction(for:) macro. (#2612)
* Add back @ViewAction macro.
* wip
* wip
* wip
* wip
* wip
* clean up
* wip
* wip
* fix migration guide'
* ViewActionable
* wip
* rename
* wip
* wip
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* Introduce @BindableStore for bindings in pre-iOS 17 (#2610)
* Introduce @BindableStore.
* docs
* wip
* wip
* fixc
* wip
* wip
* wip
* wip
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* re-record intergration logs
* wip
* wip
* localize invalid stores to store collection
* Deprecate closure-based `store.scope` operations (#2618)
These uncached operations can be problematic, especially when working
with observation, which often depends on the stable identity of stores.
* document
* Update warning message
* Performance Improvement: Skip perception checks when calling reducers. (#2622)
* Skip perception checks when calling reducers.
* inline withoutPerceptionChecking() for RELEASE
Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com>
---------
Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com>
* Don't show perception warnings in action closures. (#2614)
* Don't show perception warnings in action closures.
* wip
* wip
* wip
* clean up
* wip
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* fix BindableStore + release
* Add docs
* Change associated type names of ViewActionSending (#2629)
* Fix some @ViewAction annoyances.
* wip
* wip
* wip
* wip
* wip
* wip
* fix
* wip
* fixed merge
* Add new view modifiers for observing alerts/dialogs (#2628)
* Add new view modifiers for observing alerts/dialogs
Instead of:
```swift
.alert(store: store.scope(state: \.$alert, action: \.alert))
```
You can now do:
```swift
.alert($store.scope(state: \.alert, action: \.alert))
```
This new modifier is powered by the same store binding scope operation
that can power `sheet(item:)`, etc., and is much lighter weight than the
previous view modifier, which spun up view stores and `WithViewStore`
views.
* wip
* wip
* wip
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
* Fix uncached warning when using Store.ifLet (#2625)
* Fix uncached warning when using Store.ifLet
* wip
* wip
* wip
* wip
* wip
* wip
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* Resolve packages
* Updated scopes
* wip
* wip
* updated binding docs
* adding docs
* clean up
* wip
* wip
* wip
* clean up
* clean up
* clean up
* wip;
* lots of fixes
* update more docs
* fix
* wip
* wip
* Remove ObservationRegistrarWrapper. (#2634)
* Remove ObservationRegistrarWrapper.
* Delete Sources/ComposableArchitecture/Internal/ObservationRegistrarWrapper.swift
---------
Co-authored-by: Stephen Celis <stephen@stephencelis.com>
* more docs
* update docs
* a few more tests
* fix
* wip
* wip
* wip
* Cache data in store collections (#2635)
* fix tutorial highlighting
* wip
* wip
* wip
* wip
* tests for observation of special domain types
* another test
* fix
* wip
* Implement memoization for perception checks (#2630)
* Implement memoization for isInSwiftUIBody
* tidy up
* Perception caching updates (#2649)
* Small updates to perception caching.
* wip
* debug
* some more macro tests
* syncups tutorial beginnings
* wip
* wip
* wip
* wip
* wip
* merge fixes
* wip
* update tests
* fix
* fix
* fix perception checking in store
* rename task local
* delete old test
* deprecate test using old apis
* fix test
* perception tests for store
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* Opt out of key path for Store.ifLet
* sync ups
* lots more sync up tutorial
* more sync ups tutorial
* wip
* wip
* wio
* wip
* wip
* wip
* updated references of 1.6 to 1.7
* wip
* no need to force unwrap here
* fixed crash in ForEach with bindings
* more sync ups tutorial
* more sync ups tutorial
* wip
* more sync ups
* wip
* wip
* Better support for observing copies of values (#2650)
* Explore using _modify
* wip
* wip
* wip
* wip
* wip
* wip
* more tests
* wip
* get another failing test for an edge case
* wip
* tests all passing
* flag for determining when new state was created
* wip
* clean up
* wip
* wip
* wip;
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* New test that currently fails.
* wip
* wip
* Update Sources/ComposableArchitectureMacros/PresentsMacro.swift
* wip
* remove redundant attached member attribute
* storage
* cleanup
* more benchmarks and tests
* wip
* wip
* wip
* wip
* update tests
* wip
* wip
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
* wip
* wip
* wip
* swift-format
* fix
* wip
* wip
* wip
* wip
* Perception
* wip
* wip
* clean up shared state
* fix shared state tests
* wip
* add alert test
* wip
* wip
* wip
* wip
* Use transaction in binding
* wip
* wip
* wip
* wip
* wip
* wip
* uikit
* keep references to controllers when presenting so that we can properly dismiss
* change order of features in shared state demo
* wip
* cleanup
* cleanup
* wip
* wip
* wip
* Fix perception checking for effect actions.
* wip
* wip
* wip
* Fix perception checking for effect actions.
* wip
* wip
* remove sync ups tutorial
* wip
* wip
* wip
* wip
* wip
* docs for observe function for uikit
* Add cancellation to observation'
* re-record integration test snapshots
* fixed some todos
* update test
* remove 5.9.2 checks
* wip
* improve docs
* update docs
* updates
* lots of fixes
* more docs
* remove unneeded file;
* wip
* wip
* wip
* update readme and getting started
* wip
* simplify
* migration stuff
* wip
* Update Models.swift
* wip
* wip
* wip
* Update Bindings.md
* wip
* wip
* wip
* wip
* fix
* wip
* wip
* wip
* wip
* wip
Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
---------
Co-authored-by: Brandon Williams <mbrandonw@hey.com>
Co-authored-by: hmhv <admin@hmhv.info>
Co-authored-by: Jimmy Prime <jimmylevelup@gmail.com>
Co-authored-by: Michael Pohl <15653162+Mika5652@users.noreply.github.com>
Co-authored-by: Brandon Williams <135203+mbrandonw@users.noreply.github.com>
Co-authored-by: George Scott <gscott@gekkoto.com>
Co-authored-by: Kabir Oberai <oberai.kabir@gmail.com>
7.5 KiB
Migrating to 1.5
Update your code to make use of the new Store/scope(state:action:)-90255 operation on Store
in order to improve the performance of your features and simplify the usage of navigation APIs.
Overview
The Composable Architecture is under constant development, and we are always looking for ways to simplify the library, and make it more powerful. As such, we often need to deprecate certain APIs in favor of newer ones. We recommend people update their code as quickly as possible to the newest APIs, and this article contains some tips for doing so.
Important: Many APIs have been soft-deprecated in this release and will be hard-deprecated in a future minor release. We highly recommend updating your use of deprecated APIs to their newest version as quickly as possible.
Store scoping with key paths
Prior to version 1.5 of the Composable Architecture, one was allowed to
ComposableArchitecture/Store/scope(state:action:)-9iai9 a store with any kind of closures that
transform the parent state to the child state, and child actions into parent actions:
store.scope(
state: (State) -> ChildState,
action: (ChildAction) -> Action
)
In practice you could typically use key paths for the state transformation since key path literals
can be promoted to closures. That means often scoping looked something like this:
// ⚠️ Deprecated API
ChildView(
store: store.scope(
state: \.child,
action: { .child($0) }
)
)
However, as of version 1.5 of the Composable Architecture, the version of
ComposableArchitecture/Store/scope(state:action:)-9iai9 that takes two closures is
soft-deprecated. Instead, you are to use the version of
ComposableArchitecture/Store/scope(state:action:)-90255 that takes a key path for the state
argument, and a case key path for the action argument.
This is easiest to do when you are using the ComposableArchitecture/Reducer() macro with your
feature because then case key paths are automatically generated for each case of your action enum.
The above construction of ChildView now becomes:
// ✅ New API
ChildView(
store: store.scope(
state: \.child,
action: \.child
)
)
The syntax is now shorter and more symmetric, and there is a hidden benefit too. Because key paths
are Hashable, we are able to cache the store created by scope. This means if the store is scoped
again with the same state and action arguments, we can skip creating a new store and instead
return the previously created one. This provides a lot of benefits, such as better performance, and
a stable identity for features.
There are some times when changing to this new scoping operator may be difficult. For example, if you perform additional work in your scoping closure so that a simple key path does not work:
ChildView(
store: store.scope(
state: { ChildFeature(state: $0.child) },
action: { .child($0) }
)
)
This can be handled by moving the work in the closure to a computed property on your state:
extension State {
var childFeature: ChildFeature {
ChildFeature(state: self.child)
}
}
And now the key path syntax works just fine:
ChildView(
store: store.scope(
state: \.childFeature,
action: \.child
)
)
Another complication is if you are using data from outside the closure, inside the closure:
ChildView(
store: store.scope(
state: {
ChildFeature(
settings: viewStore.settings,
state: $0.child
)
},
action: { .child($0) }
)
)
In this situation you can add a subscript to your state so that you can pass that data into it:
extension State {
subscript(settings settings: Settings) -> ChildFeature {
ChildFeature(
settings: settings,
state: self.child
)
}
}
Then you can use a subscript key path to perform the scoping:
ChildView(
store: store.scope(
state: \.[settings: viewStore.settings],
action: \.child
)
)
Another common case you may encounter is when dealing with collections. It is common in the
Composable Architecture to use an IdentifiedArray in your feature's state and an
IdentifiedAction in your feature's actions (see doc:MigratingTo1.4#Identified-actions for more
info on IdentifiedAction). If you needed to scope your store down to one specific row of the
identified domain, previously you would have done so like this:
store.scope(
state: \.rows[id: id],
action: { .rows(.element(id: id, action: $0)) }
)
With case key paths it can be done simply like this:
store.scope(
state: \.rows[id: id],
action: \.rows[id: id]
)
These tricks should be enough for you to rewrite all of your store scopes using key paths, but if you have any problems feel free to open a discussion on the repo.
Scoping performance
The performance characteristics for store scoping have changed in this release. The primary (and intended) way of scoping is along stored properties of child features. A very basic example of this is the following:
ChildView(
store: store.scope(state: \.child, action: \.child)
)
A less common (and less supported) form of scoping is along computed properties, for example like this:
extension ParentFeature.State {
var computedChild: ChildFeature.State {
ChildFeature.State(
// Heavy computation here...
)
}
}
ChildView(
store: store.scope(state: \.computedChild, action: \.child)
)
This style of scoping will incur a bit of a performance cost in 1.5 and moving forward. The cost is greater the closer your scoping is to the root of your application. Leaf node features will not incur as much of a cost.
See the dedicated article doc:Performance#Store-scoping for more information.
Enum-driven navigation APIs
Prior to version 1.5 of the library, using enum state with navigation view modifiers, such as
sheet, popover, navigationDestination, etc, was quite verbose. You first needed to supply a
store scoped to the destination domain, and then further provide transformations for isolating the
case of the state enum to drive the navigation, as well as a transformation for embedding child
actions back into the destination domain:
// ⚠️ Deprecated API
.sheet(
store: store.scope(state: \.$destination, action: { .destination($0) }),
state: \.editForm,
action: { .editForm($0) }
)
The navigation view modifiers that take store, state and action arguments are now deprecated,
and instead you can do it all with a single store argument:
// ✅ New API
.sheet(
store: store.scope(
state: \.$destination.editForm,
action: \.destination.editForm
)
)
All navigation APIs that take 3 arguments for the store, state and action have been
soft-deprecated and instead you should make use of the version of the APIs that take a single
store argument. This includes:
alert(store:state:action:)confirmationDialog(store:state:action:)fullScreenCover(store:state:action:)navigationDestination(store:state:action)popover(store:state:action:)sheet(store:state:action:)IfLetStore.IfLetStore/init(_:state:action:then:)IfLetStore.IfLetStore/init(_:state:action:then:else:)