跳转至

3.Core Data

到目前为止,您一直依赖于XcodeCore Data模板。 从Xcode获得帮助没有什么错(这就是它存在的原因!)。 但如果你真的想知道Core Data是如何工作的,那么构建自己的Core Data堆栈是必须的。

堆栈由四个Core Data类组成:

  • NSManagedObjectModel
  • NSPersistentStore
  • NSPersistentStoreCoordinator
  • NSManagedObjectContext

在这四个类中,到目前为止,您在本书中只遇到了NSManagedObjectContext。 但其他三个一直在幕后支持您的托管上下文。

在本章中,您将详细了解这四个类的功能。 您将构建自己的Core Data堆栈,而不是依赖于默认的启动器模板;一个可定制的 wrapper 围绕这些类。

入门

本章的示例项目是一个简单的遛狗应用程序。该应用程序允许您将遛狗的日期和时间保存在一个简单的表视图中。 经常使用这个应用程序,你的狗(和他的膀胱)会喜欢你的。

您将在本书附带的参考资料中找到示例项目 DogWalk。 打开 DogWalk.xcodeproj,然后构建并运行starter项目。 img

正如您所看到的,示例应用程序已经是一个完全可用的(尽管很简单)原型。 点击右上角的加号(+)按钮可将新条目添加到行走列表中。 该图像表示您当前正在散步的狗,但除此之外什么也不做。

该应用程序具有所需的所有功能,除了一个重要功能:不会持续存在该散步列表。 如果您终止 DogWalk 并重新启动,您的整个历史记录将消失。 如果你今天早上遛狗你怎么会记得?

本章中的任务是在Core Data中保存行走列表。 如果这听起来像是你在第一章和第二章中已经做过的事情,这里有一个转折;您将编写自己的核心数据堆栈,以了解引擎盖下到底发生了什么!

滚动自己的Core Data

了解核心数据堆栈的工作原理不仅仅是一个 nice to know。 如果您正在使用更高级的设置,例如从旧的持久性存储中迁移数据,则深入研究堆栈是必不可少的。

在开始编写代码之前,让我们详细考虑一下Core Data堆栈中的四个类-- NSManagedObjectModelNSPersistentStoreNSPersistentStoreCoordinatorNSManagedObjectContext --中的每一个都做了什么。

Note

这是本书中为数不多的几个部分之一,在实践中使用这些概念之前,你会先阅读有关理论的内容。 几乎不可能将一个组件从堆栈的其余部分中分离出来并单独使用它。

托管对象模型

NSManagedObjectModel表示应用数据模型中的每个对象类型、它们可以具有的属性以及它们之间的关系。 Core Data堆栈的其他部分使用模型来创建对象、存储属性和保存数据。

正如本书前面提到的,将NSManagedObjectModel视为数据库模式可能会有所帮助。 如果您的Core Data堆栈在后台使用SQLite,则NSManagedObjectModel表示数据库的模式。

但是,SQLite只是您可以在Core Data中使用的许多持久存储类型之一(稍后将详细介绍),因此最好从更一般的角度来考虑托管对象模型。

Note

您可能想知道NSManagedObjectModel与您沿着在使用的数据模型编辑器之间的关系。 问得好! 可视化编辑器创建并编辑 xcdatamodel 文件。 有一个特殊的编译器,momc,它将模型文件编译成一组文件,放在一个 momd 文件夹中。 就像你的Swift代码经过编译和优化,可以在设备上运行一样,编译后的模型可以在运行时高效地访问。 Core Data使用 momd 文件夹的编译内容在运行时初始化NSManagedObjectModel

持久化存储

NSPersistentStore将数据读写到您决定使用的任何存储方法。 Core Data提供了四种开箱即用的NSPersistentStore类型:三个原子和一个非原子。

atomic 持久存储需要完全反序列化并加载到内存中,然后才能进行任何读写操作。 相比之下,non-atomic 持久存储可以根据需要将其自身的块加载到内存中。

