CoreData - 堆栈(3)

2017-08-06  本文已影响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。

上一篇下一篇

猜你喜欢

热点阅读