Основные данные о том, как использовать NSMangedObjectContext в многопоточном - PullRequest
1 голос
/ 11 февраля 2020

Хорошо, я занимался этим целый день и не могу понять, что я делаю неправильно. Вот так выглядит моя модель данных для основных данных.

enter image description here

Вот так выглядит мой код.

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 сущности: покемон, тип и способность. Сейчас я ничего не делаю со способностями, поэтому мы можем просто игнорировать это. Первое забавное c downloadPokemonFromServer просто захватывает первого 151 покемона, сохраняет имя и URL-адрес покемона. Затем я использую этот URL для go в другой URLSession и собираю дополнительную информацию об этом покемоне. Вот что делает fetchMoreDetails fun c. Тем не менее, это забавное c ломает мое приложение. Я не знаю, что я делаю здесь неправильно, он вылетает, когда я пытаюсь его сохранить.

Третье удовольствие c getPokemonImage I go в другой URLSession, получить данные и сохранить их в моем атрибут изображения покемонов. Дело в том, что это прекрасно работает. Он сохраняет мои CoreData и не обрабатывает sh моего приложения.

Так я называю его в моем 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()
}

Поможет ли кто-нибудь мне выяснить, что Я делаю не так Был бы очень признателен за помощь.

Ответы [ 2 ]

1 голос
/ 12 февраля 2020

Необходимо убедиться, что вы выполняете всю работу с основными данными в том же потоке , что и частный контекст , который вы создали. Для этого, пожалуйста, используйте:

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

Это может предотвратить множество головных болей и неприятностей в будущем

0 голосов
/ 11 февраля 2020

Я думаю, что проблема в том, что вы пытаетесь установить отношения между двумя объектами в разных контекстах. Ваш pokemon объект зарегистрирован в контексте представления:

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

, тогда как ваш type объект зарегистрирован в закрытом контексте:

    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()
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...