'Failed to call designated initializer on NSManagedObject class' NSManagedObject and NSCoding - swift

I'm attempting to:
Load an array of custom objects from JSON file.
Save it in CoreData.
Fetch it using NSFetchRequest.
Note: Each element in the array of custom objects named 'Cube' also contains a nested array of custom objects named 'Algorithm', which also conform to NSCoding (shown in code snippet below).
Steps 1 + 2 seem to work fine. The error occurs when fetching the top level Entities named 'Cube', specifically right after the self.init() is called inside the nested class 'Algorithm'.
Algorithm class:
#objc(Algorithm)
public class Algorithm: NSManagedObject, Decodable, Encodable, NSSecureCoding {
enum CodingKeys: String, CodingKey {
case imageNumber, alg, alternativeAlgs, type, tags, isFavorite, optimalMoves, name }
public static var supportsSecureCoding = true
public func encode(with coder: NSCoder) {
coder.encode(imageNumber, forKey: CodingKeys.imageNumber.rawValue)
coder.encode(optimalMoves, forKey: CodingKeys.optimalMoves.rawValue)
coder.encode(alg, forKey: CodingKeys.alg.rawValue)
coder.encode(name, forKey: CodingKeys.name.rawValue)
coder.encode(type, forKey: CodingKeys.type.rawValue)
coder.encode(isFavorite, forKey: CodingKeys.isFavorite.rawValue)
coder.encode(alternativeAlgs, forKey: CodingKeys.alternativeAlgs.rawValue)
coder.encode(tags, forKey: CodingKeys.tags.rawValue) }
required convenience public init?(coder: NSCoder) {
self.init()
imageNumber = coder.decodeObject(forKey: CodingKeys.imageNumber.rawValue) as? String ?? ""
optimalMoves = coder.decodeObject(forKey: CodingKeys.optimalMoves.rawValue) as? String ?? ""
alg = coder.decodeObject(forKey: CodingKeys.alg.rawValue) as? String ?? ""
name = coder.decodeObject(forKey: CodingKeys.name.rawValue) as? String ?? ""
type = coder.decodeObject(forKey: CodingKeys.type.rawValue) as? String ?? ""
isFavorite = coder.decodeBool(forKey: CodingKeys.isFavorite.rawValue)
alternativeAlgs = coder.decodeObject(forKey: CodingKeys.alternativeAlgs.rawValue) as? [String] ?? []
tags = coder.decodeObject(forKey: CodingKeys.tags.rawValue) as? [String] ?? [] }
required convenience public init(from decoder: Decoder) throws {
guard let context = decoder.userInfo[CodingUserInfoKey.managedObjectContext] as? NSManagedObjectContext else {
throw DecoderConfigurationError.missingManagedObjectContext
}
self.init(context: context)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.imageNumber = try container.decode(String.self, forKey: .imageNumber)
self.optimalMoves = try container.decode(String.self, forKey: .optimalMoves)
self.alg = try container.decode(String.self, forKey: .alg)
self.name = try container.decode(String.self, forKey: .name)
self.type = try container.decode(String.self, forKey: .type)
self.isFavorite = try container.decode(Bool.self, forKey: .isFavorite)
self.alternativeAlgs = try container.decode([String].self, forKey: .alternativeAlgs) as [String]
self.tags = try container.decode([String].self, forKey: .tags) as [String] }
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(imageNumber, forKey: .imageNumber)
try container.encode(optimalMoves, forKey: .optimalMoves)
try container.encode(alg, forKey: .alg)
try container.encode(name, forKey: .name)
try container.encode(type, forKey: .type)
try container.encode(isFavorite, forKey: .isFavorite)
try container.encode(alternativeAlgs, forKey: .alternativeAlgs)
try container.encode(tags, forKey: .tags) }
}
extension Algorithm {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Algorithm> {
return NSFetchRequest<Algorithm>(entityName: "Algorithm")
}
#NSManaged public var alg: String
#NSManaged public var alternativeAlgs: [String]
#NSManaged public var imageNumber: String
#NSManaged public var isFavorite: Bool
#NSManaged public var name: String
#NSManaged public var optimalMoves: String
#NSManaged public var tags: [String]
#NSManaged public var type: String
#NSManaged public var cube: Cube?
}
extension Algorithm : Identifiable {
}
After reading people's comments I am aware I should replace the self.init() with NSManagedObject's designated init, but I haven't found the right way to do so. The app crashes after replacing the self.init() with the following code:
let context = CoreDataManager.shared.container.viewContext
self.init(context: context)
// self.init()
FWI - in the CoreData.xcdatamodeled file inspector the algorithms array is defined as Transformable with a custom transformer - AlgorithmDataTransformer:
#objc(AlgorithmDataTransformer)
public final class AlgorithmDataTransformer: ValueTransformer {
override public func transformedValue(_ value: Any?) -> Any? {
guard let array = value as? [Algorithm] else { return nil }
do {
return try NSKeyedArchiver.archivedData(withRootObject: array, requiringSecureCoding: true)
} catch {
assertionFailure("Failed to transform `Algorithm` to `Data`")
return nil
}
}
override public func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
do {
return try NSKeyedUnarchiver.unarchivedArrayOfObjects(ofClass: Algorithm.self, from: data as Data)
} catch {
assertionFailure("Failed to transform `Data` to `Algorithm`")
return nil
}
}
}
extension AlgorithmDataTransformer {
/// The name of the transformer. This is the name used to register the transformer using `ValueTransformer.setValueTrandformer(_"forName:)`.
static let name = NSValueTransformerName(rawValue: String(describing: AlgorithmDataTransformer.self))
/// Registers the value transformer with `ValueTransformer`.
public static func register() {
let transformer = AlgorithmDataTransformer()
ValueTransformer.setValueTransformer(transformer, forName: name)
}
}

