Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use swift 4 Codable in Core Data?

Codable seems a very exciting feature. But I wonder how we can use it in Core Data? In particular, is it possible to directly encode/decode a JSON from/to a NSManagedObject?

I tried a very simple example:

enter image description here

and defined Foo myself:

import CoreData  @objc(Foo) public class Foo: NSManagedObject, Codable {} 

But when using it like this:

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) 

The compiler failed with this errror:

super.init isn't called on all paths before returning from initializer 

and the target file was the file that defined Foo

I guess I probably did it wrong, since I didn't even pass a NSManagedObjectContext, but I have no idea where to stick it.

Does Core Data support Codable?

like image 432
hgl Avatar asked Jun 09 '17 05:06

hgl


People also ask

How do I make core data Codable?

To make Codable work with Core Data we need to conform to both Encodable and Decodable in a way that allows to correctly interact with the app persistent container. Basically, we need to appropriately implement Encodable 's encode(to:) and Decodable 's init(from:) .

What does Codable do in Swift?

Codable allows you to insert an additional clarifying stage into the process of decoding data into a Swift object. This stage is the “parsed object,” whose properties and keys match up directly to the data, but whose types have been decoded into Swift objects.

What is transformable in core data?

When you declare a property as Transformable Core Data converts your custom data type into binary Data when it is saved to the persistent store and converts it back to your custom data type when fetched from the store. It does this through a value transformer.


2 Answers

You can use the Codable interface with CoreData objects to encode and decode data, however it's not as automatic as when used with plain old swift objects. Here's how you can implement JSON Decoding directly with Core Data objects:

First, you make your object implement Codable. This interface must be defined on the object, and not in an extension. You can also define your Coding Keys in this class.

class MyManagedObject: NSManagedObject, Codable {     @NSManaged var property: String?      enum CodingKeys: String, CodingKey {        case property = "json_key"     } } 

Next, you can define the init method. This must also be defined in the class method because the init method is required by the Decodable protocol.

required convenience init(from decoder: Decoder) throws { } 

However, the proper initializer for use with managed objects is:

NSManagedObject.init(entity: NSEntityDescription, into context: NSManagedObjectContext) 

So, the secret here is to use the userInfo dictionary to pass in the proper context object into the initializer. To do this, you'll need to extend the CodingUserInfoKey struct with a new key:

extension CodingUserInfoKey {    static let context = CodingUserInfoKey(rawValue: "context") } 

Now, you can just as the decoder for the 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) } 

Now, when you set up the decoding for Managed Objects, you'll need to pass along the proper context object:

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 

To encode data, you'll need to do something similar using the encode protocol function.

like image 52
casademora Avatar answered Nov 09 '22 14:11

casademora


CoreData is its own persistence framework and, per its thorough documentation, you must use its designated initializers and follow a rather specific path to creating and storing objects with it.

You can still use Codable with it in limited ways just as you can use NSCoding, however.

One way is to decode an object (or a struct) with either of these protocols and transfer its properties into a new NSManagedObject instance you've created per Core Data's docs.

Another way (which is very common) is to use one of the protocols only for a non-standard object you want to store in a managed object's properties. By "non-standard", I mean anything thst doesn't conform to Core Data's standard attribute types as specified in your model. For example, NSColor can't be stored directly as a Managed Object property since it's not one of the basic attribute types CD supports. Instead, you can use NSKeyedArchiver to serialize the color into an NSData instance and store it as a Data property in the Managed Object. Reverse this process with NSKeyedUnarchiver. That's simplistic and there is a much better way to do this with Core Data (see Transient Attributes) but it illustrates my point.

You could also conceivably adopt Encodable (one of the two protocols that compose Codable - can you guess the name of the other?) to convert a Managed Object instance directly to JSON for sharing but you'd have to specify coding keys and your own custom encode implementation since it won't be auto-synthesized by the compiler with custom coding keys. In this case you'd want to specify only the keys (properties) you want to be included.

Hope this helps.

like image 30
Joshua Nozzi Avatar answered Nov 09 '22 14:11

Joshua Nozzi