Swift 3 iOS的核心数据教程

利用Apple的Core Data框架持久存储应用程序数据

核心数据是Apple Cocoa框架的关键部分,可让您的应用管理对象数据。 开发人员经常使用它来保存(持久)到诸如数据库之类的数据存储中并从中检索数据。 例如,如果您打算构建待办事项应用程序,那么Core Data是一种将那些待办事项保存到SQLite数据库中的快速可行的方法,而您甚至不必学习SQL或产生大量的数据持久性代码。

核心数据本身并不是真正的数据库。 但是,您可以将其视为在数据库/数据源之上分层的框架。 使用Core Data,您可以专注于Swift对象,而无需创建将对象数据保存到磁盘的代码。 例如,您可以像往常一样简单地创建一个Employee对象,通过其name属性修改其name值,然后将其添加到Department对象的employees集合属性中,如下所示:

  employee.name =“约翰” 
员工年龄= 24
department.employees.append(员工)

完成后,您只需通过以下方式要求Core Data将更改推送到数据库中:

  context.save() 

瞧! Core Data的工作是用最少的代码将这些更改自动发送到数据库。

并且不必一定是您指定为基础数据源的数据库。 例如,尽管Core Data确实默认使用SQLite数据库,但可以将数据源切换为使用XML文件。

补充说明:如果您来自Java,您会发现Core Data的持久性机制与Hibernate对象关系映射框架(或Ruby on Rails下的ActiveRecord)有点相似。强大,实际上超出了任何标准ORM的功能。

从数据库表读取数据到对象也很简单,可以使用查询和获取请求来完成。 正如您所期望的那样,使用Core Data需要进行一些设置,但是起步并开始并不困难。

最后,Core Data还使定义类之间的关系变得很简单-一对一,一对多,多对多等。例如,一个Employee可以属于一个Department ,而一个Department可以拥有许多Employee ,一个Employee可以参与许多Project ,反之亦然。 您可以建立不同类型的关系,而无需自己在基础数据库上对这些相同的关系建模(创建联接表等)。 这超出了本教程的范围(尽管它相对容易),但是重点是方便,并且没有多余的东西。 核心数据确实可以节省大量时间。

我们在这里不会做任何令人难以置信的事情-只是一个显示笔记本电脑产品列表的应用程序-但足以向您展示实际的核心数据。 表格将显示笔记本电脑项目的目录,选择其中的任何一项都会显示有关笔记本电脑的更多详细信息。 另外,我们将不仅限于文本和数字,还将结合图像的显示(和保存)。

在本教程系列的第一部分中,我们将介绍Core Data的基础知识,并将重点放在仅实现表上。 在另一个教程中,我们将通过完成详细信息视图来限制所有内容。

在实际构建应用程序之前,这里简要介绍一下核心数据组件及其关系。 Core Data附带了许多类和协议,但是您只需要识别组件即可开始使用它们进行生产。

黄色突出显示的代码是您大多数时候可能在代码中使用的代码:

1. NSManagedObjectContext上下文就像一个缓冲区或暂存器,托管对象连接到该缓冲区或暂存器。 将要更新并保存到数据库中的Employee对象将需要“放置”到上下文中或与上下文关联。

2. NSFetchRequestNSPredicate用于执行查询以从数据库中获取数据。 查询可能很简单,但是您也可以自由创建复杂的查询。

让我们开始创建项目。 开始使用Core Data的最快方法是创建一个新项目,确保已选中Use Core Data选项。 继续并命名项目CoreDataProject

启用“ 使用核心数据”选项后,会发生两件事。 首先,一个名为CoreDataProject.xcdatamodeld的文件会自动添加到您的项目中。 打开文件会显示一个用户界面,从中可以轻松定义实体类(例如即将成为Laptop类)。

其次,将其他代码插入AppDelegate类。 在打开AppDelegate.swift时 ,您将看到已添加了两个类成员:

  // MARK:—核心数据栈 
懒惰的varpersistentContainer:NSPersistentContainer = {
让容器= NSPersistentContainer(名称:“ CoreDataProject”)
container.loadPersistentStores(completionHandler:{
(storeDescription,错误)在
如果让error = error as NSError? {
fatalError(“未解决的错误\(错误),\(error.userInfo)”)
}
})
返回容器
}()
// MARK:—核心数据保存支持
func saveContext(){
让上下文= persistentContainer.viewContext
如果context.hasChanges {
做{
尝试context.save()
} {
让nserror =错误为NSError
fatalError(“未解决的错误\(nserror),\(nserror.userInfo)”)
}
}
}

