版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2018.09.26 星期三 |
前言
数据是移动端的重点关注对象,其中有一条就是数据存储。CoreData是苹果出的数据存储和持久化技术,面向对象进行数据相关存储。感兴趣的可以看下面几篇文章。
1. iOS CoreData(一)
2. iOS CoreData实现数据存储(二)
3. Core Data详细解析(三) —— 一个简单的入门示例(一)
Modeling Your Data - 建模数据
现在您知道如何检查持久性,您可以深入了解Core Data。 您对HitList应用程序的目标很简单:保留您输入的名称,以便在新应用程序启动后可以查看这些names。
到目前为止,您一直使用普通的旧Swift字符串将names存储在内存中。 在本节中,您将使用Core Data对象替换这些字符串。
第一步是创建一个managed object model,该模型描述了Core Data在磁盘上表示数据的方式。
默认情况下,Core Data使用SQLite数据库作为持久性存储,因此您可以将数据模型视为数据库模式。
注意:在处理
Core Data时,您会遇到相当多的“managed”。 如果您在类的名称中看到“managed”,例如在NSManagedObjectContext中,则可能正在处理Core Data类。“Managed”是指Core Data对Core Data对象生命周期的管理。但是,不要假设所有Core Data类都包含“managed”一词。 大多数没有。 有关Core Data类的完整列表,请查看文档浏览器中的Core Data框架参考。
由于您已选择使用Core Data,因此Xcode会自动为您创建一个数据模型文件,并将其命名为HitList.xcdatamodeld。

打开HitList.xcdatamodeld。 如您所见,Xcode拥有强大的数据模型编辑器:

数据模型编辑器具有许多您可以在以后探索的功能。 现在,让我们专注于创建一个Core Data实体。
单击左下方的Add Entity以创建新实体。 双击新实体并将其名称更改为Person,如下所示:

