Skip to content

Instantly share code, notes, and snippets.

@Akhu
Created May 11, 2018 09:46
Show Gist options
  • Save Akhu/5ea1ecbd652fb269f7c4e7db27bc79cc to your computer and use it in GitHub Desktop.
Save Akhu/5ea1ecbd652fb269f7c4e7db27bc79cc to your computer and use it in GitHub Desktop.
Mixing Codable and Core Data in Swift
//
// Article.swift
// Veille
//
// Created by Anthony Da Cruz on 26/01/2018.
// Copyright © 2018 Anthony Da Cruz. All rights reserved.
//
import Foundation
import CoreData
class Article: NSManagedObject, Decodable {
@NSManaged var title:String!
@NSManaged var summary:String?
@NSManaged var link:URL!
@NSManaged var image:URL?
@NSManaged var createdDate: Date
//var tags:[String]?
@NSManaged var id:UUID
enum CodingKeys: String, CodingKey {
case title
case summary = "description"
case link
case image = "imageURL"
case createdDate = "date"
}
required convenience init(from decoder: Decoder) throws {
guard let contextUserInfoKey = CodingUserInfoKey.context else { fatalError("cannot find context key") }
guard let managedObjectContext = decoder.userInfo[contextUserInfoKey] as? NSManagedObjectContext else { fatalError("cannot Retrieve context") }
guard let entity = NSEntityDescription.entity(forEntityName: "Article", in: managedObjectContext) else { fatalError() }
self.init(entity: entity, insertInto: nil)
let values = try decoder.container(keyedBy: CodingKeys.self)
self.createdDate = Date()
self.title = try values.decode(String.self, forKey: .title)
self.summary = try values.decode(String.self, forKey: .summary)
guard let linkString = try values.decodeIfPresent(String.self, forKey: .link) else { return }
if let linkUrl = URL(string: linkString) {
self.link = linkUrl
}
if let imageURLString = try values.decodeIfPresent(String.self, forKey: .image) {
if let imageURL = URL(string: imageURLString){
self.image = imageURL
}
}
}
}
extension Article: Encodable{
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.title, forKey: .title)
try container.encodeIfPresent(self.summary, forKey: .summary)
try container.encodeIfPresent(self.image, forKey: .image)
try container.encode(self.link, forKey: .link)
try container.encode(self.createdDate.toIso8601(), forKey: .createdDate)
}
}
extension CodingUserInfoKey {
static let context = CodingUserInfoKey(rawValue: "context")
}
// Use it :
let context = CoreDataStack.store.persistentContainer.newBackgroundContext() //Getting context
let plistDecoderForArticle = PropertyListDecoder()
plistDecoderForArticle.userInfo[CodingUserInfoKey.context!] = context //Pass it to CodingUserInfoKey which is made for that
let decodedData = try plistDecoderForArticle.decode([Article].self, from: data) //Decoding init got the managedObjectContext
@Wavewash
Copy link

Appreciate you putting such a concise example up. Exactly what I was looking for. Thanks.

@TriMontana
Copy link

Yes, this was a great example.

@rdv0011
Copy link

rdv0011 commented Sep 25, 2019

@Akhu thanks for sharing this example. Do you have an idea how to handle not only an insert operation but also an updae one? Let's say some data comes from the backend and what it's need is to merge it with the existing CoreData entity.

@rdv0011
Copy link

rdv0011 commented Sep 25, 2019

one option might be using:
override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
where a decision whether we need to call insertObject might be made.

@Akhu
Copy link
Author

Akhu commented Oct 1, 2019

I would go for a DTO in that case : https://en.wikipedia.org/wiki/Data_transfer_object

If you want to go deeper in the subject I recommend this book https://www.objc.io/books/core-data/ They explain every scenario even the most complex (sync with network etc.) They also deliver some code example here https://github.com/objcio/core-data/tree/master/Moody/MoodySync . Hope it helps :)

@sisoje
Copy link

sisoje commented Oct 14, 2019

since when is placing coding into a constructor a good practice? where did the single responsibility principle go?

@albertoazzari
Copy link

hi everyone I think It should works but it throws me this error dataCorrupted(Swift.DecodingError.Context(codingPath: [], debugDescription: "The given data was not a valid property list.". Do you know how to solve it ?

@andreaagudo3
Copy link

@Akhu thanks for sharing this example. Do you have an idea how to handle not only an insert operation but also an updae one? Let's say some data comes from the backend and what it's need is to merge it with the existing CoreData entity.

Did you find out any good solution for this case? I am looking for the same

@rdv0011
Copy link

rdv0011 commented Feb 1, 2021

@Akhu thanks for sharing this example. Do you have an idea how to handle not only an insert operation but also an updae one? Let's say some data comes from the backend and what it's need is to merge it with the existing CoreData entity.

Did you find out any good solution for this case? I am looking for the same

No. I did not. For a production code I would split into three models DTO and DAO and actual model used elsewhere expect decoding a stream from the Internet and CoreData. For ex.

/// Exposed to the other parts
public struct Article {
    let name: String
}
/// Used privately to decode data that comes from the Internet
struct ArticleDTO: Decodable {
    let name: String

    func toModel() -> Article {
        Article(name: name)
    }
}
/// Used privately to decode data from CoreData
struct ArticleDAO: NSManagedObject {
    @NSManaged var summary:String?
   
    func toModel() -> Article {
        Article(name: name)
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment