如here和其他SO问题的答案所述,您不想beginBackgroundTask
仅在您的应用程序进入后台时使用;相反,您应该将后台任务用于任何您希望确保其完成的耗时操作,即使应用程序确实进入了后台。
因此,您的代码可能最终会重复使用相同的样板代码进行调用beginBackgroundTask
和endBackgroundTask
连贯。为了防止这种重复,将样板打包成一些单一的封装实体当然是合理的。
我喜欢这样做的一些现有答案,但我认为最好的方法是使用 Operation 子类:
您可以将操作排入任何 OperationQueue 并在您认为合适的时候操作该队列。例如,您可以自由地提前取消队列上的任何现有操作。
如果你有不止一件事情要做,你可以链接多个后台任务操作。操作支持依赖项。
操作队列可以(也应该)是后台队列;因此,无需担心在任务中执行异步代码,因为 Operation是异步代码。(实际上,在操作中执行另一层异步代码是没有意义的,因为操作会在代码开始之前完成。如果你需要这样做,你会使用另一个操作。)
这是一个可能的操作子类:
class BackgroundTaskOperation: Operation {
var whatToDo : (() -> ())?
var cleanup : (() -> ())?
override func main() {
guard !self.isCancelled else { return }
guard let whatToDo = self.whatToDo else { return }
var bti : UIBackgroundTaskIdentifier = .invalid
bti = UIApplication.shared.beginBackgroundTask {
self.cleanup?()
self.cancel()
UIApplication.shared.endBackgroundTask(bti) // cancellation
}
guard bti != .invalid else { return }
whatToDo()
guard !self.isCancelled else { return }
UIApplication.shared.endBackgroundTask(bti) // completion
}
}
如何使用它应该很明显,但如果不是,想象我们有一个全局 OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
所以对于一个典型的耗时的代码,我们会说:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
如果您的耗时代码批次可以分为多个阶段,那么如果您的任务被取消,您可能希望提前退出。在这种情况下,只需从关闭中过早返回。请注意,您在闭包中对任务的引用需要很弱,否则您将获得一个保留周期。这是一个人工插图:
let task = BackgroundTaskOperation()
task.whatToDo = { [weak task] in
guard let task = task else {return}
for i in 1...10000 {
guard !task.isCancelled else {return}
for j in 1...150000 {
let k = i*j
}
}
}
backgroundTaskQueue.addOperation(task)
如果您有清理工作以防后台任务本身被提前取消,我提供了一个可选的cleanup
处理程序属性(在前面的示例中未使用)。其他一些答案因不包括在内而受到批评。