news 2026/8/13 9:03:40

WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WWDC 26 全新 ResultsObserver:终于能在 View 外面盯着 SwiftData 了

引子

写 SwiftData 久了,大概都会碰到同一个别扭的地方。

@Query很好用——挂在 View 上,列表自动刷新,过滤排序也都顺手。可一旦逻辑稍微复杂一点,比如要根据所有行程算地图镜头范围,或者要在一个@Observable的 Store 里汇总预算,你就会发现:@Query跟不过去了。它只能待在 SwiftUI 视图里。

以前怎么办?自己听ModelContext通知,或者干脆把聚合计算塞进 View。能跑,

但总觉得哪里不对:业务明明不该长在界面上。

WWDC26 的 SwiftData 专场里,Apple 给这个问题塞了个答案,还盖了个绿色的 NEW 标签:ResultsObserver

让我们一起来了解一下吧?😉


它到底解决什么问题

官方说法很直白:在某个ModelContext里,按你给定的条件拉一批模型,然后持续盯着这批结果;数据变了,结果跟着变。

听起来像@Query?对,能力上确实很像——过滤、排序、分区都支持。关键差别只有一个:

@Query是 View 的属性;ResultsObserver是你自己拿着的对象。

ViewModel、后台协调器,甚至 SceneKit 那种根本不沾 SwiftUI 的代码,都能用。

Apple 自己举的例子就是:行程变了,地图相机边界要重算——这件事显然更适合放在一个 Controller 里,而不是某个Listbody里。

类型大概长这样:

finalclassResultsObserver<Element,SectionName>whereElement:PersistentModel,SectionName:Hashable

不需要分区的时候,第二个类型参数传Never就行,意思就是「别分组,给我平铺的结果」:

letobserver=tryResultsObserver<Trip,Never>(modelContext:modelContext)

observer.results,就是当前命中的那批模型。


怎么用:一个控制器 + 一条观察线

WWDC 里的写法大致如下。注意两件事:一是withContinuousObservation,二是返回的那个token

