【问题标题】:Issue using CoreData with Codable protocol将 CoreData 与 Codable 协议一起使用的问题
【发布时间】:2019-04-11 03:44:27
【问题描述】:

我为每首歌曲创建了两个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)")
        }

    }

【问题讨论】:

    标签: swift core-data decodable


    【解决方案1】:

    首先使用一个上下文,上下文在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)
    

    【讨论】:

    • 这个问题是我在jsonof 类别中没有歌曲数据。我在歌曲数据中只有类别id。所以我从服务器获取类别,然后为每个类别的歌曲下载english.json 文件。例如,我有 5 个类别,或者说语言。所以我在类别数据中获得jsonUrl 并下载它的json 文件并解析它并将数据添加到相关类别中,这就是发生的错误。所以现在的问题是我无法解析 Category 类中的歌曲,因为类别的 json 数据中没有这样的属性。
    猜你喜欢
    • 2018-10-26
    • 1970-01-01
    • 2011-12-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-30
    • 1970-01-01
    相关资源
    最近更新 更多