SwiftUI 中的全局 AppState 架构

发布: (2025年12月18日 GMT+8 05:24)
5 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的正文内容,我将为您翻译成简体中文并保留原始的 Markdown 格式和技术术语。

介绍

随着 SwiftUI 应用的增长,一个常见的问题随之而来:这个状态到底存放在哪里?
典型的全局关注点包括:

  • 认证状态
  • 选中的标签页
  • 深度链接
  • 引导流程
  • 全局错误
  • 应用生命周期事件
  • 功能协同

将这些状态散落在各个视图和视图模型中会导致导航不可预测、逻辑重复、难以调试的 bug,以及状态在错误的时机被重置。

解决方案是 Global AppState ——一种清晰、可扩展的架构,避免将应用变成巨大的上帝对象。

什么应放在 AppState 中

AppState 表示全局、跨功能的状态,具备以下特性:

  • 在单个页面销毁后仍然存在
  • 协调多个功能
  • 响应生命周期变化
  • 驱动导航
  • 在视图重新创建后仍然保持

典型示例:

  • 用户会话 / 认证状态
  • 已选标签页
  • 全局导航路由
  • 深度链接处理
  • 应用阶段(前台/后台)
  • 全局加载 / 同步标志
  • 全局错误展示

经验法则

  • 如果多个功能都需要它 → AppState
  • 如果只有单个页面需要它 → ViewModel

什么不应该放在 AppState 中

  • 文本字段的值
  • 滚动位置
  • 本地动画
  • 特定功能的数据列表
  • 临时 UI 切换

使用 @Observable 定义 AppState

@Observable
class AppState {
    var session: UserSession?
    var selectedTab: Tab = .home
    var route: AppRoute?
    var isSyncing = false
    var pendingDeepLink: DeepLink?
}

该对象在应用的整个生命周期内存在,仅注入一次,并驱动高层行为。

注入 AppState

@main
struct MyApp: App {
    @State private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environment(appState)
        }
    }
}

在视图中访问 AppState

@Environment(AppState.self) var appState

不需要单例。

通过 AppState 进行导航

enum AppRoute: Hashable {
    case login
    case home
    case profile(id: String)
}

在根视图中:

.navigationDestination(for: AppRoute.self) { route in
    switch route {
    case .login:
        LoginView()
    case .home:
        HomeView()
    case .profile(let id):
        ProfileView(userID: id)
    }
}

通过更新状态触发导航:

appState.route = .profile(id: user.id)

优势

  • 可预测的
  • 可测试的
  • 支持深度链接

集中式深链接处理

func handle(_ link: DeepLink) {
    switch link {
    case .profile(let id):
        route = .profile(id: id)
    case .home:
        selectedTab = .home
    }
}

视图不再解析 URL;AppState 将外部意图转换为内部状态。

响应生命周期变化

@Environment(\.scenePhase) var phase

.onChange(of: phase) { newPhase in
    switch newPhase {
    case .background:
        saveState()
    case .active:
        refreshIfNeeded()
    default:
        break
    }
}

这将生命周期逻辑从视图和视图模型中分离出来。

避免让 AppState 过载

错误示例

class AppState {
    var feedPosts: [Post]
    var profileViewModel: ProfileViewModel
    var searchText: String
}

正确做法

  • AppState 协调。
  • 特定功能的视图模型拥有自己的数据。
  • 服务处理副作用。

使用子状态扩展 AppState

@Observable
class AppState {
    var sessionState = SessionState()
    var navigationState = NavigationState()
    var syncState = SyncState()
}

每个子状态都有明确的职责和有限的表面区域,在大型应用中能够优雅地扩展。

测试 AppState

func test_navigation_changes_route() {
    let appState = AppState()
    appState.route = .login
    XCTAssertEqual(appState.route, .login)
}
func test_profile_deep_link() {
    let appState = AppState()
    appState.handle(.profile(id: "123"))
    XCTAssertEqual(appState.route, .profile(id: "123"))
}

不涉及 UI —— 测试专注于纯数据。

状态流

用户操作

视图模型

应用状态(如果是全局的)

视图响应

设计良好的全局 AppState 的好处

  • 简化导航
  • 稳定生命周期行为
  • 使深层链接变得简单
  • 消除全局 hack
  • 提升可测试性
  • 在团队之间可扩展

在适度使用的情况下,全局 AppState 是 SwiftUI 中最强大的架构工具之一。

Back to Blog

相关文章

阅读更多 »

SwiftUI 中的模块化特性架构

🧩 1. 什么是 Feature Module?Feature Module 是一个自包含的单元,代表应用中的一个功能块:Home、Profile、Settings、Feed、Auth 等。

团队管理现已移至设置

新功能 - “Teams”不再位于左侧导航菜单中。 - 所有团队管理已迁移到新的 Settings → Teams 页面。 - 您会看到一个可关闭的…

个人资料

当前状态 🟢 开放实习和入门级机会 使命:在 52 周内构建 30 款 App,成为世界级 Android Engineer。 我充满热情……