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. NSFetchRequest
和NSPredicate
用于执行查询以从数据库中获取数据。 查询可能很简单,但是您也可以自由创建复杂的查询。
让我们开始创建项目。 开始使用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()
}
// ...
}
现在就这样。 在本系列的下一部分中,我们将通过实现细节视图来结束,这实际上并不需要太多。