您可能想知道为什么模型编辑器使用术语Entity。你不是只是简单地定义一个新类吗?正如您将很快看到的,Core Data带有自己的词汇。以下是您将经常遇到的一些术语的快速概述:
-
entity是Core Data中的类定义。典型的例子是Employee或Company。在关系数据库中,实体对应于表。 -
attribute是附加到特定entity的一条信息。例如,Employee实体可以具有员工name、position和salary`的属性。在数据库中,属性对应于表中的特定字段。 -
relationship是多个实体之间的链接。在Core Data中,两个实体之间的关系称为to-one relationships,而一个和多个实体之间的关系称为to-many relationships。例如,经理可以与一组员工建立一对多(to-many relationship)的关系,而个人员工通常与他的经理有一对一( to-one relationship)的关系。
注意:您可能已经注意到实体听起来很像类。 同样,属性和关系听起来很像属性。 有什么不同? 您可以将Core Data实体视为类定义,将
managed object视为该类的实例。
现在您知道属性是什么,您可以向之前创建的Person对象添加属性。 仍然在HitList.xcdatamodeld中,选择左侧的Person并单击Attributes下的加号(+)。
将新属性的名称设置为,比如说,name并将其类型更改为String:

在Core Data中,属性可以是多种数据类型之一。
Saving to Core Data - 保存到Core Data
打开ViewController.swift,在UIKit导入下面添加以下Core Data模块导入:
import CoreData
要在代码中开始使用Core Data API,只需导入即可。
接下来,使用以下内容替换names属性定义:
var people: [NSManagedObject] = []
您将存储Person实体而不是字符串名称,因此您将用作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 :)中。 现在,您不是将单元格与模型数组中的相应字符串进行匹配,而是将单元格与相应的NSManagedObject匹配。
请注意如何从NSManagedObject中获取name属性。 如下所示:
cell.textLabel?.text =
person.value(forKeyPath: "name") as? String
你为什么要这样做? 事实证明,NSManagedObject不知道您在数据模型中定义的name属性,因此无法直接使用属性访问它。 Core Data提供读取值的唯一方法是键值编码,通常称为KVC。
注意:KVC是
Foundation中用于间接使用字符串访问对象属性的机制。 在这种情况下,KVC使NSMangedObject在运行时的行为有点像字典。键值编码可用于从NSObject继承的所有类,包括NSManagedObject。 您不能在不继承自NSObject的Swift对象上使用KVC访问属性。
接下来,找到addName(_ :)并用以下内容替换save 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 :)尚不存在。 在addName(_ :)下面添加它:
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的用武之地! 这是代码的作用:
- 1)在从Core Data存储中保存或检索任何内容之前,首先需要开始使用
NSManagedObjectContext。 您可以将managed object context视为内存中的“暂存器”,以便使用managed objects。
考虑将新的managed object保存到Core Data,这是一个两步过程:首先,将新的managed object插入到managed object context; 一旦您满意,您可以“提交”managed object context中的更改以将其保存到磁盘。
Xcode已经生成了一个managed object context,作为新项目模板的一部分。 请记住,只有在您选中开始时Use Core Data复选框时才会发生这种情况。 此缺省managed object context作为应用程序委托中NSPersistentContainer的属性。 要访问它,首先要获得对app delegate的引用。
- 2) 您创建一个新的
managed object并将其插入到managed object context中。 您可以使用NSManagedObject的静态方法一步完成此操作:entity(forEntityName:in :)。
您可能想知道NSEntityDescription是什么。 回想一下,NSManagedObject被称为shape-shifter类,因为它可以表示任何实体。 实体描述是在运行时将数据模型中的实体定义与NSManagedObject实例相链接的部分。
3) 使用
NSManagedObject,您可以使用键值编码设置name属性。 您必须完全按照数据模型中显示的方式拼写KVC键(在本例中为name),否则,您的应用程序将在运行时崩溃。4) 通过在
managed object context中调用save,可以将更改提交给person并保存到磁盘。 注意save可能会抛出错误,这就是你在do-catch块中使用try关键字调用它的原因。 最后,将新的managed object插入到people数组中,以便在table view重新加载时显示。
这比使用字符串数组要复杂一点,但也不算太糟糕。 这里的一些代码,例如获取managed object context和entity,可以在您自己的init()或viewDidLoad()中完成一次,然后再重复使用。 为简单起见,您可以使用相同的方法完成所有操作。
构建并运行应用程序,并在table view中添加一些名称:

如果names实际存储在Core Data中,则HitList应用程序应通过持久性测试。 将应用程序置于前台,转到快速应用程序切换器,然后终止它。
从Springboard,点击HitList应用程序以触发新的启动。 等等,发生了什么? table view为空:

您保存到Core Data,但在新应用程序启动后,people数组是空的! 那是因为数据正在你的磁盘上,但你还没有显示它。
Fetching from Core Data - 从Core Data中获取
要将持久存储中的数据导入到managed object context,您必须fetch它。 打开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)")
}
}
下面一步一步看代码的作用:
1) 在使用
Core Data执行任何操作之前,您需要一个managed object context。Fetching没有什么不同! 像以前一样,您启动应用程序委托并获取对其持久容器的引用以获取其NSManagedObjectContext。2) 顾名思义,
NSFetchRequest是负责从Core Data获取数据的类。 获取请求既强大又灵活。 您可以使用获取请求来获取符合所提供标准的一组对象(即,让我所有员工都住在威斯康星州并且已经在公司工作至少三年),个人价值(即在数据库中给我最长的名字)和 更多。
Fetch请求有几个限定符用于优化返回的结果集。 现在,你应该知道NSEntityDescription是这些必需的限定符之一。
设置获取请求的entity属性,或者使用init(entityName :)初始化它,获取特定实体的所有对象。 这是你在这里获取所有Person实体的方法。 另请注意,NSFetchRequest是泛型类型。 泛型的使用指定了获取请求的预期返回类型,在本例中为NSManagedObject。
- 3) 您将
fetch请求移交给managed object context以执行繁重的工作。fetch(_ :)返回符合获取请求指定条件的managed object数组。
注意:与
save()一样,fetch(_ :)也会抛出错误,因此您必须在doblock中使用它。 如果在fetch期间发生错误,您可以检查catchblock内的错误并进行适当的响应。
构建并运行应用程序。 您应该立即看到之前添加的names列表:

在列表中添加更多names并重新启动应用程序以验证保存和提取是否正常。 如果没有删除应用程序,重置模拟器或将手机从高层建筑物中移开,则无论如何都会在table view中显示名称。
注意:此示例应用程序中有一些粗略的边缘:您必须每次从应用程序委托获取
managed object context,并且您使用KVC访问实体的属性而不是更自然的对象样式person.name。
Key Points - 关键点
-
Core Data提供磁盘持久性(on-disk persistence),这意味着即使在终止应用程序或关闭设备后,您的数据也可以访问。 这与内存中持久性不同,内存持久性只会在应用程序位于内存中时保存您的数据,无论是在前台还是在后台。 - Xcode附带了一个功能强大的数据模型编辑器
(Data Model editor),您可以使用它来创建managed object model。 -
managed object模型由entities,attributes和relationships组成 - 实体
entities是Core Data中的类定义。 - 属性
attributes是附加到实体的一条信息。 - 关系
relationships是多个实体之间的链接。 -
NSManagedObject是Core Data实体的运行时表示。 您可以使用键值编码来读取和写入其属性。 - 您需要
NSManagedObjectContext来从或者向Core Datasave()或fetch(_:)读取和写入数据。
源码
下面看一下Swift源码。
1. ViewController.swift
import UIKit
import CoreData
class ViewController: UIViewController {
@IBOutlet weak var tableView: UITableView!
var people: [NSManagedObject] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "The List"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Person")
do {
people = try managedContext.fetch(fetchRequest)
} catch let error as NSError {
print("Could not fetch. \(error), \(error.userInfo)")
}
}
@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.save(name: nameToSave)
self.tableView.reloadData()
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alert.addTextField()
alert.addAction(saveAction)
alert.addAction(cancelAction)
present(alert, animated: true)
}
func save(name: String) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Person", in: managedContext)!
let person = NSManagedObject(entity: entity, insertInto: managedContext)
person.setValue(name, forKeyPath: "name")
do {
try managedContext.save()
people.append(person)
} catch let error as NSError {
print("Could not save. \(error), \(error.userInfo)")
}
}
}
// 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
}
}
下面看一下效果展示

后记
好几天没有更新了,因为这几天做封闭区块链开发,今天有时间就更新了下,希望大家能喜欢,给个赞或者关注,祝大家中秋快乐(迟到的祝福!)。
