当前位置: 首页 > 科技观察

如何结合CoreData和SwiftUI

时间:2023-03-17 22:37:38 科技观察

本文转载自微信公众号《Swift社区》,作者魏贤智。转载本文请联系Swift社区公众号。核心数据堆栈SwiftUI和CoreData之间的差异将近十年——SwiftUI随iOS13一起出现,而CoreData是iPhoneOS3的产物;很久以前它不叫iOS,因为iPad还没有发布。尽管时间相距遥远,Apple还是投入了大量工作来确保这两项强大的技术完美地协同工作,这意味着CoreData被集成到SwiftUI中,就好像它一直都是这样设计的一样。在这个项目中,我们只会使用少量的CoreData功能,但该功能很快就会得到扩展——我只是想先感受一下。当您创建Xcode项目时,我要求您选中UseCoreData框,这应该会导致项目发生变化:您现在有一个名为Bookworm.xcdatamodeld的文件。这描述了您的数据模型,它实际上是一个类及其属性的列表。AppDelegate.swift和SceneDelegate.swift中现在有额外的代码来设置核心数据。设置CoreData是一个两步过程:创建一个所谓的持久容器(从容器存储中加载和保存实际数据),然后将其注入到SwiftUI环境中,以便我们所有的视图都可以访问它。Xcode模板已经为我们完成了这两个步骤。所以,剩下的就是让我们决定在CoreData中存储什么数据,以及如何读出这些数据。首先,我们需要打开Bookworm.xcdatamodeld并开始使用Xcode的模型编辑器描述我们的数据。我们之前描述过这样的数据:structStudent{varid:UUIDvarname:String}但是,CoreData不是那样工作的。你看,CoreData需要提前知道我们所有的数据类型是什么样的,它们包含什么,以及它们之间的关系。这就是“xcdatamodeld”文件的来源:我们将类型定义为“实体”,然后在其中创建属性作为“属性”,CoreData负责将其转换为可在运行时使用的实际数据库布局。要试用它,请单击“添加实体”按钮创建一个新实体,然后双击其名称将其重命名为“学生”。接下来,单击“Attributes”表正下方的+按钮添加两个属性:“id”作为UUID和“name”作为字符串。这将告诉CoreData创建学生和保存他们所需的一切,所以回到ContentView.swift以便我们可以编写一些代码。使用get请求从CoreData检索信息——我们描述我们想要什么,应该如何排序以及是否应该使用任何过滤器,然后CoreData发回所有匹配的数据。我们需要确保获取请求随着时间的推移保持最新,以便在创建或删除学生时,我们的UI保持同步。SwiftUI有一个解决方案,而且——你猜对了——另一个属性包装器。这次调用它@FetchRequest,它有两个参数:我们想要查询的实体和我们想要的结果排序方式。它有一个非常特定的格式,所以让我们先为学生添加一个获取请求-现在将这个属性添加到ContentView:@FetchRequest(entity:Student.entity(),sortDescriptors:[])varstudents:FetchedResultsBreakdown之后,这个为获取的“student”实体创建请求,不进行任何排序,但将其放入FetchedResults类型的名为students的属性中。从那里开始,我们可以开始像使用常规Swift数组一样使用students,但是你会注意到有一个问题。首先,一些将数组放入列表的代码:varbody:someView{VStack{List{ForEach(students,id:\.id){studentinText(student.name??"Unknown")}}}}你找到了吗例外?是的,student.name是可选的——它可能有也可能没有值。这是CoreData的一个领域,它会让你大吃一惊:它有可选值的概念,但它与Swift的可选值完全不同。如果我们对CoreData说“这不一定是”(您可以在模型编辑器中这样做),它仍会生成可选的Swift属性,因为CoreData所关心的只是该属性在保存时有值-其他时间它们可以为零。如果你愿意,你可以运行代码,但没有什么意义——列表将是空的,因为我们还没有添加任何数据,所以我们的数据库是空的。为了解决这个问题,我们将在列表下方创建一个按钮,每次单击该按钮都会随机添加一个新学生,但首先我们需要一个新属性来存储托管对象上下文。让我重申一下,因为这很重要。当我们定义我们的“学生”实体时,实际发生的是CoreData为我们创建了一个继承自它自己的类:NSManagedObject。我们在代码中看不到这个类,因为它是在构建项目时自动生成的,就像CoreML的模型一样。这些对象被称为托管对象,因为CoreData会照顾它们:它从持久性容器加载它们并将它们的更改也写回。我们所有的托管对象都位于托管对象上下文中,该上下文负责实际获取托管对象和保存更改等。您可以拥有任意数量的托管对象上下文,但现在还有很长的路要走-事实上,您可以将它用于很长时间。我们不需要创建这个托管对象上下文,因为Xcode已经为我们创建了一个。更好的是,它已经将它添加到SwiftUI环境中,这就是@FetchRequest属性包装器起作用的原因——它使用环境中可用的任何托管对象上下文。因此,现在将此属性添加到ContentView:@Environment(\.managedObjectContext)varmoc使用该设置,下一步是添加一个按钮,用于生成随机学生并将它们保存在托管对象上下文中。为了帮助学生脱颖而??出,我们将通过创建firstNames和lastNames数组来分配随机名称,然后使用randomElement()从中选择一个。首先在列表下面添加这个按钮:Button("Add"){letfirstNames=["Ginny","Harry","Hermione","Luna","Ron"]letlastNames=["Granger","Lovegood","Potter","Weasley"]letchosenFirstName=firstNames.randomElement()!letchosenLastName=lastNames.randomElement()!//morecodetocome}**注意:**不可避免地会有人抱怨我强行调用了randomElement(),但实际上我们只是手动创建了带有值的数组——它总是会成功。如果你真的讨厌强制拆包,你可以用空合并计算和默认值来代替它。现在,有趣的部分:我们将使用为我们生成的核心数据类创建一个Student对象。这需要附加到托管对象上下文,以便对象知道它应该存储在哪里。然后我们可以像往常一样为结构体赋值。所以现在将这三行添加到按钮的动作闭包中:letstudent=Student(context:self.moc)student.id=UUID()student.name="\(chosenFirstName)\(chosenLastName)"托管对象上下文保存自身。这是一个抛出函数的调用,因为它在理论上可能会失败。事实上,我们所做的一切都没有失败的机会,所以我们可以使用try?调用它----我们不关心捕获错误。所以将最后一行添加到按钮的操作中:try?self.moc.save()最后,您现在应该能够运行该应用程序并尝试一下-单击“添加”按钮几次以生成一些随机学生,您应该会看到它们滑入我们列表的某处。更好的是,如果你重新启动应用程序,你会发现学生们仍然在那里,因为CoreData保存了他们。现在,您可能认为这是大量学习,不会带来太多收获,但您现在知道什么是实体和属性,知道什么是托管对象和请求,并且您已经了解了如何保存更改。在这个项目的后期和未来,我们将更多地关注核心数据,但到目前为止,您已经取得了长足的进步。