资讯动态

Firefox for iOS 用 Redux 替代 MVVM:ADR-0004 架构决策与实践落地

发布时间:2026/9/15 11:07:48 来源:尧图企业网站定制
Firefox for iOS 用 Redux 替代 MVVMADR-0004 架构决策与实践落地【免费下载链接】firefox-iosFirefox for iOS项目地址: https://gitcode.com/GitHub_Trending/fi/firefox-ios导读adr/0004-using-redux-to-replace-mvvm.md是 Firefox for iOS 团队于 2024-09-05 批准生效的一篇架构决策记录ADRArchitecture Decision Record它标志着项目从长期依赖的 MVVM 架构正式转向 Redux 单向数据流架构。本文围绕该 ADR 展开先还原决策的背景与动机再逐条拆解 Actions、Store、Middleware、Reducer 四大核心概念在本仓库 BrowserKit/Sources/Redux 中的真实实现最后结合 ADR-0005 的导航集成方案与BrowserViewControllerState源码说明 Redux 在真实界面中的落地形态。读完本文你将掌握这套 Redux 架构的核心约束类化 Action、主线程派发、2-line switch 规则、业务逻辑进中间件、表现逻辑进 Reducer并能定位到仓库中的全部支撑源码与测试。说明本 ADR 引用的外部链接Google Docs、Confluence 等不在仓库内本文一律不引用所有事实均来自仓库内文档与源码。一、决策背景为什么要抛弃 MVVMADR-0004 并非孤立决定它建立在前期试点Pilot的基础之上。同一目录下的 adr/0003-redux-pilot.md2023-09-07Approved记录了试点动机Firefox iOS 大量区域依赖 MVVM同时一个庞大的BrowserViewControllerBVC仍承担大量逻辑与状态协调由此带来四类问题视图控制器互相耦合出现临时拼凑的代理链delegation chains竞态条件与不确定的状态迁移race conditions / nondeterministic state transitions状态分散在控制器、视图模型和服务层中导航与副作用和 UI 逻辑纠缠在一起测试困难。ADR-0003 因此决定引入一个受 ReSwift 核心类型启发的轻量自研 Redux 风格架构先在ThemeSettingsController这类受控小范围内试点验证确定性determinism、可测试性testability与开发者体验ergonomics。ADR-0004 正是把试点结论正式化为全项目标准只要涉及状态处理就用 Redux逐步离开 MVVM。决策的核心论点ADR-0004 的决策部分只有三条硬性约定它们是后续一切规则的总纲约定含义Redux 与 MVVM 是两套互不兼容的系统不应混用同一个功能内不要同时出现 ViewModel 与 Redux 双层状态源业务逻辑由 Middleware 处理网络请求、存储读写、Feature Flag 等副作用一律进中间件表现逻辑由 Reducer 处理Reducer 只负责根据 Action 计算新的展示状态保持纯函数二、Actions从枚举到类ADR-0004 明确要求Actions 改为类class以简化载荷payload传递并消除以往每个 action 枚举上重复携带ActionContext的需求。这项约定直接体现在 Action.swift 中/// Used to describe an action that can be dispatched by the redux store public protocol Action: Sendable, CustomDebugStringConvertible { var windowUUID: WindowUUID { get } var actionType: ActionType { get } }Action协议要求每个 Action 携带两个关键属性windowUUID窗口标识符。Firefox iOS 是多窗口应用iPad 多窗口Redux 的 Store 是全局单例但每个窗口有各自的屏幕状态ScreenState因此 Action 必须携带窗口 UUID供各窗口的 reducer 判断这个 Action 是否属于我。actionType动作类型用于 reducer 中的 switch 分支。ADR-0004 还给出了命名规范创建 Action 类型时倾向于使用用户动作/API 动作名称而不是状态变化名称。例如tapOnTrackingProtection用户点击了跟踪保护入口优于trackingProtectionViewPresented视图被展示。原因在 ADR 中写得很清楚Action 应清晰表达发生了什么操作而非操作带来了什么后果——视图不应该知道后果后果由 reducer 计算、由状态承载。ModernAction正在进行的迁移值得补充的是仓库代码显示当前正处于一次内部迁移Action.swift 中新增了ModernAction协议并注明它是旧Action/ActionType的替代品未来旧的协议会被废弃/// Used to describe an action that can be dispatched by the redux store. /// ModernAction is the replacement for the old Action and ActionType protocols, which will be deprecated /// in the future. public protocol ModernAction: Sendable { var description: String { get } }ModernAction甚至用 Swift Mirror 反射自动生成枚举动作的可读描述Action.swift把关联值逐行打印成花括号包裹的调试文本方便日志与断点排查。三、Store主线程派发与串行队列ADR-0004 承诺Store 自动保证 Action 在主线程执行开发人员无需再自行加主线程检查。这在 Store.swift 中有三重印证Store类整体标注MainActorStore.swift两个dispatch重载内部都调用MainActor.assertIsolated(...)Store.swift以断言方式强制主线程派发派发入口使用actionQueueisProcessingActions实现串行处理每个 Action 必须完整走完 reducer 与 middleware 之后才处理下一个 ActionStore.swift从机制上消除并发派发导致的竞态。执行顺序先 Reducer 后 MiddlewareStore.swift 的executeAction展示了 Action 的处理流水线先用 reducer 基于当前state与 Action 计算出newState再把newState与 Action 交给所有 middleware 响应副作用在此触发最后state newState统一提交触发订阅者回调。注意一个细节注释明确说明每个活动屏幕的 reducer 都会被调用即便 Action 的 UUID 与该屏幕窗口不同reducer 也应自行比对 UUID 跳过处理而 middleware 每个 Action 只被调用一次与窗口数量无关。订阅与状态广播Store 通过subscribe/unsubscribe管理订阅者并支持transform选择器只订阅状态树中的某个子状态Store.swift。状态变更时只有真正发生改变才会通知订阅者——这一行为在 Subscription.swift 中由guard newState ! oldState else { return }保证前提是State遵循Equatable见下文 State 协议。四、Reducer 与 Middleware表现逻辑与业务逻辑的分工ADR-0004 反复强调的两条职责边界在仓库类型别名上直接可见。Reducer纯函数Reducer.swift 将 Reducer 定义为类型别名元组同时兼容旧Action与新ModernActionpublic typealias ReducerState (legacyReducer: LegacyReducerMethodState, modernReducer: ReducerMethodState) public typealias ReducerMethodState MainActor (State, ModernAction, WindowUUID) - State public typealias LegacyReducerMethodState MainActor (State, Action) - Statereducer 签名(State, Action) - State决定了它是纯函数给定相同的旧状态与 Action必然产出相同的新状态。ADR-0004 的正面收益中特别提到2-line switch 规则——每个 case 分支内只写两行一行调用明确的 reducer 方法如reduceStateForNavigationBrowserAction(action:state:)保证 reducer 简洁、状态流转可追踪。Middleware副作用集中地Middleware.swift 的注释直接呼应 ADR 决策consume Actions and are designed to perform logic with side effects (e.g. API calls, logging, or accessing storage)——API 调用、日志、存储访问等副作用都属于中间件职责。同时中间件可以向 Store 派发新的 ActionADR-0003 明确允许这一点例如一个网络请求中间件在收到.loadData后异步请求、再派发.dataLoaded。类型签名同样给出主线程与窗口上下文public typealias MiddlewareState ( legacyMiddleware: LegacyMiddlewareClosureState, modernMiddleware: MiddlewareClosureState ) public typealias MiddlewareClosureState MainActor (State, ModernAction, WindowUUID) - VoidADR-0004 的负面收益也直言中间件的风险as most dependencies and side effects move there, middleware can become bloated if not well-scoped大多数依赖与副作用都移入中间件后若不做良好拆分中间件会膨胀并强调 Feature Flag 等依赖应由中间件注入避免污染 State 与视图。State单一不可变状态树State.swift 定义了StateType协议要求每个状态类型遵循Sendable与Equatable并提供两个关键方法static func defaultState(from:)通过清空上一个状态的瞬态数据来生成默认状态所有带默认值的属性应恢复默认func resetTransientState() - Self调用defaultState(from:)重置瞬态字段、保留其余字段并应在.copy()链中第一个调用。协议扩展为所有遵循类型免费提供resetTransientState()默认实现避免每个状态类型手写一遍。结合 Reducer.swift 可以推断项目采用手写的copy宏/方法模式来创建新状态这与 adr/0011-redux-state-reducer-initializer-cleanup-with-copy-macro.md 后续的清理决策一脉相承。五、落地案例Redux 驱动的导航ADR-0005 与 BrowserViewControllerStateADR-0004 只是架构转折点紧随其后的 adr/0005-redux-and-navigation.md2024-10-17Accepted给出了 Redux 与导航集成的具体模式是 ADR-0004 决策在真实功能上的直接延续新增NavigationBrowserAction类型与NavigationDestination枚举抽象导航意图把导航处理从HomepageState迁出放入更全局的BrowserViewControllerStateBrowserViewController监听state.navigationDestination非空时调用handleNavigation(to:)。这条链路在源码中完整可查。状态定义位于 BrowserViewControllerState.swift结构体BrowserViewControllerState: ScreenState携带windowUUID第 49 行与navigationDestination: NavigationDestination?第 63 行modernReducer在处理到NavigationBrowserAction时调用专门的reduceStateForNavigationBrowserAction(action:state:)第 162-163、179 行reducer 的分支覆盖tapOnTrackingProtection、tapOnCell、tapOnLink、tapOnJumpBackInShowAllButton、tapOnBookmarksShowMoreButton等用户动作第 184-188 行正是 ADR-0004用用户动作命名 Action的实证。消费端在 BrowserViewController.swiftcase _ where state.navigationDestination ! nil: guard let destination state.navigationDestination else { return } handleNavigation(to: destination)第 2725-2727 行UI 层只响应状态不直接发起导航调用导航完成后还会派发NavigationBrowserActionType.navigationDestinationHandled第 2731-2733 行把瞬态清除配合handleNavigationActions(for:)第 2901 行统一处理动作正是导航以状态/意图形式表达而不是在 reducer 或视图逻辑中直接调用视图控制器这一 ADR-0005 模式的落地。此外BrowserViewControllerState还被 PresentedComponentsState.swift、ToolbarMiddleware.swift 等多个模块引用说明浏览器级全局状态已成为跨模块共享状态的中枢。ADR-0005 同样坦承负面代价BrowserViewControllerState could get bloated可能膨胀这也是社区对集中式状态树的普遍警惕点。六、测试与工程约束ADR-0004 明确要求State 与 Middleware 现在必须有测试以提升可靠性。仓库中 Redux 测试集中在 BrowserKit/Tests/ReduxTests10 个 Swift 文件覆盖 Action 派发、订阅、状态转换等核心行为firefox-ios/firefox-ios-tests/Tests 下另有大量针对具体 screen state 的 Redux 相关测试。对中间件测试ADR-0004 的负面收益也提醒中间件测试需要 mock 依赖与初始化设置在缺少工具类时可能冗长。整套约束可总结为一张清单方便新功能开发时对照检查Action用类承载载荷以用户动作/API 动作命名携带windowUUID主线程派发Store 已保证Reducer只做表现逻辑纯函数遵守 2-line switch 规则case 内调用显式 reducer 方法Middleware承接所有业务逻辑与副作用网络、存储、Feature Flag、日志需要时可回派 Action保持小而聚焦防止膨胀State遵循StateTypeSendableEquatable实现defaultState(from:)用.copy()链更新测试State 与 Middleware 必须有测试。七、收益与代价ADR-0004 的自我评估ADR-0004 在 Consequences 部分如实列举了正反两面这里整理为决策复盘便于团队在后续功能中对照验证。Positive正面收益统一架构所有新功能遵循 Redux消除 MVVM 混用移除 ViewModel清晰的逻辑分层中间件管业务逻辑与副作用reducer 只管表现逻辑更简单的 Action 模型类化 Action 简化载荷传递避免windowUUID等关联值的重复书写可读可维护2-line switch 规则让 reducer/中间件精简显式 reducer 调用让状态流转可追踪线程安全内建Store 保证主线程执行无需手动处理命名一致用户/API 风格的 Action 命名提升意图清晰度测试强制State 与 Middleware 必须配测试Feature Flag 集成干净由中间件处理标志与依赖不污染状态与视图。Negative负面代价样板代码增加类化 Action 与显式 reducer 调用对小功能是额外搭建成本迁移空窗老 MVVM 代码与新 Redux 区域共存过渡期出现混合复杂度中间件膨胀风险依赖与副作用集中后若不规范拆分容易失控Action 命名歧义用户/API 动作与中间件内部动作的取舍早期易混淆学习曲线开发者需适应单向数据流避免沿用 MVVM 思维测试开销中间件测试需 mock 依赖缺少工具类时较冗长。结语adr/0004-using-redux-to-replace-mvvm.md记录了 Firefox for iOS 一次影响深远的架构转向以业务逻辑进中间件、表现逻辑进 reducer、Store 统一主线程派发、Action 类化并面向用户动作命名为核心约束用单向数据流换取确定性、可测试性与可维护性。仓库中的 BrowserKit/Sources/Redux 完整实现了这套协议族BrowserViewControllerState.swift 与 BrowserViewController.swift 则展示了导航等真实场景下的落地模式。对于想在大型 iOS 工程中引入 Redux 的团队这份 ADR 连同其前后相邻的 ADR-0003试点、ADR-0005导航集成、ADR-0011copy 宏与 ADR-0012Action 规范、ADR-0013Reducer 最佳实践构成了一套完整可复制的演进路径与工程约束清单。【免费下载链接】firefox-iosFirefox for iOS项目地址: https://gitcode.com/GitHub_Trending/fi/firefox-ios创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价