SwiftUI 和 Core Data

推荐观看
推荐阅读


图片

SwiftUICore Data 之间相差将近十年 —— SwiftUI 随着 iOS 13 面世而 Core Data 则是 iPhoneOS 3 的产物;很久以前,它还没有被称为 iOS,因为 iPad 尚未发布。尽管时间相距遥远,Apple 还是投入了大量工作以确保这两种强大的技术能够完美地相互配合使用,这意味着 Core Data 就像始终以这种方式设计一样,已集成到 SwiftUI 中。

在此项目中,我们将仅使用少量 Core Data 的功能,但是这种功能将很快扩展——我只想首先了解一下它。当您创建 Xcode 项目时,我要求您选中 Use Core Data 框,它应该导致对项目的更改:

  • 现在,您有了一个名为 Bookworm.xcdatamodeld 的文件。这描述了您的数据模型,该数据模型实际上是类及其属性的列表。
  • AppDelegate.swiftSceneDelegate.swift 中现在有用于设置 Core Data 的额外代码。

设置核心数据需要两个步骤:创建所谓的持久性容器(从容器存储中加载并保存实际数据),然后将其注入 SwiftUI 环境中,以便我们所有的视图都可以访问它。

Xcode 模板已经为我们完成了这两个步骤。

因此,剩下的就是我们要决定要在 Core Data 中存储哪些数据,以及如何读出这些数据。首先,我们需要打开 Bookworm.xcdatamodeld 并开始使用 Xcode 的模型编辑器描述我们的数据。

之前我们描述过这样的数据:

struct Student {
    var id: UUID
    var name: String
}

但是,Core Data 不能那样工作。您会看到,Core Data 需要提前知道我们所有数据类型的样子,包含的内容以及它们之间的关系。这就是 “xcdatamodeld” 文件的来源:我们将类型定义为“实体”,然后在其中创建属性作为“属性”,Core Data 负责将其转换为可以在运行时使用的实际数据库布局。

为了进行试用,请点击 “Add Entity” 按钮创建一个新实体,然后双击其名称将其重命名为 “Student”。接下来,单击 “Attributes”表正下方的+按钮以添加两个属性:“id”作为 UUID 和 “name” 作为字符串。这将告诉 Core Data 创建学生并保存他们所需的一切,因此请回到 ContentView.swift,以便我们编写一些代码。

使用获取请求从 Core Data 中检索信息——我们描述了我们想要的内容,应如何对其进行排序以及是否应使用任何过滤器,然后 Core Data 会发回所有匹配的数据。我们需要确保该获取请求随着时间的推移保持最新,以便在创建或删除学生时,我们的 UI 保持同步。

SwiftUI 有一个解决方案,而且——您猜对了——这是另一个属性包装器。这次将其称为@FetchRequest,它带有两个参数:我们要查询的实体以及我们希望结果如何排序。它具有非常特定的格式,因此,我们首先为学生添加获取请求——请立即将此属性添加到 ContentView

@FetchRequest(entity: Student.entity(), sortDescriptors: []) var students: FetchedResults<Student>

分解之后,这创建了一个获取的“学生”实体的请求,不进行任何排序,而是将其放入名称为students,类型为FetchedResults<Student>的属性中。

从那里开始,我们可以像常规的 Swift 数组一样开始使用学生,但是您会发现有一个陷阱。首先,一些将数组放入List的代码:

 var body: some View {
        VStack {
            List {
                ForEach(students, id: \.id) { student in
                    Text(student.name ?? "Unknown")
                }
            }
        }
    }
}

你发现异常了吗?是的,student.name是可选的——它可能有一个值,也可能没有。这是 Core Data 的一个领域,该领域会让您大为恼火:它具有可选数据的概念,但与 Swift 的可选数据完全不同。如果我们对 Core Data 说“这不是必须的”(您可以在模型编辑器中完成),它仍然会生成可选的 Swift 属性,因为所有 Core Data 关心的是属性在保存时具有值——在其他时间它们可以为 nil

