Coredatable
在 NSManagedObject
子类中实现简单的 Codable
兼容。
该库处于测试阶段。在发布
1.0.0
版本之前,可能会添加破坏性更改。
安装
CocoaPods
将以下内容添加到您的 Podfile
pod 'Coredatable'
总结
将 Decodable
和 Encodable
兼容性添加到 NSManagedObject
子类通常很困难。使用名为 CoreDataDecodable
、CoreDataEncodable
和 CoreDataCodable
的等效协议来简化此过程:
final class Person: NSManagedObject, CoreDataCodable, UsingDefaultCodingKeys {
@NSManaged var id: Int
@NSManaged var name: String?
@NSManaged var city: String?
@NSManaged var birthday: Date?
@NSManaged var attributes: NSSet
}
class PersonAttribute: NSManagedObject, CoreDataCodable {
@NSManaged private(set) var id: Int
@NSManaged private(set) var name: String
enum CodingKeys: String, CoreDataCodingKey {
case id
case name = "attributeName"
}
}
// Decode
let decoder = JSONDecoder()
decoder.managedObjectContext = myContext
let person = try decoder.decode(Person.self, from: data)
// Encode
let encoder = JSONEncoder()
let data = try encoder.encode(person)
是的,就这么简单。
您只需将一个 NSManagedObjectContext
添加到您的解码器中,并使类对应协议。该协议强制您添加一个 CodingKeys
类型。在我们的示例中,有两种不同的情况:
- 如果使用默认键(与属性的名称相同),您只需对应
UsingDefaultCodingKeys
。或者,您可以这样做:typealias CodingKeys = CoreDataDefaultCodingKeys
。 - 如果您想使用一组不同的键,您必须创建一个名为
CodingKeys
的enum
,使它对应CoreDataCodingKeys
,并定义其情况和字符串值。
如果您需要更定制的 CodingKey
,您可以创建一个 class
、struct
或其他任何类型,然后使其遵循 AnyCoreDataCodingKey
。
标识属性。
可选地,您可以在您的 NSManagedObject
实例中确保其唯一性。要这样做,您可以使用 identityAttribute
属性。
final class Person: NSManagedObject, CoreDataCodable, UsingDefaultCodingKeys {
@NSManaged var id: Int
@NSManaged var name: String?
@NSManaged var country: String?
@NSManaged var birthday: Date?
@NSManaged var attributes: NSSet
static let identityAttribute: IdentityAttribute = #keyPath(Person.id)
}
如果您这样做,Coredatable
将会负责检查是否有具有相同 identityAttribute
值的对象已经存在于上下文中。如果存在,该对象将使用新值进行更新。如果不存在,它将直接插入。当一个对象被更新时,只有新的 JSON 中存在的值会被更新。
以这种方式支持复合标识属性。
static let identityAttribute: IdentityAttribute = [#keyPath(Person.id), #keyPath(Person.name)]
然而,只有在确实需要时才使用复合标识属性,因为的性能将会受到影响。单独的标识属性策略需要在每个 JSON 对象数组上执行一次获取操作,而复合标识属性策略需要在每个单独的 JSON 对象上执行一次获取操作。
如果不需要唯一性,您甚至可以完全不包含 identityAttribute
。
从标识符序列化关系
假设我们的 API 不返回关系的完整对象,而是只返回标识符。
我们不需要更改我们的模型来支持这种情况。
let json: [String: Any] = [
"id": "1",
"name": "Marco",
"attributes": [1, 2]
]
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let person = try decoder.decode(Person.self, from: data)
上述代码创建了一个 Person
对象。如果我们已经在上下文中有具有 id 1
或 2
的 PersonAttribute
,那些对象将会被设置在关系上。如果没有,将会创建两个具有给定 id
的 PersonAttribute
实例。
请注意,仅当实体指定
identityAttribute
的值仅由一个属性组成时,从标识符序列化关系才起作用。
KeyPath 编码键
假设我们有一个嵌套 JSON 中的值
{
"id": 1,
"name": "Marco",
"origin": {
"country" {
"id": 1,
"name": "Spain"
}
}
}
我们可以使用键路径符号直接访问国家名称
enum CodingKeys: String, CoreDataCodingKey {
case id, name, birthday, attributes
case country = "origin.country.name"
}
默认情况下,键路径使用点 ."
作为路径分隔符,但您可以使用任何其他字符串添加此操作
enum CodingKeys: String, CoreDataCodingKey {
case id, name, birthday, attributes
case country = "origin->country->name"
var keyPathDelimiter: String { "->" }
}
自定义解码
如果您需要自定义序列化,您需要做一些不同于常规Codable
的操作。而不是覆盖init(from decoder: Decoder)
,您应该覆盖func initialize(from decoder: Decoder) throws
。
让我们看一个示例
final class Custom: NSManagedObject, CoreDataDecodable {
@NSManaged var id: Int
@NSManaged var compound: String
enum CodingKeys: String, CoreDataCodingKey {
case id
case first
case second
}
static var identityAttribute: IdentityAttribute = #keyPath(Custom.id)
// 1
func initialize(from decoder: Decoder) throws {
// 2
try defaultInitialization(from: decoder, with: [.id])
// 3
let container = try decoder.container(for: Custom.self)
// 4
let first = try container.decode(String.self, forKey: .first)
let second = try container.decode(String.self, forKey: .second)
// 5
compound = [first, second].joined(separator: " ")
}
}
这里,我们有一个compound
属性,它通过连接来自不同键的两个字符串创建。我们这样做的是
// 1
: 覆盖func initialize(from container: CoreDataKeyedDecodingContainer<Custom>)
// 2
: 只考虑id
键来调用默认序列化。 (注意:还有一些其他默认实现,您可以在其中指定包含或跳过的键)// 3
: 创建一个新容器,将自己的类型作为参数传递。传递另一个类型将导致错误。// 4: 从容器中提取
first和
second`值。// 5
: 将连接后的字符串添加到compound
属性中。
如果您需要在将JSON序列化之前对identityAttributes
进行某些更改,您需要使用不同的方法。假设我们的JSON将id
发送为String
,但我们将它作为整数。我们可以使用以下方法修改值
// 1
static func container(for decoder: Decoder) throws -> AnyCoreDataKeyedDecodingContainer {
// 2
var container = try decoder.container(for: CustomDoubleId.self)
// 3
container[.id] = Int(try container.decode(String.self, forKey: .id)) ?? 0
// 4
return container
}
// 1
: 覆盖static func container(for decoder: Decoder) throws -> AnyCoreDataKeyedDecodingContainer
// 2
: 创建一个新的可变容器,将自己的类型作为参数传递。传递另一个类型将导致错误。// 3
: 将值转换为所需的类型并将其分配给.id
键// 4
: 返回修改后的容器
多个
您可以在另一个Codable
对象中嵌套不带有任何问题的CoreDataDecodable
对象
struct LoginResponse: Codable {
let token: String
let user: Person
}
但是,如果您想引用一个CoreDataDecodable
对象的数组
struct Response: Codable {
let nextPage: String
let previousPage: String
let total: Int
// don't do this
let results: [Person]
}
最好使用Many
而不是Array
struct Response: Codable {
let nextPage: String
let previousPage: String
let total: Int
let results: Many<Person>
}
使用Many
将提高性能。Many
是Array
的替代品,可以以相同的方式使用。在任何情况下,您都可以使用many.array
访问原始数组。
灵感
这个库在很大程度上受到了Groot的启发
作者
许可
Coredatable是在MIT许可证下可用的。请参阅LICENSE。