如果你写过复杂的 iOS 应用,你一定遇到过这样的困扰:
今天给你介绍一个来自 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 个世纪后... 代码终于写完了}
这还是简单的!当功能变多,你会发现:
🧩 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?
🚀 快速上手
安装
// 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 解决的问题:
- • ✅ 依赖注入复杂 → @Dependency 一行注入
💡 一句话概括 TCA:用搭积木的方式,构建可预测、可测试、可组合的 iOS 应用。