美文网首页
CoreData - 堆栈(3)

CoreData - 堆栈(3)

作者: 冰三尺 | 来源:发表于2017-08-06 13:55 被阅读36次

    至今为止, 我们使用的都是CoreData的数据模板, 但是如果想要真正了解CoreData的工作原理, 建立自己的堆栈是必须的.
    堆栈由四个核心数据类组成:
    •NSManagedObjectModel
    •NSPersistentStore
    •NSPersistentStoreCoordinator
    •NSManagedObjectContext
    在这四个类中,您至今在本书中只遇到NSManagedObjectContext。 但是另外三个都是在幕后的时候,支持你的管理上下文。
    在本章中,您将了解这四类课程的细节。 而不是依赖于默认的启动器模板,您将构建您自己的Core Data堆栈:围绕这些可以作为一个单元的类定制的“包装器”。

    NSManagedObjectModel表示应用程序数据模型中的每个对象类型,它们可以具有的属性以及它们之间的关系。核心数据堆栈的其他部分使用模型创建对象,存储属性和保存数据。
    如本书前面所述,将NSManagedObjectModel作为数据库模式考虑是有帮助的。如果您的Core Data堆栈使用SQLite,那绝对是NSManagedObjectModel所代表的。
    但是,SQLite只是您可以在Core Data中使用的许多持久存储类型之一(稍后再说),所以最好以更一般的术语来考虑管理对象模型。

    注意:您可能想知道NSManagedObjectModel如何与您一直使用的数据模型编辑器相关。好问题!
    可视化编辑器创建并编辑一个xcdatamodel文件。有一个特殊的编译器,momc,将模型文件编译成一个momd文件夹中的一组文件。
    就像您的Swift代码被编译和优化一样,它可以在设备上运行,编译的模型可以在运行时高效地访问。 Core Data使用momd文件夹的编译内容在运行时初始化NSManagedObjectModel。

    持久的商店
    NSPersistentStore读取和写入数据到您决定使用的存储方法。 Core Data提供了四种类型的NSPersistentStore开箱即用:三个原子和一个非原子。
    原子持久存储需要完全反序列化并加载到内存中,然后才能进行任何读或写操作。相比之下,非原子持久存储可以根据需要将其自身的块加载到内存上。

    以下是四个内置Core Data存储类型的简要概述:
    1.NSQLiteStoreType由SQLite数据库支持。它是Core Data支持开箱即用的唯一非原子存储类型,为其提供轻量级和高效的内存占用。这使它成为大多数iOS项目的最佳选择。默认情况下,Xcode的Core Data模板使用此存储类型。

    1. NSXMLStoreType由XML文件支持,使其成为所有商店类型中最可读的。这种存储类型是原子的,所以它可以具有大的内存占用。 NSXMLStoreType仅在OS X上可用。
      3.NSBinaryStoreType由二进制数据文件支持。像NSXMLStoreType一样,它也是一个原子存储,所以整个二进制文件必须加载到内存中,然后才能执行任何操作。您很少在现实世界的应用程序中找到这种类型的持久存储。
      4.NSInMemoryStoreType是内存中持久存储类型。在某种程度上,这种存储类型并不是真正的“持久性”。终止应用程序或关闭手机,而存储在内存中的存储类型的数据会消失。虽然这似乎打破了Core Data的目的,但内存中持久存储可以帮助单元测试和某些类型的缓存。

    创建CoreData Stack

    新建一个文件, 命名为CoreDataStack.swift

    import Foundation
    import CoreData
    
    class CoreDataStack {
      private let modelName: String
      
    //初始化方法, 根据传进来的名字去初始化一个CoreDataStack, 这个名字是.xcdatamodeld的文件名
      init(modelName: String) {
        self.modelName = modelName
      }
      //懒加载一个NSManagedObjectContext上下文
      lazy var managedContext: NSManagedObjectContext = {
        return self.storeContainer.viewContext
      }()
      //懒加载NSPersistentContainer, 该对象会对NSManagedObjectContext持有一个引用
      private lazy var storeContainer: NSPersistentContainer = { 
        let container = NSPersistentContainer(name: self.modelName)
    //创建CoreData 堆栈, 并处理处理错误
        container.loadPersistentStores { (storeDescription, error) in
          if let error = error as NSError? {
            print("Unresolved error \(error), \(error.userInfo)")
          }
        }
        return container
      }()
      //保存数据
      func saveContext () {
        guard managedContext.hasChanges else { return }
        
        do {
          try managedContext.save()
        } catch let error as NSError {
          print("Unresolved error \(error), \(error.userInfo)")
        }
      }
    }
    

    当我们创建一个工程默认勾选了CoreData, 系统会在APPDelete为我们自动创建堆栈代码, 如下

     // MARK: - Core Data stack
    
      lazy var persistentContainer: NSPersistentContainer = {
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
         */
        let container = NSPersistentContainer(name: "HitList")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
          if let error = error as NSError? {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
    
            /*
             Typical reasons for an error here include:
             * The parent directory does not exist, cannot be created, or disallows writing.
             * The persistent store is not accessible, due to permissions or data protection when the device is locked.
             * The device is out of space.
             * The store could not be migrated to the current model version.
             Check the error message to determine what the actual problem was.
             */
            fatalError("Unresolved error \(error), \(error.userInfo)")
          }
        })
        return container
      }()
    
      // MARK: - Core Data Saving support
    
      func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
          do {
            try context.save()
          } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
          }
        }
      }  
    

    CoreDataStack.swift文件就是我们自己来管理CoreData的堆栈

    接下来需要在Appdelegate里面来使用我们的 堆栈
    首先在ViewController里生命一个NSManagedObjectContext对象

      var managedContext: NSManagedObjectContext!
    

    AppDelegate.swift

    import UIKit
    import CoreData
    
    @UIApplicationMain
    class AppDelegate: UIResponder, UIApplicationDelegate {
    
      var window: UIWindow?
    //modelName即是xcdatamodeld文件的名称
      lazy var coreDataStack = CoreDataStack(modelName: "文件名")
    
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    
    //获取当前的跟视图, 也就是ViewController
        guard let navController = window?.rootViewController as? UINavigationController,
          let viewController = navController.topViewController as? ViewController else {
            return true
        }
    //  把我们自己创建的CoreDataStack的上下文复制给ViewController的NSManagedObjectContext
        viewController.managedContext = coreDataStack.managedContext
        return true
      }
    //推到后台保存数据
      func applicationDidEnterBackground(_ application: UIApplication) {
        coreDataStack.saveContext()
      }
    //程序退出保存数据
      func applicationWillTerminate(_ application: UIApplication) {
        coreDataStack.saveContext()
      }
    }
    

    创建我们自己的CoreData Model

    现在创建两个实体, 一个Dog, 一个Walk. 试想一个Dog会有很多歌Walk, 但是一个Walk, 他只能属于一个Dog.
    按照图示添加实体

    屏幕快照 2017-08-06 下午1.20.54.png
    屏幕快照 2017-08-06 下午1.21.05.png

    上面说了, 一个Dog会有多个Work, 但是要怎么表示多个Work呢? 这里不能使用数组, 仍然使用Walk实体, 而是设置Walk的type来达到数组的效果.

    屏幕快照 2017-08-06 下午1.23.39.jpg

    将Dog里面的Work设置成To Many, 表示可以存储多个Walk, Ordered表示排序.

    可以通过CoreData编辑器来查看他们直接的关系

    屏幕快照 2017-08-06 下午1.26.28.jpg

    添加NSManagedObject Subclass...

    Editor\Create NSManagedObject Subclass... 创建文件

    记住一定要把实体CodeGen设置成Manual/None, 我们不需要Xcode帮我们自动生成, 我们自己来创建文件.

    屏幕快照 2017-08-06 下午1.29.14.jpg

    会创建这四个文件
    Dog+CoreDataClass.swift/Dog+CoreDataProperties.swift/Walk+CoreDataClass.swift/Walk+CoreDataProperties.swift
    只有Dog+CoreDataProperties.swift这个文件会与以前创建的文件内容不同, 其他的三个都相同.
    在Dog+CoreDataProperties.swift文件中多个一个Extension

    
    // MARK: Generated accessors for walks
    extension Dog {
    
        @objc(insertObject:inWalksAtIndex:)
        @NSManaged public func insertIntoWalks(_ value: Walk, at idx: Int)
    
        @objc(removeObjectFromWalksAtIndex:)
        @NSManaged public func removeFromWalks(at idx: Int)
    
        @objc(insertWalks:atIndexes:)
        @NSManaged public func insertIntoWalks(_ values: [Walk], at indexes: NSIndexSet)
    
        @objc(removeWalksAtIndexes:)
        @NSManaged public func removeFromWalks(at indexes: NSIndexSet)
    
        @objc(replaceObjectInWalksAtIndex:withObject:)
        @NSManaged public func replaceWalks(at idx: Int, with value: Walk)
    
        @objc(replaceWalksAtIndexes:withWalks:)
        @NSManaged public func replaceWalks(at indexes: NSIndexSet, with values: [Walk])
    
        @objc(addWalksObject:)
        @NSManaged public func addToWalks(_ value: Walk)
    
        @objc(removeWalksObject:)
        @NSManaged public func removeFromWalks(_ value: Walk)
    
        @objc(addWalks:)
        @NSManaged public func addToWalks(_ values: NSOrderedSet)
    
        @objc(removeWalks:)
        @NSManaged public func removeFromWalks(_ values: NSOrderedSet)
    
    }
    

    这个扩展里面的额内容, 是Xcode自动帮我们创建的访问器可以让我们对Walk进行一些增删改查之类的操作. 但是为什么其他的文件没有多一个Extension呢?
    原因就在于, 我们的Dog里面有一个Walk的实体, 而这个实体的类型有时To Many类型, 是一个集合类型, 可以进行一些添加删除的操作.

    获取实体Dog
    先声明一个Dog变量

      var currentDog: Dog?
    

    然后再ViewController里添加一下代码

      override func viewDidLoad() {
        super.viewDidLoad()    
        
        let dogName = "Fido"
        //拿到所有的Dog实体
        let dogFetch: NSFetchRequest<Dog> = Dog.fetchRequest()
        
        do {
    //判断是否存在该实体对象
          let results = try managedContext.fetch(dogFetch)
          if results.count > 0 {
            // 如果存在, 则直接赋值
            currentDog = results.first
          } else {
            // 如果不存在, 则进行创建, 并赋值, 然后保存
            currentDog = Dog(context: managedContext)
            currentDog?.name = dogName
            try managedContext.save()
          }
        } catch let error as NSError {
          print("Fetch error: \(error) description: \(error.userInfo)")
        }
      }
    

    添加一个实体内容

     func add() {
    //根据   NSManagedObjectContext创建一个NSManagedObject对象
        let walk = Walk(context: managedContext)
      //对象属性赋值
        walk.date = NSDate()
    //这一步关键, 使用扩展给Dog添加一个walk
        currentDog?.addToWalks(walk)
        //异常处理
        do {
          try managedContext.save()
        } catch let error as NSError {
          print("Save error: \(error), description: \(error.userInfo)")
        }
      }
    

    删除一个实体对象

      func removeWalk(walk:Walk) {
        managedContext.delete(walk)
        do {
          try managedContext.save()
        } catch let error as NSError {
          print(error.userInfo)
        }
      }
    

    注意:在以前删除是最危险的CoreData操作之一。 为什么是这样? 当您从Core Data中删除某些内容时,必须删除磁盘上的记录以及代码中的任何未完成的引用。
    尝试访问没有Core Data后备存储的NSManagedObject导致了令人担忧的“无法访问的故障”核心数据崩溃。
    从iOS 9开始,删除比以前更安全。 Apple在NSManagedObjectContext上引入了属性shouldDeleteInaccessibleFaults,默认情况下打开。 这将坏的故障标记为已删除,并将丢失的数据视为NULL / nil / 0。

    相关文章

      网友评论

          本文标题:CoreData - 堆栈(3)

          本文链接:https://www.haomeiwen.com/subject/lskwlxtx.html