您可以根据需要运行代码,但没有太多意义——该列表将为空,因为我们尚未添加任何数据,因此我们的数据库为空。为了解决这个问题,我们将在列表下方创建一个按钮,每次点击都会添加一个新的随机学生,但是首先我们需要一个新属性来存储托管对象上下文。

让我重申一下,因为这很重要。当我们定义 “Student” 实体时,实际上发生的是 Core Data 为我们创建了一个类,该类继承自其自身的一个类:NSManagedObject。我们无法在代码中看到该类,因为它是在构建项目时自动生成的,就像 Core ML 的模型一样。这些对象之所以称为托管对象,是因为 Core Data 会照料它们:它从持久性容器中加载它们并将它们的更改也写回。

我们所有的托管对象都位于托管对象上下文中,该上下文负责实际获取托管对象以及保存更改等。如果需要的话,您可以有许多托管对象上下文,但这距离现在还有一段路要走——实际上,您可以长期使用它。

我们不需要创建此托管对象上下文,因为 Xcode 已经为我们创建了一个。更好的是,它已经将其添加到 SwiftUI 环境中,这就是@FetchRequest属性包装器起作用的原因——它使用了环境中可用的任何托管对象上下文。

因此,现在将此属性添加到ContentView

@Environment(\.managedObjectContext) var moc

设置好之后,下一步是添加一个按钮,该按钮生成随机的学生并将其保存在托管对象上下文中。为了帮助学生脱颖而出,我们将通过创建firstNameslastNames数组来分配随机名称,然后使用randomElement()从中选择一个。

首先在List下方添加此按钮:

Button("Add") {
    let firstNames = ["Ginny", "Harry", "Hermione", "Luna", "Ron"]
    let lastNames = ["Granger", "Lovegood", "Potter", "Weasley"]

    let chosenFirstName = firstNames.randomElement()!
    let chosenLastName = lastNames.randomElement()!

    // more code to come        
}

注意:不可避免地有人会抱怨我强行对randomElement()调用,但是实际上我们只是手工创建了具有值的数组——它将永远成功。如果您非常讨厌强制拆包,则可以将其替换为空合计算和默认值。

现在,有趣的部分是:我们将使用为我们生成的 Core Data 类创建一个 Student对象。这需要附加到托管对象上下文中,以便对象知道应将其存储在何处。然后,我们可以像通常为结构体那样分配值。

因此,现在将这三行添加到按钮的操作闭包中:

let student = Student(context: self.moc)
student.id = UUID()
student.name = "\(chosenFirstName) \(chosenLastName)"

最后,我们需要询问托管对象上下文以保存自身。这是一个引发函数的调用,因为理论上它可能会失败。实际上,我们所做的一切都没有失败的可能,因此我们可以使用try?来调用它——–我们不在乎捕获错误。

因此,请将最后一行添加到按钮的操作中:

try? self.moc.save()

最后,您现在应该可以运行该应用程序并对其进行尝试——单击几次 “Add” 按钮以生成一些随机的学生,您应该看到他们滑入我们列表的某个位置。更好的是,如果您重新启动该应用程序,您会发现学生还在,因为 Core Data 已保存了他们。

现在,您可能认为这需要大量的学习,但并不会带来很多结果,但是您现在知道什么是实体和属性,知道什么是托管对象和请求,并且已经了解了如何保存更改。在此项目的后面以及将来,我们都将更多地关注 Core Data,但到目前为止,您已经走了很远。

这是该项目概述的最后一部分,因此,请将您的代码重设为初始状态,并确保您从我们的数据模型中删除了Student实体——我们不再需要它。

PS: 如果预览报错,那么请跑模拟器

译自 How to combine Core Data and SwiftUI[1]

参考资料
[1]
How to combine Core Data and SwiftUI: https://www.hackingwithswift.com/books/ios-swiftui/how-to-combine-core-data-and-swiftui

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,039评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,223评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,916评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,009评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,030评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,011评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,934评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,754评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,202评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,433评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,590评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,321评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,917评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,568评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,738评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,583评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,482评论 2 352

推荐阅读更多精彩内容