当前位置:首页>iOSAPP>彻底搞懂 TCA:像搭积木一样构建 iOS 应用

彻底搞懂 TCA:像搭积木一样构建 iOS 应用

  • 2026-08-13 06:23:47
彻底搞懂 TCA:像搭积木一样构建 iOS 应用

如果你写过复杂的 iOS 应用,你一定遇到过这样的困扰:

  • • 状态管理乱成一团
  • • 某个按钮点一下,半个 app 的数据都跟着变
  • • 想写单元测试?根本无从下手
  • • 加新功能就怕改坏老代码

今天给你介绍一个来自 Point-Free 团队的神器 — The Composable Architecture (TCA)。它用一种优雅的方式,让 iOS 开发变得像搭积木一样简单!


🎬 场景带入:假如没有 TCA

想象你在开发一个购物车功能:

// 没有 TCA 的日常...class ShoppingCartViewController: UIViewController {    var items: [CartItem] = []    var totalPrice: Double = 0    var isLoading: Bool = false    var errorMessage: String?    var selectedItems: Set<UUID> = []    var couponCode: String = ""    var shippingAddress: Address?    var paymentMethod: PaymentMethod?    // 5 个世纪后... 代码终于写完了}

这还是简单的!当功能变多,你会发现:

  • • 状态分散在各处
  • • 不知道谁在改状态
  • • 改一个 bug 冒出三个新 bug

🧩 TCA 三剑客:State、Action、Reducer

TCA 的核心只有三个概念,看完你就能懂!

1. State — 你的"状态快照"

struct AppState {    var counter: Int = 0    var isLoading: Bool = false    var userName: String = ""}

就像一张照片,某一时刻 app 长什么样,全部在这张照片里。

2. Action — "发生了什么事"

enum AppAction {    case incrementButtonTapped    case decrementButtonTapped    case loadUser    case userLoaded(User)    case userLoadFailed(Error)}

就像事件簿,记录用户做了什么、系统发生了什么。

3. Reducer — "状态如何变化"

let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, environment in    switch action {    case .incrementButtonTapped:        state.counter += 1        return .none    case .decrementButtonTapped:        state.counter -= 1        return .none    case .loadUser:        state.isLoading = true        return environment.api.fetchUser()            .map(AppAction.userLoaded)            .catch { Just(AppAction.userLoadFailed($0)) }    case let .userLoaded(user):        state.isLoading = false        state.userName = user.name    case let .userLoadFailed(error):        state.isLoading = false    }    return .none}

这就是状态机:给定当前状态 + 一个动作 → 输出新状态。


🔄 数据流向:单向循环

TCA 的数据流像一个循环:

┌─────────────────────────────────────────┐│                  View                    ││         (UI, 展示状态)                   │└──────────────────┬──────────────────────┘                   │ 发送 Action                   ▼┌─────────────────────────────────────────┐│               Store                      ││           (状态仓库)                      │└──────────────────┬──────────────────────┘                   │ 分发 Action                   ▼┌─────────────────────────────────────────┐│               Reducer                   ││        (处理逻辑,返回新状态)              │└──────────────────┬──────────────────────┘                   │ 返回新 State                   ▼┌─────────────────────────────────────────┐│                  View                    ││         (UI 自动刷新)                     │└─────────────────────────────────────────┘

永远只朝一个方向流:View → Store → Reducer → View

就像流水线的传送带,永远不会倒着走!


🧱 组合魔法:Feature 模块化

TCA 最强大的地方来了 — 组合!

假设你有两个独立功能:

计数器 Feature

struct CounterState { var count: Int = 0 }enum CounterAction { case increment, case decrement }let counterReducer = Reducer<CounterState, CounterAction, Void> { ... }

天气 Feature

struct WeatherState { var temperature: Int? }enum WeatherAction { case loadWeather }let weatherReducer = Reducer<WeatherState, WeatherAction, Void> { ... }

组合在一起!

struct AppState {    var counter: CounterState = CounterState()    var weather: WeatherState = WeatherState()}enum AppAction {    case counter(CounterAction)    case weather(WeatherAction)}let appReducer = Reducer.combine(    counterReducer.pullback(        state: \.counter,        action: \.counter,        environment: { _ in () }    ),    weatherReducer.pullback(        state: \.weather,        action: \.weather,        environment: { _ in () }    ))

🎉 这就是 TCA 的精髓:小模块 → 大模块 → 整个 app!


🎯 TCA 核心原则

1. 单一数据源

整个 app 只有一个 State,所有状态一目了然。

2. 不可变数据

状态只能被替换,不能被修改:

// ❌ 错误:直接修改state.count = 10// ✅ 正确:创建新状态state = CounterState(count: 10)

3. 纯函数 Reducer

同样的输入 → 同样的输出,可预测、可测试。

4. 副作用在 Effects 中

网络请求、数据库操作都在 Effects 里,逻辑清晰:

case .fetchUser:    return environment.api.fetchUser()        .map(Action.userLoaded)        .catch { Just(Action.fetchFailed($0)) }

💉 依赖注入:@Dependency

TCA 的依赖注入简单到哭:

定义依赖

struct AppEnvironment {    var api: APIClient    var database: DatabaseClient    var analytics: AnalyticsClient}

使用依赖

@Dependency(\.api) var api@Dependency(\.database) var database// 直接用!return api.fetchUser()    .map(Action.userLoaded)

测试时替换

let store = TestStore(    AppState(),    reducer: appReducer,    environment: AppEnvironment(        api: .mock,        database: .mock,        analytics: .mock    ))

🎯 一行代码切换真实/测试依赖!


🧪 测试:前所未有的简单

因为 TCA 是纯函数,测试变得超级简单:

func testIncrement() {    let store = TestStore(CounterState(), reducer: counterReducer)    // 发送 action    store.send(.increment)    // 验证状态变化    store.assertState { $0.count == 1 }}
func testAPI() {    let store = TestStore(AppState(), reducer: appReducer, environment: .mock)    // 模拟 API 返回    store.receive(.userLoaded(.mockUser))         .assertState { $0.userName == "Mock" }}

💡 不需要 mock View,不需要异步等待,测试像读小说一样简单!


🔧 TCA vs 其他方案

特性
TCA
MVVM
Redux
状态管理
集中式
分散式
集中式
副作用处理
Effect
Combine
Middleware
测试性
⭐⭐⭐⭐⭐
⭐⭐
⭐⭐⭐
模块化
组合
继承
组合
学习曲线
中等
低
高
依赖注入
内置
手动
手动

📦 谁在用 TCA?

  • • Point-Free (TCA 作者团队)
  • • 1Password (密码管理巨头)
  • • Slack (部分功能)
  • • many small teams

🚀 快速上手

安装

// Swift Package Manager.package(url: "https://github.com/pointfreeco/swift-composable-architecture", from: "1.9.0")

最小示例

import ComposableArchitecture// 1. 定义 Statestruct Counter: Equatable { var count: Int = 0 }// 2. 定义 Actionenum CounterAction: Equatable {    case increment    case decrement}// 3. 定义 Reducerlet counterReducer = Reducer<Int, Void, Void> { state, action, _ in    state += 1    return .none}

总结

TCA 解决的问题:

  • • ✅ 状态管理混乱 → 单一 State
  • • ✅ 难以测试 → 纯函数,可测试
  • • ✅ 模块化困难 → Reducer 组合
  • • ✅ 依赖注入复杂 → @Dependency 一行注入

💡 一句话概括 TCA:用搭积木的方式,构建可预测、可测试、可组合的 iOS 应用。


最新文章

随机文章