Как использовать swift 4 Codable в базовых данных?
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
?
Ответы
Ответ 1
Вы можете использовать интерфейс 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
Чтобы кодировать данные, вам нужно сделать что-то подобное, используя функцию протокола encode.
Ответ 2
CoreData - это собственная платформа persistence, и в соответствии с ее полной документацией вы должны использовать назначенные инициализаторы и следовать довольно определенному пути к созданию и хранению объектов с ней.
Вы все еще можете использовать Codable
с ним в ограниченных вариантах, как вы можете использовать NSCoding
.
Одним из способов является декодирование объекта (или структуры) с помощью любого из этих протоколов и перенос его свойств в новый экземпляр NSManagedObject
, который вы создали для каждого документа с основными данными.
Другим способом (который очень распространен) является использование одного из протоколов только для нестандартного объекта, который вы хотите сохранить в свойствах управляемого объекта. Под "нестандартным" я имею в виду все, что не соответствует стандартным типам атрибутов Core Data, как указано в вашей модели. Например, NSColor
не может быть сохранен непосредственно как свойство управляемого объекта, так как он не поддерживает один из базовых типов атрибутов. Вместо этого вы можете использовать NSKeyedArchiver
для сериализации цвета в экземпляр NSData
и сохранить его как свойство Data в управляемом объекте. Отмените этот процесс с помощью NSKeyedUnarchiver
. Это упрощенно и есть гораздо лучший способ сделать это с помощью Core Data (см. Transient Attributes), но это иллюстрирует мою мысль.
Вы также могли бы принять Encodable
(один из двух протоколов, которые составляют Codable
- можете ли вы угадать имя другого?), чтобы преобразовать экземпляр управляемого объекта непосредственно в JSON для совместного использования, но вам придется указать ключи кодирования и собственную собственную реализацию encode
, поскольку она не будет автоматически синтезирована компилятором с помощью специальных ключей кодирования. В этом случае вам нужно указать только ключи (свойства), которые вы хотите включить.
Надеюсь, что это поможет.
Ответ 3
Swift 4.2:
После решения касадемора,
guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }
должно быть
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }
.
Это предотвращает ошибки, которые XCode ложно распознает как проблемы среза массива.
Редактировать: использовать неявно развернутые опции, чтобы избавить от необходимости принудительно разворачивать .context
каждом его использовании.
Ответ 4
В качестве альтернативы для тех, кто хотел бы использовать современный подход 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 без необходимости вручную создавать все мои классы/свойства.