概述
用 SwiftUI + CoreData 这对“双剑合璧”的强力开发组合,我们可以事倍功半、非常 easy 的开发出界面元素丰富且背后拥有持久数据库支持的 App。
不过,在某些情况下它们被误用或错用也可能带来一些“藏形匿影”的顽疾。
在本篇博文中,您将学到如下内容:
- 概述
- 4. “神兵天降”:自定义托管对象上下文(Custom NSManagedObjectContext)
- 5. “诡异”消失的上下文!
- 总结
相信学完本课后,大家一定会对自定义托管对象上下文(NSManagedObjectContext)的理解更加纯熟和精进!
那还等什么呢?Let‘s go!!!😉
4. “神兵天降”:自定义托管对象上下文(Custom NSManagedObjectContext)
熟悉 CoreData 框架组织架构的小伙伴们都知道,要想恣意操作内存中的托管对象,我们必须借助于托管对象上下文(NSManagedObjectContext)来达成。
在一般情况下,CoreData 运行时(Runtime)会为我创建默认的托管对象上下文。在大多数场景下用它就足够了。不过,在某些情况下我们可以创建自己的托管对象上下文,比如后台数据操作、撤销数据的保存或修改以及对持久存储的细粒度控制等。
在这里,利用特殊定制的 NSManagedObjectContext 对象恰巧可以帮我们“排忧解难”。
我们的思路很简单:以实际工作的托管对象上下文为父对象(Root),创建一个新的托管对象上下文(Custom)。这样的话,所有插入 Custom 上下文的 Worry 托管对象都必须再用 Root 上下文保存才可以真正存储到内存和数据库中。
struct NewWorryView: View {
@ObservedObject private var newWorry: Worry
init() {
let context = Common.moc_auto
let tmpContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
tmpContext.parent = context
// 将新建的 Worry 对象插入到自定义托管对象上下文(tmpContext)而不是父上下文(context)中
newWorry = Worry.new(tmpContext)
}
}
对于实际的保存操作,我们需要“双重” Save:
Button("保存") {
guard let tmpContext = newWorry.managedObjectContext, let parentContext = tmpContext.parent else { return }
guard verify() else { return }
// “双重”保存
try! tmpContext.save()
try! parentContext.save()
dismissor()
}
如此这般,我们就成功避免了“空白” Worry 对象的产生:
虽然我们现在搞定了“空白”托管对象“野蛮繁殖”的问题,不过还有一个隐藏的 bug 急需解决。
更多关于 CoreData 开发相关知识的介绍,请小伙伴们移步如下链接观赏精彩的博文:
- 『第十一章』数据持久化:CoreData 与 CloudKit
- 漫谈初学者处理 CoreData 数据之启示录
- 探究 CoreData 使用索引(Index)机制加速查表究竟如何实现?
- iOS 16 中 CoreData 托管对象发生变化但其衍生 (Derived) 属性在 SwiftUI 中不刷新的解决
- 何时Xcode中CoreData托管对象Optional和Default Value选项会变得尤为敏感?
5. “诡异”消失的上下文!
上面,我们圆满解决了“空白” Worry 对象的问题,不过通过实际运行可以发现,当我们试图保存新建的 Worry 托管对象时,却无法完成保存操作:
这又是为什么呢?
问题就出在我们刚才创建的自定义托管对象上下文上!
回到我们前面新建 Worry 对象的保存方法上,可以看到当用户点击保存按钮时,我们实际从 newWorry 对象的 managedObjectContext 属性获取到的上下文竟然为 nil。
Button("保存") {
// tmpContext 为 nil,导致 guard 语句直接退出
guard let tmpContext = newWorry.managedObjectContext, let parentContext = tmpContext.parent else { return }
guard verify() else { return }
// “双重”保存
try! tmpContext.save()
try! parentContext.save()
dismissor()
}
在 OC ARC 背后支持的 Swift 运行时里,要想一个对象实例驻留于内存中,至少要保证它被一个强引用(Strong Reference)所“套牢”。
回忆一下:我们之前创建的自定义 tmpContext 上下文,在 init() 方法结束时就会“灰飞烟灭”。虽然我们的 newWorry 托管对象引用了它,但它们只是弱引用(Weak Reference)的关系(即托管对象对其上下文的引用是 weak 引用类型),无奈不顶用啊。
知道了问题的症结,解决起来就很简单了。我们只需要确保自定义托管对象上下文被一个强引用“牢牢抓住”即可,这可以通过在视图层面创建它来完成:
struct V3_NewWorryView: View {
@ObservedObject private var newWorry: Worry
// 托管对象中的上下文是弱引用,如果不在下面将其保存则其会立即“丢失”
private let tmpContext: NSManagedObjectContext
}
最后我们运行代码,可以看到新建的 Worry 对象已经可以被稳妥的存储啦:
至此,我们在一个“鸟语花香、山清水秀”的“新世界”,利用自定义托管对象上下文完美的解决了博客开头引出的问题,棒棒哒!💯
总结
在本篇博文中,我们进一步讨论了如何用自定义托管对象上下文(NSManagedObjectContext)来消除 SwiftUI 视图中冗余的“空白”对象,并顺面解决了上下文“诡异”消失的问题。
感谢观赏,再会啦!😎