Swift 核心数据如何在多线程中使用NSMangedObjectContext

Swift 核心数据如何在多线程中使用NSMangedObjectContext,swift,core-data,Swift,Core Data,好吧,我已经做了一天了,似乎不知道我做错了什么。这就是我的数据模型对于核心数据的外观 这就是我的代码的样子 class Service { static let shared = Service() private let numberOfPokemons = 151 func downloadPokemonsFromServer(completion: @escaping ()->()) { let urlString = "https://pokeapi.co/api/v2

好吧,我已经做了一天了,似乎不知道我做错了什么。这就是我的数据模型对于核心数据的外观

这就是我的代码的样子

class Service {
static let shared = Service()
private let numberOfPokemons = 151

func downloadPokemonsFromServer(completion: @escaping ()->()) {
    let urlString = "https://pokeapi.co/api/v2/pokemon?limit=\(numberOfPokemons)"
    guard let url = URL(string: urlString) else { return }
    var id: Int16 = 0


    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let err = error {
            print("Unable to fetch pokemon", err)
        }

        guard let data = data else { return }
        let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        do {
            let pokemonJSON = try decoder.decode(PokemonsJSON.self, from: data)
            pokemonJSON.pokemons.forEach { (JSONPokemon) in
                id += 1

                let pokemon = Pokemon(context: privateContext)
                pokemon.name = JSONPokemon.name
                pokemon.url = JSONPokemon.detailUrl
                pokemon.id = id

            }

            try? privateContext.save()
            try? privateContext.parent?.save()
            completion()
        } catch let err {
            print("Unable to decode PokemonJSON. Error: ",err)
            completion()
        }
    }.resume()
}

private var detailTracker = 0
func fetchMoreDetails(objectID: NSManagedObjectID) {

    guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }

    print(pokemon.name)
    print()

    guard let url = URL(string: urlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let err = error {
            print("Unable to get more details for pokemon", err)
        }

        guard let data = data else { return }
        let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext

        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        do {
            let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
            pokemonDetailJSON.types.forEach { (nestedType) in

                let type = Type(context: privateContext)
                type.name = nestedType.type.name
                type.addToPokemons(pokemon)

            }

            try? privateContext.save()
            try? privateContext.parent?.save()

        } catch let err {
            print("Unable to decode pokemon more details", err)
        }

    }.resume()
}

private var imageTracker = 0
func getPokemonImage(objectID: NSManagedObjectID) {
    guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon else { return }

    let id = String(format: "%03d", pokemon.id)
    let urlString = "https://assets.pokemon.com/assets/cms2/img/pokedex/full/\(id).png"
    print(urlString)



    guard let url = URL(string: urlString) else { return }
    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let err = error {
            print("Unable to load image from session.", err)
        }

        guard let data = data else { return }
        let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext
        pokemon.image = data

        self.imageTracker += 1

        if self.imageTracker == self.numberOfPokemons {
            try? privateContext.save()
            try? privateContext.parent?.save()
        }
    }.resume()
}
}
我有3个实体,分别是口袋妖怪、类型和能力。我现在没有能力做任何事情,所以我们可以忽略这一点。第一个func下载pokemonfromserver只需抓取第一个151个pokemon,保存pokemon的名称和url。然后,我使用该url进入另一个url会话并获取有关该口袋妖怪的更多信息。这就是fetchMoreDetails函数所做的。但是,此函数使我的应用程序崩溃。我不知道我做错了什么,当我试图保存它时它崩溃了

第三个func getPokemonImage我进入另一个URLSession,获取数据并将其保存到我的pokemon图像属性中。问题是,这工作非常好。它保存到我的CoreData,不会使我的应用程序崩溃

这就是我在ViewController中对它的调用方式

@objc func handleRefresh() {

    if pokemonController.fetchedObjects?.count == 0 {
        Service.shared.downloadPokemonsFromServer {
            let pokemons = self.pokemonController.fetchedObjects
            pokemons?.forEach({ (pokemon) in

               Service.shared.getPokemonImage(objectID: pokemon.objectID)
               //If I uncomment the line below it will crash my app.
               //Service.shared.fetchMoreDetails(objectID: pokemon.objectID)
            })
        }
    }
     tableView.refreshControl?.endRefreshing()
}

请有人帮我找出我做错了什么。非常感谢你的帮助

我认为问题在于您试图在来自不同上下文的两个对象之间设置关系。您的
pokemon
对象已在视图上下文中注册:

    guard let pokemon = CoreDataManager.shared.persistentContainer.viewContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }
而您的
类型
对象是在私有上下文中注册的:

    let type = Type(context: privateContext)
    type.name = nestedType.type.name
所以这条线不行:

    type.addToPokemons(pokemon)
我会尝试修改代码,只使用privateContext,如下所示:

func fetchMoreDetails(objectID: NSManagedObjectID) {
    let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
    privateContext.parent = CoreDataManager.shared.persistentContainer.viewContext    
    guard let pokemon = privateContext.object(with: objectID) as? Pokemon, let urlString = pokemon.url else { return }

    print(pokemon.name)
    print()

    guard let url = URL(string: urlString) else { return }

    URLSession.shared.dataTask(with: url) { (data, response, error) in
        if let err = error {
            print("Unable to get more details for pokemon", err)
        }

        guard let data = data else { return }


        let decoder = JSONDecoder()
        decoder.keyDecodingStrategy = .convertFromSnakeCase

        do {
            let pokemonDetailJSON = try decoder.decode(PokemonDetailJSON.self, from: data)
            pokemonDetailJSON.types.forEach { (nestedType) in

                let type = Type(context: privateContext)
                type.name = nestedType.type.name
                type.addToPokemons(pokemon)

            }

            try? privateContext.save()
            try? privateContext.parent?.save()

        } catch let err {
            print("Unable to decode pokemon more details", err)
        }

    }.resume()
}

您需要确保在您创建的同一台计算机上执行所有核心数据工作。为此,请使用:

privateContext.perform {
   //Core data work: create new entities, connections, delete, edit and more...
}

这可以避免你以后遇到很多头痛和麻烦

谢谢,@pbasdf花时间回答我的问题。我想知道如何获得要添加到的实体。如果我没有使用CoreDataManager.shared.persistentContainer.viewContext.object(with:)?@LuisRamirez,我已经修改了答案,加入了建议的代码。谢谢,@pbasdf。我明白了,这需要努力。非常感谢。我需要对核心数据做更多的研究。我强烈推荐这本书:要获取私有上下文,只需使用
persistentContainer.newBackgroundContext()