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

92 阅读3分钟

在这里插入图片描述

概述

在 WWDC 24 中,苹果推出了数据库框架 SwiftData 2.0 版本。听说里面新增了能让数据记录“借尸还魂”的绝妙法器,到底是真是假呢?

在这里插入图片描述

我们在上篇博文中介绍了 History Trace 是如何稳妥的处理数据删除操作的。而在这里,我们将继续介绍 SwiftData 2.0 中另一个新特性:“墓碑”(Tombstone)。

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

  1. SwiftData 2.0 中的“墓碑”机制让已“死”的数据“借尸还魂”
  2. History Trace 机制的“美中不足”

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

Let‘s dive in!!!;)


10. SwiftData 2.0 中的“墓碑”机制让已“死”的数据“借尸还魂”

从 SwiftData 2.0 开始,苹果加入了新的“墓碑”(Tombstone)机制。它的作用很简单:就是让”有事烧纸“的数据“起死回骸”。

我们可以在托管类型中任意指定的属性上开启“墓碑”:

@Model
class Item {
    @Attribute(.preserveValueOnDeletion)
    var name: String
    var timestamp: Date
    
    init(name: String) {
        self.name = name
        timestamp = .now
    }
}

如上代码所示,我们在 Item 类型的 name 属性上通过 @Attribute(.preserveValueOnDeletion) 宏开启了 Tombstone 模式。一旦为托管类型开启“墓碑”模式,当该类型的实例从容器被删除后就会变为“死而不僵的尸体”,就问你们怕不怕?:)

在开启了“墓碑”模式之后,当记录被删除时我们可以通过历史记录追踪 Change 中 DefaultHistoryDelete 结构的 tombstone 属性来访问它:

在这里插入图片描述

为了更方便的从 DefaultHistoryDelete 内部访问已删除对象的“墓碑”信息,我们可以在 HistoryTombstone 的扩展中添加一个下标(subscript)访问方法,用它来访问该类型中任意属性的“墓碑”内容(如果存在的话):

extension HistoryTombstone {
    subscript<T>(keyPath: KeyPath<Model, T>) -> T {
        let mirror = Mirror(reflecting: self)
        guard
            let storageChild = mirror.children.first(where: { $0.label == "storage" }),
            let dictionary = storageChild.value as? [PartialKeyPath<Model> : Any],
            let value = dictionary[keyPath] as? T
        else {
            fatalError("找不到期待的“墓碑”键 \(keyPath)")
        }
        
        return value
    }
}

如此一来,我们即可“堂而皇之”的在 History Trace 中恣意访问已删除对象的“墓碑”信息啦,如下代码演示了如何为之:

switch change {
case .insert(let historyInsert):
    //...
case .update(let historyUpdate):
    //...
case .delete(let historyDelete):
    if let deleteInfo = historyDelete as? DefaultHistoryDelete<Item> {
        print("\(deleteInfo.tombstone[\Item.name] ?? "null") 已被删除!")
    }
@unknown default:
    fatalError()
}

运行效果如下所示:

在这里插入图片描述

11. History Trace 机制的“美中不足”

虽说 SwiftData 2.0 中的历史记录追踪让我们 App 对数据库的支持仿佛如虎添翼一般,但说实话从目前看来它还是有一些小“瑕疵”的。

首先,它无法提供容器内容(持久存储)改变的原生(Native)通知支持。如大家所见,目前我们还是需要通过 CoreData 暴露出来的 NSPersistentStoreRemoteChange 消息来捕获这些更改。

按照 Apple 的范儿,SwiftData 应该“知趣”的提供一种类似异步序列(AsyncSequence)的方式来让我们这些秃头码农们监听持久存储的改变:

for await changedNotification in modelContext.persistentStoreChanges {
    // 对数据库中的改变进行处理...
}

此外,SwiftData 底层的 CoreData 也好似“犹抱琵琶半遮面”一般,对于某些数据库中的高级操作也让我这些秃头码农们感觉“心有余而力不足”。

当然,上面只是我的一些遐想而已。不知苹果是否会在 SwiftData 3.0 中加入这些原生支持呢?让我们拭目以待吧。

总结

在本篇博文中,我们讨论了 SwiftData 2.0 中新的“墓碑”(Tombstone)机制,在最后我们还顺面聊了聊 History Trace 机制的些许“美中不足”。

本系列博文至此就要告一段落啦!但 SwiftData 的故事还没有完,我们会在后续创作更多的优质的博文,敬请期待吧!

感谢观赏,再会啦!8-)