Related

Store custom data type in #AppStorage with optional initializer?

I'm trying to store a custom data type into AppStorage. To do so, the model conforms to RawRepresentable (followed this tutorial). It's working fine, but when I initialize the #AppStorage variable, it requires an initial UserModel value. I want to make the variable optional, so it can be nil if the user is signed out. Is this possible?
Within a class / view, I can init like this:
#AppStorage("user_model") private(set) var user: UserModel = UserModel(id: "", name: "", email: "")
But I want to init like this:
#AppStorage("user_model") private(set) var user: UserModel?
Model:
struct UserModel: Codable {
let id: String
let name: String
let email: String
enum CodingKeys: String, CodingKey {
case id
case name
case email
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
do {
id = try String(values.decode(Int.self, forKey: .id))
} catch DecodingError.typeMismatch {
id = try String(values.decode(String.self, forKey: .id))
}
self.name = try values.decode(String.self, forKey: .name)
self.email = try values.decode(String.self, forKey: .email)
}
init(id: String, name: String, email: String) {
self.id = id
self.name = name
self.email = email
}
}
// MARK: RAW REPRESENTABLE
extension UserModel: RawRepresentable {
// RawRepresentable allows a UserModel to be store in AppStorage directly.
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(UserModel.self, from: data)
else {
return nil
}
self = result
}
var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(email, forKey: .email)
}
}
The code below works because you added a conformance UserModel: RawRepresentable:
#AppStorage("user_model") private(set) var user: UserModel = UserModel(id: "", name: "", email: "")
You need to do the same for UserModel? if you want the following to work:
#AppStorage("user_model") private(set) var user: UserModel? = nil
Here is a possible solution:
extension Optional: RawRepresentable where Wrapped == UserModel {
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let result = try? JSONDecoder().decode(UserModel.self, from: data)
else {
return nil
}
self = result
}
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let result = String(data: data, encoding: .utf8)
else {
return "[]"
}
return result
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: UserModel.CodingKeys.self)
try container.encode(self?.id, forKey: .id)
try container.encode(self?.name, forKey: .name)
try container.encode(self?.email, forKey: .email)
}
}
Note: I reused the implementation you already had for UserModel: RawRepresentable - it might need some corrections for this case.
Also because you conform Optional: RawRepresentable you need to make
UserModel public as well.
A possible generic approach for any optional Codable:
extension Optional: RawRepresentable where Wrapped: Codable {
public var rawValue: String {
guard let data = try? JSONEncoder().encode(self),
let json = String(data: data, encoding: .utf8)
else {
return "{}"
}
return json
}
public init?(rawValue: String) {
guard let data = rawValue.data(using: .utf8),
let value = try? JSONDecoder().decode(Self.self, from: data)
else {
return nil
}
self = value
}
}
With that in place, any Codable can now be persisted in app storage:
#AppStorage("user_model") var user: UserModel? = nil

Swift - Decode/encode an array of generics with different types

