精通CoreData(第17部分,多线程并发策略上下文UndoManager)

这一部分是上一部分的延续。 必须查看使用父子策略解决的临时问题。 现在,在这一部分中,我们将使用undo manager解决此问题。 要了解什么是临时更改问题,请参考上一部分

在第二部分中,我们说

托管对象上下文(MOC)

托管对象上下文提供缓存,更改跟踪,延迟加载, 重做,撤消和验证功能

撤消管理员

UndoManager提供了一种简单的方法来向您的托管对象上下文添加撤消/重做功能。 它是托管对象上下文的Instance属性。

如果您遵循这些教程,请下载入门项目或删除应用程序。 首先注释掉这些行, 如图1所示。

从流程图了解撤消管理器

如您在图2中看到的,我们执行了许多任务以了解undo manager的工作方式

  1. 首先,我们启动/开始撤消组以跟踪我们将执行的更改。 此时撤消管理器开始跟踪更改
  2. 用户1添加到托管对象上下文中,并将其引用到撤消堆栈
  3. 用户2添加到托管对象上下文中,并将其引用到撤消堆栈
  4. 结束撤消组(这很重要,否则应用程序将崩溃)
  5. 执行context.undo撤消更改
  6. 如您所见, 用户1用户2现在已删除并移至重做堆栈中
  7. 现在出现执行重做用户1用户2

如您在图3中看到的,我们将流程图论证明为代码

  1. 已创建“托管对象上下文”的undoManager实例属性。 默认情况下,在iOS中,其值为nil
  2. 通过调用beginUndoGrouping ,我们开始跟踪对象并将其分组到撤消堆栈中
  3. 在托管对象上下文中添加了两个用户对象
  4. endUndoGrouping将结束该组。 这个非常重要
  5. 最后,我们通过执行撤消和重做操作来查看日志

如您所见,撤消操作后未打印任何User对象。 通过重做在控制台上打印的两个用户对象

注意:首先beginUndoGrouping将分组级别增加到2。

嵌套撤消组

在最后一个撤消组中执行撤消操作

如图4所示undoNestedGroup组撤消添加用户2时的最后一个组。 如您所见,我们在第一个撤消组上添加了用户,在第二个或最后一个撤消组上添加了用户2。 通过执行undoNestedGroup ,它将撤消添加了User2的最后一个撤消组。 现在,第一个撤消组成为再次调用undoNestedGroup之后的最后一个撤消组,它将删除User1, 如图4所示。

如您在图5中看到的,我们使用代码进行了证明。 执行撤消嵌套组撤消最后一组。

有很多事情你应该知道

undo →如有必要,关闭顶级撤消组,并调用undoNestedGroup()

undoNestedGroup →在最后一个撤消组(顶级或嵌套)中执行撤消操作,将重做堆栈上的操作记录为单个组

levelOfUndo:Int →接收者拥有的顶级撤消组的最大数量。

beginUndoGrouping →标记撤消组的开始。

endUndoGrouping →标记撤消组的结束。

removeAllActions →清除撤消和重做堆栈并重新启用接收器。

使用撤消管理器解决应用程序中的临时更改问题

首先删除该应用程序,并取消注释viewDidLoadviewWillAppear方法,如图6所示。要了解该应用程序,项目结构和我们要解决的问题,请参考上一部分,然后再进行介绍。 之前我们使用父子策略解决了临时更改问题,现在我们将使用上下文undoManger功能解决它。

如您在图7中看到的,我们做了很多事情来设置undoManager

  1. 创建自定义后退按钮以获取后退事件的操作
  2. 从主要单例受管对象上下文中获取的用户数据以及直接填充视图的用户数据。 信息编辑也会更新此上下文对象。
  3. 在获取之前,我们开始使用undoManager来跟踪临时更改

现在,当用户点击“后退”按钮时,我们做了几件事,如图8所示。

  1. 首先,我们结束了撤消组。 在调用undo方法之前需要先调用它,这一点非常重要
  2. 最后,我们通过调用undo方法来放弃更改,它将放弃从undo组的开始到结束在上下文中所做的所有更改。

运行应用程序并运行导致临时更改的流程保存在数据库中,现在使用undoManager解决了该问题。

回滚管理对象上下文

从撤消堆栈中删除所有内容,放弃所有插入和删除操作,并将更新后的对象恢复为其上次提交的值

了解流程图

如您在图9中看到的,在调用rollback它丢弃了上下文中的所有临时更改,并将它们的状态移动到最后提交的值,该值与持久性存储的状态相同。 为了举例说明,首先删除应用程序,然后在ViewController.swift上注释掉viewDidLoadviewwillAppear方法。

如您在图10中看到的,我们做了很多事情

  1. 首先,我们创建了User1并通过在上下文上调用save方法savesave持久性存储
  2. 下一个用户2用户3仅在上下文中创建。 这些是尚未位于永久存储中的用户
  3. 通过调用rollback它丢弃了上下文中的所有临时更改。 如您在日志中所看到的,由于用户2用户3不在持久性存储中,因此删除了用户2用户3 ,正如我们之前提到的回滚将上下文移至最后一个提交的值,这意味着持久性存储的状态
  4. 如果您在现有的UI应用程序中使用,记住用户1引用的一件事不会改变。 由于它不会刷新持久性存储中的数据,因此引用保持不变。 (重要)

重置托管对象上下文

将上下文恢复为其基本状态

了解流程图

如您在图11中看到的,它执行与回滚相同的操作。 我们首先将User1保存到持久性存储中,然后将User1缓存在托管对象上下文中 。 然后,我们临时添加了持久存储中还没有的User2User3 。 之后,我们在上下文上调用了reset方法,它的功能与rollback did,的功能相同rollback did,但有一点点不同。它再次从持久性存储中获取了User1 ,这就是为什么它的地址被更改了,而在回滚中User1的地址却是相同的。

如您在图12中看到的,我们做了很多事情并删除了应用程序

  1. 首先,我们创建了User1并通过在上下文上调用save方法将其保存到持久性存储中
  2. 下一个用户2用户3仅在上下文中创建。 这是尚未位于持久性存储中的用户
  3. 通过调用reset,它丢弃了上下文中的所有临时更改。 如您在日志中看到的,由于用户2用户3不在持久性存储中,因此已删除
  4. 记住用户1所引用的一件事已更改,如果您在现有的UI应用程序中使用它将会崩溃。 我使用了回滚屏幕截图,因为两者之间存在微小差异。 由于它将刷新持久性存储中的数据,因此其引用将发生更改。

注意:所有接收者的托管对象都是“被遗忘的”。如果使用此方法,则应确保还丢弃对使用接收者获取的任何托管对象的引用,因为它们以后将无效。

重置和回滚之间的区别

首先删除应用程序

重置:您可以看到参考值更改,数据也出现故障。 由于它从持久性存储中获取数据,如图13所示。

首先删除应用程序

回滚 :引用是相同的,因为它没有删除缓存数据。 数据仍然存在于缓存中,如图14所示。

有用的链接

https://www.raywenderlich.com/5229-undomanager-tutorial-how-to-implement-with-swift-value-types