persistentContainer属性(实现为自调用闭包)为您提供了一个NSPersistentContainer对象。 从根本NSManagedObjectContext ,我们稍后将使用对象来获取上下文( NSManagedObjectContext ),这对于执行诸如保存对象,执行查询等操作非常关键。

saveContext()只是一个帮助程序方法,它使指示Core Data保存托管对象的过程稍微容易一些。

再次打开CoreDataProject.xcdatamodeld 。 您将看到一个用于直观地构建实体类的用户界面。 实体类实际上只是常规的Swift类,并添加了一些配件,以便它们可以与Core Data一起使用。

具体来说,该工具在幕后生成类,这些类映射到SQL表,并且其属性映射到表的列。 生成后,您可以在代码中使用该类。

让我们创建一个代表笔记本电脑条目的实体。 单击“添加实体”按钮,并将其命名为“笔记本电脑”。

接下来,在“属性”部分中,单击加号以添加显示在图片中的属性。 确保在“类型”列中选择适当的类型。

注意pic属性? 顾名思义,这就是我们将为项目中每个笔记本电脑条目存储图片的方式。 类型是“二进制数据”,实际上可以将其设置为可以表示为NSData任何值,而不仅仅是图像。

默认情况下,二进制数据直接保存到数据库中。 但是,如果您不满意直接将原始图片保存到数据库中的想法,该怎么办? 不用担心:您可以指示Core Data进行外部保存。 只需选择pic属性,然后在最右边的导航器面板中,选中选项A会降低External Storage

现在我们已经掌握了基础知识,让我们继续进行该项目。 在情节提要中,首先将UITableViewController对象拖到布局中。 选择对象后,选择“ 嵌入”>“导航控制器”,用导航栏将其很好地包裹起来。 选择导航栏,并为其提供标题“笔记本电脑”。

接下来,单击视图中的单元格原型,然后通过“属性检查器”面板将单元格的样式属性设置为“ 字幕 ”。 在使用它时,还要为其指定一个标识符字符串值“ cell”。

创建一个新的Cocoa Touch类,并将其命名为LaptopListingTableViewController 。 它应该是UITableViewController的子类。

与往常一样,不要忘记返回情节LaptopListingTableViewController布局,然后将LaptopListingTableViewController UITableView对象的类。

让我们花点时间看一下在代码中使用Core Data的基础。 本节教您创建,保存和获取对象的基本知识。

1.第一件事:获取上下文

每当您要对对象执行Core Data操作(创建,保存,查询)时,第一步总是要有一个可用的上下文对象( NSManagedObjectContext类型)。 上下文本质上是“托管”对象的集合,Core Data会跟踪其属性以进行更改。

那么我们如何获得上下文呢? 好吧,我们可以通过NSPersistentContainer对象访问一个对象,如果您还记得的话,它已经在AppDelegate被深思熟虑地提供给我们(当我们一开始在项目中启用Core Data时)。

首先,通过代码访问AppDelegate

 让委托= UIApplication.shared.delegate为!  AppDelegate 

获取委托后,现在可以像这样访问上下文(还记得persistentContainer吗?):

 让上下文=委托.persistentContainer.viewContext 

2.创建和保存新对象

创建一个新的托管对象实际上涉及几个步骤,而不仅仅是普通的类实例化。

您首先需要一个您要实例化的类的实体描述( NSEntityDescription ),并且获得一个简单明了。 只需使用静态方法NSEntityDescription.entity(forEntityName:in:) ,传递实体的字符串名称,然后传递上下文对象。

 让entityDesc = NSEntityDescription.entity(forEntityName:“笔记本电脑”,在:上下文中) 

实际上,这里需要上下文对象来从NSManagedObjectModel组件中检索类定义(如先前图中所示)。 无论如何,我们还没有真正创建任何Laptop对象。 我们只是获取其结构定义。