How can I decode/encode an array of different generic types?
I have a data structure, which has properties, that conform to a protocol Connection, thus I use generics:
// Data structure which saves two objects, which conform to the Connection protocol
struct Configuration<F: Connection, T: Connection>: Codable {
var from: F
var to: T
private var id: String = UUID.init().uuidString
enum CodingKeys: String, CodingKey {
case from, to, id
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.from = try container.decode(F.self, forKey: .from)
self.to = try container.decode(T.self, forKey: .to)
self.id = try container.decode(String.self, forKey: .id)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(from, forKey: .from)
try container.encode(to, forKey: .to)
try container.encode(id, forKey: .id)
}
}
protocol Connection: Codable {
var path: String { get set }
}
// Two implementations of the Connection protocol
struct SFTPConnection: Connection, Codable {
var path: String
var user: String
var sshKey: String
}
struct FTPConnection: Connection, Codable {
var path: String
var user: String
var password: String
}
This works fine when I know of what type the connections F and T are. But I have cases, where I want to load a configuration, not knowing which type F and T are.
public static func load<F: Connection, T: Connection>(for key: String) throws -> Configuration<F, T>? {
// Load from UserDefaults
guard let configurationData = defaults.object(forKey: key) as? Data else {
return nil
}
// Decode
guard let configuration = try? PropertyListDecoder().decode(Configuration<F, T>.self, from: configurationData) else {
return nil
}
return configuration
}
// OR
func loadAll<F:Connection, T: Connection>() -> [String: Configuration<F, T>]? {
return UserDefaults.standard.dictionaryRepresentation() as? [String: Configuration<F, T>]
}
In the above cases F and T could be of any unknown type, that conforms to the Connection protocol. So the above functions wouldn't work, since I would need to specify a specific type for F and T when calling the function, which I don't know.
In the second function, F alone could actually be of different types. That's where it gets difficult. I figured I need to somehow store the types of F and T in the User Defaults as well and then use them in the decode and encode function (thus discarding the generics). But I have no idea how I would elegantly do that.
I would appreciate any ideas on how to solve this problem!
The following solutions resolves all the issues, that I had with generics and not knowing the specific type of Connection. The key to the solution was
saving the type of a Connection implementation in the implementation itself and
Using superEncoder and superDecoder to encode/decode the from and to properties.
This is the solution:
import Foundation
protocol Connection: Codable {
var type: ConnectionType { get }
var path: String { get set }
}
struct LocalConnection: Connection {
let type: ConnectionType = ConnectionType.local
var path: String
}
struct SFTPConnection : Connection {
let type: ConnectionType = ConnectionType.sftp
var path: String
var user: String
var sshKey: String
init(path: String, user: String, sshKey: String) {
self.path = path
self.user = user
self.sshKey = sshKey
}
}
struct FTPConnection: Connection {
let type: ConnectionType = ConnectionType.ftp
var path: String
var user: String
var password: String
}
struct TFTPConnection: Connection {
let type: ConnectionType = ConnectionType.tftp
var path: String
}
enum ConnectionType : Int, Codable {
case local
case sftp
case ftp
case tftp
func getType() -> Connection.Type {
switch self {
case .local: return LocalConnection.self
case .sftp: return SFTPConnection.self
case .ftp: return FTPConnection.self
case .tftp: return TFTPConnection.self
}
}
}
struct Configuration {
var from : Connection
var to : Connection
private var id = UUID.init().uuidString
var fromType : ConnectionType { return from.type }
var toType : ConnectionType { return to.type }
init(from: Connection, to: Connection) {
self.from = from
self.to = to
}
}
extension Configuration : Codable {
enum CodingKeys: String, CodingKey {
case id, from, to, fromType, toType
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.id = try container.decode(String.self, forKey: .id)
var type : ConnectionType
type = try container.decode(ConnectionType.self, forKey: .fromType)
let fromDecoder = try container.superDecoder(forKey: .from)
self.from = try type.getType().init(from: fromDecoder)
type = try container.decode(ConnectionType.self, forKey: .toType)
let toDecoder = try container.superDecoder(forKey: .to)
self.to = try type.getType().init(from: toDecoder)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.id, forKey: .id)
try container.encode(self.fromType, forKey: .fromType)
let fromContainer = container.superEncoder(forKey: .from)
try from.encode(to: fromContainer)
try container.encode(self.toType, forKey: .toType)
let toContainer = container.superEncoder(forKey: .to)
try to.encode(to: toContainer)
}
}

How to encode/decode a dictionary with Codable values for storage in UserDefaults?

