如何正确处理 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 中设置后台任务,如我的问题所示。

希望这对某人有所帮助!