如何正确处理 watchOS CoreData 后台保存?
How to handle watchOS CoreData background save correctly?
我的 watchOS 应用使用核心数据进行本地存储。保存托管上下文在后台完成:
var backgroundContext = persistentContainer.newBackgroundContext()
//…
backgroundContext.perform {
//…
let saveError = self.saveManagedContext(managedContext: self.backgroundContext)
completion(saveError)
}
//…
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
很少,我的上下文没有保存。我能想到的原因之一如下:
我的数据发生变化后,发起后台核心数据上下文保存操作。
但是在后台任务开始之前,watch 扩展被用户放入后台,然后被 watchOS 终止。
这可能也阻止了核心数据后台保存的执行。
我的问题是:
- 这种情况可能吗?
- 如果是这样,核心数据后台上下文保存的正确处理是什么?
PS:在iOS方面,我也是这样做的,但是这里可以使用
请求额外的后台处理时间
var bgTask: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: {
//…
application.endBackgroundTask(bgTask)
}
到现在为止,我想我可以回答我的问题了:
如果用户将 watch 扩展置于后台,则扩展委托调用 applicationDidEnterBackground()
。 docs 说:
The system typically suspends your app shortly after this method
returns; therefore, you should not call any asynchronous methods from
your applicationDidEnterBackground()
implementation. Asynchronous
methods may not be able to complete before the app is suspended.
我认为这也适用于之前启动的后台任务,所以实际上有可能核心数据后台保存没有完成。
因此,核心数据的保存应该在主线程上完成。我当前的解决方案如下:
我的后台上下文不再使用 persistentContainer.newBackgroundContext()
设置,因为这样的上下文直接连接到 persistentContainer
,并且当保存此上下文时,更改将写入持久存储,这可能需要相对较长的时间。相反,我现在通过
设置背景上下文
var backgroundContext = NSManagedObjectContext.init(concurrencyType: .privateQueueConcurrencyType)
并将其父级 属性 设置为
backgroundContext.parent = container.viewContext
其中 container
是持久容器。现在,当保存后台上下文时,它不会写入持久存储,而是写入其父级,即由主线程处理的视图内容。由于此保存仅在内存中完成,因此速度非常快。
此外,在扩展委托的 applicationDidEnterBackground()
中,我保存了视图上下文。由于这是在主线程上完成的,docs 说:
The applicationDidEnterBackground()
method is your last chance to
perform any cleanup before the app is terminated.
正常情况下,watchOS应该会提供足够的时间。如果没有,other docs 说:
If needed, you can request additional background execution time by
calling the ProcessInfo
class’s
performExpiringActivity(withReason:using:)
method.
这可能相当于在 iOS 中设置后台任务,如我的问题所示。
希望这对某人有所帮助!
我的 watchOS 应用使用核心数据进行本地存储。保存托管上下文在后台完成:
var backgroundContext = persistentContainer.newBackgroundContext()
//…
backgroundContext.perform {
//…
let saveError = self.saveManagedContext(managedContext: self.backgroundContext)
completion(saveError)
}
//…
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
很少,我的上下文没有保存。我能想到的原因之一如下:
我的数据发生变化后,发起后台核心数据上下文保存操作。
但是在后台任务开始之前,watch 扩展被用户放入后台,然后被 watchOS 终止。
这可能也阻止了核心数据后台保存的执行。
我的问题是:
- 这种情况可能吗?
- 如果是这样,核心数据后台上下文保存的正确处理是什么?
PS:在iOS方面,我也是这样做的,但是这里可以使用
请求额外的后台处理时间var bgTask: UIBackgroundTaskIdentifier = application.beginBackgroundTask(expirationHandler: {
//…
application.endBackgroundTask(bgTask)
}
到现在为止,我想我可以回答我的问题了:
如果用户将 watch 扩展置于后台,则扩展委托调用 applicationDidEnterBackground()
。 docs 说:
The system typically suspends your app shortly after this method returns; therefore, you should not call any asynchronous methods from your
applicationDidEnterBackground()
implementation. Asynchronous methods may not be able to complete before the app is suspended.
我认为这也适用于之前启动的后台任务,所以实际上有可能核心数据后台保存没有完成。
因此,核心数据的保存应该在主线程上完成。我当前的解决方案如下:
我的后台上下文不再使用 persistentContainer.newBackgroundContext()
设置,因为这样的上下文直接连接到 persistentContainer
,并且当保存此上下文时,更改将写入持久存储,这可能需要相对较长的时间。相反,我现在通过
var backgroundContext = NSManagedObjectContext.init(concurrencyType: .privateQueueConcurrencyType)
并将其父级 属性 设置为
backgroundContext.parent = container.viewContext
其中 container
是持久容器。现在,当保存后台上下文时,它不会写入持久存储,而是写入其父级,即由主线程处理的视图内容。由于此保存仅在内存中完成,因此速度非常快。
此外,在扩展委托的 applicationDidEnterBackground()
中,我保存了视图上下文。由于这是在主线程上完成的,docs 说:
The
applicationDidEnterBackground()
method is your last chance to perform any cleanup before the app is terminated.
正常情况下,watchOS应该会提供足够的时间。如果没有,other docs 说:
If needed, you can request additional background execution time by calling the
ProcessInfo
class’sperformExpiringActivity(withReason:using:)
method.
这可能相当于在 iOS 中设置后台任务,如我的问题所示。
希望这对某人有所帮助!