swift - 核心数据如何在多线程中使用 NSMangedObjectContext
问题描述
好的,我已经做了一天了,似乎无法弄清楚我做错了什么。这就是我的核心数据数据模型的样子。
这就是我的代码的样子。
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 个实体,分别是口袋妖怪、类型和能力。我现在不是什么都不做,所以我们可以忽略这一点。第一个函数 downloadPokemonFromServer 只是抓取前 151 个 pokemon,保存 pokemon 的名称和 url。然后我使用该 url 进入另一个 URLSession 并获取有关该 pokemon 的更多信息。这就是 fetchMoreDetails 函数的作用。但是,此功能使我的应用程序崩溃。我不知道我在这里做错了什么,当我尝试保存它时它崩溃了。
第三个函数 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()
}
有人请帮我弄清楚我做错了什么。非常感谢您的帮助。
解决方案
您需要确保您在与您创建的私有上下文相同的线程上完成所有核心数据工作。为此,请使用:
privateContext.perform {
//Core data work: create new entities, connections, delete, edit and more...
}
这可以防止你在路上遇到很多头痛和麻烦
推荐阅读
- windows-terminal - 在 WSL windows 终端中排除 Windows 文件
- c++ - CPP 中的分段错误(核心转储)
- python - Pymongo 将我的变量读取为 licteral,如果我用美元 $ 编写也是如此
- python - Python浮点类型导致无效结果?
- excel - 在 CSV 中包含格式说明?
- javascript - 如何在javascript中更改背景颜色
- c++ - 在 C++ 中读取和写入制表符分隔的数据
- nginx - 想要所有 Nginx 配置之上的通用 Nginx 配置
- azure-devops-migration-tools - 通过 VSTS 将工作项从 TFS 迁移到 Azure 时源和目标区域路径不同时的区域路径错误
- acumatica - 如何获得自定义处理状态屏幕以显示进度或处理的记录