2026/10/10 6:00:48

swiftui-ui-patterns - app-wiring

swiftui-ui-patterns - app-wiring 应用装配与依赖图意图展示如何装配应用外壳TabView NavigationStack 弹窗并在一个地方安装全局依赖图环境对象、服务、流式客户端、SwiftData ModelContainer。推荐结构根视图设置标签页、各标签页的路由器和弹窗。一个专门的视图修饰器安装全局依赖和生命周期任务认证状态、流式监听器、推送令牌、数据容器。功能视图只从环境中获取它们需要的内容功能特有的状态保持局部化。依赖选择对应用级服务、共享客户端、主题/配置以及许多后代确实需要的值使用Environment。对功能局部的依赖和模型优先使用初始化器注入。不要仅仅为了避免传一两个参数就把依赖放进环境。保持可变的特性状态不出现在环境中除非它是有意跨应用大部分区域共享的。仅在作为旧代码回退方案或项目已经针对真正共享的对象标准化使用它时才使用EnvironmentObject。根外壳示例通用MainActorstructAppView:View{StateprivatevarselectedTab:AppTab.homeStateprivatevartabRouterTabRouter()varbody:someView{TabView(selection:$selectedTab){ForEach(AppTab.allCases){tabinletroutertabRouter.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)}}}路由骨架MainActorObservablefinalclassRouterPath{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:Boolfalse)-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){letclientaccountManager.currentClientifletnamespace{quickLook.namespacenamespace}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.tokensaccountManager.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:BindingSheetDestination?)-someView{sheet(item:sheet){destinationinswitchdestination{case.composer:ComposerView().withEnvironments()case.settings:SettingsView().withEnvironments()}}}}原因枚举驱动的弹窗使展示集中化且可测试新增一个弹窗只需增加一个枚举 case 和一个 switch 分支。何时使用拥有多个共享环境对象和服务的包/模块的应用。需要响应账户/客户端变化并安全地重接线流式/推送的应用。任何希望在不重复环境设置的情况下保持一致 TabView NavigationStack 弹窗装配的应用。注意事项保持依赖修饰器精简不要把功能状态或重量级逻辑放进去。确保.task(id:)的工作是轻量的或被适当取消长时间运行的工作应放在服务中。如果存在未认证客户端对流式/监听调用进行门控以避免重连风暴。