CoreData中的值类型

坚持使用CoreValue

是否可以仅使用值类型来实现业务模型对象? 使用CoreData保留它们怎么办?

在这篇文章中,我将研究CoreValue。 一种轻量级框架,旨在为完成此类任务提供支持。 我已经构建了一个小应用程序来对其进行测试。 这是Github中的代码。

使用值类型对对象进行建模。

在我开始谈论CoreValue之前,有两个潜在问题值得一提:

  • 双向关联无法使用值类型完全表示。 我写了 另一篇 关于这个问题的文章。
  • 结构不支持继承。

关联必须是单向的,以便仅使用值类型正确表示。 例如,如果您需要一部电影来了解其导演,而导演则需要了解这部电影。 仅使用结构表示是不可能的。

您可以忍受这些限制并仍然代表模型对象吗? 如果真是这样,那么仅值类型方法可能对您有效。 否则,您需要课程。

核心价值

它是围绕CoreData的轻量级包装框架。 它负责将值类型unboxing到CoreData对象中,以及将CoreData对象unboxing到值类型中。 它还包含简单的抽象,以便于查询,更新,保存和删除

该框架允许您使用结构/枚举,使用它们,然后保存/删除/获取它们。 当需要将它们发送到托管上下文时,它将值转换为托管对象。 反之亦然。 真的很酷的主意。

它是如何工作的?

您定义结构和枚举。 然后,您遵循框架的协议之一,例如CVManagedPersistentStruct 基本上,您可以指定如何从NSManagedObject获取值类型的实例。

每次尝试保存值时,框架都会将其转换为NSManagedObject 。 当您尝试从数据库中获取一个对象时,框架将使用objectID来获取托管对象。 然后它将使用您的转换函数来获取值类型实例。

好的,让我们看看当您仅用值类型表示模型对象并尝试通过CoreValue使用CoreData实现持久性时会发生什么。

开始

集成非常容易,可以进行pod安装,导入CoreValue,然后就可以编写代码了。 但是,我确实遇到了一些非常早期的问题。

第一个问题是尝试保留第一个值类型时发生编译错误 。 使用咖喱将数据从NSManagedObject到值类型中:

 无法将类型'((NSManagedObjectID ?, String,Director,Genre)-> Movie'的值转换为预期的参数类型'(_)->(_)throws-> _' 

如果尝试尝试curry init与另一个持久值类型连接的类型,则会发生这种情况。 在我的示例中, Movie知道其Director 。 Director是一个持久的CVManagedPersistentStruct,Movie出现编译错误。 该解决方案非常简单,但并不容易找到。 似乎编译器缺少某些信息:

 typealias StructureType = Movie 

您需要为您保留的每种值类型定义该类型typealias 。 在示例情况下, MovieDirector需要定义它。

从托管对象获取数据。

您需要从托管对象中获取属性,并且其中没有类型安全性。 您需要使用字符串,这会导致出错。 错字或错误的重构,仅此而已。 编译器无法帮助我们。

初始化

使用咖喱可使代码更清晰。 我确实碰到了我上面经常提到的编译错误。 每当尝试咖喱init函数时我犯了一个错误。

要考虑的一些事情:

  • 遵守初始化参数顺序。 如果您在Director之前声明姓名,则需要在curry调用中在Director之前提取/推送名称。
  • 尊重参数类型:如果参数是可选的,则需要<|? 如果是列表,则需要<|| 等等
  • 编写显式init形式可能会有所帮助:尝试使用curry(self.init(objectID:name:director:genre:))而不是curry(self.init)
  • 所有参数都必须是装箱/拆箱。 您需要在所有涉及的值类型中遵守CoreValue的协议。 否则,您将遇到该编译错误。
  • 您也可以使用init而不使用curry。 您仍然可以在被管理对象上使用操作数(例如<|? )。

不变性问题

保存值类型实例时,将为其分配一个objectID。 那是NSManagedObjectID的一个实例。 再次保存并更新objectID 。 这意味着值类型实例需要变异。 该实例需要用包含该objectID的新值覆盖。

这就是为什么save()是一个变异函数的原因:

每次保存结构时,实际上是将其替换为新结构。 这意味着什么? -您不能使用let,需要使用var。

我想将数据访问封装在一个层中。 这样,我的应用程序的其余部分将与持久性解决方案无关。 CoreData,CoreValue或其他任何内容。 为了尝试建立一个干净的体系结构。 我创建了一个数据库抽象,它提供了数据访问所需的方法。 例如,数据库知道如何保存电影。

问题是,值是不可变的。 您不能在作为参数的结构上调用变异函数。 因为默认情况下参数是不可变的(let)。

因此,数据库可以收到对该结构的inout引用:

另一种解决方案是将影片复制到save函数中,使其成为变量(可变),然后将其保存:

我没有选择这种方法,因为在调用保存时,我将丢失在保存范围之外所做的更改。 因此,发送给该函数的电影实例将过时,它没有正确的objectID

重复的条目

值在分配时复制。 如果使用同一导演创建2部电影并将其保存,则会在数据库中获得重复的条目。 看一下这个例子:

etjurassic都有spielberg的副本。 他们还没有objectID, spielberg也没有。 当您保存电影时,也将保存导演,它们是不同的副本,没有objectID 。 因此,最终在数据库中创建2个不同的控制器,而不是仅创建1个。

这是您需要注意和注意的事项。 发生这种情况很有意义,并且可能导致真正的问题。

好的,如果您先保存导演然后保存2部电影,该怎么办。 它应该工作正常吗? objectID应该在objectID上设置。 那么这两部电影将具有不同的导演价值。 但是每个值都应具有相同的objectID

框架中一个小错误。 这导致该示例也不起作用: etjurassic是新实体,因为它们没有objectID 。 每次保存新电影时,CoreValue都会为导演创建一个新的管理对象。 因此,它没有引用同一托管对象,而是创建了2个新控制器。 结果是3位董事,而不是1位。

该错误现已修复。 该示例应有的效果!

局限性

CoreData已为UIKit准备。 例如,用于显示按需数据。 您使用NSFetchedResultsController。 请参阅将UITableViewController与CoreData配对。

这种功能是必需的,CoreValue不支持此类功能。 因此,您需要手动执行操作,从而产生更多工作并降低性能。

考虑一下我一开始提到的值类型限制。 还有我在使用框架时遇到的问题。 在这一点上,我觉得我在强迫事情而不是与CoreData战斗。

包起来

我认为,如果您想使用框架,则需要与其一起使用并遵循其规则和风格。 CoreData基于继承。 您需要子类NSManagedObject

还有其他方法可以使用结构和枚举作为引用类型的协作者。 对于枚举,您可以保存原始值并将其包装以返回实际的枚举。 结构可以转换为其他形式,例如NSData或JSON。 CoreData的可转换类型对此很有用。

CoreValue非常有趣。 尝试填补值类型和CoreData之间的空白是一个很好的尝试。 我的目标是仅使用值类型表示模型实体,并将其保留在CoreData中。 可能,但是它有很大的局限性,而且问题不小。 因此,我觉得我不会在真正的应用程序中使用CoreValue和这种方法。 集成起来很棒,但是尝试使用CoreData尝试不同的体系结构。

感谢Benedikt Terhechte。 他创建了CoreValue,非常乐意看这篇简短的文章。

您可以签出示例项目:

leandromperez /中核心价值
medium-corevalue –评估CoreValue的小型示例项目 github.com

迅速的照片