ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Swift Composable Architecture 核心运行时 Store 完全指南:创建、状态访问、Action 发送与 Store Scoping 实战

Swift Composable Architecture 核心运行时 Store 完全指南:创建、状态访问、Action 发送与 Store Scoping 实战 前端移动开发【免费下载链接】swift-composable-architectureA library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.项目地址https://gitcode.com/GitHub_Trending/sw/swift-composable-architecture点击查看免费下载导读Store是 Swift Composable ArchitectureTCA中驱动整个应用运行的运行时对象也是视图与业务逻辑之间的唯一桥梁。本指南以官方文档 Store.md 为主线结合 Store.swift 源码与相关观察、绑定扩展系统讲解 Store 的创建方式、状态读取、Action 发送、Store 作用域切分scoping、Binding 绑定以及 Combine 集成。读完本指南你将掌握如何在一个真实 TCA 项目中正确地构造根 Store、向子视图派生子 Store、绑定可写状态并理解 StoreTask 生命周期管理与底层缓存机制。Store 在 TCA 架构中的定位从源码注释Store.swift可以看到Store 被明确定义为驱动应用的运行时runtime是需要在视图中传递、用于读取功能状态state和发送用户操作action的对象。它的类声明为dynamicMemberLookup preconcurrency MainActor public final class StoreState, Action: _Store几个关键设计点MainActorStore 的所有操作都限定在主线程执行这是 UI 框架安全性的硬性保证dynamicMemberLookup允许视图直接通过store.count这样的语法读取状态属性final classStore 是引用类型应用通常只创建一个根 Store再通过scope派生多个子 Store。一个典型的应用入口会在App中持有根 Store并在WindowGroup中传递给根视图main struct MyApp: App { static let store Store(initialState: AppFeature.State()) { AppFeature() } var body: some Scene { WindowGroup { RootView(store: Self.store) } } }注意Xcode 预览运行时同样会创建 App 入口导致 Store 及依赖被提前执行因此源码特别建议将根 Store 保存在static let中见 Store.swift避免预览场景下的意外副作用。创建 Storeinit(initialState:reducer:withDependencies:)与StoreOf初始化方法Store 的公开初始化方法签名如下Store.swiftpublic convenience initR: ReducerState, Action( initialState: autoclosure () - R.State, ReducerBuilderState, Action reducer: () - R, withDependencies prepareDependencies: ((inout DependencyValues) - Void)? nil )三个参数的作用参数类型说明initialState自动闭包应用启动时的初始状态使用autoclosure保证惰性求值reducerReducerBuilder驱动业务逻辑的 Reducer可由Reduce、.onChange、.ifLet等操作符组合构建withDependencies可选闭包覆盖依赖注入容器中的依赖nil时使用默认依赖底层实现Store.swift在初始化时会做三件事读取当前依赖注入环境、为当前状态追加一个新的导航 IDnavigationIDPath.append(NavigationID())、最后将固定后的依赖写回 reducer 的dependency(\.self, dependencies)。这意味着创建 Store 的时刻就是依赖固化snapshot的时刻后续依赖变化不会影响该 Store。实际使用示例let store Store( initialState: AppFeature.State(), reducer: { AppFeature() }, withDependencies: { $0.apiClient .mock } )StoreOf便捷别名Store需要两个泛型参数State与Action而一个 Reducer 的领域通常固定为R.State和R.Action。因此源码定义了一个类型别名Store.swiftpublic typealias StoreOfR: Reducer StoreR.State, R.Action对比两种写法// 完整写法 let store: StoreFeature.State, Feature.Action // StoreOf 写法 let store: StoreOfFeatureStoreOf大量出现在视图属性声明中例如let store: StoreOfAppFeature不仅减少样板代码还让 Reducer 与 Store 的领域自动保持同步。访问状态state、动态成员查找与withState(_:)直接读取state当State符合ObservableState协议即状态标注了ObservableState宏时Store 暴露只读的state属性StoreObservation.swiftpublic var state: State { self.observableState }其中observableState会先通过 observation registrar 登记对该状态的访问再返回当前状态——这正是视图内读取store.state能够被 Observation 框架跟踪依赖的原因。动态成员查找得益于dynamicMemberLookupStore 上定义了读下标StoreObservation.swiftpublic subscriptValue(dynamicMember keyPath: KeyPathState, Value) - Value { self.state[keyPath: keyPath] }这使得视图可以直接写struct RootView: View { let store: StoreOfAppFeature var body: some View { Form { Text(\(store.count)) // 等价于 store.state.count Button(Tap) { store.send(.buttonTapped) } } } }withState(_:)已废弃早期版本通过withState读取状态但现在它被标记为废弃Deprecations.swift官方迁移建议是改用ObservableState宏// 旧写法已废弃 store.withState { $0.count } // 新写法 store.count详细迁移步骤可参考 MigratingTo1.7.md。发送 Actionsend(_:)系列与StoreTask基本发送send是视图与业务逻辑交互的唯一入口discardableResult public func send(_ action: Action) - StoreTask标注discardableResult意味着可以忽略返回值内部实现会调用底层 core 的send(action, origin: .store)Store.swift。带动画与事务的发送文档列出另外两个重载send(_:animation:)携带 SwiftUI 动画发送 actionsend(_:transaction:)携带完整Transaction发送。不过源码中这两个 API 已被标注为计划废弃deprecation 年份设为 9999推荐的新写法是// 旧写法计划废弃 store.send(.increment, animation: .default) // 推荐写法 withAnimation(.default) { store.send(.increment) }对于事务同理withTransaction(transaction) { store.send(action) }见 Store.swift 与 #L236-L270。在 Effect 内部发送带动画的 action 时应使用.run { send in await send(.response, animation: .default) }见 Deprecations.swift。StoreTask效果生命周期与取消send返回的StoreTask代表该 action 触发的 Effect 的生命周期Store.swift它提供三个成员成员类型作用cancel()方法取消底层任务finish()async 方法等待任务执行完毕isCancelled只读属性任务是否已被取消典型用法是把效果的生命周期绑定到 SwiftUI 的task视图修饰符上.task { await store.send(.task).finish() }当视图离开屏幕时task修饰符自动取消异步上下文StoreTask会随之取消对应的 Effect——这正是 TCA 中效果自动清理的核心机制。与 Swift 原生Task不同StoreTask会在当前异步上下文与任务之间自动建立取消处理Store.swift。派生子 Storescope(_:action:)与作用域切分为什么需要 scope大型应用中根 Store 承载整个应用的领域State Action。如果把根 Store 直接传给每个子视图子视图将被迫依赖全局领域模块化将无从谈起。scope方法允许把根 Store 变换为只处理某个子领域child state/child action的 Storepublic func scopeChildState, ChildAction( _ state: KeyPathState, ChildState, action: CaseKeyPathAction, ChildAction ) - StoreChildState, ChildAction例如一个包含登录、搜索、个人页三个 Tab 的应用struct AppView: View { let store: StoreOfAppFeature var body: some View { TabView { ActivityView(store: store.scope(\.activity, action: \.activity)) .tabItem { Text(Activity) } SearchView(store: store.scope(\.search, action: \.search)) .tabItem { Text(Search) } ProfileView(store: store.scope(\.profile, action: \.profile)) .tabItem { Text(Profile) } } } }通过 scopingSearchView可以被打包成独立模块完全不感知AppFeature.State与AppFeature.Action的存在Store.swift。底层实现ScopedCore 与子 Store 缓存scope的实现Store.swift值得深挖构建ScopedCore(base:stateKeyPath:actionKeyPath:)在父 core 之上完成状态投影与 action 包装用statekey path 和actioncase key path 组合成ScopeIDStore.swift作为子 Store 的缓存键通过children字典缓存子 StoreStore.swift相同 key path 组合重复调用 scope 会返回同一个 Store 实例避免视图刷新时不断创建新 Store 导致状态丢失。当子状态是可选值Optional时还有对应的 optional 版本scope(_:action:fileID:filePath:line:column:)它返回StoreChildState, ChildAction?常用于if let解包子 Storeif let childStore store.scope(\.child, action: \.child) { ChildView(store: childStore) }重要此操作只能在 SwiftUI 视图内部或withPerceptionTracking中使用才能正确观察可选状态的变化StoreObservation.swift。文件路径、行号参数用于定位运行时警告自动由#fileID、#line等字面量填充。可写绑定动态成员下标与BindableAction当 Store 的Action符合BindableAction且State与Action.State一致时Store 额外获得可写的动态成员下标BindingObservation.swiftpublic subscriptValue: Equatable Sendable( dynamicMember keyPath: WritableKeyPathState, Value ) - Value { get { self.state[keyPath: keyPath] } set { self.send(.set(keyPath.unsafeSendable(), newValue, ...)) } }读取返回当前状态写入则通过发送.set绑定 action 走完整 reducer 管道。于是视图可以极简地绑定可写字段TextField(Search, text: $store.query)这里的$store.query通过Binding(subscript:)桥接将 SwiftUI 的Binding与 Store 的可写下标打通。若Action符合ViewAction且其ViewAction符合BindableAction同样支持通过.view(.set(...))间接绑定BindingObservation.swift。Combine 集成StorePublisher对于仍在使用 Combine 的场景Store 提供publisher属性Store.swift返回类型为StorePublisherState——一个dynamicMemberLookup的 Publisher其Output State、Failure Never。它支持动态成员查找可直接抽取状态中的某个字段且该字段必须Equatable以便自动removeDuplicates()Store.swiftstore.publisher.alert .sink { ... }需要注意StorePublisher 目前已被标注为计划废弃官方建议改用 Observation 框架ObservableStateobserve替代但在存量 Combine 代码中仍可正常使用。ObservableObject 与 ObservationStore 的观察方式Store 声明为ObservableObject但这一协议符合是惰性的Store.swift它的唯一用途是让 Store 能被 SwiftUI 的StateObject持有而不是通过ObservedObject观察。extension Store: ObservableObject {}真正的状态观察依赖两套机制iOS 17或 macOS 14 等新平台Store 在canImport(Observation)条件下扩展为ObservableStore.swift配合状态上的ObservableState宏使用旧系统iOS 17通过 Perception 包实现等价的感知跟踪Store 扩展为PerceptibleStoreObservation.swift。此外 Store 还实现了值语义比较Equatable采用引用相等Hashable基于对象标识并符合IdentifiableStoreObservation.swift这使得 Store 可以直接放入 SwiftUI 的ForEach、task等场景使用。其他值得了解的接口与弃用接口Store 文档中还列出了以下补充接口scope 的另一种形式早期带参数标签的scope(state:action:)已被renamed: scope(_:action:)标记弃用Store.swiftcase运算符与 case key path 模式匹配相关的运算符用于对 Store 的 action 做 case 分解与绑定调试描述Store 的debugDescription会智能输出类型名例如当State/Action恰好以.State/.Action结尾且前缀一致时输出StoreOfFeatureStore.swift方便日志与调试生命周期日志Store 初始化与析构都会写入统一 LoggerStore.swift结合storeTypeName可以追踪 Store 的创建与释放排查内存泄漏。所有已弃用接口的完整说明集中收录在 StoreDeprecations.md 中。测试验证与进一步探索Store 的核心行为在仓库中有大量测试覆盖可作为理解与验证的参考StoreTests.swiftStore 初始化、send、scope 的基础行为StorePerceptionTests.swiftObservation/Perception 感知跟踪的正确性BindableStoreTests.swift可写绑定下标的读写与 reducer 联动StoreLifetimeTests.swiftStore 生命周期、子 Store 缓存与释放行为。在实际项目中建议遵循一个根 Store 按功能 scope 子 Store的实践根 Store 只在 App 入口创建一次子视图一律通过store.scope(...)接收最小领域的 Store需要写回状态时优先使用ObservableState配合可写动态成员下标让绑定始终经过 reducer从而保证整个应用的状态流转可预测、可测试。赞分享前端移动开发【免费下载链接】swift-composable-architectureA library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.项目地址https://gitcode.com/GitHub_Trending/sw/swift-composable-architecture点击查看免费下载相关推荐深入Zustand核心Store创建与状态管理深入Zustand核心Store创建与状态管理 本文深入探讨了Zustand状态管理库的核心机制从create函数的工作原理与实现机制开始详细解析了其函数前端Pinia 组合式 Store 实战跨 Store 共享状态、Getter 与 Action 的完整指南Pinia 组合式 Store 实战跨 Store 共享状态、Getter 与 Action 的完整指南 组合式 StoreComposing Stores前端状态管理Yup 扩展指南通过 addMethod、transform 与继承构建自定义 Schema 校验能力Yup 扩展指南通过 addMethod、transform 与继承构建自定义 Schema 校验能力 Yup 是一个用于运行时值解析与校验的 schema前端移动开发上一篇A-to-Z-Resources-for-Students技术博客流量增长策略下一篇A-to-Z-Resources-for-Students开源许可证详解与选择指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表