以下是四种内置Core Data存储类型的简要概述:

  1. NSSQLiteStoreTypeSQLite数据库支持。 它是Core Data支持的唯一非原子存储类型,使其具有轻量级和高效的内存占用。 这使得它成为大多数iOS项目的最佳选择。 XcodeCore Data模板默认使用这种存储类型。
  2. NSXMLStoreTypeXML文件支持,使其成为所有存储类型中最易于阅读的类型。 此存储类型是原子的,因此它可能会占用大量内存。 NSXMLStoreType仅在OS X上可用。
  3. NSBinaryStoreType 由二进制数据文件支持。 与NSXMLStoreType一样,它也是一个原子存储,因此在对它执行任何操作之前,必须将整个二进制文件加载到内存中。在现实世界的应用程序中,很少能找到这种类型的持久存储。
  4. NSInMemoryStoreType 为内存持久存储类型。 在某种程度上,这种存储类型并不是真正的持久化。 终止应用程序或关闭手机,存储在内存存储类型中的数据就会消失在空气中。 虽然这似乎违背了Core Data的目的,但内存中的持久存储对于单元测试和某些类型的缓存是有帮助的。

Note

您是否对JSON文件或CSV文件支持的持久化存储类型屏住了呼吸? 真扫兴 好消息是,您可以通过子类化NSIncrementalStore来创建自己的持久存储类型。 如果您对此选项感到好奇,请参阅Apple的增量存储编程指南: https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/IncrementalStorePG/Introduction/Introduction.html

持久化存储协调器

NSPersistentStoreCoordinator是托管对象模型和持久存储之间的桥梁。 它负责使用模型和持久性存储来完成Core Data中的大部分繁重工作。 它理解NSManagedObjectModel,并且知道如何向NSPersistentStore发送信息和从NSPersistentStore获取信息。

NSPersistentStoreCoordinator还隐藏了如何配置持久存储的实现细节。 这是有用的,原因有二:

  1. NSManagedObjectContext(即将推出!) 不需要知道它是保存到SQLite数据库、XML文件还是自定义增量存储。
  2. 如果您有多个持久性存储,则持久性存储协调器将提供一个到托管上下文的统一接口。 就托管上下文而言,它总是与单个聚合持久性存储交互。

托管对象上下文

在日常工作中,您将使用四个堆栈组件中最常用的NSManagedObjectContext。 当您需要对Core Data执行更高级的操作时,您可能只会看到其他三个组件。

由于使用NSManagedObjectContext非常普遍,因此了解上下文的工作方式非常重要! 以下是你可能已经从书中学到的一些东西:

  • 上下文是用于处理托管对象的内存中的暂存器。
  • 您可以在托管对象上下文中使用Core Data对象完成所有工作。
  • 你所做的任何更改都不会影响磁盘上的底层数据,直到你在上下文上调用save()

这里有五件关于上下文的事情之前没有提到。 其中一些对后面的章节非常重要,所以请密切关注:

  1. 上下文 manages 它创建或获取的对象的生命周期。 这种生命周期管理包括强大的功能,如故障、反向关系处理和验证。
  2. 没有关联的上下文,托管对象就不能存在。 事实上,托管对象和它的上下文是紧密耦合的,每个托管对象都保持对其上下文的引用,可以这样访问:
let managedContext = employee.managedObjectContext
  1. 背景是非常领土; 一旦被管理对象已经与特定上下文相关联,它将在其生命周期的持续时间内保持与相同上下文相关联。
  2. 一个应用程序可以使用多个上下文-大多数重要的Core Data应用程序都属于这一类。 由于上下文是磁盘上内容的内存中暂存器,因此实际上可以同时将同一个Core Data对象加载到两个不同的上下文中。
  3. 上下文不是线程安全的。 这同样适用于托管对象:您只能在创建上下文和托管对象的同一线程上与它们进行交互。

Apple提供了许多在多线程应用程序中处理上下文的方法。 您将在第9章“多个托管对象上下文”中阅读到有关不同并发模型的所有内容。

持久化存储容器

如果您认为核心数据堆栈只有四个部分,那么您将大吃一惊! 从iOS 10开始,有一个新的类来编排所有四个Core Data堆栈类:托管模型、存储协调器、持久存储和托管上下文。

这个类的名称是NSPersistentContainer,顾名思义,它是一个将所有内容保存在一起的容器。 您不必浪费时间编写样板代码来将所有四个堆栈组件连接在一起,只需初始化一个NSPersistentContainer,加载其持久化存储,就可以开始了。

创建堆栈对象

现在你知道了每个组件的作用,是时候回到 DogWalk 并实现你自己的Core Data堆栈了。

正如你从前面的章节中所知道的,Xcodeapp委托中创建了它的Core Data堆栈。 你要用不同的方式。 您将创建一个单独的类来封装堆栈,而不是将应用程序委托代码与Core Data代码混合在一起。

