核心数据中具有自定义类型的所有属性都必须是关系吗?

Posted

技术标签:

【中文标题】核心数据中具有自定义类型的所有属性都必须是关系吗?【英文标题】:Do all attributes with a custom type in core-data have to be a relationship? 【发布时间】:2018-12-31 17:20:31 【问题描述】:

这是来自以下How to map nested complex JSON objects and save them to core data?的cmets的后续问题。

想象一下,我的应用程序已经有了这段代码。

class Passenger
    var name: String
    var number: String
    var image : UIImage
    // init method

class Trip 
    let tripNumber : Int
    let passenger : Passenger

    init(tripNumber: Int, passenger: Passenger) 
        self.tripNumber = tripNumber
        self.passenger = passenger
    

现在我决定为我的应用添加持久性。我只是想要有一张旅行表。我想显示旅行下的乘客,但不需要表格来直接查询乘客。它只是旅行的自定义对象/属性。每次我访问乘客时,都会通过 Trips。

那么有没有一种方法可以创建一个名为“TripEntity”的 NSManagedObject 的新子类并存储我的乘客——无需 1. 为“Passenger”创建另一个 NSManagedObject 子类 2. 在乘客和 Trip 之间创建一个反向关系?简单地说,我只是希望它成为一个属性。从概念上对我来说,它也只是一个属性。这不是真正的关系......

或者,一旦您使用 Core-data,那么每个自定义类型都需要显式地成为 NSManagedObject 的子类?否则不会持久化。我猜这也是 object graph 的意思。你的图表需要是完整的。图表之外的任何内容都会被忽略...

我问这个是因为我真正想要存储的 JSON 对象是巨大的,我正在努力减少需要完成的工作。

【问题讨论】:

您能否澄清一下:1) 同一个Passenger 对象可以分配给多个行程吗?如果是这样,您将需要想出某种方法来区分它们,因为如果它们被编码在一个属性中,每个 Trip 对象将有不同的Passengers。 2)每次旅行可以有超过一名乘客吗?如果没有,您可以将每个Passenger 属性添加为Trip 的属性,并避免编码麻烦。 3) UIImage 有多大 - 它们可能更好地存储在文件系统中,并且文件路径存储在 CoreData 中。 1) 如果我得到一份旅行清单,那么可以。 Passenger 对象可以分配给一段时间内的多次行程。话虽如此,我不明白你提到的问题。你能详细说明吗? 2) 是的。它可以不止一个。 3)好点。 注意:确保您也看到了未接受的答案。它的解决方案更好,但不完全符合问题的特殊性 【参考方案1】:

您可以将乘客添加到一个 Trip 实体,但由于属性类型受到限制,您必须使用 transformable 类型,归档和取消归档对象可能会非常昂贵。

如果源数据是 JSON,最有效的方法是为 PassengerTrip 创建 Core Data 实体并添加反向关系。然后让所有NSManagedObject类都采用Codable,并为每个类添加init(from decoderencode(to encoder:方法。

例如,假设Trip 类与Passenger 具有一对多关系,它可能看起来像

@NSManaged public var tripNumber: Int32
@NSManaged public var passengers: Set<Passenger>

Passenger 类中有一个属性trip

@NSManaged public var trip: Trip?

这是Trip 中必需的Decodable 初始化程序。解码器可以解码数组和集合。

private enum CodingKeys: String, CodingKey  case tripNumber, passengers 

public required convenience init(from decoder: Decoder) throws 
    guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else  fatalError("Context Error") 
    let entity = NSEntityDescription.entity(forEntityName: "Trip", in: context)!
    self.init(entity: entity, insertInto: context)
    let values = try decoder.container(keyedBy: CodingKeys.self)
    tripNumber = try values.decode(Int32.self, forKey: .tripNumber)
    passengers = try values.decode(Set<Passenger>.self, forKey: .passengers)
    passengers.forEach $0.trip = self 

反向关系通过forEach 行设置。 您必须添加 JSONDecoder 的扩展名才能在 userInfo 对象中传递当前的 NSManagedObjectContext

对应的编码器——非常简单——

public func encode(to encoder: Encoder) throws 
    var container = encoder.container(keyedBy: CodingKeys.self)
    try container.encode(tripNumber, forKey: .tripNumber)
    try container.encode(passengers, forKey: .passengers)

采用CodableNSManagedObject 类非常适合从 JSON 数据预填充数据库或进行 JSON 备份。

【讨论】:

再次感谢瓦迪安。 :|所以你基本上是在建议我做我不想做的事情。我怀疑符合NSCoding 的代码量也是一样的。好的。还需要passengers.forEach $0.trip = self 吗?我以为只要有一个反向关系,它就会自动为你设置...... 正如我所说,您不能将自定义类或结构添加为核心数据属性,您必须使用 transformable 类型,这需要额外的工作来序列化和反序列化对象。因此,在使用 JSON 时,您将反序列化 JSON 并再次对其进行序列化以使其与 transformable 兼容。闻起来效率低下。在自定义初始化程序中,您必须建立 one 关系。反向连接是隐式建立的。 passengers = try values.decode(Set&lt;Passenger&gt;.self, forKey: .passengers) 不建立关系吗? 如果 trip 将在相应的 Passenger 初始化程序中设置,则它会这样做。但是一对一的关系比一对多的关系更努力。 是的,对于反向关系,您需要两个类(方向)中相关类型的属性。【参考方案2】:

任何自定义属性都必须是可以表示为核心数据属性类型之一的东西。这包括明显的东西,如字符串和数值。它还包括“二进制”,即任何可以转换为NSData(Swift 中为Data)的东西。

根据您的描述,最好的方法可能是

    为您的Passenger 班级采用NSCoding。 使passenger 属性成为Core Data“可转换”类型。 (顺便说一句,这应该是一组乘客吗?您有一个相关的乘客,但您的问题将其描述为好像不止一个)。 在执行 #2 之后,将乘客属性的“自定义类”字段设置为您的类的名称 -- Passenger

如果你做这两件事,Core Data 会自动调用NSCoding 方法在你的Passenger 类和可以保存在Core Data 中的二进制blob 之间进行转换。

【讨论】:

再次感谢汤姆。在阅读了您的第一段后,我终于明白了为什么它被命名为可转换的。话虽如此,我确实遇到过here 和here。我不明白这些到底是什么......但我不需要实现ValueTransformer吗? nv.我找到了我的答案here你自己的另一个答案:)

以上是关于核心数据中具有自定义类型的所有属性都必须是关系吗?的主要内容,如果未能解决你的问题,请参考以下文章

具有自定义核心数据模型的部分名称键路径

核心数据和自定义属性类型

iPhones SDK:使用核心数据设置关系属性对象?

在自定义对象类型的核心数据中保存带有子项的类别

核心数据:迁移具有自引用属性的实体

具有自定义类类型的 Swift CoreData 保存属性