我的第一个Core Data APP -- Swift

引言

在这个教程中,你会看到在Xcode提供的初始化代码模板和数据模型编辑器资源中,用Swift语言写出你的第一个Core Data app,将会是一件多么容易上手的事情

  • 使用Xcode的模型编辑器在Core Data中创建你想存储的模型数据
  • 在Core Data中添加一条新记录
  • 从Core Data中获取一组记录
  • 在列表视图中显示用户的数据

你还可以看到一个Core Data在在后台是如何与各组件进行交互的。我们正在超越自我,但-是时候简历一个应用程序了!

本文翻译自 http://www.raywenderlich.com/85578/first-core-data-app-using-swift

Ray的说明:本文是Core Data by Tutorials一书中iOS8 Feast部分章节的微缩版本,让你了解一下这是一本什么样的书。我们希望你喜欢!

开始教程

打开Xcode,然后选择 Single View Application 模板,创建一个新的iPhone工程,将其命名为 HitList,并勾选 Use Core data

p1

检查看看Xcode的 Use Core data 框架 AppDelegate.swift中自动生成了 Core data stack 样板代码。

Core Data 堆栈是由一组便于保存和检索信息的对象所组成。还有一个目的是管理Core Data状态作为一个表示数据模型等等的整体。


Note:不是所有的Xcode模板都可以选择 Use Core data 的选项。在Xcode 6中,只有 Master-Detail ApplicationSingle View Application 两个模板可以有该选项。


本文例子中得app非常简单。就是会有一个命名为“hit list”的列表视图,你能够将名称添加到列表中,你将使用Core Data来保证数据存储。在本教程中,我们不会以任何暴力形式强迫你,你可以认为这个app是个“favorites list”,用于记录你的好友,这当然没有问题!

点击进入Main.storyboard在Interface Builder中打开。选择View Controller 然后改变他的常规高度和宽度匹配iPhone的纵向模式:

p2

接着,嵌入一个导航控制器到视图控制器中。从Xcode的 Editor 菜单,选择 Embed In…\ Navigation Controller

p3

回到Interface Builder,从对象库的标示图中拖拽一个Table View出来,并覆盖整个视图。

然后再拖拽一个Bar Button Item放置到新增加的视图控制器的导航栏上。最后,双击Bar Button Item将文本设置为 Add 。你的画布应该像下面的截图那样:

p4

每次你点击顶端右侧的Add时,将会在屏幕上出现一个包含文本框的警告,你可以在里面输入某人的名字到文本框中。退去警告将保存名字并刷新列表视图显示所有你保存过的名字。

在你完成这些功能之前,你需要连接视图控制器和列表视图的数据源。按住Ctrl拖拽列表视图到导航栏上方黄色的视图控制器图标,如下图所示,然后点击dataSource

p5

你可能会疑惑为什么不需要设置列表视图的代理,这是因为点击cell并不会触发任何的事件。没有比这更简单的了!

通过按下 Command-Option-Enter 或者选择Xcode工具条目上的中间按钮,打开辅助编辑器。按住Ctrl拖拽
列表视图到ViewController.swift,在类定义中插入outlet:

p6

取名为tableView,如下行所示:

1
@IBOutlet weak var tableView: UITableView!

按住Ctrl拖拽Add按钮到ViewController.swift,同时创建一个action代替outlet生成一个方法名为addName的事件:

1
2
3
@IBAction func addName(sender: AnyObject){ 

}

现在你可以参考下表格视图和按钮条目的action代码。接着,设置标示图的模型,在ViewController.swift中添加如下属性。

1
2
//Insert below the tableView IBOutlet
var names = [String]()

names是一个可变的数组,用来保存显示列表视图上的字符。

viewDidLoad的实现替换成如下代码:

1
2
3
4
5
6
override func viewDidLoad() {
super.viewDidLoad()
title = "\"The List\""
tableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
}

这里将在列表视图中注册一个UITableViewCell的类,你这样做将会使你再队列cell的时候,让列表视图返回一个正确类型的cell。

还是在ViewController.swift, ViewController将通过编辑类声明来确认遵守UITableViewDataSource协议:

1
2
//Add UITableViewDataSource to class declaration
class ViewController: UIViewController, UITableViewDataSource {

此时,Xcode将报错关于viewController没有遵守协议。

viewDidLoad下面实现如下数据源方法来解决这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// MARK: UITableViewDataSource
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int)
-> Int {

return names.count
}

func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath)
-> UITableViewCell {


let cell =
tableView.dequeueReusableCellWithIdentifier("Cell")
as UITableViewCell

cell.textLabel!.text = names[indexPath.row]

return cell
}

如果你使用过UITableView,这些代码将会看起来很熟悉。第一个方法是说明列表视图的行数和names数组的字符串个数一样多。

