Core Data 教程入门
这是《Core Data by Turoials》一书的缩略章节,目前已经升级至 Swift 4 和 iOS 11。值得高兴的是,本教程被作为 iOS 11 Lanuch Party 中的一部分放出。 欢迎来到 Core Data 的世界!在本教程中,你将编写一个非常简单的 Core Data app。你会看到通过 Xcode 提供的一系列工具,比如开始的模板代码和 Data Model 编辑器,我们能够轻易上手。 我们将马上开始。 当本教程结束,你将学会:
你也会了解 Core Data 在底层做的工作,以及如何和各种 moving pieces 打交道。 开始打开 Xcode ,新建 iOS 项目,模板使用 Single View App 。项目名称命名为 HitList,勾上 Use Core Data 选项。 勾选 Use Core Data 选项将导致 Xcode 生成模板代码,也就是 AppDelegate.swift 中的 NSPersistentContainer。 NSPersistentContainer 包含了一系列用于在 Core Data 中保存和检索数据的对象。在这个容器中,有负责将 Core Data 状态作为一个整体进行管理的对象,也有代表数据模型的对象,等等。 对于大部分 app 来说,这种标准栈足以使用,但根据 app 和它的数据需求的不同,你可以定制这个栈以获得更高的效率。
这个示例 app 的功能很简单:有一个 table view,展示一个你自己的“黑名单”列表。你可以在列表中添加名字,然后保存到 Core Data 中,确保数据在会话之间不被丢失。这本书当然不会宣扬暴力,因此你可以把 app 当成一个记录你的朋友喜好的列表。 点击 Main.storyboard,打开 IB。 在画布中选中这个 view controller,将它用 navigation controller 包装起来。在 Xcode 的 Editor 菜单中,选择 Embed In…Navigation Controller。 点击 navigation controller 的导航栏,然后在属性检查器中点击 Prefers Large Tittles。这将使 app 显示为 iOS 11 风格。 然后,从 Object Library 拖一个 Table View 到 view controller 中,然后修改大小为整个 view 大小。 如果 Document Outline 窗口未打开,请点击画布左下角的图标打开它。 右键,从 document outline 中的 Table View 拖一条线到它的父 view,并选择 Leading Space to Safe Area: 重复这个动作 3 次,分别选择 Trailing Space to Safe Area,Top Space to Safe Area 和 Bottom Space to Safe Area。这 4 个约束将使 table view 填充整个父视图。 接着,拖一个 Bar Button Item 到 View controller 的导航条上。最后,选择 bar button item,将它的 system item 修改为 Add。你的画布会变成这个样子: 当你点击 Add 按钮,会显示一个 alert controller。你可以在它的 text field 中输入一个人的名字。点击 Save,保存这个人名,alert 消失,table view 会刷新,显示出你输入的名字。 但首先,你必须将 View controller 设置 table view 的数据源。在画布中,右键点击 table view 拖一条线到导航条的黄色 view controller 图标上,然后点击 dataSource,如下图所示: 你可能奇怪,为什么不设置 table view 的 delegate,因为点击 cell 时我们不需要触发任何动作。这再简单不过了! 按下 Command-Option-Enter,或者点击 Xcode 工具栏上 Editor 工具中间的按钮,打开助手编辑器。删除 didReceiveMemoryWarning()方法。然后,右键,从 table view 拖到 ViewController.swift 的类定义中,创建一个 IBOutlet。 然后,为 IBOutlet 属性取名为 tableView,这会添加一句代码: @IBOutlet weak var tableView: UITableView! 然后,右键,从 Add 按钮拖一条线到 ViewController.swift 的 viewDidLoad() 方法以下。这次,会创建一个 IBAction 而不是 IBOutlet,命名为 addName,Type 栏选择 UIBarButtonItem: @IBAction func addName(_ sender: UIBarButtonItem) {
}
现在,你可以在代码中引用 table view 和 bar button item 的 action 了。 然后,为 table view 创建模型。在 ViewController.swift 的 tableView 属性声明后添加一个属性: var names: [String] = [] names 属性是一个可变数组,用于保存要显示在 table view 中的字符串。接着,将 viewDidLoad() 方法修改为: override func viewDidLoad() {
super.viewDidLoad()
title = "The List"
tableView.register(UITableViewCell.self,forCellReuseIdentifier: "Cell")
}
这将设置 navigation bar 的标题,并注册 UITableViewCell 类到 table view。
继续在 ViewController.swift 中,添加一个 UITableViewDataSource 扩展: // MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
如果你用过 UITableView,这段代码应该是熟悉的。首先你返回了 names 数组的数目作为表格的行数。 然后,tableView(_:cellForRowAt:) 方法从缓存中获得一个 cell,然后用 names 数组中对应的字符串渲染 cell。 接着,我们需要添加新的名字到表格中去显示。实现前面通过右键拖到代码中的 addName IBAction 方法: // Implement the addName IBAction
@IBAction func addName(_ sender: UIBarButtonItem) {
let alert = UIAlertController(title: "New Name",message: "Add a new name",preferredStyle: .alert)
let saveAction = UIAlertAction(title: "Save",style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,let nameToSave = textField.text else {
return
}
self.names.append(nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel",style: .default)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert,animated: true)
}
当你点击 Add 按钮,这个方法会弹出一个带文本框和两个按钮 Save 、Cancel 的 UIAlertController。 Save 按钮将文本框中的文字保存到 names 数组,然后重新加载 table view。因为 names 数组是 table view 的模型,所以你输入到文本框中的文本会显示在 table view 里。 Build & run。点击 Add 按钮,alert 将显示: 添加 4、5 个名字到列表中。你会看到: table view 中能够显示这些数据,数组也会保存所有名字,但却没有持久化。数组只是放在了内存里,但如果你退出 app,重启设备,这些数据都会丢失。 Core Data 提供了持久化,它能将数据保存在一种耐久状态,以便数据在 app 重启或设备重启后不被丢失。 我们还没有添加任何 Core data,因此从 app 离开后任何东西都不会保存。我们来试试看。如果你使用真机调试,按下 Home 键,如果使用模拟器,则按下 shift+command+H 键,回到熟悉的 Home 界面。 在 Home 界面,点击 HitList 图标启动 app 进入前台。names 数组仍然在屏幕上显示。为什么呢?你按 Home 按钮时,app 从前台进入后台。这时,操作系统会将内存中的一切进行缓存,包括 names 数组中的字符串。 同样,当它从后台返回前台,操作系统会恢复内存中的数据,就好像你从来没有离开一样。 苹果从 iOS4 开始引入了这些多任务的高级属性。它们向 iOS 用户提供了一种无缝的体验,但是给 iOS 开发者添加了一种持久化持久化的假象。但是 names 数组真的被持久化了吗? 答案是不。如果你完全杀死 app 进程或者手机关机,names 数组中将什么都没有了。你可以试一下。当 app 在前台时,双击 Home 按钮,进入快速 app 切换工具: 在这个界面中,将 HitList APP 的截图向上划,终止 app 进程。这将让 HitList 从内存中移除。回到 Home 界面,点击 HitList,再次打开 app,你会发现 names 数组被清空了。 缓存到内存和持久化是两个截然不同的概念,如果你熟悉 iOS 和多任务的工作方式的话。只不过在用户的眼中,它们没有区别。用户不关心为什么 names 仍然还在,到底是 app 进入后台又返回前台,还是 app 保存了它们后重新加载。 总之,重点在于当 app 返回之后,names 数组仍然存在! 因此,要真正测试是否持久化,需要重新启动 app 之后再看你的数据是否仍然存在。 建立数据模型现在你知道如何判断有没有持久化了,我们可以开始讲 Core Data 了。我们的目的很简单:对你输入的名字进行持久化,以便当 app 重启后仍然可以看到这些数据。 到目前为止,你还在用简单的古老的 Swift 字符串方式将 names 保存在内存里。在这一节,你会用 Core Data 对象来代替这些字符串。 首先要创建一个托管对象模型,这是 Core Data 用于描述存储在磁盘上的数据的方式。 默认,Core Data 使用 SQLite 数据库来进行持久化,因此你可以把数据模型看成是数据库中 schema 的概念。
因为你勾选了 use Core Data,Xcode 会自动创建一个 Data Model 文件并命名为 HitList.xcdatamodeld. 打开 HitList.xcdatamodeld。你会看到,Xcode 的强大的数据模型编辑器打开了: 数据模型编辑器有许多功能,但目前我们只需要知道怎么创建单个 Core Data 实体就可以了。 点击左下角的 Add Entity,创建一个新的实体。双击这个新实体,修改它的名字为 Person: 你也许奇怪,为什么模型编辑器会使用“实体”一词。为什么不是简单地用类来表示?你后面就知道了,Core Data 拥有自己的一套术语。其中比较常见的会有这几个:
知道了属性是什么之后,你可以为 Person 对象添加一个属性了。打开HitList.xcdatamodeld。然后,选择左侧的 Person 对象,点击 Attributes 下面的 + 按钮。 将新属性的名字设置为 name,类型则改为 String。 保存到 Core Data打开 ViewController.swift,在 UIKit 的导入语句之后加入 Core Data 的导入语句: import CoreData 这个 import 语句使你能够在代码中使用 Core Data API。 然后,将 names 属性的声明修改为: var people: [NSManagedObject] = [] 我们准备用 Person 实体而不是 String 来保存数据,因此我们将 table view 的数据模型的名字修改为 people。它现在保存的是 NSManagedObject 实例而不是简单字符串。 NSManagedObject 代表了一个 Core Data 中存储的对象,你必须用它来创建、编辑、保存和删除 Core Data 持久化存储中的数据。待会你会看到,NSManagedObject 是一种变形动物。它可以用于表示你的数据模型中的任意实体,自动适配你所定义的任意属性和关系。 因为你修改了 table view 的模型,你必须同时修改数据源方法。将你的 UITableViewDataSource 扩展修改为: // MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView,numberOfRowsInSection section: Int) -> Int {
return people.count
}
func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let person = people[indexPath.row]
let cell =
tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath)
cell.textLabel?.text =
person.value(forKeyPath: "name") as? String
return cell
}
}
主要是 tableView(_:cellForRowAt:) 方法。原来设置 cell 时用的是模型数组中的字符串,现在改成了 NSManagedObject。 注意如何从 NSManagedObjectd 中获取 name 属性。也就是这句: cell.textLabel?.text = person.value(forKeyPath: "name") as? String 为什么呢?前面说过,NSManagedObject 并不知道你在数据模型中定义了一个 name 属性,因此你没有办法直接通过属性来访问 name。Core Data 只提供键值编码的方式,也就是 KVC。
接着,找到 addName(_:) 方法,将保存按钮的 UIAlertAction 替换成: let saveAction = UIAlertAction(title: "Save",style: .default) {
[unowned self] action in
guard let textField = alert.textFields?.first,let nameToSave = textField.text else {
return
}
self.save(name: nameToSave)
self.tableView.reloadData()
}
去除 text field 中的文本,将它传递到 save(name:) 方法中。xcode 会报错,因为 save(name:) 方法还没有写。新增一个方法: func save(name: String) {
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
// 1
let managedContext =
appDelegate.persistentContainer.viewContext
// 2
let entity =
NSEntityDescription.entity(forEntityName: "Person",in: managedContext)!
let person = NSManagedObject(entity: entity,insertInto: managedContext)
// 3
person.setValue(name,forKeyPath: "name")
// 4
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("Could not save. (error),(error.userInfo)")
}
}
这里使用 Core Data 代码!这段代码做了这些事:
比起字符串数组来说,这是有点复杂,但算不得什么了。其中一些代码,比如获取上下文和实体,可以在你自己的 init() 方法或 viewDidLoad() 方法中只编写一次,然后重用。这里是为了简单,所以将全部代码都写在一个方法里。 Build & run,添加几个名字到表格里: 如果名字真滴保存到了 Core Data,HitList app 应该能够通过我们的持久化测试。双击 Home 键,打开快速 app 切换工具。向上划,终止 HitList app。在 Springboard 中,点击 HitList app 打开它。 咦?为什么 table view 变成空的了呢? 我们虽然保存了 Core Data,但 app 重启之后,people 数组仍然是空的!因为磁盘上的数据仍然待在那里,你并没有显示它们呀! 从 Core Data 中读取数据要从持久化存储中读取数据到托管对象上下文,你必须自己去抓取它们。打开 ViewController.swift 在 viewDidLoad() 中添加代码: override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//1
guard let appDelegate =
UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext =
appDelegate.persistentContainer.viewContext
//2
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Person")
//3
do {
people = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. (error),(error.userInfo)")
}
}
逐一看看这段代码都做了些什么:
Build & run,你立马就可以在列表中看到之前添加的名字了: 太好了!它们复活了(双关语)。添加几个名字,重启 app,检查能够正常保存和抓取数据。只要你不要删除 app、重置模拟器或者从高楼大厦上扔下你的 iPhone,这些名字都会显示。 接下来做什么从这里下载完成后的项目。 在这几页内容中,你已经学习了 Core Data 中的几个基本概念:数据模型、实体、属性、托管对象、托管对象上下文以及 fetch request。 如果你喜欢本教程,那么请阅读完整的[ Core Data by Tutorials]一书。 这本书中你会学到:
为了促销,该书的数字版仅售 49.99 美元!不要犹豫,这个价格只在短时间内有效。 提到促销,请一定要看看我们的年度大奖 要参加这次抽奖,请点击下面的按钮用 #ios11lanuchparty 井号标签在 tweeter 中转发本文: Tweet 希望你喜欢本文,敬请关注更多书籍和更新! (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |