tca-swiftui-architecture

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese
Approach: Production-First Iterative Refactoring — This skill is built for production enterprise codebases using The Composable Architecture. Architecture changes are delivered through iterative refactoring — small, focused PRs tracked in a
refactoring/
directory. AI tools consistently generate outdated TCA code (pre-1.4 patterns, WithViewStore, Environment, Combine Effects). Every rule here exists to prevent those mistakes.
方法:生产优先的迭代重构 —— 本技能专为使用The Composable Architecture的企业级生产代码库设计,架构变更通过迭代重构完成:所有小范围、专注的PR都会归类在
refactoring/
目录下跟踪。AI工具经常生成过时的TCA代码(1.4版本之前的写法、WithViewStore、Environment、Combine Effects),本技能的所有规则都是为了避免这类错误。

TCA SwiftUI Architecture (iOS 16+, TCA 1.7+)

TCA SwiftUI 架构(iOS 16+, TCA 1.7+)

Enterprise-grade skill for The Composable Architecture (point-free/swift-composable-architecture). Opinionated: prescribes
@Reducer
macro,
@ObservableState
, struct-of-closures dependencies, delegate actions for child-parent communication, and modern navigation via
@Presents
/
StackState
. TCA's API changed substantially across versions — AI tools trained on pre-1.7 code consistently generate
WithViewStore
,
IfLetStore
,
Environment
, and other removed patterns. This skill encodes the modern (1.7+) patterns validated against real enterprise codebases.
If the project uses TCA <1.7, consult
references/migration.md
for pre-macro patterns and incremental upgrade path.
面向The Composable Architecture(point-free/swift-composable-architecture)的企业级技能。强规约:推荐使用
@Reducer
宏、
@ObservableState
、闭包结构体依赖、父子组件通信的代理action,以及基于
@Presents
/
StackState
的现代导航方案。TCA的API在不同版本间有较大变动——基于1.7版本之前的代码训练的AI工具经常生成
WithViewStore
IfLetStore
Environment
等已经被移除的写法,本技能收录了经过真实企业代码库验证的现代(1.7+)TCA写法。
如果项目使用的TCA版本低于1.7,请参考
references/migration.md
了解宏之前的写法和渐进式升级路径。

Architecture Layers

架构分层

View Layer         → SwiftUI Views. Direct store access, NO WithViewStore.
                     Owns store via let/var. Uses @Bindable for $ bindings.
Reducer Layer      → @Reducer struct. State + Action + body. Pure logic.
                     Returns Effect for async work. ALWAYS runs on main thread.
Effect Layer       → .run { send in } for async. .send for sync delegate actions.
                     Cancellable, mergeable, concatenatable.
Dependency Layer   → @DependencyClient structs. Struct-of-closures, NOT protocols.
                     liveValue (app) / testValue (auto-unimplemented) / previewValue.
View Layer         → SwiftUI Views. Direct store access, NO WithViewStore.
                     Owns store via let/var. Uses @Bindable for $ bindings.
Reducer Layer      → @Reducer struct. State + Action + body. Pure logic.
                     Returns Effect for async work. ALWAYS runs on main thread.
Effect Layer       → .run { send in } for async. .send for sync delegate actions.
                     Cancellable, mergeable, concatenatable.
Dependency Layer   → @DependencyClient structs. Struct-of-closures, NOT protocols.
                     liveValue (app) / testValue (auto-unimplemented) / previewValue.

Quick Decision Trees

快速决策树

"Should this be its own Feature reducer?"

"这部分逻辑是否应该拆分为独立的Feature reducer?"

Does this component have its own screen/view?
├── YES → Own @Reducer struct
└── NO → Does it have business logic you want to unit test?
    ├── YES → Own @Reducer struct, compose via Scope
    └── NO → Is its state optional (not always visible)?
        ├── YES → Own @Reducer, use ifLet (avoids unnecessary work)
        └── NO → Keep logic in parent reducer
Does this component have its own screen/view?
├── YES → Own @Reducer struct
└── NO → Does it have business logic you want to unit test?
    ├── YES → Own @Reducer struct, compose via Scope
    └── NO → Is its state optional (not always visible)?
        ├── YES → Own @Reducer, use ifLet (avoids unnecessary work)
        └── NO → Keep logic in parent reducer

"What navigation pattern do I need?"

"我应该使用哪种导航模式?"

