应用装配与依赖图
意图
展示如何装配应用外壳(TabView + NavigationStack + 弹窗),并在一个地方安装全局依赖图(环境对象、服务、流式客户端、SwiftData ModelContainer)。
推荐结构
- 根视图设置标签页、各标签页的路由器和弹窗。
- 一个专门的视图修饰器安装全局依赖和生命周期任务(认证状态、流式监听器、推送令牌、数据容器)。
- 功能视图只从环境中获取它们需要的内容;功能特有的状态保持局部化。
依赖选择
- 对应用级服务、共享客户端、主题/配置,以及许多后代确实需要的值,使用
@Environment。 - 对功能局部的依赖和模型,优先使用初始化器注入。不要仅仅为了避免传一两个参数就把依赖放进环境。
- 保持可变的特性状态不出现在环境中,除非它是有意跨应用大部分区域共享的。
- 仅在作为旧代码回退方案,或项目已经针对真正共享的对象标准化使用它时,才使用
@EnvironmentObject。
根外壳示例(通用)
@MainActorstructAppView:View{@StateprivatevarselectedTab:AppTab=.home@StateprivatevartabRouter=TabRouter()varbody:someView{TabView(selection:$selectedTab){ForEach(AppTab.allCases){tabinletrouter=tabRouter.router(for:tab)NavigationStack(path:tabRouter.binding(for:tab)){tab.makeContentView()}.withSheetDestinations(sheet:Binding(get:{router.presentedSheet},set:{router.presentedSheet=$0})).environment(router).tabItem{tab.label}.tag(tab)}}.withAppDependencyGraph()}}最小AppTab示例:
@MainActorenumAppTab:Identifiable,Hashable,CaseIterable{casehome,notifications,settingsvarid:String{String(describing:self)}@ViewBuilderfuncmakeContentView()->someView{switchself{case.home:HomeView()case.notifications:NotificationsView()case.settings:SettingsView()}}@ViewBuildervarlabel:someView{switchself{case.home:Label("Home",systemImage:"house")case.notifications:Label("Notifications",systemImage:"bell")case.settings:Label("Settings",systemImage:"gear")}}}路由骨架:
@MainActor@ObservablefinalclassRouterPath{varpath:[Route]=[]varpresentedSheet:SheetDestination?}enumRoute:Hashable{casedetail(id:String)}依赖图修饰器(通用)
使用一个修饰器来安装环境对象,并在活动账户/客户端变化时处理生命周期钩子。这样能保持装配一致,避免在调用点忘记某个依赖。
extensionView{funcwithAppDependencyGraph(accountManager:AccountManager=.shared,currentAccount:CurrentAccount=.shared,currentInstance:CurrentInstance=.shared,userPreferences:UserPreferences=.shared,theme:Theme=.shared,watcher:StreamWatcher=.shared,pushNotifications:PushNotificationsService=.shared,intentService:AppIntentService=.shared,quickLook:QuickLook=.shared,toastCenter:ToastCenter=.shared,namespace:Namespace.ID?=nil,isSupporter:Bool=false)->someView{environment(accountManager).environment(accountManager.currentClient).environment(quickLook).environment(currentAccount).environment(currentInstance).environment(userPreferences).environment(theme).environment(watcher).environment(pushNotifications).environment(intentService).environment(toastCenter).environment(\.isSupporter,isSupporter).task(id:accountManager.currentClient.id){letclient=accountManager.currentClientifletnamespace{quickLook.namespace=namespace}currentAccount.setClient(client:client)currentInstance.setClient(client:client)userPreferences.setClient(client:client)awaitcurrentInstance.fetchCurrentInstance()watcher.setClient(client:client,instanceStreamingURL:currentInstance.instance?.streamingURL)ifclient.isAuth{watcher.watch(streams:[.user,.direct])}else{watcher.stopWatching()}}.task(id:accountManager.pushAccounts.map(\.token)){pushNotifications.tokens=accountManager.pushAccounts.map(\.token)}}}备注:
.task(id:)钩子响应账户/客户端变化,重新初始化服务和监听器状态。- 保持该修饰器专注于全局装配;功能特有状态保持在功能内部。
- 将类型(AccountManager、StreamWatcher 等)调整为与你项目匹配。
SwiftData / ModelContainer
在根处安装你的ModelContainer,使所有功能视图共享同一个 store。将模型列表保持最小化,只包含需要持久化的模型。
extensionView{funcwithModelContainer()->someView{modelContainer(for:[Draft.self,LocalTimeline.self,TagGroup.self])}}原因:单一容器避免了每个弹窗或标签页重复创建 store,并保持数据一致。
弹窗路由(枚举驱动)
用一个小的枚举和辅助修饰器集中管理弹窗。
enumSheetDestination:Identifiable{casecomposercasesettingsvarid:String{String(describing:self)}}extensionView{funcwithSheetDestinations(sheet:Binding<SheetDestination?>)->someView{sheet(item:sheet){destinationinswitchdestination{case.composer:ComposerView().withEnvironments()case.settings:SettingsView().withEnvironments()}}}}原因:枚举驱动的弹窗使展示集中化且可测试;新增一个弹窗只需增加一个枚举 case 和一个 switch 分支。
何时使用
- 拥有多个共享环境对象和服务的包/模块的应用。
- 需要响应账户/客户端变化并安全地重接线流式/推送的应用。
- 任何希望在不重复环境设置的情况下,保持一致 TabView + NavigationStack + 弹窗装配的应用。
注意事项
- 保持依赖修饰器精简;不要把功能状态或重量级逻辑放进去。
- 确保
.task(id:)的工作是轻量的或被适当取消;长时间运行的工作应放在服务中。 - 如果存在未认证客户端,对流式/监听调用进行门控,以避免重连风暴。