importSwiftDataimportObservationimportMapKit@Observable@MainActorfinalclassMapCameraController{privateletresultsObserver:ResultsObserver<Trip,Never>varbounds:MapCameraBounds?@ObservationIgnoredprivatevartoken:ObservationTracking.Token?init(modelContext:ModelContext)throws{resultsObserver=tryResultsObserver<Trip,Never>(modelContext:modelContext)token=withContinuousObservation(options:[.didSet]){[weakself]_inguardletselfelse{return}bounds=calculateBounds(trips:resultsObserver.results)}}privatefunccalculateBounds(trips:[Trip])->MapCameraBounds?{// 根据 trips 算镜头范围nil}}

流程其实不玄:

先建一个ResultsObserver盯住 store;再用withContinuousObservation(options: [.didSet])注册回调,结果集变了就重算;最后把返回的ObservationTracking.Token存起来。

Token 这东西容易被忽略。它相当于「这条监听还活着」的凭证——属性没了,观察也就停了。所以别建完就扔,挂在实例上。

还有个细节:闭包里一定要读到resultsObserver.results。Swift Observation 靠「你访问了什么」决定「下次通知谁」。你不读results,它就不知道你在乎这个集合。


再挖一点:withContinuousObservation在干什么

这不是 SwiftData 私有的魔法,是 Observation 框架里偏「长期监听」的那一挂。

以前的withObservationTracking更像单次跟踪:属性要变时喊一声。withContinuousObservation则是持续回调,所以才需要外部持有 Token;不再持有,或者主动cancel,监听就结束。

[.didSet]的意思是:值已经写完再通知你。适合做汇总、重算边界这类「根据最新快照更新派生状态」的事。如果在@MainActor上注册,回调也会回到主线程,写 UI 状态会省心不少。

可以简单记:Observer 负责数据源,Continuous Observation 负责把变化递给你,Token 负责线路不断。


更贴近日常的例子:预算汇总

地图镜头有点「演示味」。换个更常见的:预算 App 要显示总额度、已花、剩余,还有哪些超支了。这些数字依赖一整批Budget,既不适合塞进单个模型,也不适合永远写在 View 里——尤其你还想单测的时候。

@ObservablefinalclassBudgetSummaryStore{privateletobserver:ResultsObserver<Budget,Never>@ObservationIgnoredprivatevartoken:ObservationTracking.Token?vartotalBudget:Double=0vartotalSpent:Double=0varremaining:Double=0varoverspentBudgets:[Budget]=[]init(modelContext:ModelContext)throws{observer=tryResultsObserver<Budget,Never>(modelContext:modelContext)token=withContinuousObservation(options:[.didSet]){[weakself]_inself?.updateSummary()}}privatefuncupdateSummary(){letbudgets=observer.results totalBudget=budgets.reduce(0){$0+$1.limit}totalSpent=budgets.reduce(0){sum,budgetinsum+spentAmount(for:budget)}remaining=totalBudget-totalSpent overspentBudgets=budgets.filter{spentAmount(for:$0)>$0.limit}}privatefuncspentAmount(forbudget:Budget)->Double{budget.expenses.reduce(0){$0+($1.amount*Double($1.quantity))}}}

View 只负责展示:

structBudgetDashboardView:View{@Environment(BudgetSummaryStore.self)privatevarstorevarbody:someView{VStack(alignment:.leading,spacing:8){Text("总额度:\(store.totalBudget,format:.currency(code:"CNY"))")Text("已花费:\(store.totalSpent,format:.currency(code:"CNY"))")Text("剩余:\(store.remaining,format:.currency(code:"CNY"))")}}}

单测也好办:内存版ModelContainer,插入几条数据,直接断言 Store 里的数字。不必为了测业务逻辑去拉起一整棵 SwiftUI。


过滤、排序也行,不是只能全表盯着

需要条件时,塞FetchDescriptor即可:

letdescriptor=FetchDescriptor<Trip>(predicate:#Predicate{$0.endDate>Date.now},sortBy:[SortDescriptor(\.startDate)])letupcoming=tryResultsObserver<Trip,Never>(fetchDescriptor:descriptor,modelContext:modelContext)

要按字段分区,用带sectionBy的初始化,并给SectionName传具体类型。之后可以拿sections,也有element(at:)indexPath(for:)这类接口,和现在强化过的 sectioned@Query是同一套思路。

有个现实限制:目前分区的 key path 更偏向字符串一类场景。要是你的分区键更复杂,可能得拆多个 Observer,或者给 Apple 提 Feedback。


别跟 HistoryObserver 搞混

同一场 WWDC 还带了个HistoryObserver。名字都带 Observer,用途却不一样。

ResultsObserver关心的是:现在这批结果长什么样。适合汇总、派生状态、驱动非 SwiftUI 的展示。

HistoryObserver关心的是:发生了哪些变更事务。它暴露一个会递增的eventCounter,适合你跟着去fetchHistory,做同步、审计、推后端。

一个看快照,一个看脚印。地图跟着行程动,用 Results;本地改动要推到自建服务器,用 History。


什么时候该用,什么时候别用

说白了就三条:

界面上就是展示列表、详情——继续@Query,别为了新而新。

逻辑依赖一批模型、又不该长在 View 上,或者根本不在 SwiftUI 里——上ResultsObserver

你要的是变更流水,而不是当前集合——看HistoryObserver

另外,社区里有个提醒我觉得挺对:别因为有了 ResultsObserver,就把所有逻辑都搬进@Observable类。展示逻辑可以留在 View,单模型规则留在 Model;只有那些「跨一批模型、又不属于界面」的东西,才值得单独拎出来。


收个尾

SwiftData 早期最让人难受的地方之一,就是观察能力几乎绑死在 SwiftUI 上。业务要么挤进 View,要么自己接通知。

ResultsObserver没发明全新范式,只是把大家早就想要的能力做成了框架原生 API:查询结果变成可持有的 Observable 对象,过滤排序分区还在,观察走的是 Swift Observation。

对写列表的人来说,可能感知不强;对开始拆 Store、写单测、做非 SwiftUI 消费端的人来说,这缺口终于补上了。

参考:WWDC26 Session 274、ResultsObserver 文档。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/13 9:02:36

BBC Alphablocks自然拼读动画:3-8岁儿童英语启蒙系统学习指南

1. 先搞清楚 Alphablocks 到底是什么&#xff0c;以及它为什么值得看 如果你正在寻找一套能让孩子主动开口、系统学习自然拼读的动画资源&#xff0c;那么《Alphablocks》是一个绕不开的名字。它不是一部普通的娱乐动画&#xff0c;而是一套由英国BBC出品的、专门为英语启蒙阶段…

作者头像 李华
网站建设 2026/8/13 9:00:36

【AI Agent实战】构建可信 AI Agent:从系统消息框架到安全防护的完整指南——基于 Microsoft Agent Framework 的生产级安全实践

文章目录 一、为什么可信度是 AI Agent 的生命线? 1.1 从"能用"到"值得信赖"的跨越 1.2 本课学习目标 二、安全基础:构建系统消息框架(System Message Framework) 2.1 为什么系统提示词对 Agent 至关重要? 2.2 系统消息框架的四步法 Step 1:创建 Met…

作者头像 李华
网站建设 2026/8/13 8:56:08

从零掌握Win32 GUI编程:用C++和windows.h打造原生Windows应用

1. 项目概述&#xff1a;为什么选择Windows.h做GUI&#xff1f;如果你在Windows平台上用C&#xff0c;想写个带窗口、按钮的小工具&#xff0c;但又不想引入MFC、Qt、WinForms这些动不动几百兆的第三方库&#xff0c;那么windows.h就是你绕不开的起点。很多人一听到“原生Win32…

作者头像 李华
网站建设 2026/8/13 8:53:14

单向阀被高压冲击打坏,振动信号里其实早有征兆

ZPM泵的单向阀最怕高压脉动冲击&#xff0c;可它出故障前&#xff0c;振动信号早打了"暗号"。这套LabVIEW振动采集与诊断方案&#xff0c;教你提前读懂它。预计阅读约 3 分钟01 最坏的往往不是大件&#xff0c;而是那个小阀片ZPM泵停机检修&#xff0c;十次里有八次&…

作者头像 李华
网站建设 2026/8/13 8:52:46

Angry IP Scanner终极指南:3小时从入门到精通网络扫描配置

Angry IP Scanner终极指南&#xff1a;3小时从入门到精通网络扫描配置 【免费下载链接】ipscan Angry IP Scanner - fast and friendly network scanner 项目地址: https://gitcode.com/gh_mirrors/ip/ipscan Angry IP Scanner是一款功能强大且用户友好的开源网络扫描工…

作者头像 李华