引言
在这个教程中,你会看到在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。

检查看看Xcode的 Use Core data 框架 AppDelegate.swift中自动生成了 Core data stack 样板代码。
Core Data 堆栈是由一组便于保存和检索信息的对象所组成。还有一个目的是管理Core Data状态作为一个表示数据模型等等的整体。
Note:不是所有的Xcode模板都可以选择 Use Core data 的选项。在Xcode 6中,只有 Master-Detail Application 和 Single View Application 两个模板可以有该选项。
本文例子中得app非常简单。就是会有一个命名为“hit list”的列表视图,你能够将名称添加到列表中,你将使用Core Data来保证数据存储。在本教程中,我们不会以任何暴力形式强迫你,你可以认为这个app是个“favorites list”,用于记录你的好友,这当然没有问题!
点击进入Main.storyboard在Interface Builder中打开。选择View Controller 然后改变他的常规高度和宽度匹配iPhone的纵向模式:

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

回到Interface Builder,从对象库的标示图中拖拽一个Table View出来,并覆盖整个视图。
然后再拖拽一个Bar Button Item放置到新增加的视图控制器的导航栏上。最后,双击Bar Button Item将文本设置为 Add 。你的画布应该像下面的截图那样:

每次你点击顶端右侧的Add时,将会在屏幕上出现一个包含文本框的警告,你可以在里面输入某人的名字到文本框中。退去警告将保存名字并刷新列表视图显示所有你保存过的名字。
在你完成这些功能之前,你需要连接视图控制器和列表视图的数据源。按住Ctrl拖拽列表视图到导航栏上方黄色的视图控制器图标,如下图所示,然后点击dataSource:

你可能会疑惑为什么不需要设置列表视图的代理,这是因为点击cell并不会触发任何的事件。没有比这更简单的了!
通过按下 Command-Option-Enter 或者选择Xcode工具条目上的中间按钮,打开辅助编辑器。按住Ctrl拖拽
列表视图到ViewController.swift,在类定义中插入outlet:

取名为tableView,如下行所示:
@IBOutlet weak var tableView: UITableView!
按住Ctrl拖拽Add按钮到ViewController.swift,同时创建一个action代替outlet生成一个方法名为addName的事件:
@IBAction func addName(sender: AnyObject){
}
现在你可以参考下表格视图和按钮条目的action代码。接着,设置标示图的模型,在ViewController.swift中添加如下属性。
//Insert below the tableView IBOutlet
var names = [String]()
names是一个可变的数组,用来保存显示列表视图上的字符。
将viewDidLoad的实现替换成如下代码:
override func viewDidLoad() {
super.viewDidLoad()
title = "\"The List\""
tableView.registerClass(UITableViewCell.self,
forCellReuseIdentifier: "Cell")
}
这里将在列表视图中注册一个UITableViewCell的类,你这样做将会使你再队列cell的时候,让列表视图返回一个正确类型的cell。
还是在ViewController.swift, ViewController将通过编辑类声明来确认遵守UITableViewDataSource协议:
//Add UITableViewDataSource to class declaration
class ViewController: UIViewController, UITableViewDataSource {
此时,Xcode将报错关于viewController没有遵守协议。
在viewDidLoad下面实现如下数据源方法来解决这个问题:
// 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:
//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按钮时,该方法将会弹出一个带有文本框和两个按钮Save和Cancel的UIAlertController。
点击Save将会插入你当前文本框上的内容到names数组中,并且会刷新列表视图。由于names数组是支持列表视图的模型,所以无论你输入什么到文本框中,都会在列表视图上展示。
最后,该第一次运行你的程序了,点击Add按钮,警告控制器就会像这个样子:

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

你的列表视图会展示你保存名称的数组中的数据,但这里最大的问题是缺少持续性。这个数组只是存在内存中,如果你强退或者重启你的设备,你的名单将会消失。
Core Data提供了持续性,这意味着他能以一个更持久的状态存储数据,使他能在app重启或者设备重启之后还存在。
你还没有添加Core Data, 所以在你退出app后没有什么可以保存下来。让我们试一下看看。如果你用的是真机调试,按住Home建,或者在你的模拟器中按下 Shift+Command+H,这样将会让你回到熟悉的网格主页。

从主页上点击HitList图标,重新回到app的前台。名称还是留在屏幕上,怎么回事?
但你点击Home键时,app从前台进入到后台。这时,操作系统会暂时冻结你内存上的所有内容,包括你的names数组。同样地道理,当你唤醒app返回到前台,操作系统将恢复到之前的状态,就像你没有离开过一样。
Apple 在iOS4之后引入了多线程, 他们为用户创建了一个无缝的体验,但这为开发者的持久性数据带来了来疑惑。这些名称真的是持久的吗?
不,这并不是持久的。如果你在程序快速切换页面完全杀死app或者关掉你的手机,那些名称将会消失。你当然可以去验证他。在程序运行的前台,双击Home键进入程序快速切换页面,如下:

从这里,向上滑出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的数据模型文件。

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

数据模型编辑器界面有很多功能,从现在开始,我们专注于创建单一的Core Data实体。
在左侧点击 Add Entity创建一个新的实体,双击新的实体,将名称更改为Person,如下:

你可能会疑惑卫生么模型编辑器要使用术语"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 :

在Core Data中,属性有很多种数据类型--其中一种是String。
保存数据到Core Data
在ViewController.swift顶端引入头文件:
//Add below "import UIKit"
import CoreData
如果你之前使用过OC的框架,你可能需要在你工程的Build Phases中链接框架,在Swift中,一个简单地import就是你所有需要开始使用Core Data的API要做的事情。
接着,替换之前列表视图的模型如下:
//Change [String] to [NSManagedObject]
var people = [NSManagedObject]()
你需要存储的时Person的实体,而不仅仅只是名字,所以你需要将列表视图的数据模型数组重命名为people。它现在拥有的时一个NSManagedObject的实例而不仅仅只是简单地字符串。
NSManagedObject表示存储在Core Data中的一个单一对象,你必须使用它来创建,编辑,保存和删除来完成你的Core Data持久化存储。正如你很快就要看到的,NSManagedObject是一个模型接口(shap shifter)。他以实体的形式存在在你的数据模型中,你可以随意使用你定义的attributes和relationship。
因为你改变了列表视图模型,你必须替换掉你之前实现的数据源的方法,如下:
//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中获取的名字属性,如下:
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方法如下:
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。添加saveName到ViewController.swift,如下所示:
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的补充,这段代码的作用:
-
在你可以保存或者检索你的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的引用,你才访问它。 -
你创建了一个新的管理对象,然后插入到managed object context中。你可以同时完成
NSManagedObject的初始化:init(entity:insertIntoManagedObjectContext:)。你可能想知道
NSEnityDescription是怎么一回事,回想一下,我们把NSManagedObject称作模型接口(shap shifter)类是因为它表示一个实体。一个实体描述是在运行时从带有NSManagedObject实例的数据模型的实体定义的链接。 随着得到
NSManagedObject,你可以通过使用键-值编码设置name属性。你必须拼写KVC的key值(这里指"name"),而且它必须存在在你的数据模型中,否则你的app可能会在运行时奔溃。提交你你修改到
person然后通过调用managed object context中得save来保存到磁盘上。注意,save的参数是一个指向NSError的指针。如果你的存储操作有任何的错误,你将能够检查出错误,并在必要的时候提醒用户。恭喜你!你新的管理对象现在已经可以安全的存储在Core data的持久性存储中。插入新的管理对象到
people数组中,这样就能在你刷新时,显示在列表视图里。
这里会比一个字符串数组稍稍复杂一些,但并不会难。这里的一些代码-获取managed object context 和实体-能搞在你的init或者viewDidLoad只生成一次,然后重复使用。这里为了简单,在每一个方法你都实现了一次。
编译并运行app,然后添加一些名字:

如果你的名字确实被储存在Core Data中,你的HitList app应该能通过持久化测试。双击Home键回到快速app切换页面,上滑HitList app杀死进程。
从页面中点击app并重新启动,稍等片刻,怎么回事?列表视图怎么会是空的?

你已经保存到Core Data了,但是重启app后,people数组却依然还是什么都没有!其实那些数据已经坐在那里等你饿了,只是你还没有取得它。
从Core Data中读取数据
为了从你的持久化存储中为managed object context读取数据,你必须把他拿出来。添加如下方法到ViewController.swift中:
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)")
}
}
一步一步来解释这些代码都做了什么:
正如上一章节提到的,在你使用Core Data之前,你需要一个managed object context,读取数据也一样。你需要通过application delegate来获取一个managed object context的引用。
顾名思义,
NSFetchRequest是负责从Core Data中读取数据的类。获取请求十分强大且灵活,你可以用请求来读取一组满足特定条件的对象(例如:给我读取所有举着在Wisconsin并且至少在公司工作三年以上的雇员),也可以单个值(例如:给我读取数据库中名字最长的雇员),功能不单单这些。
读取请求有一些提炼返回结果的修饰词。从现在开始,你应该知道NSEntityDescription是其中一个修饰语(这一个是必须的)。
设置过去请求的实体property,或者以init(entityName:)进行初始化,读取特定实体的对象。这就是你再获取所有Person实体要做的事。处理读取请求给managed object context去做一些繁重的任务。
executeFetchRequest(_:error:)返回一个在请求中指定标准的管理对象可选数组。
Note:如果没有匹配的读取请求条件的对象,该方法返回一个包含空数组的可选值。
如果在读取时发生了错误,则方法返回一个包含0的可选值。如果发生这种情况,你可以检查NSError并采取相应措施。
再次编译并运行代码,现在,你应该可以看到你之前添加的名字列表。

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

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