进入 File ▸ New ▸ File…,选择iOS ▸ Source ▸ Swift File模板,点击【下一步】。 将文件命名为 CoreDataStack,单击Create保存文件。

进入新创建的 CoreDataStack.swift。 您将逐个创建此文件。 首先将文件的内容替换为以下内容:

import Foundation
import CoreData

class CoreDataStack {
  private let modelName: String

  init(modelName: String) {
    self.modelName = modelName
  }

  private lazy var storeContainer: NSPersistentContainer = {

    let container = NSPersistentContainer(name: self.modelName)
    container.loadPersistentStores { _, error in
      if let error = error as NSError? {
        print("Unresolved error \(error), \(error.userInfo)")
      }
    }
    return container
  }()
}

首先导入FoundationCoreData模块。 接下来,创建一个私有属性来存储modelName。 接下来,创建一个初始化器,将modelName保存到私有属性中。

接下来,设置一个延迟实例化的NSPersistentContainer,传递初始化期间存储的modelName。 您需要做的唯一其他事情是在持久化容器上调用loadPersistentStores(completionHandler:)(尽管出现了完成处理程序,但该方法默认情况下不会异步运行)。 最后,在modelName下面添加如下延迟实例化属性:

lazy var managedContext: NSManagedObjectContext = {
  return self.storeContainer.viewContext
}()

尽管NSPersistentContainer有公共访问器用于其托管上下文、托管模型、存储协调器和持久存储(通过[NSPersistentStoreDescription]),但CoreDataStack的工作方式略有不同。

例如,CoreDataStack的唯一可公开访问的部分是NSManagedObjectContext,因为您刚刚添加了惰性属性。 其他的都标着private。 这是为什么呢?

托管上下文是访问堆栈其余部分所需的唯一入口点。 持久存储协调器是NSManagedObjectContext上的公共属性。 类似地,托管对象模型和持久存储数组都是NSPersistentStoreCoordinator上的公共属性。

最后,在storeContainer属性下面添加如下方法:

func saveContext () {
  guard managedContext.hasChanges else { return }

  do {
    try managedContext.save()
  } catch let error as NSError {
    print("Unresolved error \(error), \(error.userInfo)")
  }
}

这是保存堆栈的托管对象上下文并处理任何由此产生的错误的方便方法。

打开 ViewController.swift,进行以下更改。 首先,导入Core Data模块。 在import UIKit下面添加以下内容:

import CoreData

接下来,在dateFormatter下面添加以下属性来保存Core Data堆栈:

lazy var coreDataStack = CoreDataStack(modelName: "DogWalk")

数据建模

现在你闪亮的新核心数据堆栈已经安全地固定在主视图控制器上,是时候创建你的数据模型了。

转到项目导航器,然后...等一下。 没有数据模型文件! 是的。 因为我生成这个示例应用程序时没有启用使用Core Data的选项,所以没有 .xcdatamodeld 文件。

别担心 进入File ▸ New ▸ File…,选择iOS ▸ Core Data ▸ Data Model模板,点击Next

将文件命名为 DogWalk.xcdatamodeld,单击*Create保存文件。

img

Note

如果您没有将数据模型文件精确地命名为 DogWalk.xcdatamodeld ,那么以后就会出现问题。 这是因为 CoreDataStack.swift 期望在 DogWalk.momd 找到编译后的版本。

打开数据模型文件,新建一个名为 Dog 的实体。 您现在应该能够自己完成此操作,但如果您忘记了如何操作,请单击左下方的 Add Entity 按钮。

添加一个名为 name 的属性,类型为 String。 你的数据模型应该看起来像这样:

img

您还希望跟踪特定狗的散步。 毕竟,这就是应用程序的全部意义!

定义另一个实体并将其命名为 Walk。 然后添加名为 date 的属性,并将其类型设置为 Date

img

返回到Dog实体。 您可能认为需要添加一个类型为 Array 的新属性来保存遍历,但Core Data中没有数组类型。 相反,实现这一点的方法是将其建模为一种关系。 添加一个新关系并将其命名为 walks

将目的地设置为 Walk

img

你可以把目的地想象成一段关系的接收端。 默认情况下,每个关系都是以一对一的关系开始的,这意味着您目前只能跟踪每只狗的一次散步。 除非你不打算让你的狗很长时间,你可能想跟踪一个以上的步行。

