如此处所述以及在其他SO问题的答案中,您不希望beginBackgroundTask
仅在应用程序进入后台时使用它;相反,您应该对任何耗时的操作都使用后台任务,即使应用程序确实进入了后台,您也要确保其完成性。
因此,您的代码很可能最终会重复使用相同的样板代码进行重复调用,beginBackgroundTask
并且endBackgroundTask
连贯一致。为了防止这种重复,将样板包装到某个单个封装的实体中当然是合理的。
我喜欢这样做的一些现有答案,但是我认为最好的方法是使用Operation子类:
您可以将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
处理程序属性(在前面的示例中未使用)。有人批评其他答案,因为其中不包括这一点。