Проблема с использованием CoreData с протоколом Codable - PullRequest
0 голосов
/ 07 ноября 2018

Я создал два NSManagedObject класса, один для Songs и один для Categories каждой песни. И у них есть отношения один ко многим. Я загружаю файл json с сервера, анализирую его с помощью Decodable и сохраняю данные в CoreData. Все гладко, за исключением того, что когда я пытаюсь добавить песни к определенному типу категории, я получаю сбой.

'Незаконная попытка установить категорию' отношения 'между объектами в разных контекстах

Я знаю, что это за авария, и я знаю, что у меня есть два контекста, один для класса категории и один для класса песни. Проблема в том, что учебники для CoreData с использованием Decodable очень мало. Так что теперь я думаю о том, как, может быть, я смогу создать родительский класс этих классов и init контекст в нем и просто вызвать super.init() в подклассах категории и песен. Но я действительно не могу этого сделать. Или, может быть, есть гораздо более простой способ. Я поделюсь кодом моих классов здесь и кодом, где происходит ошибка.

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")
    }
}

Здесь происходит сбой из-за двух разных контекстов.

 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)")
        }

    }

1 Ответ

0 голосов
/ 07 ноября 2018

Прежде всего используйте один контекст, контекст передается в JSONDEcoder

  • В CategoryManagedObject объявить songs как необязательный собственный тип

    @NSManaged var songs: Set<Song>
    
  • Расшифруйте songs как Set (да, это возможно) и установите категорию каждой песни на self

    songs = try container.decode(Set<Song>.self, forKey: .song)
    songs.forEach{ $0.category = self }
    

Вот и все. Вам не нужно устанавливать обратную зависимость в CategoryManagedObject

Для вставки данных необходимо декодировать [CategoryManagedObject]

 let decoder = JSONDecoder()
 decoder.userInfo[CodingUserInfoKey.context!] = dbContext    
 coreDataStack.deleteAllRecords("Songs")
 _ = try decoder.decode([CategoryManagedObject].self, from: data)
...