如何将 newBackgroundContext() 与 URLSession.shared.dataTaskPublisher 一起使用?
How to use newBackgroundContext() with URLSession.shared.dataTaskPublisher?
我创建了 a simple Core Data project at Github 来演示我的问题:
我的测试应用程序下载了一个 JSON 对象列表,将其存储在 Core Data 中并通过 @FetchRequest
.
显示在 SwiftUI 列表中
因为对象列表在我的 real 应用程序中有 1000 多个元素,我想在后台线程而不是主线程上将实体保存到核心数据中.
我最好使用相同的默认后台线程,URLSession.shared.dataTaskPublisher.
已经使用了该线程
所以在 Xcode 生成的标准 Persistence.swift 中我只添加了 2 行:
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.viewContext.automaticallyMergesChangesFromParent = true
在我的 DownloadManager.swift 单例中,我调用:
static let instance = DownloadManager()
var cancellables = Set<AnyCancellable>()
// How to run this line on the background thread of URLSession.shared.dataTaskPublisher?
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
private init() {
getTops()
}
func getTops() {
guard let url = URL(string: "https://slova.de/ws/top") else { return }
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { [weak self] returnedTops in
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: self!.backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
self?.save()
}
.store(in: &cancellables)
}
如您在上面的屏幕截图中所见,此操作失败
Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
因为我在 Xcode 中添加了以下“启动时传递的参数”:
-com.apple.CoreData.ConcurrencyDebug 1
谁能告诉我如何在正确的线程上调用 newBackgroundContext()
?
更新:
我已尝试按照以下代码解决我的问题,但错误是相同的:
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { returnedTops in
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
do {
try backgroundContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
更新 2:
我假设当调用 newBackgroundContext()
时,它使用当前线程,然后您可以从同一线程使用该上下文...
这似乎不是这种情况,我必须调用 perform
、performAndWait
或 performBackgroundTask
(我已更新 my code at Github 来做到这一点)。
我仍然想知道,newBackgroundContext
的线程是否可以与 URLSession.shared.dataTaskPublisher
的线程相同...
看来你已经自己解决了大部分问题。
This seems not to be the case and I have to call perform, performAndWait
or performBackgroundTask
(I have updated my code at Github to do just that).
还没解决的就在这里-
Still I wonder, if the thread of newBackgroundContext
can be the same as of the URLSession.shared.dataTaskPublisher...
URLSession
API 允许您提供自定义响应队列,如下所示。
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://google.com")!)
.receive(on: DispatchQueue(label: "API.responseQueue", qos: .utility)) // <--- HERE
.sink(receiveCompletion: {_ in }, receiveValue: { (output) in
print(output)
})
或者传统上是这样的-
let queue = OperationQueue()
queue.underlyingQueue = DispatchQueue(label: "API.responseQueue", qos: .utility)
let session = URLSession(configuration: .default, delegate: self, delegateQueue: queue)
所以理想情况下,我们应该传入 NSManagedObjectContext
的 DispatchQueue
作为 URLSession
的响应队列。
问题在于 NSManagedObjectContext
API -
- 两者都不允许您从外部提供自定义
DispatchQueue
实例。
- 也不会公开只读 属性 因为它是内部管理的
DispatchQueue
实例。
我们无法访问 NSManagedObjectContext
实例的基础 DispatchQueue
。此规则的唯一例外是使用 DispatchQueue.main
的 .viewContext
。当然,我们不想处理网络响应解码和数千条记录 created/persisted on/from 主线程。
我创建了 a simple Core Data project at Github 来演示我的问题:
我的测试应用程序下载了一个 JSON 对象列表,将其存储在 Core Data 中并通过 @FetchRequest
.
因为对象列表在我的 real 应用程序中有 1000 多个元素,我想在后台线程而不是主线程上将实体保存到核心数据中.
我最好使用相同的默认后台线程,URLSession.shared.dataTaskPublisher.
已经使用了该线程所以在 Xcode 生成的标准 Persistence.swift 中我只添加了 2 行:
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.viewContext.automaticallyMergesChangesFromParent = true
在我的 DownloadManager.swift 单例中,我调用:
static let instance = DownloadManager()
var cancellables = Set<AnyCancellable>()
// How to run this line on the background thread of URLSession.shared.dataTaskPublisher?
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
private init() {
getTops()
}
func getTops() {
guard let url = URL(string: "https://slova.de/ws/top") else { return }
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { [weak self] returnedTops in
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: self!.backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
self?.save()
}
.store(in: &cancellables)
}
如您在上面的屏幕截图中所见,此操作失败
Thread 4: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
因为我在 Xcode 中添加了以下“启动时传递的参数”:
-com.apple.CoreData.ConcurrencyDebug 1
谁能告诉我如何在正确的线程上调用 newBackgroundContext()
?
更新:
我已尝试按照以下代码解决我的问题,但错误是相同的:
URLSession.shared.dataTaskPublisher(for: url)
.tryMap(handleOutput)
.decode(type: TopResponse.self, decoder: JSONDecoder())
.sink { completion in
print(completion)
} receiveValue: { returnedTops in
let backgroundContext = PersistenceController.shared.container.newBackgroundContext()
for top in returnedTops.data {
// the next line fails with EXC_BAD_INSTRUCTION
let topEntity = TopEntity(context: backgroundContext)
topEntity.uid = Int32(top.id)
topEntity.elo = Int32(top.elo)
topEntity.given = top.given
topEntity.avg_score = top.avg_score ?? 0.0
}
do {
try backgroundContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
更新 2:
我假设当调用 newBackgroundContext()
时,它使用当前线程,然后您可以从同一线程使用该上下文...
这似乎不是这种情况,我必须调用 perform
、performAndWait
或 performBackgroundTask
(我已更新 my code at Github 来做到这一点)。
我仍然想知道,newBackgroundContext
的线程是否可以与 URLSession.shared.dataTaskPublisher
的线程相同...
看来你已经自己解决了大部分问题。
This seems not to be the case and I have to call perform,
performAndWait
orperformBackgroundTask
(I have updated my code at Github to do just that).
还没解决的就在这里-
Still I wonder, if the thread of
newBackgroundContext
can be the same as of theURLSession.shared.dataTaskPublisher...
URLSession
API 允许您提供自定义响应队列,如下所示。
URLSession.shared
.dataTaskPublisher(for: URL(string: "https://google.com")!)
.receive(on: DispatchQueue(label: "API.responseQueue", qos: .utility)) // <--- HERE
.sink(receiveCompletion: {_ in }, receiveValue: { (output) in
print(output)
})
或者传统上是这样的-
let queue = OperationQueue()
queue.underlyingQueue = DispatchQueue(label: "API.responseQueue", qos: .utility)
let session = URLSession(configuration: .default, delegate: self, delegateQueue: queue)
所以理想情况下,我们应该传入 NSManagedObjectContext
的 DispatchQueue
作为 URLSession
的响应队列。
问题在于 NSManagedObjectContext
API -
- 两者都不允许您从外部提供自定义
DispatchQueue
实例。 - 也不会公开只读 属性 因为它是内部管理的
DispatchQueue
实例。
我们无法访问 NSManagedObjectContext
实例的基础 DispatchQueue
。此规则的唯一例外是使用 DispatchQueue.main
的 .viewContext
。当然,我们不想处理网络响应解码和数千条记录 created/persisted on/from 主线程。