iOS Swift Redux 架构详解
Redux 是一种可预测的状态管理架构,非常适合复杂 iOS 应用的状态管理。下面我将全面介绍如何在 Swift 中实现 Redux 模式。
1. Redux 核心概念
Redux 基于三个基本原则:
- 单一数据源:整个应用状态存储在一个 Store 中
- 状态只读:只能通过派发 Action 来改变状态
- 纯函数修改:使用 Reducer 纯函数处理状态变更
Redux 数据流
sql
Action → Store → Reducer → New State → View Update
2. 基础实现
核心协议定义
swift
protocol Action {} // 所有动作的标记协议
protocol State {} // 所有状态的标记协议
protocol Reducer {
associatedtype S: State
func reduce(state: S, action: Action) -> S
}
Store 实现
swift
class Store<S: State, R: Reducer>: ObservableObject where R.S == S {
@Published private(set) var state: S
private let reducer: R
private let middlewares: [Middleware]
init(initialState: S, reducer: R, middlewares: [Middleware] = []) {
self.state = initialState
self.reducer = reducer
self.middlewares = middlewares
}
func dispatch(action: Action) {
// 中间件处理
let dispatchFunction: (Action) -> Void = { [weak self] action in
self?.realDispatch(action: action)
}
var currentDispatch: (Action) -> Void = dispatchFunction
for middleware in middlewares.reversed() {
currentDispatch = middleware.create(store: self, dispatch: currentDispatch)
}
currentDispatch(action)
}
private func realDispatch(action: Action) {
DispatchQueue.main.async {
self.state = self.reducer.reduce(state: self.state, action: action)
}
}
}
3. 完整示例:计数器应用
定义 Action
swift
enum CounterAction: Action {
case increment
case decrement
case incrementBy(Int)
case reset
}
定义 State
swift
struct CounterState: State {
var count: Int = 0
var lastAction: String = "None"
}
实现 Reducer
swift
struct CounterReducer: Reducer {
func reduce(state: CounterState, action: Action) -> CounterState {
var newState = state
switch action {
case let action as CounterAction:
switch action {
case .increment:
newState.count += 1
newState.lastAction = "Incremented by 1"
case .decrement:
newState.count -= 1
newState.lastAction = "Decremented by 1"
case .incrementBy(let amount):
newState.count += amount
newState.lastAction = "Incremented by \(amount)"
case .reset:
newState.count = 0
newState.lastAction = "Reset to 0"
}
default:
break
}
return newState
}
}
在 SwiftUI 中使用
swift
struct CounterView: View {
@EnvironmentObject var store: Store<CounterState, CounterReducer>
var body: some View {
VStack(spacing: 20) {
Text("Count: \(store.state.count)")
.font(.largeTitle)
Text("Last action: \(store.state.lastAction)")
.font(.caption)
HStack(spacing: 20) {
Button("+") { store.dispatch(action: CounterAction.increment) }
Button("-") { store.dispatch(action: CounterAction.decrement) }
}
Button("+10") { store.dispatch(action: CounterAction.incrementBy(10)) }
Button("Reset") { store.dispatch(action: CounterAction.reset) }
}
.buttonStyle(.borderedProminent)
}
}
// 应用入口设置
@main
struct CounterApp: App {
let store = Store(
initialState: CounterState(),
reducer: CounterReducer()
)
var body: some Scene {
WindowGroup {
CounterView()
.environmentObject(store)
}
}
}
4. 高级功能实现
中间件 (Middleware)
swift
protocol Middleware {
func create(store: Any, dispatch: @escaping (Action) -> Void, next: @escaping (Action) -> Void) -> (Action) -> Void
}
// 日志中间件示例
struct LoggerMiddleware: Middleware {
func create(store: Any, dispatch: @escaping (Action) -> Void, next: @escaping (Action) -> Void) -> (Action) -> Void {
return { action in
print("Will dispatch: \(action)")
next(action)
if let store = store as? AnyStore {
print("New state: \(store.state)")
}
}
}
}
protocol AnyStore {
var state: Any { get }
}
// 让 Store 符合 AnyStore
extension Store: AnyStore {
var state: Any { self.state }
}
异步 Action (Side Effects)
swift
// 异步 Action 协议
protocol AsyncAction: Action {
func execute(dispatch: @escaping (Action) -> Void, state: () -> State?)
}
// 网络请求 Action 示例
struct FetchUserAction: AsyncAction {
let userId: Int
func execute(dispatch: @escaping (Action) -> Void, state: () -> State?) {
DispatchQueue.global().async {
// 模拟网络请求
sleep(1)
// 返回结果
DispatchQueue.main.async {
dispatch(UserFetchedAction(user: User(id: self.userId, name: "User \(self.userId)")))
}
}
}
}
struct UserFetchedAction: Action {
let user: User
}
// 扩展 Store 支持异步 Action
extension Store {
func dispatch(asyncAction: AsyncAction) {
asyncAction.execute(dispatch: self.dispatch, state: { self.state })
}
}
组合 Reducer
swift
struct CombinedReducer: Reducer {
let reducers: [AnyReducer]
init(reducers: [AnyReducer]) {
self.reducers = reducers
}
func reduce(state: Any, action: Action) -> Any {
return reducers.reduce(state) { currentState, reducer in
reducer._reduce(state: currentState, action: action)
}
}
}
protocol AnyReducer {
func _reduce(state: Any, action: Action) -> Any
}
struct AnyReducerWrapper<R: Reducer>: AnyReducer {
let reducer: R
func _reduce(state: Any, action: Action) -> Any {
guard let typedState = state as? R.S else { return state }
return reducer.reduce(state: typedState, action: action)
}
}
5. 实际应用架构
模块化状态管理
swift
// AppState.swift - 组合所有模块状态
struct AppState: State {
var counter: CounterState
var user: UserState
var settings: SettingsState
}
// AppReducer.swift - 组合所有 Reducer
struct AppReducer: Reducer {
func reduce(state: AppState, action: Action) -> AppState {
var newState = state
newState.counter = CounterReducer().reduce(state: state.counter, action: action)
newState.user = UserReducer().reduce(state: state.user, action: action)
newState.settings = SettingsReducer().reduce(state: state.settings, action: action)
return newState
}
}
使用 Selector 优化性能
swift
struct CounterSelector {
static func getCount(_ state: AppState) -> Int {
return state.counter.count
}
static func getLastAction(_ state: AppState) -> String {
return state.counter.lastAction
}
}
// 在 View 中使用
struct CounterView: View {
@EnvironmentObject var store: Store<AppState, AppReducer>
var count: Int {
CounterSelector.getCount(store.state)
}
var lastAction: String {
CounterSelector.getLastAction(store.state)
}
var body: some View {
// 使用 count 和 lastAction 而不是直接访问 store.state
}
}
6. 与 Combine 集成
swift
extension Store {
func publisher<T>(_ selector: @escaping (S) -> T) -> AnyPublisher<T, Never> {
return $state
.map(selector)
.removeDuplicates()
.eraseToAnyPublisher()
}
}
// 使用示例
class SomeViewModel: ObservableObject {
@Published var count: Int = 0
private var cancellables = Set<AnyCancellable>()
init(store: Store<AppState, AppReducer>) {
store.publisher(CounterSelector.getCount)
.assign(to: \.count, on: self)
.store(in: &cancellables)
}
}
7. 测试策略
测试 Reducer
swift
class CounterReducerTests: XCTestCase {
let reducer = CounterReducer()
func testIncrement() {
let initialState = CounterState(count: 0)
let newState = reducer.reduce(state: initialState, action: CounterAction.increment)
XCTAssertEqual(newState.count, 1)
XCTAssertEqual(newState.lastAction, "Incremented by 1")
}
func testDecrement() {
let initialState = CounterState(count: 5)
let newState = reducer.reduce(state: initialState, action: CounterAction.decrement)
XCTAssertEqual(newState.count, 4)
}
}
测试 Async Action
swift
class FetchUserActionTests: XCTestCase {
func testFetchUser() {
let expectation = XCTestExpectation(description: "Fetch user")
let action = FetchUserAction(userId: 1)
var receivedAction: Action?
let dispatch: (Action) -> Void = { action in
receivedAction = action
expectation.fulfill()
}
action.execute(dispatch: dispatch, state: { nil })
wait(for: [expectation], timeout: 2)
guard let userAction = receivedAction as? UserFetchedAction else {
XCTFail("Expected UserFetchedAction")
return
}
XCTAssertEqual(userAction.user.id, 1)
XCTAssertEqual(userAction.user.name, "User 1")
}
}
8. 性能优化技巧
- 使用 Equatable 状态 :实现状态对象的
Equatable
协议避免不必要的视图更新 - 细粒度订阅:只订阅需要的状态部分而非整个状态树
- 使用值类型状态:结构体比类更适合作为状态类型
- 延迟状态更新:对高频操作进行防抖处理
- 后台处理:将复杂计算移到后台队列,只将结果派发到主线程
9. 与其他架构对比
特性 | Redux | MVVM | VIPER |
---|---|---|---|
数据流 | 单向 | 双向 | 单向 |
状态管理 | 集中式 | 分布式 | 分布式 |
复杂性 | 中等 | 低 | 高 |
可测试性 | 高 | 中 | 高 |
适合规模 | 中大型 | 中小型 | 大型 |
学习曲线 | 中等 | 低 | 高 |
10. 推荐库
- ReSwift: 最接近 JavaScript Redux 的实现
- TCA (The Composable Architecture): 更强大的 Redux 变体,来自 Point-Free
- ReduxKit: 轻量级 Redux 实现
- SwiftUIFlux: 专为 SwiftUI 设计的 Flux/Redux 实现
Redux 架构为 iOS 应用提供了可预测的状态管理方案,特别适合状态复杂、多人协作的大型项目。通过严格的单向数据流和不可变状态,可以显著减少难以追踪的 bug,提高代码的可维护性和可测试性。