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
@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