第二个方法tableView(_:cellForRowAtIndexPath:),队列他们的列表视图cell并按照names中字符串填充.

先不要运行app。你还需要输入字符串好让列表视图来显示他们。

实现你之前拖拽的代码addName IBAction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Implement the addName IBAction
@IBAction func addName(sender: AnyObject) {

var alert = UIAlertController(title: "New name",
message: "Add a new name",
preferredStyle: .Alert)

let saveAction = UIAlertAction(title: "Save",
style: .Default) { (action: UIAlertAction!) -> Void in

let textField = alert.textFields![0] as UITextField
self.names.append(textField.text)
self.tableView.reloadData()
}

let cancelAction = UIAlertAction(title: "Cancel",
style: .Default) { (action: UIAlertAction!) -> Void in
}

alert.addTextFieldWithConfigurationHandler {
(textField: UITextField!) -> Void in
}

alert.addAction(saveAction)
alert.addAction(cancelAction)

presentViewController(alert,
animated: true,
completion: nil)
}

每当你点击Add按钮时,该方法将会弹出一个带有文本框和两个按钮SaveCancelUIAlertController
点击Save将会插入你当前文本框上的内容到names数组中,并且会刷新列表视图。由于names数组是支持列表视图的模型,所以无论你输入什么到文本框中,都会在列表视图上展示。

最后,该第一次运行你的程序了,点击Add按钮,警告控制器就会像这个样子:

p7

添加4到5个名称到列表中。你应该会获得如下样式:

p8

你的列表视图会展示你保存名称的数组中的数据,但这里最大的问题是缺少持续性。这个数组只是存在内存中,如果你强退或者重启你的设备,你的名单将会消失。

Core Data提供了持续性,这意味着他能以一个更持久的状态存储数据,使他能在app重启或者设备重启之后还存在。

你还没有添加Core Data, 所以在你退出app后没有什么可以保存下来。让我们试一下看看。如果你用的是真机调试,按住Home建,或者在你的模拟器中按下 Shift+Command+H,这样将会让你回到熟悉的网格主页。

p9

从主页上点击HitList图标,重新回到app的前台。名称还是留在屏幕上,怎么回事?

但你点击Home键时,app从前台进入到后台。这时,操作系统会暂时冻结你内存上的所有内容,包括你的names数组。同样地道理,当你唤醒app返回到前台,操作系统将恢复到之前的状态,就像你没有离开过一样。

Apple 在iOS4之后引入了多线程, 他们为用户创建了一个无缝的体验,但这为开发者的持久性数据带来了来疑惑。这些名称真的是持久的吗?

不,这并不是持久的。如果你在程序快速切换页面完全杀死app或者关掉你的手机,那些名称将会消失。你当然可以去验证他。在程序运行的前台,双击Home键进入程序快速切换页面,如下:

p10

从这里,向上滑出HitList的快照来终止应用程序。这应该不会存在一丝HitList的内存(这里没有双关语)。返回到主页点击HitList的图标重新启动app,验证名称是否消失。

如果你开发过iOS或者熟悉多线程编程的方法,内存暂存和持久性的区别显而易见。但在用户的眼里,他们并不关心名称是否还存在内存中,他们不关心app是进入后台,然后回来,或者app是否被保存,并重新加载。

他们在意的是当他们返回的时候,名称是否还在。

所以,在本教程中你即将学会的是你一个让你在重新启动app时依然保存有之前数据的持久化教程。

为你的数据创建模型

现在你知道改如何验证数据的持久性了,那就让我们开始Core Data吧!HitList的目标非常简单:持久化你输入的名称,使它们在你重新启动app后,依然可见。

到现在为止,你已经实现用Swift字符串在内存中储存名称。在这个章节,你将用Core Data对象替换这些字符串。

第一步,先创建一个阐述在磁盘上表示Core Data的managed object model。默认情况下,Core Data 使用SQLite数据库进行持久化存储,所以你可以认为数据模型就是数据库架构。


Note: 你会在这个教程中遇到颇多managed单词。如果你再类名中见到managed这个单词,比如NSManagedObjectContrxt,说明你再处理一个Core Data的类。Managed表示Core Data对象生命周期的Core Data数据管理。

但是不要以为所有的Core Data类都包含managed,事实上,大多数是不包含的。对于Core Data类的完整列表,请查看OC中得头文件CoreData/CoreData.h


当你在创建HitList工程时勾选了Core Data,Xcode会为你自动生成一个名为HitList.xcdatamodeld的数据模型文件。

p11

点击并打开HitList.xcdatamodeld,你将看到如下强大的数据模型编辑器界面:

p12

