@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css);
@import url(/css/cuteeditor.css);
1、组成结构
1.1 功能简介
Core Data是iOS的一个持久化框架,它提供了对象-关系映射(ORM)的功能,即能够将程序中的对象(swift或Object-C中类的实例)转化成数据,保存在SQLite数据库文件中,也能够将保存在数据库中的数据还原成程序中的对象。在此数据操作期间,我们不需要编写任何SQL语句。
图 6
左边是关系模型,即数据库,数据库里面有张person表,person表里面有id、name、age三个字段,而且有2条记录;右边是对象模型,可以看到,有2个OC对象;利用Core Data框架,我们就可以轻松地将数据库里面的2条记录转换成2个OC对象,也可以轻松地将2个OC对象保存到数据库中,变成2条表记录,而且不用写一条SQL语句。
1.2 Core Data堆栈
Core Data框架帮助程序员实现了在数据库上的查询、添加和修改的功能,从而程序员只需调用Core Data提供的接口就能实现增删改查的功能。其中Core Data的堆栈结构由如下组成:
- NSManagedObjectModel:称为被管理对象模型类,是系统中的"实体",与数据库中的表对象对应,可以了解为图4中对象的结合,该模型是通过项目中的.xcdatamodeld文件进行声明的。
- NSPersisntentStoreCoordinator:称为持久化存储协调器类,在持久化对象存储之上提供了一个接口,可以把它考虑成为数据库的连接。即相当是SQLite数据库中的SQLite3类型。
- NSManageedObjectContext:称为被管理对象上下文类,在上下文中可以查找、删除和插入对象,然后通过栈同步到持久化对象存储,即相当是SQLite数据库中的语句(sqlite3_Stmt类型)。其中程序员主要使用该实例对象间接地与数据库进行交互。
图 7
提示:
core data架构中的持久化对象存储执行所有底层转换,即实现从对象到数据之间的转换,并负责打开和关闭数据文件,其中Core Data的存储文件类型可以是:SQLite、二进制文件和内存文件。
2、第一个应用
如下是通过Xcode编译器的帮助来创建一个最简单的Core Data实例,本实例的功能是定义一个对象,从而能够讲对象保存到数据库,同时能够从数据库中读取对象。
2.1 创建项目
首先通过Xcode工具创建项目,其中为了减少工作量,我们通过Xcode的帮助来创建Core Data堆栈的三个对象。那么在创建项目时,需要勾选"Use core Data"的复选框,如图 6所示。
图 8
当创建IOS项目后,Xcode就会帮我们实现Core Data堆栈中三个对象的创建过程,其中它是创建在AppDelegate类中,如下所示:
1 class AppDelegate: UIResponder, UIApplicationDelegate {
2
3 ……
4
5 // MARK: - Core Data stack
6 lazy var applicationDocumentsDirectory: NSURL = {
7 let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
8 return urls[urls.count-1]
9 }()
10
11 lazy var managedObjectModel: NSManagedObjectModel = {
12 let modelURL = NSBundle.mainBundle().URLForResource("coreDataProject", withExtension: "momd")!
13 return NSManagedObjectModel(contentsOfURL: modelURL)!
14 }()
15
16 lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator = {
17 // Create the coordinator and store
18 let coordinator = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
19 let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("SingleViewCoreData.sqlite")
20 var failureReason = "There was an error creating or loading the application's saved data."
21 do {
22 try coordinator.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil)
23 } catch {
24 var dict = [String: AnyObject]()
25 dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
26 dict[NSLocalizedFailureReasonErrorKey] = failureReason
27
28 dict[NSUnderlyingErrorKey] = error as NSError
29 let wrappedError = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
30 NSLog("Unresolved error \(wrappedError), \(wrappedError.userInfo)")
31 abort()
32 }
33 return coordinator
34 }()
35
36 lazy var managedObjectContext: NSManagedObjectContext = {
37 let coordinator = self.persistentStoreCoordinator
38 var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
39 managedObjectContext.persistentStoreCoordinator = coordinator
40 return managedObjectContext
41 }()
42
43 // MARK: - Core Data Saving support
44 func saveContext () {
45 if managedObjectContext.hasChanges {
46 do {
47 try managedObjectContext.save()
48 } catch {
49 let nserror = error as NSError
50 NSLog("Unresolved error \(nserror), \(nserror.userInfo)")
51 abort()
52 }
53 }
54 }
55 }
2.2 定义实体
实体就是在项目中被持久化的对象,其中是通过在项目的.xcdatamodeld文件中进行声明的,如图 7所示添加了Entity实体,并在该实体中定义了两个属性:age和 name。
2.3 创建实体类(可选)
在上述中,我们定义了被持久化的实体,其中还可以为该实体定义一个相关的类,其中这个过程是可选的,不创建也可以获得被持久化的实例,其创建实体类的过程如图 10-图 12所示:
图 10
图 11
图 12
2.4 源码实现
对数据库的增删改查都是通过managedObjectContext对象来完成,所以首先需要获得该对象。
1)插入数据
1 func insertEntity()
2 {
3 // 获取appDelegate类中的managedObjectContext成员变量。
4 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
5 let context = appDelegate.managedObjectContext
6 // 创建一个实体对象,然后通过 context 对象将其保存到数据库中。
7 let entity = NSEntityDescription.insertNewObjectForEntityForName("Entity", inManagedObjectContext: context)
8 entity.setValue(1, forKey: "age")
9 entity.setValue("hello", forKey: "name")
10
11 do{
12 try context.save(); // 保存到数据库中。
13 }
14 catch
15 {
16 }
17 }
2)查询数据
1 func selectEntity()
2 {
3 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
4 let context = appDelegate.managedObjectContext
5
6 let request = NSFetchRequest(entityName:"Entity") // 创建查询语句,是无条件查询
7 var objects:[AnyObject]!
8 do
9 { // 执行查询,相当是SQLite3的封装查询,查询的结果返回到objects数组中。
10 try objects = context.executeFetchRequest(request)
11 }
12 catch
13 {
14 }
15
16 for object in objects // 验证查询的结果
17 {
18 var managerObject = object as! NSManagedObject
19 print(managerObject.valueForKey("age")!.integerValue);
20 print(managerObject.valueForKey("name")!)
21 }
22 }
3、基本功能
3.1 创建Managed Object 模型
在Core Data框架中的managed object模型就是要持久化的对象模型,即被管理的实体,其中该实体可以在Xcode中进行定义和声明,包括实体的属性、关系和类名,如图 13所示。
图 13
3.2初始化Core Data堆栈
Core Data堆栈由三个主要对象组成:NSManagedObjectModel、NSPersistentStoreCoordinator、NSManagedObjectContext。可以简单将三者理解为:
- NSManagedObjectModel:数据库文件,即是.xcdatamodeld文件;
- NSPersistentStoreCoordinator:与数据库的连接,相当是sqlite 3类型;
- NSManagedObjectContext:是与数据库连接后的语句句柄,相当是sqlite3_Stmt类型。
其三者的创建过程如图 12所示,从底层到上层的创建过程,而有关源码的内容可以参考4.2.1小结,同时可以将这部分创建的代码移到其它地方,不一定要放在appDelegate类中。
图 14
3.3 创建与保存Managed Object
Managed Objects就是在项目中的实体,即要被持久化的对象。一旦定义了Managed Object和初始化了Core Data堆栈,那么就可以创建被管理的对象。
1)创建managed Object
创建被管理的对象是通过NSEntityDescription类的静态方法 insertNewObjectForEntityForName来完成的,其声明如下:
public class func insertNewObjectForEntityForName(entityName: String,
inManagedObjectContext context: NSManagedObjectContext)
-> NSManagedObject
其在swift语言中的例子为:
1 let employee = NSEntityDescription.insertNewObjectForEntityForName("Employee", inManagedObjectContext:
self.managedObjectContext) as! AAAEmployeeMO
2)创建managed Object的子类
默认情况下,Core Data框架将会返回NSManagedObject类的实例,若有特殊的要求也可以实现NSManagedObject类的子类,如在4.2.3小结所示,但一般情况下都不需要。这里就不对其深入的讨论。
3)保存Managed Object
创建了NSManagedObject对象后并不能保证它们被持久化,必须手动进行持久化保存操作。这个过程相当是将数据插入到SQLite中。Swift的保存语句为:
1 do {
2 try self.managedObjectContext.save()
3 } catch {
4 fatalError("Failure to save context: \(error)")
5 }
3.4 请求Managed Object
若需要获取存储在Core Data框架下的持久化对象时,可以使用NSFetchRequest 对象请求Core Data框架,这个过程相当是查询数据库。其中这种查询有两种类型:无条件查询和有条件查询。
1)无条件查询
对于无条件查询,只需创建一个NSFetchRequest 对象,并调用NSManagedObjectContext 对象的executeFetchRequest函数进行查询数据库,从而该函数会返回一个NSManagedObject数组。如swift程序:
1 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
2 let context = appDelegate.managedObjectContext
3 let request = NSFetchRequest(entityName:"Entity")
4 var objects:[AnyObject]!
5 do
6 {
7 try objects = context.executeFetchRequest(request)
8 }
9 catch
10 {
11 }
2)有条件查询
有条件查询也是通过NSFetchRequest 对象进行查询,而需配置NSFetchRequest 对象查询的条件,即设置谓词逻辑。
1 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
2 let context = appDelegate.managedObjectContext
3 let request = NSFetchRequest(entityName:"Entity")
4 request.predicate = NSPredicate(format: "name = %@", "world")
5 var objects:[AnyObject]!
6 do
7 {
8 try objects = context.executeFetchRequest(request)
9 }
10 catch
11 {
12 }
3.5 删除和修改Managed Object
删除和修改Managed Object是指对Core Data的存储文件进行操作,相当是数据库中的删除和修改。
1)删除操作
删除只需调用NSManagedObjectContext对象的deleteObject()函数删除指定的NSManaged Object对象。删除后,必须调用NSManagedObjectContext对象的save()函数,将其保存到底层存储文件中。如swift例子为:
1 func deleteEntity()
2 {
3 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
4 let context = appDelegate.managedObjectContext
5 let request = NSFetchRequest(entityName:"Entity")
6 request.predicate = NSPredicate(format: "name = %@", "hello")
7
8 var objects:[AnyObject]!
9 do
10 {
11 try objects = context.executeFetchRequest(request)
12 }
13 catch
14 {}
15
16 for object in objects
17 {
18 var managerObject = object as! NSManagedObject
19 context.deleteObject(managerObject)
20 do{
21 try context.save();
22 }
23 catch
24 {}
25 }
26 }
2)修改操作
修改操作也非常简单,只需直接修改查询获得的NSManagedObject对象,然后调用NSManagedObjectContext对象的save()函数,即会将修改的内容保存到数据库中。如swift例子为:
1 func modifyEntity()
2 {
3 let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
4 let context = appDelegate.managedObjectContext
5 let request = NSFetchRequest(entityName:"Entity")
6 request.predicate = NSPredicate(format: "name = %@", "world")
7
8 var objects:[AnyObject]!
9
10 do
11 {
12 try objects = context.executeFetchRequest(request)
13 }
14 catch
15 {}
16
17 for object in objects
18 {
19 var managerObject = object as! NSManagedObject
20 managerObject.setValue("hello world", forKey: "name")
21 do{
22 try context.save();
23 }
24 catch
25 {}
26 }
27 }
4、集成到UITableView
将Core Data集成到UITableView是指通过某些技术来简化Core Data或数据库的查询工作。因为UITableview是IOS中最好的显示数据,而Core Data是IOS中最好的存储数据,所以将两者相结合能够简化编程和提供性能。
其中这种集成是通过一个类和一个协议来完成的,它们分别是:
-
NSFetchedResultsController类
该类的功能是在执行了performFetch()方法后,从数据库中取得所有实体(NSManagedObject对象),并存放在NSFetchedResultsController对象中,从而免除了查询的步骤。
-
NSFetchedResultsControllerDelegate协议
该协议是辅助NSFetchedResultsController来进行查询,即当底层数据库发生了内容变化(即增删改查)时,那么就会调用该协议的相应方法,从而可以在这些方法中修改货刷新tableView的显示内容。
4.1 显示Core Data数据
这里的显示Core Data数据是指将Core Data下的存储数据显示在tableView中。将UITableView与Core Data结合使用相当简单,其使用方式类似以CoreData的查询,通过调用NSFetchedResultsController对象的performFetch()方法就能够获取Core Data底层的数据,数据就存储在NSFetchedResultsController对象中,但还需手动获取NSFetchedResultsController对象中的数据设置tableView单元格(cell)的内容。
表 3 NSFetchedResultsController
Method/attribute |
description |
init() |
初始化函数,里面的参数非常重要 |
performFetch() |
提出request请求,即查询数据库数据 |
fetchedObjects |
是将查询的NsmanagedObject对象集合存在该数组中 |
objectAtIndexPath(indexPath: NSIndexPath) |
根据指定的序号获取NsmanagedObject对象 |
sectionIndexTitles |
是每节的标题集合,是个数组 |
sections |
也是个数组 |
比如我们在UITableView控制器类中创建了一个NSFetchedResultsController成员变量,它在viewDidLoad()方法中被初始化,即查询了数据库的数据在该变量中,当需要显示tableView时,就获得该变量的NSManagedObject对象。如下所示:
1 class tableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
2 var fetchedResultsController: NSFetchedResultsController!
3 var dataController:AppDelegate!
4
5 override func viewDidLoad() {
6 super.viewDidLoad()
7 dataController = UIApplication.sharedApplication().delegate as! AppDelegate
8 initializeFetchedResultsController()
9 }
10
11 func initializeFetchedResultsController() { // 自定义方法:初始化fetchedResultsController变量
12 let request = NSFetchRequest(entityName: "Entity")
13 let departmentSort = NSSortDescriptor(key: "name", ascending: true)
14 request.sortDescriptors = [departmentSort]
15 let moc = self.dataController.managedObjectContext
16 self.fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: nil , cacheName: nil) // init方法
17
18 do {
19 try self.fetchedResultsController.performFetch()
20 } catch {
21 fatalError("Failed to initialize FetchedResultsController: \(error)")
22 }
23 }
24
25 func configureCell(cell: UITableViewCell,indexPath: NSIndexPath) {//自定义方法:设置cell的内容,cell为引用变量
26 let entity = self.fetchedResultsController.objectAtIndexPath(indexPath) as! Entity
27 cell.textLabel?.text = entity.valueForKey("name") as! String
28 }
29
30 override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
31 let cell = tableView.dequeueReusableCellWithIdentifier("cellIdentifier", forIndexPath: indexPath)
32 // Set up the cell
33 self.configureCell(cell, indexPath: indexPath)
34 return cell
35 }
36
37 override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
38 return self.fetchedResultsController.sections!.count
39 }
40
41 override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
42 let sections = self.fetchedResultsController.sections as! [NSFetchedResultsSectionInfo]!
43 let sectionInfo = sections[section]
44 return sectionInfo.numberOfObjects
45 }
4.3 更新Core Data数据
更新Core Data数据是指当底层的数据库文件发生内容变化时,能够实时地更新tableview中的显示内容。其中这种更新是通过NSFetchedResultsControllerDelegate协议来完成的,当对数据库进行增删改查时,该协议的相应方法就会被调用。
如下是在上述tableViewController类基础上添加的代码:
1 class tableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
2 var fetchedResultsController: NSFetchedResultsController!
3 var dataController:AppDelegate!
4 override func viewDidLoad() {
5 super.viewDidLoad()
6 dataController = UIApplication.sharedApplication().delegate as! AppDelegate
7 initializeFetchedResultsController()
8 }
9
10 func initializeFetchedResultsController() {
11 let request = NSFetchRequest(entityName: "Entity")
12 let departmentSort = NSSortDescriptor(key: "name", ascending: true)
13 request.sortDescriptors = [departmentSort]
14 let moc = self.dataController.managedObjectContext
15 self.fetchedResultsController = NSFetchedResultsController(fetchRequest: request, managedObjectContext: moc, sectionNameKeyPath: nil , cacheName: nil)
16 self.fetchedResultsController.delegate = self
17 do {
18 try self.fetchedResultsController.performFetch()
19 } catch {
20 fatalError("Failed to initialize FetchedResultsController: \(error)")
21 }
22 }
23
24 …… //省略了有关table data source的方法
25
26 //如下是NSFetchedResultsControllerDelegate协议的方法。
27 func controllerWillChangeContent(controller: NSFetchedResultsController) {//将发生改变
28 self.tableView.beginUpdates()
29 }
30
31 func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {//section发生了改变
32 switch type {
33 case .Insert:
34 self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
35 case .Delete:
36 self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Fade)
37 case .Move:
38 break
39 case .Update:
40 break
41 }
42 }
43
44 func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { //已经发生改变
45 switch type {
46 case .Insert:
47 self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
48 case .Delete:
49 self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
50 case .Update:
51 self.configureCell(self.tableView.cellForRowAtIndexPath(indexPath!)!, indexPath: indexPath!)
52 case .Move:
53 self.tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
54 self.tableView.insertRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
55 }
56 }
57 func controllerDidChangeContent(controller: NSFetchedResultsController) {//已经完成了改变
58 self.tableView.endUpdates()
59 }
5、高级功能
5.1 Change Management
由于Core Data允许有多个NSManagedObjectContext对象连接到同一个Core Data底层数据文件,那么如果有多个NSManagedObjectContext对象。所以当需要在一个已改变数据的context对象同步到另一个context对象时,可以有两种方式:
1)注册NSNotificationCenter
当一个context对象发生内容改变时,那么它会自动通过NSNotificationCenter发生一个NSManagedObjectContextDidSaveNotification消息;此时会触发已经注册的action,但不会改变另一个已经fetch操作的context对象,那么这个需要更新内容的context对象就需要手动进行 重新fetch操作。
2)选择Synchronization策略
若有两个NSManagedObjectContext对象:moc1和moc2,它们都同时发生了内容改变,但都没有同步到底层的数据文件中,那么有如下的处理方法:
- 那么可以在丢弃其中一个moc的数据;
- 或者是可以在一个moc1设置NSOverwriteMergePolicy属性,那么当moc1发生数据改变时,将会更新moc2的数据内容。
5.2 Concurrency
Concurrency是指在同一时刻可以有多个队列同时访问数据,其中若需要并发访问core Data,那么需要考虑应用的环境。因为AppKit 和UIKit 是非线程安全的,所以如果使用这些技术,那么多线程将会非常复杂。
1)NSManagedObjectContext类型
在Core Data框架中进行并发访问数据,可以给NSManagedObjectContext设置两种并发模式:
-
NSMainQueueConcurrencyType
这是一种全局队列的模型,只有在应用程序的全局队列( main queue)可以使用。
-
NSPrivateQueueConcurrencyType
这是一种私有的类型,仅可以在私有的队列(Private Queue)中使用,并且它是通过performBlock()和 performBlockAndWait()方法访问。
2)使用私有NSManagedObjectContext并发访问
一般情况下,应该避免在main queue中进行数据的处理,因为若在main queue中处理密集型的数据,那么将导致用户响应速度缓慢。所以如果在应用程序中需要处理数据(比如需要将JSON数据导入Core Data),那么可以创建一个 private queue类型的NSManagedObjectContext,该类型来处理数据的导入工作。
比如:
1 let jsonArray = … //JSON data to be imported into Core Data
2 let moc = … //Our primary context on the main queue
3 let privateMOC = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
4 privateMOC.parentContext = moc
5 privateMOC.performBlock {
6 for jsonObject in jsonArray {
7 let mo = … //Managed object that matches the incoming JSON structure
8 //update MO with data from the dictionary
9 }
10 do {
11 try privateMOC.save()
12 } catch {
13 fatalError("Failure to save context: \(error)")
14 }
15 }
在这个例子中,创建了一个私有类型的NSManagedObjectContext对象,并将该对象的父context设置为全局的NSManagedObjectContext对象。其中将json的导入工作放在了performBlock 函数块内完成,所以当导入工作完成后,就可以调用私有的context进行保存(save),那么将保存后将会将数据传递给全局的NSManagedObjectContext对象,而且这个过程不需要阻塞全局context对象。
6、参考文献
- Core Data programming guide
- 精通IOS开发(第7版)
- IOS开发指南(第3版)