由一个 SwiftData “诡异”运行时崩溃而引发的钩深索隐(三)

概述

从 WWDC 23 开始,苹果推出了崭新的数据库框架 SwiftData。而在今年的 WWDC 24 中苹果再接再厉,为 SwiftData 2.0 加入了全新的历史记录追踪(History Trace)、"墓碑(Tombstone)"等诸多激动人心的新功能。

那么,究竟什么是 History Trace?我们又该如何驾驭它呢?

在本篇博文中,您将学到如下内容:

  1. SwiftData 2.0 中全新的 History Trace 机制如何改变游戏规则?
  2. 如何实时监听 SwiftData 持久存储中数据的变化?

这是本系列第三篇博文。闲言少叙,让我们马上开始 SwiftData 精彩的探究之旅吧!

Let's dive in!!!;)


5. SwiftData 2.0 中全新的 History Trace 机制如何改变游戏规则?

在今年的 WWDC 24 中,苹果推出了 SwiftData 2.0。新版本 SwiftData 在增强原有功能的基础之上还添加了一系列全新的特性,其中就包括 History Trace

历史记录追踪(History Trace)机制专门用来查询 SwiftData 模型数据更改的历史记录,主要来说它有以下几种用途:

  • 了解数据存储何时发生了更改?发生了什么更改?即使记录从数据库中被彻底删除之后仍然可以获取其部分信息("墓碑"机制);
  • 了解如何使用该信息构建远程服务器同步;
  • 处理进程外的更改事件;

History Trace "雪中送炭"的一个用处是同步 App 和 Widgets 间的数据变化,这在 SwiftData 2.0 之前我们需要用比较复杂的手段才可以完成,而用历史跟踪则会非常简单。

那么有的小伙伴可能要问了:History Trace 对于 SwiftData 后台数据更改与界面之间的同步又有什么用呢?

其实,History Trace 的核心是监听持久存储上数据的改变,而我们 App 中不同模型上下文都对应着同一个底层存储,这意味着:我们可以在主上下文中利用 History Trace 捕捉到其它私有上下文对底层数据修改的"蛛丝马迹"!


注意:History Trace 只存在于 SwiftData 2.0 以后的框架中,这意味只有在不低于 iOS 18+、watchOS 11+ 以及 macOS 15+ 等这些系统上才可以使用它。


而且更妙的是,我们不再需要"囫囵吞枣"似得刷新整个视图,只需刷新变化对象所对应的那个视图即可。

不过,首先我们必须先能够知道 SwiftData 底层数据究竟在何时发生了改变。

6. 如何实时监听 SwiftData 持久存储中数据的变化?

众所周知,SwiftData 是建立在 CoreData 基础之上的。这意味着在运行时它必然隐藏了 CoreData "活着的灵魂"。

在 CoreData 中我们可以通过 NSPersistentStoreRemoteChange 消息来监听本地存储是否发生改变,这同样也可以适用于 SwiftData。

所不同的是:现在我们无法细粒度捕获某个特定 NSPersistentStoreCoordinator 上的变化,只能全盘"照单全收"。不过,这并不影响大局。

我们只需在之前 ContentView 视图的代码上"略施小计",即可捕获后台线程中私有上下文里数据的更改:

swift 复制代码
struct ContentView: View {
    
    @Environment(\.modelContext) var modelContext
    @Query var items: [Item]
    @State var refreshID = false
        
    var body: some View {
        NavigationStack {
            VStack {
                List(items) { item in
                    Text(item.name).font(.headline.weight(.heavy))
                }
                .id(refreshID)
            }
            .toolbar {
                ToolbarItem(placement:.topBarTrailing) {
                    Button("New", systemImage: "plus.app") {
                        Task.detached {
                            let modelContext = ModelContext(.preview)
                            
                            let item = Item(name: "\(Int.random(in: 0...10000))")
                            modelContext.insert(item)
                            
                            try! modelContext.save()
                            
                            await MainActor.run {
                                refreshID.toggle()
                            }
                        }
                    }
                    .foregroundStyle(.white)
                    .tint(.green)
                }
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange).receive(on: DispatchQueue.main)) { _ in
            print("DB changed!")
        }
    }
}

从上面的代码能够看到,我们在视图里使用 onReceive 修改器方法在主线程中稳妥的监听了 NSPersistentStoreRemoteChange 消息。

在 Xcode 预览中运行看一下效果:

如您所愿,现在我们已经可以顺利捕获到 SwiftData 本地持久存储上的变化啦!

不过,目前我们仍然是通过强制刷新整个视图来触发后台私有上下文新增数据显示的。

别急,利用上面介绍的 History Trace 机制,我们在下一篇博文将尝试来重构它。

总结

在本篇博文中,我们介绍了 SwiftData 2.0 中新引入的历史记录追踪(History Trace)机制;我们还讨论了如何在 SwiftUI 中实时监听本地持久存储中数据的变化。

在下一篇博文中,我们将会把 History Trace 应用在实际的代码中以解决后台线程与 App 界面间数据同步的问题,敬请期待吧!

感谢观赏,再会!8-)

相关推荐
文牧之6 分钟前
PostgreSQL 的扩展pg_freespacemap
运维·数据库·postgresql
deriva23 分钟前
某水表量每15分钟一报,然后某天示数清0了,重新报示值了 ,如何写sql 计算每日水量
数据库·sql
Leo.yuan2 小时前
数据库同步是什么意思?数据库架构有哪些?
大数据·数据库·oracle·数据分析·数据库架构
Kookoos2 小时前
ABP VNext 与 Neo4j:构建基于图数据库的高效关系查询
数据库·c#·.net·neo4j·abp vnext
云之兕2 小时前
MyBatis 的动态 SQL
数据库·sql·mybatis
gaoliheng0062 小时前
Redis看门狗机制
java·数据库·redis
?ccc?2 小时前
MySQL主从复制与读写分离
数据库·mysql
会飞的Anthony3 小时前
数据库优化实战分享:高频场景下的性能调优技巧与案例解析
数据库
MaoJiu3 小时前
Flutter造轮子系列:flutter_permission_kit
flutter·swiftui