至今为止, 我们使用的都是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模板使用此存储类型。
- 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.
按照图示添加实体
上面说了, 一个Dog会有多个Work, 但是要怎么表示多个Work呢? 这里不能使用数组, 仍然使用Walk实体, 而是设置Walk的type来达到数组的效果.
将Dog里面的Work设置成To Many, 表示可以存储多个Walk, Ordered表示排序.
可以通过CoreData编辑器来查看他们直接的关系
添加NSManagedObject Subclass...
Editor\Create NSManagedObject Subclass... 创建文件
记住一定要把实体CodeGen设置成Manual/None, 我们不需要Xcode帮我们自动生成, 我们自己来创建文件.
会创建这四个文件
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。