版本记录
版本号 | 时间 |
---|---|
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(_ :)
也会抛出错误,因此您必须在do
block中使用它。 如果在fetch
期间发生错误,您可以检查catch
block内的错误并进行适当的响应。
构建并运行应用程序。 您应该立即看到之前添加的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
}
}
下面看一下效果展示
后记
好几天没有更新了,因为这几天做封闭区块链开发,今天有时间就更新了下,希望大家能喜欢,给个赞或者关注,祝大家中秋快乐(迟到的祝福!)。