数据模型编辑器界面有很多功能,从现在开始,我们专注于创建单一的Core Data实体。

在左侧点击 Add Entity创建一个新的实体,双击新的实体,将名称更改为Person,如下:

p13

你可能会疑惑卫生么模型编辑器要使用术语”Enity”,难道你只是简单地定义一个新的类?正如你很快要看到,Core Data有他自己的词汇。下面是你会经常遇到的一些术语的简单整合:

  • entity在Core Data中是一个类定义。典型例子是雇员或者公司。在关系型数据库中,一个实体相当于一个表。
  • attribute是附加到特定实体上的一条信息。例如雇员实体能包含雇员的名字,职位和年薪。在数据库中,一个属性相当于一个表中得特定字段。
  • relationship是多个实体之间的链接。在Core Data中,两个实体之间的关系叫做对一关系,多个实体间的关系叫做对多关系。例如,一个经理可以和多个雇员这个就是对多关系,但一个雇员只能有一个经理,这就是对一关系。

Note:你可能已经注意到,实体看起来有点像类,类似的,attribute/relationship看起来有点像properties。他们有什么区别呢?你可以认为Core Data的实体当做一个定义类,把Core Data管理对象当做这个类的实例


现在你已经知道attribute是什么了,回到模型编辑器界面在Person中添加一个attribute。选择左手边的Person,点击Attributes下的加号(+)。设置一个新的属性名,取名为name并设置他的type为String

p14

在Core Data中,属性有很多种数据类型–其中一种是String。

保存数据到Core Data

ViewController.swift顶端引入头文件:

1
2
//Add below "import UIKit"
import CoreData

如果你之前使用过OC的框架,你可能需要在你工程的Build Phases中链接框架,在Swift中,一个简单地import就是你所有需要开始使用Core Data的API要做的事情。

接着,替换之前列表视图的模型如下:

1
2
//Change [String] to [NSManagedObject]
var people = [NSManagedObject]()

你需要存储的时Person的实体,而不仅仅只是名字,所以你需要将列表视图的数据模型数组重命名为people。它现在拥有的时一个NSManagedObject的实例而不仅仅只是简单地字符串。

NSManagedObject表示存储在Core Data中的一个单一对象,你必须使用它来创建,编辑,保存和删除来完成你的Core Data持久化存储。正如你很快就要看到的,NSManagedObject是一个模型接口(shap shifter)。他以实体的形式存在在你的数据模型中,你可以随意使用你定义的attributes和relationship。

因为你改变了列表视图模型,你必须替换掉你之前实现的数据源的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//Replace both UITableViewDataSource methods
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int)
-> Int {

return people.count
}

func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath)
-> UITableViewCell {


let cell =
tableView.dequeueReusableCellWithIdentifier("Cell")
as UITableViewCell

let person = people[indexPath.row]
cell.textLabel!.text = person.valueForKey("name") as String?

return cell
}

最显著的变化在cellForRowAtIndexPath中,替换模型数组中与cell匹配的对应字符串,你可以使用语cell相对应的NSManagedObject
设置你从NSManagedObject中获取的名字属性,如下:

1
cell.textLabel.text = person.valueForKey("name") as String

为什么必须这样做?事实证明,NSManagedObject并不知道你再数据模型重定义的name属性,所以没有办法直接用属性访问它。Core Data 提供读出值的唯一办法只有键-值编码,就是通常所说的KVC。


Note:如果你是iOS开发的新手,你可能对KVC或者键-值编码不熟悉。

KVC是Cocoa和Cocoa Touch通过间接使用标识对象属性的字符串来访问对象属性的机制。在这种情况下,KVC使NSManagedObject感觉像是字典。

键-值编码可用于继承自NSObject的所有类,包括NSManagedObject。你不能够在没有继承自NSObject的Swift对象中用KVC访问属性


接着,替换保存事件addName @IBAction方法如下:

1
2
3
4
5
6
7
let saveAction = UIAlertAction(title: "Save",
style: .Default) { (action: UIAlertAction!) -> Void in

let textField = alert.textFields![0] as UITextField
self.saveName(textField.text)
self.tableView.reloadData()
}

这需要在输入文本到文本框中,然后通过一个方法叫saveName。添加saveNameViewController.swift,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func saveName(name: String) {
//1
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate

let managedContext = appDelegate.managedObjectContext!

//2
let entity = NSEntityDescription.entityForName("Person",
inManagedObjectContext:
managedContext)

let person = NSManagedObject(entity: entity!,
insertIntoManagedObjectContext:managedContext)

//3
person.setValue(name, forKey: "name")

//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
//5
people.append(person)
}

