zoukankan      html  css  js  c++  java
  • 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版)
  • 相关阅读:
    Spider爬虫清洗数据(re方法)
    Python 操作 mongodb 数据库
    python操作mysql数据库
    BeautifulSoup高级应用 之 CSS selectors /CSS 选择器
    mongoDB在centos7上的安装
    CentOS7安装mongoDB数据库
    [洛谷P4602] CTSC2018 混合果汁
    [洛谷P2605] ZJOI2016 基站选址
    [CF1039D] You Are Given a Tree
    [CF1105E] Helping Hiaset
  • 原文地址:https://www.cnblogs.com/huliangwen/p/5425730.html
Copyright © 2011-2022 走看看