I am trying to store a dictionary of company names (string) mapped to Company objects (from a struct Company) in iOS UserDefaults. I have created the Company struct and made it conform to Codable. I have one example a friend helped me with in my project where we created a class Account and stored it in UserDefaults by making a Defaults struct (will include example code). I have read in the swift docs that dictionaries conform to Codable and in order to stay Codable, must contain Codable objects. That is why I made struct Company conform to Codable.
I have created a struct for Company that conforms to Codable. I have tried using model code to create a new struct CompanyDefaults to handle the getting and setting of the Company dictionary from/to UserDefaults. I feel I have some beginner misconceptions about what needs to happen and about how it should be implemented (with good design in mind).
The dictionary I wish to store looks like [String:Company]
where company name will be String and a Company object for Company
I used conform to Codable as I did some research and it seemed like a newer method for completing similar tasks.
Company struct
struct Company: Codable {
var name:String?
var initials:String? = nil
var logoURL:URL? = nil
var brandColor:String? = nil // Change to UIColor
enum CodingKeys:String, CodingKey {
case name = "name"
case initials = "initials"
case logoURL = "logoURL"
case brandColor = "brandColor"
}
init(name:String?, initials:String?, logoURL:URL?, brandColor:String?) {
self.name = name
self.initials = initials
self.logoURL = logoURL
self.brandColor = brandColor
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
name = try values.decode(String.self, forKey: .name)
initials = try values.decode(String.self, forKey: .initials)
logoURL = try values.decode(URL.self, forKey: .logoURL)
brandColor = try values.decode(String.self, forKey: .brandColor)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(name, forKey: .name)
try container.encode(initials, forKey: .initials)
try container.encode(logoURL, forKey: .logoURL)
try container.encode(brandColor, forKey: .brandColor)
}
}
Defaults struct to control storage
struct CompanyDefaults {
static private let companiesKey = "companiesKey"
static var companies: [String:Company] = {
guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
let companies = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String : Company] ?? [:]
return companies!
}() {
didSet {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: companies, requiringSecureCoding: false) else {
return
}
UserDefaults.standard.set(data, forKey: companiesKey)
}
}
}
I should be able to reference the stored dictionary throughout my code like CompanyDefaults.companies.count
For reference, a friend helped me do a similar task for an array of Account classes stored in user defaults. The code that works perfectly for that is below. The reason I tried a different way is that I had a different data structure (dictionary) and made the decision to use structs.
class Account: NSObject, NSCoding {
let service: String
var username: String
var password: String
func encode(with aCoder: NSCoder) {
aCoder.encode(service)
aCoder.encode(username)
aCoder.encode(password)
}
required init?(coder aDecoder: NSCoder) {
guard let service = aDecoder.decodeObject() as? String,
var username = aDecoder.decodeObject() as? String,
var password = aDecoder.decodeObject() as? String else {
return nil
}
self.service = service
self.username = username
self.password = password
}
init(service: String, username: String, password: String) {
self.service = service
self.username = username
self.password = password
}
}
struct Defaults {
static private let accountsKey = "accountsKey"
static var accounts: [Account] = {
guard let data = UserDefaults.standard.data(forKey: accountsKey) else { return [] }
let accounts = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Account] ?? []
return accounts
}() {
didSet {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: accounts, requiringSecureCoding: false) else {
return
}
UserDefaults.standard.set(data, forKey: accountsKey)
}
}
}
You are mixing up NSCoding and Codable. The former requires a subclass of NSObject, the latter can encode the structs and classes directly with JSONEncoder or ProperListEncoder without any Keyedarchiver which also belongs to NSCoding.
Your struct can be reduced to
struct Company: Codable {
var name : String
var initials : String
var logoURL : URL?
var brandColor : String?
}
That's all, the CodingKeys and the other methods are synthesized. I would at least declare name and initials as non-optional.
To read and save the data is pretty straightforward. The corresponding CompanyDefaults struct is
struct CompanyDefaults {
static private let companiesKey = "companiesKey"
static var companies: [String:Company] = {
guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
return try? JSONDecoder.decode([String:Company].self, from: data) ?? [:]
}() {
didSet {
guard let data = try? JSONEncoder().encode(companies) else { return }
UserDefaults.standard.set(data, forKey: companiesKey)
}
}
}

How to save custom type in CoreData with Codable Protocol Swift