What type of navigation?
├── Modal (sheet/alert/dialog/fullScreenCover)?
│   → Tree-based: @Presents + PresentationAction + ifLet
├── Single drill-down?
│   → Tree-based: optional state + navigationDestination
├── Multi-level push (NavigationStack)?
│   → Stack-based: StackState + StackAction + forEach
├── Deep linking is critical?
│   → Stack-based (easy to construct state array)
└── Modals FROM within a NavigationStack?
    → BOTH: stack for push nav, tree for sheets/alerts
What type of navigation?
├── Modal (sheet/alert/dialog/fullScreenCover)?
│   → Tree-based: @Presents + PresentationAction + ifLet
├── Single drill-down?
│   → Tree-based: optional state + navigationDestination
├── Multi-level push (NavigationStack)?
│   → Stack-based: StackState + StackAction + forEach
├── Deep linking is critical?
│   → Stack-based (easy to construct state array)
└── Modals FROM within a NavigationStack?
    → BOTH: stack for push nav, tree for sheets/alerts

"How should child communicate with parent?"

"子组件应该如何和父组件通信?"

Child needs to tell parent something happened?
├── Use delegate action: case delegate(DelegateAction)
│   Parent observes ONLY .delegate cases, never child internals
Child needs to share logic between action cases?
├── Use mutating func on State or private helper method
│   NEVER send an action to share logic — it traverses the entire reducer tree unnecessarily,
│   creating hidden coupling and making performance profiling difficult
Sibling reducers need to communicate?
└── Delegate up to parent, parent coordinates
    NEVER send actions between siblings directly — siblings should not know about each other
Child needs to tell parent something happened?
├── Use delegate action: case delegate(DelegateAction)
│   Parent observes ONLY .delegate cases, never child internals
Child needs to share logic between action cases?
├── Use mutating func on State or private helper method
│   NEVER send an action to share logic — it traverses the entire reducer tree unnecessarily,
│   creating hidden coupling and making performance profiling difficult
Sibling reducers need to communicate?
└── Delegate up to parent, parent coordinates
    NEVER send actions between siblings directly — siblings should not know about each other

Workflows

工作流

Workflow: Create a New TCA Feature

工作流:创建新的TCA功能

When: Building a new screen or self-contained feature from scratch.
  1. Define State as
    @ObservableState struct
    inside
    @Reducer
    — conform to
    Equatable
  2. Define Action enum inside
    @Reducer
    — NO
    Equatable
    conformance needed (TCA 1.4+)
  3. Use three-category action pattern:
    view(ViewAction)
    ,
    internal(InternalAction)
    ,
    delegate(DelegateAction)
    (
    rules.md
    )
  4. Implement
    var body: some ReducerOf<Self>
    — use
    Reduce<State, Action> { }
    for explicit generics (helps Xcode autocomplete)
  5. Add child features via
    Scope
    ,
    ifLet
    , or
    forEach
    BEFORE parent
    Reduce
    block
  6. Create dependencies with
    @DependencyClient
    macro (
    dependencies.md
    )
  7. Wire view: direct
    store.property
    access,
    @Bindable var store
    for
    $
    bindings
  8. Write exhaustive TestStore tests (
    testing.md
    )
