Как использовать swift 4 Codable в Core Data?
Codable кажется очень интересной функцией. Но мне интересно, как мы можем использовать его в Core Data? В частности, возможно ли напрямую кодировать / декодировать JSON из / в NSManagedObject?
Я попробовал очень простой пример:
и определил Foo
себя:
import CoreData
@objc(Foo)
public class Foo: NSManagedObject, Codable {}
Но при использовании это так:
let json = """
{
"name": "foo",
"bars": [{
"name": "bar1",
}], [{
"name": "bar2"
}]
}
""".data(using: .utf8)!
let decoder = JSONDecoder()
let foo = try! decoder.decode(Foo.self, from: json)
print(foo)
Сбой компилятора с этой ошибкой:
super.init isn't called on all paths before returning from initializer
и целевой файл был файл, который определил Foo
Я думаю, что я, вероятно, сделал это неправильно, так как я даже не прошел NSManagedObjectContext
, но я понятия не имею, куда его втыкать.
Поддерживает ли Core Data Codable
?
3 ответа
Вы можете использовать интерфейс Codable с объектами CoreData для кодирования и декодирования данных, однако он не такой автоматический, как при использовании с простыми старыми объектами swift. Вот как вы можете реализовать JSON-декодирование напрямую с объектами Core Data:
Во-первых, вы заставляете свой объект реализовывать Codable. Этот интерфейс должен быть определен на объекте, а не в расширении. Вы также можете определить свои ключи кодирования в этом классе.
class MyManagedObject: NSManagedObject, Codable {
@NSManaged var property: String?
enum CodingKeys: String, CodingKey {
case property = "json_key"
}
}
Далее вы можете определить метод init. Это также должно быть определено в методе класса, поскольку метод init требуется протоколом Decodable.
required convenience init(from decoder: Decoder) throws {
}
Однако правильный инициализатор для использования с управляемыми объектами:
NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
Итак, секрет здесь в том, чтобы использовать словарь userInfo для передачи нужного объекта контекста в инициализатор. Для этого вам нужно продлить CodingUserInfoKey
структура с новым ключом:
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
Теперь вы можете просто как декодер для контекста:
required convenience init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
guard let entity = NSEntityDescription.entity(forEntityName: "MyManagedObject", in: context) else { fatalError() }
self.init(entity: entity, in: context)
let container = decoder.container(keyedBy: CodingKeys.self)
self.property = container.decodeIfPresent(String.self, forKey: .property)
}
Теперь, когда вы настроите декодирование для управляемых объектов, вам нужно будет передать соответствующий контекстный объект:
let data = //raw json data in Data object
let context = persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[.context] = context
_ = try decoder.decode(MyManagedObject.self, from: data) //we'll get the value from another context using a fetch request later...
try context.save() //make sure to save your data once decoding is complete
Для кодирования данных вам нужно сделать нечто подобное, используя функцию протокола кодирования.
CoreData является его собственной средой персистентности, и, согласно ее исчерпывающей документации, вы должны использовать ее назначенные инициализаторы и следовать довольно специфическому пути создания и хранения объектов с ее помощью.
Вы все еще можете использовать Codable
с этим ограниченным образом, так как вы можете использовать NSCoding
, тем не мение.
Одним из способов является декодирование объекта (или структуры) с помощью любого из этих протоколов и передача его свойств в новый NSManagedObject
экземпляр, который вы создали в соответствии с документами Core Data.
Другой способ (который очень распространен) - использовать один из протоколов только для нестандартного объекта, который вы хотите сохранить в свойствах управляемого объекта. Под "нестандартным" я подразумеваю все, что не соответствует стандартным типам атрибутов Core Data, указанным в вашей модели. Например, NSColor
не может быть сохранен непосредственно как свойство управляемого объекта, так как он не является одним из базовых типов атрибутов, поддерживаемых CD. Вместо этого вы можете использовать NSKeyedArchiver
сериализовать цвет в NSData
экземпляр и сохранить его как свойство данных в управляемом объекте. Обратный процесс с NSKeyedUnarchiver
, Это упрощенно, и есть гораздо лучший способ сделать это с Core Data (см. Transient Attributes), но это иллюстрирует мою точку зрения.
Вы также можете принять Encodable
(один из двух протоколов, которые составляют Codable
- можете ли вы угадать имя другого?), чтобы преобразовать экземпляр управляемого объекта непосредственно в JSON для совместного использования, но вам придется указать ключи кодирования и свой собственный encode
реализация, поскольку он не будет автоматически синтезироваться компилятором с пользовательскими ключами кодирования. В этом случае вы хотите указать только те ключи (свойства), которые вы хотите включить.
Надеюсь это поможет.
Swift 4.2:
Следуя решению Казадеморы,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
должно быть
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
,
Это предотвращает ошибки, которые XCode ложно распознает как проблемы среза массива.
Редактировать: использовать неявно развернутые опции, чтобы убрать необходимость принудительного развертывания .context
каждый раз, когда он используется.
В качестве альтернативы для тех, кто хотел бы использовать современный подход XCode к NSManagedObject
генерация файлов, я создал DecoderWrapper
класс, чтобы выставить Decoder
объект, который я затем использую в моем объекте, который соответствует JSONDecoding
протокол:
class DecoderWrapper: Decodable {
let decoder:Decoder
required init(from decoder:Decoder) throws {
self.decoder = decoder
}
}
protocol JSONDecoding {
func decodeWith(_ decoder: Decoder) throws
}
extension JSONDecoding where Self:NSManagedObject {
func decode(json:[String:Any]) throws {
let data = try JSONSerialization.data(withJSONObject: json, options: [])
let wrapper = try JSONDecoder().decode(DecoderWrapper.self, from: data)
try decodeWith(wrapper.decoder)
}
}
extension MyCoreDataClass: JSONDecoding {
enum CodingKeys: String, CodingKey {
case name // For example
}
func decodeWith(_ decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.name = try container.decode(String.self, forKey: .name)
}
}
Это, вероятно, полезно только для моделей без каких-либо необязательных атрибутов, но это решает мою проблему желания использовать Decodable
но также управлять отношениями и постоянством с Core Data без необходимости вручную создавать все мои классы / свойства.