如何在Core Data中使用swift 4 Codable?

hgl*_*hgl 43 core-data swift swift4 xcode9 codable

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

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

在此输入图像描述

并定义Foo自己:

import CoreData

@objc(Foo)
public class Foo: NSManagedObject, Codable {}
Run Code Online (Sandbox Code Playgroud)

但是当它像这样使用时:

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)
Run Code Online (Sandbox Code Playgroud)

编译器因此错误而失败:

super.init isn't called on all paths before returning from initializer
Run Code Online (Sandbox Code Playgroud)

目标文件是定义的文件 Foo

我想我可能做错了,因为我甚至没有通过NSManagedObjectContext,但我不知道在哪里坚持下去.

核心数据是否支持Codable

cas*_*ora 75

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

首先,您使对象实现Codable.必须在对象上定义此接口,而不是在扩展中定义.您还可以在此课程中定义编码密钥.

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

    enum CodingKeys: String, CodingKey {
       case property = "json_key"
    }
}
Run Code Online (Sandbox Code Playgroud)

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

required convenience init(from decoder: Decoder) throws {
}
Run Code Online (Sandbox Code Playgroud)

但是,与托管对象一起使用的正确初始化程序是:

NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext)
Run Code Online (Sandbox Code Playgroud)

所以,这里的秘诀是使用userInfo字典将适当的上下文对象传递给初始化器.为此,您需要CodingUserInfoKey使用新键扩展结构:

extension CodingUserInfoKey {
   static let context = CodingUserInfoKey(rawValue: "context")
}
Run Code Online (Sandbox Code Playgroud)

现在,您可以作为上下文的解码器:

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)
}
Run Code Online (Sandbox Code Playgroud)

现在,当您为托管对象设置解码时,您需要传递正确的上下文对象:

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
Run Code Online (Sandbox Code Playgroud)

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

  • 很好的主意.有没有办法以这种方式初始化然后更新现有对象?例如,检查id是否已经在CoreData中.如果存在则加载对象并更新,否则创建新的(如上所述). (8认同)
  • 如果您想**更新现有**对象,则此答案无济于事。它总是创建新对象并复制您现有的记录。 (2认同)

Jos*_*zzi 12

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

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

一种方法是使用这些协议中的任何一个解码对象(或结构),并将其属性转移到NSManagedObject您为每个Core Data的文档创建的新实例中.

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

您还可以设想采用Encodable(组合的两个协议之一Codable- 您能猜出另一个的名称吗?)将Managed Object实例直接转换为JSON进行共享,但是您必须指定编码键和自己的自定义encode实现它不会由编译器使用自定义编码密钥自动合成.在这种情况下,你要指定唯一要列入键(属性).

希望这可以帮助.

  • 我真的不会指望它.定制编码/解码的能力几乎涵盖了应用程序商店与大多数真实案例之间的数据传输的所有基础,仅使用JSON de/coder.由于这两种方法对于应用程序持久性是互斥的(由于它们完全不同的设计方法和用例),因此这种支持的可能性几乎为零.但希望永无止境.;-) (2认同)

Sch*_*cal 6

Swift 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每次使用时强制解包的需要.

  • 是的,我知道两者之间的区别。我只是建议将展开内容放到常量(放在一个位置)上,而不是放在userInfo访问器(可能到处都是)上 (2认同)

Chr*_*isH 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能只对没有任何非可选属性的模型有用,但它解决了我想要使用Decodable但也管理与 Core Data 的关系和持久性的问题,而无需手动创建我的所有类/属性。

编辑:使用中的示例

如果我有一个 json 对象:

let myjson = [ "name" : "Something" ]
Run Code Online (Sandbox Code Playgroud)

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

let myObject = NSEntityDescription.insertNewObject(forEntityName: "MyCoreDataClass", into: myContext) as! MyCoreDataClass
Run Code Online (Sandbox Code Playgroud)

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

do {
    try myObject.decode(json: myjson)
}
catch {
    // handle any error
}
Run Code Online (Sandbox Code Playgroud)

现在myObject.name"Something"