这里是Core Data的补充,这段代码的作用:

  1. 在你可以保存或者检索你的Core Data存储之前,你需要先获得你的NSManagedObjectContext,你可以把托管对象文本认为是在内存中暂存的管理对象。

    想象一下把储存一个新的管理对象到Core Data看成两个步骤:首先,你插入一个新的管理对象到managed object context;然后,随你乐意提交你的管理对象到managed object context来储存到磁盘上。

    Xcode已经生成了一个managed object context 当做新工程模板的一部分——记住,这里仅仅是在你一开始已经选择了Use Core Data选项。这样会默认创建managed object context的property在application delegate当中。你要先得到app delegate的引用,你才访问它。

  2. 你创建了一个新的管理对象,然后插入到managed object context中。你可以同时完成NSManagedObject的初始化:init(entity:insertIntoManagedObjectContext:)

    你可能想知道NSEnityDescription是怎么一回事,回想一下,我们把NSManagedObject称作模型接口(shap shifter)类是因为它表示一个实体。一个实体描述是在运行时从带有NSManagedObject实例的数据模型的实体定义的链接。

  3. 随着得到NSManagedObject,你可以通过使用键-值编码设置name属性。你必须拼写KVC的key值(这里指”name”),而且它必须存在在你的数据模型中,否则你的app可能会在运行时奔溃。

  4. 提交你你修改到person然后通过调用managed object context中得save来保存到磁盘上。注意,save的参数是一个指向NSError的指针。如果你的存储操作有任何的错误,你将能够检查出错误,并在必要的时候提醒用户。

  5. 恭喜你!你新的管理对象现在已经可以安全的存储在Core data的持久性存储中。插入新的管理对象到people数组中,这样就能在你刷新时,显示在列表视图里。

这里会比一个字符串数组稍稍复杂一些,但并不会难。这里的一些代码-获取managed object context 和实体-能搞在你的init或者viewDidLoad只生成一次,然后重复使用。这里为了简单,在每一个方法你都实现了一次。

编译并运行app,然后添加一些名字:

p15

如果你的名字确实被储存在Core Data中,你的HitList app应该能通过持久化测试。双击Home键回到快速app切换页面,上滑HitList app杀死进程。

从页面中点击app并重新启动,稍等片刻,怎么回事?列表视图怎么会是空的?

p16

你已经保存到Core Data了,但是重启app后,people数组却依然还是什么都没有!其实那些数据已经坐在那里等你饿了,只是你还没有取得它。

从Core Data中读取数据

为了从你的持久化存储中为managed object context读取数据,你必须把他拿出来。添加如下方法到ViewController.swift中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)

//1
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate

let managedContext = appDelegate.managedObjectContext!

//2
let fetchRequest = NSFetchRequest(entityName:"Person")

//3
var error: NSError?

let fetchedResults =
managedContext.executeFetchRequest(fetchRequest,
error: &error) as [NSManagedObject]?

if let results = fetchedResults {
people = results
} else {
println("Could not fetch \(error), \(error!.userInfo)")
}
}

一步一步来解释这些代码都做了什么:

  1. 正如上一章节提到的,在你使用Core Data之前,你需要一个managed object context,读取数据也一样。你需要通过application delegate来获取一个managed object context的引用。

  2. 顾名思义,NSFetchRequest是负责从Core Data中读取数据的类。获取请求十分强大且灵活,你可以用请求来读取一组满足特定条件的对象(例如:给我读取所有举着在Wisconsin并且至少在公司工作三年以上的雇员),也可以单个值(例如:给我读取数据库中名字最长的雇员),功能不单单这些。
    读取请求有一些提炼返回结果的修饰词。从现在开始,你应该知道NSEntityDescription是其中一个修饰语(这一个是必须的)。
    设置过去请求的实体property,或者以init(entityName:)进行初始化,读取特定实体的对象。这就是你再获取所有Person实体要做的事。

  3. 处理读取请求给managed object context去做一些繁重的任务。executeFetchRequest(_:error:)返回一个在请求中指定标准的管理对象可选数组。


Note:如果没有匹配的读取请求条件的对象,该方法返回一个包含空数组的可选值。

如果在读取时发生了错误,则方法返回一个包含0的可选值。如果发生这种情况,你可以检查NSError并采取相应措施。


再次编译并运行代码,现在,你应该可以看到你之前添加的名字列表。

p17

非常棒!他们起死回生了!再添加一些名字到列表里,然后重新启动app来验证保存和读取是否正确运行。只要不是删除app,重置你的模拟器或把你的手机进行出厂恢复,无论如何你的米那个字列表都会出现在列表视图当中。

p18

接下来该何去何从?

在这个教程中,你已经体验到了基本的Core Data概念:数据模型,实体,attributes,管理对象,managed object context和读取请求。这里是完整的Hitlist工程

热评文章