适用场景: 从零开始构建新页面或独立功能。
  1. @Reducer
    内部将State定义为
    @ObservableState struct
    —— 遵循
    Equatable
    协议
  2. @Reducer
    内部定义Action枚举 —— 不需要遵循
    Equatable
    协议(TCA 1.4+支持)
  3. 使用三类action模式:
    view(ViewAction)
    internal(InternalAction)
    delegate(DelegateAction)
    (参考
    rules.md
  4. 实现
    var body: some ReducerOf<Self>
    —— 使用
    Reduce<State, Action> { }
    显式声明泛型(帮助Xcode自动补全)
  5. 在父
    Reduce
    代码块之前通过
    Scope
    ifLet
    forEach
    引入子功能
  6. 使用
    @DependencyClient
    宏创建依赖(参考
    dependencies.md
  7. 视图层对接:直接通过
    store.property
    访问属性,使用
    @Bindable var store
    实现
    $
    绑定
  8. 编写覆盖全面的TestStore测试(参考
    testing.md

Workflow: Decompose a God Reducer

工作流:拆解上帝Reducer

When: A single reducer handles logic for multiple screens, has 800+ lines, or testing is impossible to isolate.
  1. Identify feature boundaries — each screen = own reducer
  2. Scan for anti-patterns using detection checklist (
    anti-patterns.md
    )
  3. Create
    refactoring/
    directory with per-feature plan files (
    refactoring-workflow.md
    )
  4. Extract child reducers bottom-up: leaf features first, then mid-level, then root
  5. Use
    Scope(state:action:)
    for always-present children
  6. Use
    .ifLet(\.$destination, action: \.destination)
    for optional children — effects auto-cancel on dismissal
  7. Use
    .forEach(\.path, action: \.path)
    for stack navigation
  8. Convert child-parent communication to delegate actions — parent must NEVER observe child internal actions
  9. Write non-exhaustive integration tests for cross-feature flows (
    testing.md
    )
适用场景: 单个Reducer处理多个页面的逻辑、代码量超过800行,或是无法隔离测试。
  1. 识别功能边界 —— 每个页面对应独立的reducer
  2. 使用检查清单扫描反模式(参考
    anti-patterns.md
  3. 创建
    refactoring/
    目录,存放每个功能的重构计划文件(参考
    refactoring-workflow.md
  4. 自底向上抽取子reducer:先处理叶子节点功能,再处理中间层,最后处理根节点
  5. 对始终存在的子组件使用
    Scope(state:action:)
  6. 对可选子组件使用
    .ifLet(\.$destination, action: \.destination)
    —— 组件关闭时副作用会自动取消
  7. 对栈导航使用
    .forEach(\.path, action: \.path)
  8. 将父子通信逻辑改为代理action —— 父组件绝对不能监听子组件的内部action
  9. 为跨功能流程编写非全覆盖的集成测试(参考
    testing.md

Workflow: Migrate Legacy TCA to Modern (1.7+)

工作流:将旧版TCA迁移到现代(1.7+)版本

When: Modernizing pre-1.7 TCA code that uses WithViewStore, IfLetStore, Environment, etc.
  1. Ensure TCA 1.4+ with
    @Reducer
    macro on all reducers (prerequisite)
  2. Migrate feature-by-feature, bottom-up (leaf features first) (
    migration.md
    )
  3. Per feature: add
    @ObservableState
    to State AND update view simultaneously — never one without the other
  4. Replace
    @PresentationState
    with
    @Presents
  5. Replace
    WithViewStore(store, observe: { $0 })
    with direct
    store.property
    access
  6. Replace
    IfLetStore
    /
    ForEachStore
    /
    SwitchStore
    with native SwiftUI + store.scope
  7. Replace
    NavigationStackStore
    with
    NavigationStack(path: $store.scope(...))
  8. For iOS 16: wrap views in
    WithPerceptionTracking { }
  9. Run existing tests — all must pass before proceeding to next feature
  10. See full migration checklist and syntax transformations in
    migration.md
适用场景: 升级使用WithViewStore、IfLetStore、Environment等写法的1.7版本之前的TCA代码。
  1. 确保TCA版本不低于1.4,所有reducer都使用
    @Reducer
    宏(前置条件)
  2. 按功能逐个迁移,自底向上推进(先处理叶子节点功能)(参考
    migration.md
  3. 每个功能:给State添加
    @ObservableState
    的同时同步更新视图 —— 不能只改其中一端
  4. @Presents
    替换
    @PresentationState
  5. 用直接
    store.property
    访问替换
    WithViewStore(store, observe: { $0 })
  6. 用原生SwiftUI + store.scope替换
    IfLetStore
    /
    ForEachStore
    /
    SwitchStore
  7. NavigationStack(path: $store.scope(...))
    替换
    NavigationStackStore
  8. 针对iOS 16:将视图包裹在
    WithPerceptionTracking { }
  9. 运行现有测试 —— 所有测试通过后再处理下一个功能
  10. 完整迁移清单和语法转换规则可参考
    migration.md

Code Generation Rules

代码生成规则

<critical_rules> AI tools consistently generate outdated TCA code. Every output must use MODERN TCA (1.7+). ALWAYS:
  1. Use
    @Reducer
    macro — never bare
    struct Feature: Reducer
    conformance
  2. Use
    @ObservableState
    on ALL State types — never generate State without it
  3. Define State AND Action INSIDE the
    @Reducer
    struct body — never in extensions (macros can't see them)
  4. Use
    @Reducer enum
    for Destination/Path reducers (TCA 1.8+) — eliminates massive boilerplate
  5. Action does NOT need
    Equatable
    — remove it. Why: TCA 1.4+ uses case key paths for cancellation and
    receive()
    matching, making Equatable conformance unnecessary and a maintenance burden
  6. Access store properties directly:
    store.count
    ,
    store.send(.tapped)
    — NEVER use
    WithViewStore
  7. Use
    @Bindable var store
    when
    $store
    bindings are needed
  8. Use
    .run { send in }
    for effects — never
    EffectTask
    ,
    .task { }
    ,
    .fireAndForget { }
  9. Use enum-with-cases for cancel IDs — never empty enum types (pruned in release builds)
  10. Never capture whole
    @ObservableState
    in effect closures — extract needed values first
  11. Never do expensive work in reducers — they run on main thread. Offload to
    .run
    effects. </critical_rules>
<fallback_strategies> When working with TCA code, you may encounter cryptic compiler errors. If you fail to fix the same error twice:
  1. "compiler is unable to type-check this expression": State/Action likely defined in extension instead of inside
    @Reducer
    struct. Move them inside.
  2. "Circular reference resolving attached macro 'Reducer'": Don't nest
    @Reducer struct Y
    inside extension of another
    @Reducer struct X
    . Extract to top level.
  3. Macro + property wrapper conflict: Avoid property wrappers inside
    @ObservableState
    State. Use
    @ObservationStateIgnored
    as workaround (changes won't trigger re-renders).
  4. "A 'reduce' method should not be defined in a reducer with a 'body'": Never define BOTH
    reduce(into:action:)
    AND
    var body
    in the same reducer.
  5. NavigationStack dismiss fights: Ensure
    .navigationDestination
    is OUTSIDE
    ForEach
    /
    List
    , not inside. </fallback_strategies>
<critical_rules> AI工具经常生成过时的TCA代码,所有输出必须使用现代(1.7+)版本的TCA写法,始终遵循以下规则:
  1. 使用
    @Reducer
    宏 —— 不要直接使用
    struct Feature: Reducer
    声明
  2. 所有State类型都要添加
    @ObservableState
    —— 不要生成不带该注解的State
  3. State和Action都要定义在
    @Reducer
    结构体内部 —— 不要定义在扩展中(宏无法识别扩展中的定义)
  4. 目的地/路径reducer使用
    @Reducer enum
    (TCA 1.8+支持) —— 可大幅减少样板代码
  5. Action不需要遵循
    Equatable
    —— 移除相关声明。原因:TCA 1.4+使用case key path实现取消和
    receive()
    匹配,Equatable协议遵循没有必要,还会增加维护成本
  6. 直接访问store属性:
    store.count
    store.send(.tapped)
    —— 绝对不要使用
    WithViewStore
  7. 需要使用
    $store
    绑定时声明
    @Bindable var store
  8. 副作用使用
    .run { send in }
    —— 不要使用
    EffectTask
    .task { }
    .fireAndForget { }
  9. 取消ID使用带case的枚举 —— 不要使用空枚举类型( release构建时会被裁剪)
  10. 不要在副作用闭包中捕获整个
    @ObservableState
    —— 先提取需要用到的值
  11. 不要在reducer中执行耗时操作 —— 它们运行在主线程,耗时逻辑要放到
    .run
    副作用中执行。 </critical_rules>
<fallback_strategies> 处理TCA代码时可能会遇到晦涩的编译错误,如果同一个错误尝试两次都无法修复,可以参考以下方案:
  1. "compiler is unable to type-check this expression":State/Action大概率定义在扩展中而非
    @Reducer
    结构体内部,将它们移到结构体内部即可。
  2. "Circular reference resolving attached macro 'Reducer'":不要将
    @Reducer struct Y
    嵌套在另一个
    @Reducer struct X
    的扩展中,将其提取到顶层即可。
  3. 宏和属性包装器冲突:避免在
    @ObservableState
    修饰的State中使用属性包装器,可使用
    @ObservationStateIgnored
    作为临时解决方案(属性变更不会触发重渲染)。
  4. "A 'reduce' method should not be defined in a reducer with a 'body'":同一个reducer中不要同时定义
    reduce(into:action:)
    var body
  5. NavigationStack dismiss冲突:确保
    .navigationDestination
    定义在
    ForEach
    /
    List
    外部,而非内部。 </fallback_strategies>

Confidence Checks

校验清单

Before finalizing generated or refactored TCA code, verify ALL:
[ ] @Reducer macro — present on all feature structs
[ ] @ObservableState — present on all State types
[ ] State/Action — defined INSIDE @Reducer struct, not in extensions
[ ] No WithViewStore — direct store access everywhere
[ ] No Equatable on Action — removed (unnecessary since TCA 1.4)
[ ] Delegate actions — child-parent communication uses .delegate pattern
[ ] Effect closures — capture only needed values, not whole state
[ ] Cancel IDs — enum with cases, NOT empty enum types
[ ] Navigation — @Presents for modals, StackState for push nav, never nested NavigationStack
[ ] Dependencies — @DependencyClient struct-of-closures, not protocols
[ ] Tests — TestStore with exhaustive assertions, @MainActor annotated
[ ] File size — new files <= 400 lines; oversized files have split task in refactoring/
最终输出生成或重构后的TCA代码前,请确认所有检查项都已满足:
[ ] @Reducer macro — present on all feature structs
[ ] @ObservableState — present on all State types
[ ] State/Action — defined INSIDE @Reducer struct, not in extensions
[ ] No WithViewStore — direct store access everywhere
[ ] No Equatable on Action — removed (unnecessary since TCA 1.4)
[ ] Delegate actions — child-parent communication uses .delegate pattern
[ ] Effect closures — capture only needed values, not whole state
[ ] Cancel IDs — enum with cases, NOT empty enum types
[ ] Navigation — @Presents for modals, StackState for push nav, never nested NavigationStack
[ ] Dependencies — @DependencyClient struct-of-closures, not protocols
[ ] Tests — TestStore with exhaustive assertions, @MainActor annotated
[ ] File size — new files <= 400 lines; oversized files have split task in refactoring/

Companion Skills

配套技能

Project needCompanion skillApply when
Swift Concurrency patterns
skills/ios/swift-concurrency/SKILL.md
Writing async effects, actor isolation, Sendable compliance
GCD/OperationQueue legacy code
skills/ios/gcd-operationqueue/SKILL.md
Legacy async work before migrating to TCA effects
Comprehensive testing guidance
skills/ios/ios-testing/SKILL.md
Advanced testing patterns beyond TCA TestStore
Security audit
skills/ios/ios-security-audit/SKILL.md
Auditing Keychain usage, network security in TCA apps
项目需求配套技能适用场景
Swift 并发模式
skills/ios/swift-concurrency/SKILL.md
编写异步副作用、actor隔离、遵循Sendable协议
GCD/OperationQueue 旧代码
skills/ios/gcd-operationqueue/SKILL.md
迁移到TCA副作用之前的旧异步逻辑处理
全面测试指南
skills/ios/ios-testing/SKILL.md
TCA TestStore之外的高级测试模式
安全审计
skills/ios/ios-security-audit/SKILL.md
审计TCA应用的钥匙串使用、网络安全

References

参考文档

ReferenceWhen to Read
rules.md
Do's and Don'ts quick reference: modern TCA patterns and critical anti-patterns
reducer-architecture.md
@Reducer macro rules, feature decomposition, parent-child scoping, state design
effects.md
Effect API (.run, .send, .merge, .cancel), cancellation, long-running effects, anti-patterns
dependencies.md
@DependencyClient, DependencyKey, liveValue/testValue, module boundaries, test guards
navigation.md
Tree-based (@Presents/ifLet), stack-based (StackState/forEach), dismissal, deep linking
testing.md
TestStore exhaustive/non-exhaustive, TestClock, case key paths, per-feature checklist
migration.md
Version progression, per-feature migration checklist, syntax transformations, known issues
anti-patterns.md
AI-specific mistakes, god reducer signs, performance pitfalls, enterprise concerns
performance.md
Action costs, _printChanges, .signpost, scope performance, high-frequency action mitigation
refactoring-workflow.md
refactoring/
directory protocol, per-feature plans, PR sizing, phase ordering
参考文档阅读场景
rules.md
最佳实践速查:现代TCA模式和核心反模式
reducer-architecture.md
@Reducer宏规则、功能拆分、父子作用域、状态设计
effects.md
Effect API(.run、.send、.merge、.cancel)、取消机制、长驻副作用、反模式
dependencies.md
@DependencyClient、DependencyKey、liveValue/testValue、模块边界、测试防护
navigation.md
树结构导航(@Presents/ifLet)、栈结构导航(StackState/forEach)、关闭逻辑、深度链接
testing.md
TestStore 全覆盖/非全覆盖测试、TestClock、case key path、单功能检查清单
migration.md
版本演进、单功能迁移清单、语法转换、已知问题
anti-patterns.md
AI特有的错误、上帝Reducer特征、性能陷阱、企业级注意事项
performance.md
Action开销、_printChanges、.signpost、作用域性能、高频Action优化
refactoring-workflow.md
refactoring/
目录规范、单功能计划、PR粒度、阶段排序