Files
swift-composable-architectu…/Sources/ComposableArchitecture/Macros.swift
Stephen Celis c373d8eae3 Observable Architecture (#2593)
* 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>
2024-01-26 16:59:03 -08:00

241 lines
8.5 KiB
Swift
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#if swift(>=5.9)
import Observation
/// Helps implement the conformance to the ``Reducer`` protocol for a type.
///
/// To use this macro you will define a new type, typically a struct, and add inner types for the
/// ``Reducer/State`` and ``Reducer/Action`` associated types, as well as an implementation of the
/// reducer's ``Reducer/body-8lumc``:
///
/// ```swift
/// @Reducer
/// struct Feature {
/// struct State {
/// var count = 0
/// }
/// enum Action {
/// case decrementButtonTapped
/// case incrementButtonTapped
/// }
/// var body: some ReducerOf<Self> {
/// Reduce { state, action in
/// switch action {
/// case .decrementButtonTapped:
/// state.count -= 1
/// return .none
/// case .incrementButtonTapped:
/// state.count += 1
/// return .none
/// }
/// }
/// }
/// }
/// ```
///
/// This will expand Swift code to conform `Feature` to the ``Reducer`` protocol:
///
/// ```diff
/// +extension Feature: Reducer {}
/// ```
///
/// It will also apply the `@CasePathable` macro to the `enum Action`:
///
/// ```diff
/// +@CasePathable
/// enum Action {
/// // ...
/// }
/// ```
///
/// This will allow you to use key path syntax for specifying enum cases in various APIs in the
/// library, such as ``Reducer/ifLet(_:action:destination:fileID:line:)-4f2at``,
/// ``Reducer/forEach(_:action:destination:fileID:line:)-yz3v``, ``Scope``, and more.
///
/// Further, if the ``Reducer/State`` of your feature is an enum, which is useful for modeling a
/// feature that can be one of multiple mutually exclusive values, the ``Reducer()`` will apply
/// the `@CasePathable` macro, as well as `@dynamicMemberLookup`:
///
/// ```diff
/// +@CasePathable
/// +@dynamicMemberLookup
/// enum State {
/// // ...
/// }
/// ```
///
/// This will allow you to use key path syntax for specifying case paths to the `State`'s cases,
/// as well as allow you to use dot-chaining syntax for optionally extracting a case from the
/// state. This can be useful when using the operators that come with the library that allow for
/// driving navigation from an enum of options:
///
/// ```swift
/// .sheet(
/// item: $store.scope(state: \.destination?.editForm, action: \.destination.editForm)
/// )
/// ```
///
/// The syntax `state: \.destination?.editForm` is only possible due to both
/// `@dynamicMemberLookup` and `@CasePathable` being applied to the `State` enum.
///
/// ## Gotchas
///
/// ### Autocomplete
///
/// Applying `@Reducer` can break autocompletion in the `body` of the reducer. This is a known
/// [issue](https://github.com/apple/swift/issues/69477), and it can generally be worked around by
/// providing additional type hints to the compiler:
///
/// 1. Adding an explicit `Reducer` conformance in addition to the macro application can restore
/// autocomplete throughout the `body` of the reducer:
///
/// ```diff
/// @Reducer
/// -struct Feature {
/// +struct Feature: Reducer {
/// ```
///
/// 2. Adding explicit generics to instances of `Reduce` in the `body` can restore autocomplete
/// inside the `Reduce`:
///
/// ```diff
/// var body: some Reducer<State, Action> {
/// - Reduce { state, action in
/// + Reduce<State, Action> { state, action in
/// ```
///
/// ### Circular reference errors
///
/// There is currently a bug in the Swift compiler and macros that prevents you from extending
/// types that are inside other types with macros applied in the same file. For example, if you
/// wanted to extend a reducer's `State` with some extra functionality:
///
/// ```swift
/// @Reducer
/// struct Feature {
/// struct State { /* ... */ }
/// // ...
/// }
///
/// extension Feature.State { // 🛑 Circular reference
/// // ...
/// }
/// ```
///
/// This unfortunately does not work. It is a
/// [known issue](https://github.com/apple/swift/issues/66450), and the only workaround is to
/// either move the extension to a separate file, or move the code from the extension to be
/// directly inside the `State` type.
///
/// ### CI build failures
///
/// When testing your code on an external CI server you may run into errors such as the following:
///
/// > Error: CasePathsMacros Target 'CasePathsMacros' must be enabled before it can be used.
/// >
/// > ComposableArchitectureMacros Target 'ComposableArchitectureMacros' must be enabled
/// before it can be used.
///
/// You can fix this in one of two ways. You can write a default to the CI machine that allows
/// Xcode to skip macro validation:
///
/// ```shell
/// defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES
/// ```
///
/// Or if you are invoking `xcodebuild` directly in your CI scripts, you can pass the
/// `-skipMacroValidation` flag to `xcodebuild` when building your project:
///
/// ```shell
/// xcodebuild -skipMacroValidation
/// ```
@attached(memberAttribute)
@attached(extension, conformances: Reducer)
public macro Reducer() =
#externalMacro(
module: "ComposableArchitectureMacros", type: "ReducerMacro"
)
/// Defines and implements conformance of the Observable protocol.
@attached(extension, conformances: Observable, ObservableState)
@attached(member, names: named(_$id), named(_$observationRegistrar), named(_$willModify))
@attached(memberAttribute)
public macro ObservableState() =
#externalMacro(module: "ComposableArchitectureMacros", type: "ObservableStateMacro")
@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(_))
public macro ObservationStateTracked() =
#externalMacro(module: "ComposableArchitectureMacros", type: "ObservationStateTrackedMacro")
@attached(accessor, names: named(willSet))
public macro ObservationStateIgnored() =
#externalMacro(module: "ComposableArchitectureMacros", type: "ObservationStateIgnoredMacro")
/// Wraps a property with ``PresentationState`` and observes it.
///
/// Use this macro instead of ``PresentationState`` when you adopt the ``ObservableState()``
/// macro, which is incompatible with property wrappers like ``PresentationState``.
@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`$`), prefixed(_))
public macro Presents() =
#externalMacro(module: "ComposableArchitectureMacros", type: "PresentsMacro")
/// Provides a view with access to a feature's ``ViewAction``s.
///
/// If you want to restrict what actions can be sent from the view you can use this macro along
/// the ``ViewAction`` protocol. You start by conforming your reducer's `Action` enum to the
/// ``ViewAction`` protocol, and moving view-specific actions to its own inner enum:
///
/// ```swift
/// @Reducer
/// struct Feature {
/// struct State { /* ... */ }
/// enum Action: ViewAction {
/// case loginResponse(Bool)
/// case view(View)
///
/// enum View {
/// case loginButtonTapped
/// }
/// }
/// // ...
/// }
/// ```
///
/// Then you can apply the ``ViewAction(for:)`` macro to your view by specifying the type of the
/// reducer that powers the view:
///
/// ```swift
/// @ViewAction(for: Feature.self)
/// struct FeatureView: View {
/// let store: StoreOf<Feature>
/// // ...
/// }
/// ```
///
/// The macro does two things:
///
/// * It adds a `send` method to the view that you can use instead of `store.send`. This allows
/// you to send view actions more simply, without wrapping the action in `.view()`:
/// ```diff
/// Button("Login") {
/// - store.send(.view(.loginButtonTapped))
/// + send(.loginButtonTapped)
/// }
/// ```
/// * It creates warning diagnostics if you try sending actions through `store.send` rather than
/// using the `send` method on the view:
/// ```swift
/// Button("Login") {
/// store.send(.view(.loginButtonTapped))
/// //
/// // Do not use 'store.send' directly when using '@ViewAction'
/// }
/// ```
@attached(extension, conformances: ViewActionSending)
public macro ViewAction<R: Reducer>(for: R.Type) =
#externalMacro(
module: "ComposableArchitectureMacros", type: "ViewActionMacro"
) where R.Action: ViewAction
#endif