57

Codable 似乎是一个非常令人兴奋的功能。但我想知道我们如何在 Core Data 中使用它?特别是,是否可以直接从/到 NSManagedObject 编码/解码 JSON?

我尝试了一个非常简单的例子:

在此处输入图像描述

并定义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

我想我可能做错了,因为我什至没有通过 a NSManagedObjectContext,但我不知道该把它贴在哪里。

核心数据支持Codable吗?

4

4 回答 4

97

您可以将 Codable 接口与 CoreData 对象一起使用来编码和解码数据,但是它不像与普通的旧 swift 对象一起使用时那样自动。以下是您可以使用 Core Data 对象直接实现 JSON 解码的方法:

首先,你让你的对象实现 Codable。此接口必须在对象上定义,而不是在扩展中。您还可以在此类中定义您的编码键。

class MyManagedObject: NSManagedObject, Codable {
    @NSManaged var property: String?

    enum CodingKeys: String, CodingKey {
       case property = "json_key"
    }
}

接下来,您可以定义 init 方法。这也必须在类方法中定义,因为可解码协议需要 init 方法。

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[CodingUserInfoKey.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

要对数据进行编码,您需要使用编码协议功能执行类似的操作。

于 2017-10-24T17:53:24.913 回答
13

CoreData 是它自己的持久性框架,根据其详尽的文档,您必须使用其指定的初始化程序并遵循相当具体的路径来创建和存储对象。

但是,您仍然可以像使用Codable一样以有限的方式使用它NSCoding

一种方法是使用这些协议中的任何一个解码对象(或结构)并将其属性传输到NSManagedObject您根据 Core Data 的文档创建的新实例中。

另一种方法(非常常见)是仅将其中一个协议用于要存储在托管对象属性中的非标准对象。“非标准”是指任何不符合模型中指定的 Core Data 标准属性类型的东西。例如,NSColor不能直接存储为托管对象属性,因为它不是 CD 支持的基本属性类型之一。相反,您可以使用NSKeyedArchiver将颜色序列化为NSData实例并将其作为数据属性存储在托管对象中。用 反转这个过程NSKeyedUnarchiver。这很简单,并且有一个更好的方法可以使用 Core Data 来做到这一点(请参阅Transient Attributes),但这说明了我的观点。

您还可以想象采用Encodable(组成的两个协议之一Codable- 您能猜出另一个的名称吗?)将托管对象实例直接转换为 JSON 以进行共享,但您必须指定编码键和您自己的自定义encode实现,因为编译器不会使用自定义编码键自动合成它。在这种情况下,您只想指定包含的键(属性)。

希望这可以帮助。

于 2017-06-09T14:20:05.463 回答
7

斯威夫特 4.2:

按照 casademora 的解决方案,

guard let context = decoder.userInfo[.context] as? NSManagedObjectContext else { fatalError() }

应该

guard let context = decoder.userInfo[CodingUserInfoKey.context!] as? NSManagedObjectContext else { fatalError() }.

这可以防止 Xcode 错误地将错误识别为数组切片问题。

编辑:使用隐式展开的选项来消除.context每次使用时强制展开的需要。

于 2018-07-24T22:00:16.840 回答
5

作为那些想要使用 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 的关系和持久性的问题,而无需手动创建我的所有类/属性。

编辑:它的使用示例

如果我有一个 json 对象:

let myjson = [ "name" : "Something" ]

我在 Core Data 中创建对象(为简洁起见,在此处强制转换):

let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass

我使用扩展让对象解码 json:

do {
    try myObject.decode(json: myjson)
}
catch {
    // handle any error
}

现在myObject.name"Something"

于 2019-02-05T18:30:24.620 回答