生成实体描述后,可以使用它实例化Laptop对象。 这再次要求上下文对象作为其insertInto:参数传递。 这样做会将对象与该上下文相关联,使其真正成为“托管对象”,其数据正在被核心数据跟踪。

 让笔记本电脑=笔记本电脑(实体:entityDesc!,insertInto:上下文) 

现在,您可以随意进行所有更改,并根据需要修改Laptop对象的任何属性:

  laptop.id =“ 13–4101dx” 
laptop.manufacturer =“ HP”
laptop.model =“ Spectre x360(2017)”
laptop.desc =“ ...一些文本...”
laptop.price = Decimal(1_068.06)作为NSDecimalNumber吗?
laptop.pic = NSData(data:UIImagePNGRepresentation(UIImage(named:“ spectre”)!)!)

正如您在最后一行中注意到的那样,应该为pic属性(在模型编辑器中定义为“二进制数据”类型)赋予NSData对象作为其值。 UIImage还需要通过UIImagePNGRepresentation进行桥接,以便可以由NSData构造函数接收。

完成后,不要忘记从上下文对象调用save() 。 该方法引发异常。

 尝试!  context.save() 

3.从数据库中获取对象

要读取对象,您首先需要创建一个请求(特别是NSFetchRequest )。

 让fetchRequest = NSFetchRequest (entityName:实体名称) 

然后使用context.fetch(request)执行查询,将fetchRequest作为参数传入。

 让笔记本电脑=尝试!  context.fetch(fetchRequest) 

仅此而已。

我们始终希望使我们的代码LaptopCoreDataService和井井有条,因此我们将定义服务类LaptopCoreDataService 。 这样,我们可以通过单个可全局访问的对象方便地执行任何与Laptop相关的任务。

LaptopCoreDataService将包含以下方法:

  • 获取所有笔记本电脑的数组作为对象
  • 创建并插入示例笔记本电脑数据
  • 通过ID值查找笔记本电脑
  • 删除具有ID的笔记本电脑
  • 给定ID更新笔记本电脑
  • 其他任何与笔记本电脑有关的任务

现在,我们将重点放在仅实现前两个方面。 fetchAll()从数据库中检索所有便携式计算机,并将它们作为对象返回,另一个loadSampleData()加载初始样本数据。