若要解决此问题,请在选中 walkes 关系的情况下,打开 Data Mode 检查器:

img

点击 Type 下拉菜单,选择 To Many,勾选 Ordered。 这意味着一只狗可以有很多次散步,散步的顺序很重要,因为您将按日期排序显示散步。

选择 Walk 实体并创建反向关系回到 Dog。 将目的地设置为 dog,反之设置为 walks

img

把这段关系当作一对一的关系是可以的。 一只狗可以有很多散步,但散步只能属于一只狗-至少对于这个应用程序的目的。

可以说,逆模型让模型知道如何找到返回的路。 给出一个步行记录,你可以跟踪狗的关系。 多亏了逆,模型知道遵循 walkes 关系返回到·记录。

这是让您了解数据模型编辑器具有另一种视图样式的好时机。 这段时间您一直在研究表编辑器样式。

切换右下角的分段控件以切换到图形编辑器样式:

img

图形编辑器是一个很好的工具,可以可视化核心数据实体之间的关系。 这里,从DogWalk的对多关系用双箭头表示。 Walk用一个箭头指向Dog,表示一对一的关系。

您可以随意在这两种编辑器样式之间来回切换。 您可能会发现,使用表格样式添加和删除实体和属性,以及使用图形样式查看数据模型的整体情况更容易。

添加托管对象子类

在前一章中,您学习了如何为Core Data实体创建自定义托管对象子类。 这样做更方便,所以这也是你对DogWalk所做的。

与上一章一样,您将手动生成自定义托管对象子类,而不是让Xcode为您完成,这样您就可以看到幕后发生了什么。 打开 DogWalk.xcdatamodeld,选择Dog实体,在数据模型检查器中的 Codegen 下拉菜单中设置为 Manual/None。 对Walk实体重复相同的过程。

然后,进入Editor ▸ Create NSManagedObject Subclass…,选择DogWalk模型,然后选择DogWalk实体。 在下一个界面中,单击Create,即可创建文件。

正如您在第2章中看到的,这样做会为每个实体创建两个文件:一个用于在模型编辑器中定义的核心数据属性,另一个用于将来可能添加到托管对象子类中的任何功能。

Dog+CoreDataProperties.swift 应该是这样的:

import Foundation
import CoreData

extension Dog {

  @nonobjc public class func fetchRequest()
    -> NSFetchRequest<Dog> {
    return NSFetchRequest<Dog>(entityName: "Dog")
  }

  @NSManaged public var name: String?
  @NSManaged public var walks: NSOrderedSet?
}

// 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)
}

extension Dog : Identifiable {

}

和前面一样,name属性是一个String可选。 但是,walks的关系又如何呢? Core Data使用集合而不是数组表示多对关系。 因为您使Walks关系有序化,所以得到了一个NSOrderedSet

Note

NSSet似乎是一个奇怪的选择,不是吗? 与数组不同,集合不允许通过索引访问其成员。 事实上,根本没有秩序! Core Data使用NSSet,因为集合强制其成员具有唯一性。 同一个对象不能在一对多关系中出现多次。 如果需要通过索引访问单个对象,可以在可视化编辑器中选中 Ordered 复选框,就像在这里所做的那样。 然后,核心数据将关系表示为NSOrderedSet

类似地,Walk+CoreDataProperties.swift 应该如下所示:

import Foundation
import CoreData

extension Walk {

  @nonobjc public class func fetchRequest()
    -> NSFetchRequest<Walk> {
    return NSFetchRequest<Walk>(entityName: "Walk")
  }

  @NSManaged public var date: Date?
  @NSManaged public var dog: Dog?
}

extension Walk : Identifiable {

}

返回到Dog的反向关系只是Dog类型的属性。 易如反掌。

Note

有时候Xcode会使用通用的NSManagedObject类型来创建关系属性,而不是特定的类,特别是当你同时创建很多子类的时候。 如果发生这种情况,只需自己更正类型或重新生成特定的文件。

漫步在坚持的小路上

现在您的设置完成了;核心数据堆栈、数据模型和托管对象子类。 是时候将 DogWalk 转换为使用Core Data了。 您以前已经做过几次了,所以这应该是一个简单的部分。

假设这个应用程序在某个时候支持跟踪多只狗。 第一步是跟踪当前选择的狗。

打开 ViewController。swift 并将walks数组替换为以下属性。 现在忽略错误,您将在一分钟内修复这些错误:

var currentDog: Dog?

