核心数据中具有自定义类型的所有属性都必须是关系吗?
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,最有效的方法是为 Passenger
和 Trip
创建 Core Data 实体并添加反向关系。然后让所有NSManagedObject
类都采用Codable
,并为每个类添加init(from decoder
和encode(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)
采用Codable
的NSManagedObject
类非常适合从 JSON 数据预填充数据库或进行 JSON 备份。
【讨论】:
再次感谢瓦迪安。 :|所以你基本上是在建议我做我不想做的事情。我怀疑符合NSCoding
的代码量也是一样的。好的。还需要passengers.forEach $0.trip = self
吗?我以为只要有一个反向关系,它就会自动为你设置......
正如我所说,您不能将自定义类或结构添加为核心数据属性,您必须使用 transformable
类型,这需要额外的工作来序列化和反序列化对象。因此,在使用 JSON 时,您将反序列化 JSON 并再次对其进行序列化以使其与 transformable
兼容。闻起来效率低下。在自定义初始化程序中,您必须建立 one 关系。反向连接是隐式建立的。
passengers = try values.decode(Set<Passenger>.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你自己的另一个答案:)以上是关于核心数据中具有自定义类型的所有属性都必须是关系吗?的主要内容,如果未能解决你的问题,请参考以下文章