iOS 数据持久化(3):Core Data

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

 
图 9

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、参考文献

  1. Core Data programming guide
  2. 精通IOS开发(第7版)
  3. IOS开发指南(第3版)
原文地址:https://www.cnblogs.com/huliangwen/p/5425730.html