我把CoreData叫做大宝剑,为什么呢,因为CoreData用起来着实让人感到舒爽~~这一篇让我们来详细的了解一下大宝剑~
- 参考书籍:CORE DATA by Tutorials
- 默认有swift基础。
- 默认已阅读上一篇内容。
这一篇主要内容:
- 深入了解CoreData对象;
- 创建自己的栈;
- data model中的Relationships属性;
- 删除数据;
(1)、深入了解CoreData对象
之前在我们创建工程的时候勾选了Use CoreData的选择框,勾选这个选择框以后在AppDelegate.swift中自动生成了以下四个对象:
- NSManagedObjectModel
- NSPersistentStore
- NSPersistentStoreCoordinator
- NSManagedObjectContext
在这一篇中我们不勾选这个选择框,自己添加这四个类以便于详细学习“大宝剑”。像这样:
NSManagedObjectModel是什么?
这个对象就是你的data model,代表了里面的每一个对象类型。
你可以把他当作是你的数据库的图形化显示。呼呼~~和“度娘”的解释差不多。
Note:如何将该对象与data model联系起来?
来看这个方法:
let modelURL = NSBundle.mainBundle().URLForResource("CoreDataTest3", withExtension: "momd")!
通过这个方法,编译器将data model文件编译以后放入了一个.momd文件夹,并返回了这个文件夹的地址,通过这个地址我们初始化了我们的NSManagedObjectModel对象。
NSPersistentStore是什么?
无论你决定使用哪种方法来进行存储你都得使用NSPersistentStore来进行存储或者读写数据。CoreData提供了三种原子操作对象和一种非原子操作对象。
原子操作对象在你操作任何读写操作之前将数据全部读写到内存中,非原子操作对象则相反,在需要的时候加载数据。
来看看这四种NSPersistentStore对象
- NSQLiteStoreType支持SQLite数据库。他是CoreData中唯一的非原子操作存储类型。这基本是绝大多数应用最好的选择了,xcode在默认状态下使用的就是这种存储类型。
- NSXMLStoreType支持XML文件,这是一种可读的存储类型。这是一种原子操作类型,只在OS X上使用
- NSBinaryStoreType支持二进制文件,原子操作类型,很少在项目中用到。
- NSInMemoryStoreType是一个对内存中的数据进行存储的类型,所以这并不是一个正真的“持久化”存储类型。存储在内存中有助于测试,但并没有做到数据持久化。
NSPersistentStoreCoordinator是什么?
这是NSManagedObjectModel和NSPersistentStore之间的桥梁。他能够向NSManagedObjectModel发送信息并存储,也能够从NSPersistentStore中读取信息。
NSManagedObjectContext是什么?
这是我们之前唯一见到过的一个类型了。我们已经提到过这些特性(更多特性后面的篇章会提到):
- 在内存中也就是我们一直说的‘暂存器’中工作。
- 如同前面所说CoreData的任何操作的第一步就是创建NSManagedObjectContext对象。
- 在你使用save()方法之前,你的任何改变都不会影响我们磁盘中的数据
以上内容不了解也没事,因为我们会一个一个用到的。
(2)、自己来创建一个栈
创建一个新文件CoreDataStack.swift(在这个文件中,我们将自己添加全部之前xcode自动生成的代码,以便于对那四个对象的理解),添加以下代码:
import CoreData
class CoreDataStack {
let context:NSManagedObjectContext!
let psc:NSPersistentStoreCoordinator!
let model:NSManagedObjectModel!
let store:NSPersistentStore!
init() {
//1
let bundle = NSBundle.mainBundle()
let modelURL = bundle.URLForResource("CoreDataTest3", withExtension:"momd")
model = NSManagedObjectModel(contentsOfURL: modelURL!)
//2
psc = NSPersistentStoreCoordinator(managedObjectModel:model)
//3
context = NSManagedObjectContext()
context.persistentStoreCoordinator = psc
//4
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
let documentsURL = urls[0]
let storeURL = documentsURL.URLByAppendingPathComponent("Dog Walk")
let options = [NSMigratePersistentStoresAutomaticallyOption: true]
var error: NSError? = nil
store = psc.addPersistentStoreWithType(NSSQLiteStoreType,
configuration: nil, URL: storeURL, options: options, error:&error)
if store == nil {
println("Error adding persistent store: \(error)")
abort()
}
}
func saveContext() {
var error: NSError? = nil
if context.hasChanges && !context.save(&error) {
println("Could not save: \(error), \(error?.userInfo)") }
}
}
}
一下子添加了这么一大块代码,一眼看过去一定是晕晕的,好的,一句一句来解释一下吧。
- 首先添加了四个常量,这四个常量就是我们之前讲了很久的那四个CoreData的对象,而接下来的初始化方法就是对这四个常量进行初始化。
- 初始化函数。
- //1 前面已经说过了NSManagedObjectModel代表着我们的data model。
let modelURL = bundle.URLForResource("CoreDataTest3", withExtension:"momd")
这个方法将data model编译成一个‘momd’文件,并返回他的地址,NSManagedObjectModel对象则是通过这个地址来进行初始化。 - //2 对NSPersistentStoreCoordinator进行初始化,他是data model与‘暂存器’NSManagedObjectContext之间的桥梁。
- //3 对‘暂存器’NSManagedObjectContext进行初始化,并与我们的‘桥梁’连接起来。
- //4 通过
func addPersistentStoreWithType(storeType: String, configuration: String?, URL storeURL: NSURL?, options: [NSObject : AnyObject]?, error: NSErrorPointer) -> NSPersistentStore?
方法对NSPersistentStore进行初始化,在这里我们指定了persistent store类型。 - 最后我们添加了保存方法,也就是将‘暂存器’context保存到磁盘中,代码很容易理解,就不进行解释了。
以上添加的代码仔细观察的话,很容易就会发现基本就是在我们勾选了 use core data 以后xcode自动生成的代码,而我们只是将它写在了一个类中,以便于观察及理解。
像我们上面那样创建了一个类,但是这个类根本没有参与到我们的程序中来,接下来就是将这个类参与到程序中。
打开AppDelegate.swift,添加以下代码:
import CoreData
lazy var coreDataStack = CoreDataStack()
如此一来coreDataStack这个变量就拥有了我们之前写的四个对象了。
在下面两个方法中添加coreDataStack.saveContext():
func applicationDidEnterBackground(application: UIApplication) {
coreDataStack.saveContext()
}
func applicationWillTerminate(application: UIApplication) {
coreDataStack.saveContext()
}
这俩方法确定应用在发生意外退出的时候保存数据,使数据不会丢失。
再来观察一下我们的文件目录,哦~我们还差一个.xcdatamodeld文件,那么就来新建一个吧。
右击文件目录->New file...->选择iOS Core Data->选择Data Model->next->命名为‘CoreDataTest3’,像这样:
现在来看我们的程序就和勾选了use core data选择一模一样了,接下来就可以进行前面两篇的操作,而且效果相同。哦~唯一的区别就是使用coreDataStack.context来替代前面篇章中AppDelegate.swift中的managedObjectContext。
(3)、relationships属性和删除功能
接下来的内容,我们通过一个Demo,来演示一下data model中的relationships属性和删除功能。
打开storyboard删除原有的控制器,拖入一个tableviewcontroller,并将之设置为程序人口,如图所示:
再为这个控制器创建一个类,如以下步骤:
New File->选择iOS Source,继续选择cocoa touch class->next->将其选择为UITableviewController的子类->next->Create
同时将控制器和我们新建的文件进行绑定。
将控制器转化为Navigationcontroller,日图操作:
给navigation Bar添加一个right Button,并添加动作‘addTime’,添加代码。
将实现以下功能,由于这段实现并不复杂,在这里就不进行描述,有问题的可以留言。
目前这个版本并没有实现数据持久化,当我们退出应用以后数据将消失,接下来我们做的就是将数据持久化,这听起来好像在前两篇我们已经做过了,不然~~~在这里我们将引进relationships的概念,同时实现删除功能。
- relationships
这个逻辑是这样的,我们需要一个数组来存放时间,我们暂且把这个数组叫做‘时间组’,这个‘时间组’存放了许多时间,那么这个时间组就是一个Entity,里面存放的时间也是Entity,不过时间组这个Entity的每一个对象都拥有很多的时间Entity,先来创建这两个Entity,一个起名为“TimeArry”,一个起名为“Time”,在"Time"这个Entity中有一个属性time类型选择为NSDate:
而在“TimeArry”中则存在一个relationship,起名为“times”,指向“Time”Entity:
Note:relationship生成的属性是什么类型的?“To Many”生成的是NSSet类型,如果想使用下标来来使用对象的话,请在右边的属性栏中勾选Ordered选项,当你勾选了这个选项以后生成的“To Many”类型就是NSOrderedSet类型。
在这里我们使用NSOrderedSet类型
同时将Type选择为“To Many”,勾选Ordered。
给我们的两个Entity生成对象类吧,生成方法在上一篇中已经讲过:
Editor—>Create NSManagedObject Subclass .............
首先要添加的代码当然是addTime方法来添加数据,在此方法中添加以下代码:
@IBAction func addTime(sender: AnyObject) {
//获取当前时间
let date=NSDate()
//1
let entity = NSEntityDescription.entityForName("Time", inManagedObjectContext: managedContext)
let TimeObject = Time(entity: entity!, insertIntoManagedObjectContext: managedContext)
TimeObject.time=date
//2 Insert the new times into the TimeArry's times set
var times = timearry.times.mutableCopy() as! NSMutableOrderedSet
times.addObject(TimeObject)
timearry.times = times.copy() as! NSOrderedSet
//3 Save the managed object context
var error: NSError?
if !managedContext!.save(&error) {
println("Could not save: \(error)")
}
//4
let timeFetch = NSFetchRequest(entityName: "TimeArry")
let result = managedContext.executeFetchRequest(timeFetch, error: &error) as! [TimeArry]!
self.timearry=result[0]
self.tableview.reloadData()
}
代码解释:
- //1 初始化一个“Time”对象实例。
- //2
- 初始化times属性
- 将之前的“Time”对象实例
- 添加到relationships中
- 将relationship添加到当前显示的“TimeArry”
- //3 保存数据
- //4 更新timearry数组
Note:timearry就是我们在界面上显示的“TimeArry”对象的一个实例,按理来说TimeArry有很多个对象,每一个“TimeArry”对象都有自己的“Time”,这样说起来好像很像一个二维数组,而事实上我们只显示了这个二维数组的第一行。那么在进入程序的时候我们就得创建这个timearry对象。
添加一下代码:
var timearry:TimeArry!
override func viewDidLoad() {
super.viewDidLoad()
//初始化暂存器
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
managedContext = appDelegate.coreDataStack.context
//1 获取“TimeArry”对象,并初始化timearry
var error: NSError?
let timeFetch = NSFetchRequest(entityName: "TimeArry")
let result = managedContext.executeFetchRequest(timeFetch, error: &error) as! [TimeArry]!
if result.count == 0 {
let entity = NSEntityDescription.entityForName("TimeArry", inManagedObjectContext: managedContext)
self.timearry = TimeArry(entity: entity!, insertIntoManagedObjectContext: managedContext)
}else{
self.timearry=result[0]
}
// 添加Edit按钮
self.navigationItem.leftBarButtonItem = self.editButtonItem()
}
这段代码很容易理解(如果你看了我前面的两篇内容的话),就是读取了“TimeArry”的内容,然后如果存在数据,则使timearry为第一组数据,若不存在数据则初始化。
因为我们是用一个tableview来显示数据,所以添加以下代码:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.timearry.times.count
}
就不对这段代码做解释了,大家应该都懂。
接下来就是在界面上显示数据了:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
var fmt=NSDateFormatter()
fmt.dateFormat = "yyyy-MM-dd-hh-mm-ss"
let date = self.timearry.times[indexPath.row] as! Time
let showtime = fmt.stringFromDate(date.time)
cell.textLabel!.text = showtime
return cell
}
"timearry"这个对象的times属性就是我们要读取的内容,你可以把他当作一个数组来操作,因为他也可以用下表来获取期中的每一个数据。
若你在之前data model没有勾选Ordered则在这里生成的times是NSSet类型,那么就不可以用下标来获取内容了。
现在来运行下app:
现在来添加删除功能,以下代码:
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return NO if you do not want the specified item to be editable.
return true
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
//1
let timeToRemove = self.timearry.times[indexPath.row] as! Time
//2
let times = self.timearry.times.mutableCopy() as! NSMutableOrderedSet
times.removeObject(timeToRemove)
self.timearry.times = times.copy() as! NSOrderedSet
//3
managedContext.deleteObject(timeToRemove)
//4
var error: NSError?
if !managedContext.save(&error) {
println("Could not save: \(error)")
}
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
} else if editingStyle == .Insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
第一个方法添加左滑编辑功能。
来解释下第二个方法,当你点击Delete会调用此方法:
- 首先获取要删除的对象
-
- 先获取NSOrderedSet对象
- 从中删除对象
- 同步到self.timearry.times
- 从内存中删除对象
- 保存‘暂存器’
大功告成,这一篇写的好艰苦啊,逻辑混乱,有看不懂的小朋友,实在不好意思了,不清楚的部分请留言,我来解释。
最后运行一下吧:
源代码已上传Github:https://github.com/superxlx/CoreDataTest3