概览
我们已经知道,用 CoreData 在背后默默支持的 SwiftUI 视图在使用 @FetchRequest 来查询托管对象集合时,若查询结果中的托管对象在别处被改变将不会在 FetchedResults 中得到及时的刷新。
那么这一“囧境”在 SwiftData 里是否也会“卷土重来”呢?空说无益,就让我们在这里来一场钩深索隐、推本溯源的探究之旅吧。
在本篇博文中,您将学到如下内容:
- 概览
- 1. CoreData 托管对象多个实例的同步问题
- 2. SwiftData 是否会重蹈覆辙?
- 3. SwiftData 超简洁的解决方案
- 总结
相信学完本课后,小伙伴们一定会惊叹在 SwiftData 模型对象多个实例间的同步竟如此之简单,简直不可思议!
无需等待,让我们马上开始同步大冒险吧!Let’s go!!!😉
1. CoreData 托管对象多个实例的同步问题
我们知道为了和 SwiftUI “亲密无间”,何曾几时(iOS 13.0+) CoreData 的托管类 NSManagedObject 也悄然遵守了 ObservableObject 协议。
从那一刻起,CoreData 托管对象便可以乖巧的作为 SwiftUI 视图中的状态“乐此不疲”。
不过,在 SwiftUI 视图 @FetchRequest 查询结果 FetchedResults 中的托管对象若在外部被修改,则该查询结果并不会自动进行同步:
@FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Cave.name, ascending: false)], predicate: NSPredicate(format: "challenge.stateValue = \(ChallengeState.inProgress.rawValue)"), animation: .bouncy) var inProcessingCaves: FetchedResults<Cave>
拿上面的 inProcessingCaves 状态来说,它包括了所有正在“进行中”的 Cave 托管对象(用 NSPredicate 来过滤数据),这些对象都会显示在主视图顶部“正在进行”的 Section 里:
如果我们在子视图里将 inProcessingCaves 中的任何对象状态由“进行中”改成了“已失败”,那么它们理应从“正在进行”的 Section 中“销声匿迹”,但实际情况却事与愿违:
如上图所示:红色的“已失败”Cave 托管对象仍在“厚颜无耻”的占据着“正在进行” Section 中的宝贵空间。
关于上面 CoreData 中 @FetchRequest 托管对象的过滤结果不能被及时刷新的解决之道,我们将会在后续博文中详述,小伙伴们敬请期待吧!
那么这种情况在最新的 SwiftData 中还会存在吗?让我们探寻一番吧。
2. SwiftData 是否会重蹈覆辙?
SwiftData 是苹果在 WWDC 23 推出的完全符合 Swift 范儿的数据库框架,其描述性的语法非常适合托管表本身、表字段以及表间关系的构建。
更多 SwiftData 相关内容的介绍,请小伙伴们尽情观赏如下链接中的精彩内容:
- iOS 18 中全新 SwiftData 重装升级,其中一个功能保证你们“爱不释手”
- SwiftData(iOS 17+)如何在数据新建和更新中途出错时恢复如初?
- 『第十二章』数据持久化:SwiftData
为了模拟 CoreData 中的数据结构,我们分别创建了两个 SwiftData 数据模型:Item 和 SubItem,其中每个 Item 都至多包含一个 SubItem。
import SwiftData
@Model
final class Item {
@Attribute(.unique) var id: UUID
var timestamp: Date
var name: String
@Relationship
var subItem: SubItem?
init(timestamp: Date, name: String) {
self.timestamp = timestamp
self.name = name
id = UUID()
}
static var sampleItems: [Item] = {
var items = [Item]()
let names = ["Apple", "Jujube", "Watermelon"]
for name in names {
let new = Item(timestamp: Date.now, name: name)
let newSub = SubItem(timestamp: Date.now, name: "Sub \(name)", state: .unstarted)