How to use swift 4 Codable in Core Data?
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.
Implementing Codable and NSManagedObject simultaneously in Swift
In the end, it sounds like there is no "good" solution when migrating from an API dynamic app without caching to a cached app.
I decided to just bite the bullet and try the method in this Question: How to use swift 4 Codable in Core Data?
EDIT:
I couldn't figure out how to make that work so I used the following solution:
import Foundation
import CoreData
/*
SomeItemData vs SomeItem:
The object with 'Data' appended to the name will always be the codable struct. The other will be the NSManagedObject class.
*/
struct OrderData: Codable, CodingKeyed, PropertyLoopable
{
typealias CodingKeys = CodableKeys.OrderData
let writer: String,
userID: String,
orderType: String,
shipping: ShippingAddressData
var items: [OrderedProductData]
let totals: PaymentTotalData,
discount: Float
init(json:[String:Any])
{
writer = json[CodingKeys.writer.rawValue] as! String
userID = json[CodingKeys.userID.rawValue] as! String
orderType = json[CodingKeys.orderType.rawValue] as! String
shipping = json[CodingKeys.shipping.rawValue] as! ShippingAddressData
items = json[CodingKeys.items.rawValue] as! [OrderedProductData]
totals = json[CodingKeys.totals.rawValue] as! PaymentTotalData
discount = json[CodingKeys.discount.rawValue] as! Float
}
}
extension Order: PropertyLoopable //this is the NSManagedObject. PropertyLoopable has a default implementation that uses Mirror to convert all the properties into a dictionary I can iterate through, which I can then pass directly to the JSON constructor above
{
convenience init(from codableObject: OrderData)
{
self.init(context: PersistenceManager.shared.context)
writer = codableObject.writer
userID = codableObject.userID
orderType = codableObject.orderType
shipping = ShippingAddress(from: codableObject.shipping)
items = []
for item in codableObject.items
{
self.addToItems(OrderedProduct(from: item))
}
totals = PaymentTotal(from: codableObject.totals)
discount = codableObject.discount
}
}
How do I properly use Codable, CoreData and NSSet relationships?
Declare photos as a swift native type
@NSManaged var photos: Set<Photo>
In decoder
photos = try values.decode(Set<Photo>.self, forKey: .photos)
Save to CoreData array of strings with Codable
You need an inverse relationship to Movie
in Genre
. Add this
@NSManaged var movie: Movie?
and establish the connection in the model file.
Then decode an array of strings, map it to Genre instances and assign self
to that relationship at the end of the init method
let genreData = try container.decodeIfPresent([String].self, forKey: .genres) ?? []
let genreArray = genreData.map { name in
let genre = Genre(context: manageObjContext)
genre.name = name
genre.movie = self
return genre
}
self.genres = Set(genreArray)
Consider to use a to-many relationship from Genre
to Movie
as well because otherwise you will have a lot of Genre
instances with the same name. And consider also to reduce the optionals in the Core Data classes. It seems that the JSON source provides always all fields. You can get rid of a lot of redundant code.
Swift: Loading Codable Class into CoreData from JSON is generating Objects with all properties to nil
To summarize from the questions comments:
It's important to remember that CoreData still relies heavily on Objective-C. In your example code, the properties on your class, although expressible as CoreData attributes, the implementation is not being handled by CoreData.
You'll need to add the @NSManaged
attribute to your properties like this:
@NSManaged var name: String?
@NSManaged var desc: String?
@NSManaged var image: String?
@NSManaged var quantity: Double?
This will expose them to Obj-C as dynamic and allow CoreData to handle the implementation. This would also help to explain your debugging, in that at runtime the print statement would show values, but the saved managed object had nil values.
Related Topics
How to Run a Terminal Command in a Swift Script? (E.G. Xcodebuild)
Swiftui Ios14 - Navigationview + List - Won't Fill Space
All Dates Between Two Date Objects (Swift)
Binary Operator * Cannot Be Applied to Operands of Type Int and Double
Why Constant Constraints the Property from a Structure Instance But Not the Class Instance
Iterate Over Collection Two At a Time in Swift
Swift - Extra Argument in Call
Extensions in My Own Custom Class
How to Cast Self to Unsafemutablepointer≪Void≫ Type in Swift
Swift: Print() VS Println() VS Nslog()
What Does It Mean That String and Character Comparisons in Swift Are Not Locale-Sensitive
When Are Argument Labels Required in Swift
Swift Equality Operator on Nested Arrays
Value of Type 'Storagemetadata' Has No Member 'Downloadurl'
How to Display My App Documents in the Files App For Iphone
Why Is 'Nil' Not Compatible With 'Unsafepointer≪Cgaffinetransform≫' in Swift 3