接下来,在viewDidLoad()的末尾添加以下代码:

let dogName = "Fido"
let dogFetch: NSFetchRequest<Dog> = Dog.fetchRequest()
dogFetch.predicate = NSPredicate(format: "%K == %@",
                                 #keyPath(Dog.name), dogName)

do {
  let results = try coreDataStack.managedContext.fetch(dogFetch)
  if results.isEmpty {
    // Fido not found, create Fido
    currentDog = Dog(context: coreDataStack.managedContext)
    currentDog?.name = dogName
    coreDataStack.saveContext()
  } else {
    // Fido found, use Fido
    currentDog = results.first
  }
} catch let error as NSError {
  print("Fetch error: \(error) description: \(error.userInfo)")
}

首先,从Core Data中获取名称为Fido的所有Dog实体。 在下一章中,您将了解更多关于类似于这样的奇特获取请求的信息。

如果fetch请求返回结果,则将第一个实体(应该只有一个)设置为当前选定的狗。

如果fetch请求返回零结果,这可能意味着这是用户第一次打开应用程序。如果是这种情况,则插入一只新狗,将其命名为Fido,并将其设置为当前选定的狗。

Note

您刚刚实现了通常称为 Find or Create 的模式。 此模式的目的是操作存储在Core Data中的对象,而不会冒在过程中添加重复对象的风险。 在iOS 9中,Apple引入了在核心数据实体上指定 唯一约束 的功能。 使用唯一约束,可以在数据模型中指定哪些属性在实体上必须始终唯一,以避免添加重复项。

接下来,将tableView(_:numberOfRowsInSection:)的实现替换为以下内容:

func tableView(_ tableView: UITableView,
               numberOfRowsInSection section: Int) -> Int {
  currentDog?.walks?.count ?? 0
}

正如您可能猜到的,这将表视图中的行数与当前选定的狗中设置的行走次数联系起来。 如果当前没有选择的狗,则返回0。

接下来,将tableView(_:cellForRowAt:)替换为以下内容:

func tableView(
  _ tableView: UITableView,
  cellForRowAt indexPath: IndexPath
  ) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
    withIdentifier: "Cell", for: indexPath)

  guard let walk = currentDog?.walks?[indexPath.row] as? Walk,
    let walkDate = walk.date as Date? else {
      return cell
  }

  cell.textLabel?.text = dateFormatter.string(from: walkDate)
  return cell
}

只有两行代码发生了变化。 现在,您将获取每次巡查的日期并将其显示在相应的表视图单元格中。

add(_:)方法仍然有对旧的walks数组的引用。 现在就把它评论出来;你将在下一步中重新实现这个方法:

@IBAction func add(_ sender: UIBarButtonItem) {
  // walks.append(Date())
  tableView.reloadData()
}

构建并运行以确保您已正确连接所有内容:

img

万岁! 如果您已经走到这一步,那么您已经将一只狗插入到Core Data中,并且当前正在用它的散步列表填充表视图。 这个列表目前没有任何散步,所以表看起来不是很令人兴奋。

点击加号(+)按钮,可以理解它什么也不做。 您尚未在此控件下实现任何内容! 在转换到Core Data之前,add(_:)只是简单地将一个Date添加到数组中,并重新加载了表视图。 重新实现如下:

@IBAction func add(_ sender: UIBarButtonItem) {
  // Insert a new Walk entity into Core Data
  let walk = Walk(context: coreDataStack.managedContext)
  walk.date = Date()

  // Insert the new Walk into the Dog's walks set
  if let dog = currentDog,
    let walks = dog.walks?.mutableCopy()
      as? NSMutableOrderedSet {
      walks.add(walk)
      dog.walks = walks
  }

  // Save the managed object context
  coreDataStack.saveContext()

  // Reload table view
  tableView.reloadData()
}

这种方法的核心数据版本要复杂得多。 首先,您必须创建一个新的Walk实体,并将其date属性设置为now。 接下来,您必须将此散步插入到当前选定的狗的散步列表中。

但是,walks属性的类型为NSOrderedSetNSOrderedSet是不可变的,所以你首先必须创建一个可变副本(NSMutableOrderedSet),插入新的walk,然后在dog上重置这个可变有序集的不可变副本。

Note

在一个对多的关系中加入一个新的对象会让你头晕吗? 很多人都可以理解,这就是为什么 Dog+CoreDataProperties 包含了生成的walkers有序集的访问器,这些访问器将为您处理所有这些。 例如,您可以将最后一个代码片段中的整个if-let语句替换为以下内容:

currentDog?.addToWalks(walk)
给予看!

但是,核心数据可以让事情变得更容易。 如果关系不是有序的,你只可以设置关系的one(例如:walk.dog = currentDog)而不是many侧,Core Data将使用模型编辑器中定义的反向关系来将行走添加到狗的行走集合中。

最后,通过在Core Data堆栈上调用saveContext()将更改提交到持久性存储,并重新加载表视图。

构建并运行应用程序,然后点击加号(+)按钮几次。

img

太好了! 现在应将行走列表保存在Core Data中。 通过在快速应用切换器中终止应用并从头开始重新启动来验证这一点。

删除核心数据对象

让我们假设你太喜欢触发器了,当你不想的时候点击了加号(+)按钮。 您实际上并没有遛狗,因此您希望删除刚刚添加的遛狗。

你已经将对象添加到核心数据中,你已经获取它们,修改它们并再次保存它们。 你还没有做的是删除它们-但你接下来要做的是删除它们。

首先,打开 ViewController。swift 并在UITableViewDataSource扩展中添加如下方法:

func tableView(_ tableView: UITableView,
               canEditRowAt indexPath: IndexPath) -> Bool {
  true
}

您将使用UITableView的默认行为来删除项目:向左滑动,显示红色的 Delete 按钮,然后点击该按钮即可删除。

表视图调用这个UITableViewDataSource方法来询问特定单元格是否可编辑,返回true意味着所有单元格都应可编辑。

接下来,将以下方法添加到同一个UITableViewDataSource扩展中:

func tableView(
  _ tableView: UITableView,
  commit editingStyle: UITableViewCell.EditingStyle,
  forRowAt indexPath: IndexPath
) {

  //1
  guard let walkToRemove =
    currentDog?.walks?[indexPath.row] as? Walk,
    editingStyle == .delete else {
      return
  }

  //2
  coreDataStack.managedContext.delete(walkToRemove)

  //3
  coreDataStack.saveContext()

  //4
  tableView.deleteRows(at: [indexPath], with: .automatic)
}

点击红色的Delete 按钮,调用此表视图数据源方法。 让我们一步一步地看一下代码:

  1. 首先,你得到一个你想要删除的walk的引用。
  2. 通过调用NSManagedObjectContextdelete()方法从Core Data中删除遍历。 Core Data还负责从当前狗的行走关系中删除删除已删除的行走。
  3. 在保存托管对象上下文之前,任何更改都不是最终的-甚至连删除也不是!
  4. 最后,如果保存操作成功,您将对表视图进行动画处理,以告知用户有关删除的信息。

再次构建并运行应用程序。 你应该从以前的跑步中走了几次。 选择任意一个,然后向左滑动。

img

点击删除按钮以删除漫游。 通过终止应用程序并从头开始重新启动来验证行走是否真的消失了。 你刚刚移除的走道永远消失了。 核心数据是给予的,核心数据是带走的。

Note

删除曾经是最dangerous的核心数据操作之一。 这是为什么呢? 当您从Core Data中删除某些内容时,您必须删除磁盘上的记录以及代码中任何未完成的引用。 尝试访问没有核心数据备份存储的NSManagedObject会导致非常可怕的“不可访问故障”核心数据崩溃。 从iOS 9开始,删除比以往任何时候都更安全。 Apple在NSManagedObjectContext上引入了属性shouldDeleteInaccessibleFaults,默认情况下该属性是打开的。 这会将错误标记为已删除,并将丢失的数据视为NULL/nil/0

关键点

  • Core Data stack 由五个类组成:NSManagedObjectModelNSPersistentStoreNSPersistentStoreCoordinatorNSManagedObjectContextNSPersistentContainer将所有内容保存在一起。
  • managed object mode 表示应用数据模型中的每个对象类型、它们可以拥有的属性以及它们之间的关系。
  • 持persistent store 支持SQLite数据库(默认)、XML、二进制文件或内存存储。 您也可以通过 incremental store API提供自己的后台存储。
  • persistent store coordinator 隐藏了您的持久化存储配置的实现细节,为您的 managed object context 提供了一个简单的接口。
  • managed object context 管理其创建或获取的托管对象的生命周期。 它们负责获取、编辑和删除托管对象,以及更强大的功能,如验证、故障和反向关系处理。