LaptopCoreDataService { 
功能
fetchAll ()-> [笔记本电脑] {//…}
功能
loadSampleData (){//…}
//更多服务方法
}

实作

以下是基于您刚刚学习的基本Core Data概念的LaptopCoreDataService的实现。

LaptopCoreDataService { 
让context:NSManagedObjectContext
entityName :String =“笔记本电脑”
私人的
初始化 (context:NSManagedObjectContext){
self.context =上下文
}
func fetchAll ()-> [笔记本电脑] {
让fetchRequest =
NSFetchRequest (entityName:entityName)
让笔记本电脑=尝试! context.fetch(fetchRequest)
返回笔记本电脑
}
func saveContext (){
尝试! context.save()
}
// ...}

函数loadSampleData()加载足够的样本数据。 加载是有条件的,只有在商店中根本没有找到“ Laptop记录时才执行加载。 理想情况下,示例数据应该已经外部化,但是由于这是一个简单的教程,因此我们将使用硬编码方法。

  func loadSampleData (){ 
让entityDesc = NSEntityDescription.entity
(forEntityName:“笔记本电脑”,位于:context中)
let request = NSFetchRequest (entityName:“ Laptop”)
如果尝试! context.count(for:request)== 0 {
让笔记本电脑0 =笔记本电脑
(实体:entityDesc!,insertInto:上下文)
laptop0.id =“ MQD42LL / A”
laptop0.model =“ Macbook Air 2017”
laptop0.manufacturer =“苹果”
laptop0.desc =“ MacBook Air是由Apple Inc.开发和制造的Macintosh子笔记本计算机产品线。它由一个全尺寸键盘,一个机械加工的铝制外壳和一个轻巧的结构组成。Air的屏幕尺寸可用(对角线测量)为13.3英寸(33.782厘米),苹果生产的规格不同。自2011年以来,所有MacBook Air型号均使用固态驱动器存储和Intel Core i5或i7CPU。11.6英寸(29.46英寸)的MacBook Air厘米)屏幕于2010年面世。”
laptop0.price = Decimal(900.0)作为NSDecimalNumber吗?
laptop0.pic = NSData(数据:UIImagePNGRepresentation(UIImage(名称:“ macbookair”)!)!)
让laptop1 =
笔记本电脑(实体:entityDesc!,insertInto:上下文)
laptop1.id =“ 13-4101dx”
laptop1.manufacturer =“ HP”
laptop1.model =“ Spectre x360(2017)”
laptop1.desc =“ Spectre 13是HP高端Spectre系列的最新超极本。它的起价为1000美元,入门级机型配备13.3英寸显示屏,双核Core i5-4200U处理器,4GB RAM,128GB SSD和它具有1920 x 1080的显示屏。双核Core i7-4500U处理器,8GB RAM,256GB SSD和2560 x 1440分辨率的标价为1400美元,重量为3.34磅,于2013年10月发布。”
laptop1.price = Decimal(1_068.06)作为NSDecimalNumber吗?
laptop1.pic = NSData(数据:UIImagePNGRepresentation(UIImage(named:“ spectre”)!)!)
让laptop2 =笔记本电脑(实体:entityDesc!,insertInto:上下文)
laptop2.id =“ XPS9360-1718SLV”
laptop2.manufacturer =“戴尔”
laptop2.model =“ XPS 13”
laptop2.desc =“ Dell XPS 13在CES 2012上亮相。这是该公司的第一款Ultrabook,由Intel创造。XPS13具有13.3英寸的屏幕(1366 x 768 NON Touch Corning Gorilla Glass)并使用闪光灯内存可帮助快速启动XPS 13具有某些独特的设计元素,边缘是圆形的,底部是碳纤维制成,并经过柔和的硅酮表面处理注意:英特尔芯片组是第二代I系列。开发者版本的运行Ubuntu Linux的XPS13。”
laptop2.price = Decimal(779.99)为NSDecimalNumber吗?
laptop2.pic = NSData(数据:UIImagePNGRepresentation(UIImage(named:“ spectre”)!)!)
saveContext()
}
}
}

使服务类成为单例

最重要的是, LaptopCoreDataService类将遵循单例设计模式,以便:(a)曾经创建过一个实例,并且只有一个实例,并且(b)我们可以在多个视图控制器之间共享服务。

对于初学者,请将init()方法设为私有,以防止无意或完全实例化服务类。 我们只想通过另一种方法实例化它。

  // LaptopCoreDataService.swift 

LaptopCoreDataService {
私人的
初始化 (context:NSManagedObjectContext){
self.context =上下文
}
// ...
}

仍在LaptopCoreDataService ,将实例化代码分组到一个名为shared的内部类中。 在shared是一个实例属性instance ,该instance 有条件地返回private _instance

  // LaptopCoreDataService.swift 

LaptopCoreDataService {
// ...
//将实例化方法/属性分组到内部类中

公开课共享 {
私有静态变量
_instance :LaptopCoreDataService?
//确保仅创建一个实例
公共静态变量
实例 :LaptopCoreDataService {
如果_instance == nil {
让委托=
UIApplication.shared.delegate为! AppDelegate
_instance =
LaptopCoreDataService(
context:delegate.persistentContainer.viewContext)
}
返回_instance!
}
}
// ...
}

建立完类后,现在tt几乎可以从任何地方访问该服务。 做就是了:

 让服务= LaptopCoreDataService.shared.instance 
让结果= service.fetchAll()

现在,在TableViewController类中,我们终于可以利用新创建的LaptopCoreDataService类,在表视图中填充笔记本电脑条目及其对应的型号。

 类LaptopListingTableViewController:UITableViewController { 
var ultrabooks:[((String,String)] = []

让服务= LaptopCoreDataService.shared.instance
功能
populateTable() {
service.loadSampleData()
让笔记本电脑=
service.fetchAll()超极本= laptops.map {
笔记本电脑在
如果让id = laptop.id,
让制造商= laptop.manufacturer,
让模型= laptop.model {
return(id,“ \(制造商)\(模型)​​”)
}其他{
返回(“ ”,“ ”)
}
}
self.tableView.reloadData()
}
覆盖func viewDidLoad (){
super.viewDidLoad()
populateTable()
}
// ...
}

现在就这样。 在本系列的下一部分中,我们将通过实现细节视图来结束,这实际上并不需要太多。