I am trying to save the custom object of type codable, In which I am able to store Int16 type. But for [Movie] type in Coredata its NSObject, Entity I have an attribute movie is of type Transformable.
Error: No 'decodeIfPresent' candidates produce the expected contextual
result type 'NSObject?'
How can save this custom type Array with Transformable type
class MovieResults: Results, Codable {
required convenience public init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = (UIApplication.shared.delegate as? AppDelegate)?.persistentContainer.viewContext,
let entity = NSEntityDescription.entity(forEntityName: "Results", in: managedObjectContext) else {
fatalError("Failed to retrieve managed object context")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.page = try container.decodeIfPresent(Int16.self, forKey: .page) ?? 0
self.numberOfResults = try container.decodeIfPresent(Int16.self, forKey: .numberOfResults) ?? 0
self.numberOfPages = try container.decodeIfPresent(Int16.self, forKey: .numberOfPages) ?? 0
self.movies = try container.decodeIfPresent([Movie].self, forKey: .movies) ?? nil
}
// MARK: - Encodable
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(page, forKey: .page)
try container.encode(numberOfResults, forKey: .numberOfResults)
try container.encode(numberOfPages, forKey: .numberOfPages)
try container.encode(movies, forKey: .movies)
}
private enum CodingKeys: String, CodingKey {
case page
case numberOfResults = "total_results"
case numberOfPages = "total_pages"
case movies = "results"
}
}
Movie Array is an custom attribute of type Transformable in CoreData
class Movies: Movie, Codable {
public func encode(to encoder: Encoder) throws {
}
required convenience init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Movie", in: managedObjectContext) else {
fatalError("Failed to decode User")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
self.identifier = try container.decodeIfPresent(Int16.self, forKey: .identifier) ?? 0
self.posterPath = try container.decodeIfPresent(String.self, forKey: .identifier)
self.backdrop = try container.decodeIfPresent(String.self, forKey: .identifier)
self.title = try container.decodeIfPresent(String.self, forKey: .identifier)
self.releaseDate = try container.decodeIfPresent(String.self, forKey: .identifier)
self.rating = try container.decodeIfPresent(Int16.self, forKey: .rating) ?? 0
self.overview = try container.decodeIfPresent(String.self, forKey: .identifier)
}
enum CodingKeys: String, CodingKey {
case identifier
case posterPath = "poster_path"
case backdrop = "backdrop_path"
case title
case releaseDate = "release_date"
case rating = "vote_average"
case overview
}
}
With this, it's working fine.
self.movies = try container.decodeIfPresent([Movies].self, forKey: .movies)! as NSObject
I am Inheriting NSManagedObject Class Is this Correct way. I tried
using the extension but it throws an error for initializers.?
public convenience init(from decoder: Decoder) throws
Initializer requirement 'init(from:)' can only be satisfied by a
'required' initializer in the definition of non-final class
'MovieResult'
I think this could be done in a more simpler way. Here is how you can give a try. Here is MovieResult class [It's good to have the name of the class in singular].
public class MovieResult: Codable {
public var stringVariable: String
public var intVariable: Int
public init(stringVariable: String, intVariable: Int) {
self.stringVariable = stringVariable
self.intVariable = intVariable
}
}
Here you can persist MovieResult in UserDefaults.
public var savedMovieResult: MovieResult? {
get {
let data = UserDefaults.standard.object(forKey: "savedMovieResultKey") as? Data
var movieResult: MovieResult?
if let data = data {
do {
movieResult = try PropertyListDecoder().decode(MovieResult.self, from: data)
} catch {
print(error)
}
}
return movieResult
} set {
do {
try UserDefaults.standard.set(PropertyListEncoder().encode(newValue), forKey: "savedMovieResultKey")
UserDefaults.standard.synchronize()
} catch {
print(error)
}
}
}

Issue using CoreData with Codable protocol

I created two NSManagedObject classes one for Songs and one for Categories of each song. And they have a one to many relationship. What i do is that i download a json file from the server and parse it using Decodable and save the data in CoreData. Every thing is smooth except when i try to add songs to a certain category type i get a crash.
'Illegal attempt to establish a relationship 'category' between objects in different contexts
I know what this crash is and i know i have two context one for the category class and one for the song class. The issue is that tutorials for CoreData using Decodable is so little. So now i am thinking of a way maybe i can create a parent class of these classes and init the context in it and just call super.init() in the subclasses of category and songs. But i really cannot do it. Or maybe there is a much simpler way. I will share the code of my classes here and the code where the error is happening.
struct CategoryData: Decodable {
let data: [CategoryManagedObject]
}
#objc(CategoryManagedObject)
class CategoryManagedObject: NSManagedObject, Decodable {
// MARK: - Core Data Managed Object
#NSManaged var id: Int
#NSManaged var name: String
#NSManaged var imgUrl: String
#NSManaged var coverPhotoBit64: String
#NSManaged var jsonUrl: String
#NSManaged var version: Int
#NSManaged var order: Int
#NSManaged var songs: NSSet?
//var coreDataStack: CoreDataManager!
enum CodingKeys: String, CodingKey {
case name, coverPhotoBit64, id, jsonUrl, version, order
case imgUrl = "coverPhoto"
}
// MARK: - Decodable
required convenience init(from decoder: Decoder) throws {
//try super.init(from: decoder, type: "Categories")
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Categories", in: managedObjectContext) else {
fatalError("FALIED TO DECODE CATEGORIES")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
imgUrl = try container.decode(String.self, forKey: .imgUrl)
coverPhotoBit64 = try container.decode(String.self, forKey: .coverPhotoBit64)
version = try container.decode(Int.self, forKey: .version)
jsonUrl = try container.decode(String.self, forKey: .jsonUrl)
order = try container.decode(Int.self, forKey: .order)
// if let sArray = songs.allObjects as? [Song] {
// songs = try container.decode(sArray.self, forKey: .song)
// }
}
#nonobjc public class func fetchRequest() -> NSFetchRequest<CategoryManagedObject> {
return NSFetchRequest<CategoryManagedObject>(entityName: "Categories")
}
}
public extension CodingUserInfoKey {
// Helper property to retrieve the context
static let context = CodingUserInfoKey(rawValue: "managedObjectContext")
}
// MARK: Generated accessors for songs
extension CategoryManagedObject {
#objc(addSongsObject:)
#NSManaged public func addToSongs(_ value: Song)
#objc(removeSongsObject:)
#NSManaged public func removeFromSongs(_ value: Song)
#objc(addSongs:)
#NSManaged public func addToSongs(_ values: NSSet)
#objc(removeSongs:)
#NSManaged public func removeFromSongs(_ values: NSSet)
}
#objc(Song)
public class Song: NSManagedObject, Decodable {
#NSManaged var id: Int
#NSManaged var name: String
#NSManaged var artist: String
#NSManaged var code: String
#NSManaged var category: CategoryManagedObject
enum CodingKeys: String, CodingKey {
case name, id, artist, code
}
// MARK: - Decodable
required convenience public init(from decoder: Decoder) throws {
guard let codingUserInfoKeyManagedObjectContext = CodingUserInfoKey.context,
let managedObjectContext = decoder.userInfo[codingUserInfoKeyManagedObjectContext] as? NSManagedObjectContext,
let entity = NSEntityDescription.entity(forEntityName: "Songs", in: managedObjectContext) else {
fatalError("FALIED TO DECODE CATEGORIES")
}
self.init(entity: entity, insertInto: managedObjectContext)
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(Int.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
artist = try container.decode(String.self, forKey: .artist)
code = try container.decode(String.self, forKey: .code)
}
#nonobjc public class func fetchRequest() -> NSFetchRequest<Song> {
return NSFetchRequest<Song>(entityName: "Songs")
}
}
This is where the crash happens because of two different context.
func saveJsonSongsInDB(filename fileName: String, category: CategoryManagedObject) {
do {
let data = try Data(contentsOf: URL(string: fileName)!)
//let context = CoreDataManager.shared.persistentContainer.newBackgroundContext()
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.context!] = dbContext
//decoder.userInfo[CodingUserInfoKey.deferInsertion] = true
coreDataStack.deleteAllRecords("Songs")
let songs = try decoder.decode([Song].self, from: data)
let s = NSSet(array: songs)
// category.managedObjectContext?.insert(<#T##object: NSManagedObject##NSManagedObject#>)
// dbContext.insert(category)
//print("SONGS: \(songs)")
category.addToSongs(s) //----> CRASH
try dbContext.save()
} catch let err {
print("error:\(err)")
}
}
First of all use one context, the context passed in the JSONDEcoder
In CategoryManagedObject declare songs as non-optional native type
#NSManaged var songs: Set<Song>
Decode songs as Set (yes, this is possible) and set the category of each song to self
songs = try container.decode(Set<Song>.self, forKey: .song)
songs.forEach{ $0.category = self }
That's all. You don't have to set the inverse relationship in CategoryManagedObject
To insert the data you have to decode [CategoryManagedObject]
let decoder = JSONDecoder()
decoder.userInfo[CodingUserInfoKey.context!] = dbContext
coreDataStack.deleteAllRecords("Songs")
_ = try decoder.decode([CategoryManagedObject].self, from: data)