Como se indica aquí y en las respuestas a otras preguntas de SO, NO desea usar beginBackgroundTask
solo cuando su aplicación pasará a segundo plano; por el contrario, se debe utilizar una tarea de fondo para cualquier operación que consume tiempo cuya finalización desea asegurarse incluso si la aplicación no entra en el fondo.
Por lo tanto, es probable que su código termine salpicado de repeticiones del mismo código repetitivo para llamar beginBackgroundTask
y de forma endBackgroundTask
coherente. Para evitar esta repetición, es ciertamente razonable querer empaquetar el texto estándar en una sola entidad encapsulada.
Me gustan algunas de las respuestas existentes para hacer eso, pero creo que la mejor manera es usar una subclase de Operación:
Puede poner la operación en cola en cualquier OperationQueue y manipular esa cola como mejor le parezca. Por ejemplo, puede cancelar prematuramente cualquier operación existente en la cola.
Si tiene más de una cosa que hacer, puede encadenar varias operaciones de tareas en segundo plano. Dependencias de soporte de operaciones.
Operation Queue puede (y debe) ser una cola en segundo plano; por lo tanto, no hay necesidad de preocuparse por realizar código asincrónico dentro de su tarea, porque la Operación es el código asincrónico. (De hecho, no tiene sentido ejecutar otro nivel de código asincrónico dentro de una Operación, ya que la Operación terminaría antes de que ese código pudiera siquiera comenzar. Si necesitara hacer eso, usaría otra Operación).
Aquí hay una posible subclase de Operación:
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
}
}
Debería ser obvio cómo usar esto, pero en caso de que no lo sea, imagine que tenemos una OperationQueue global:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Entonces, para un lote de código típico que consume mucho tiempo, diríamos:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Si su lote de código que consume mucho tiempo se puede dividir en etapas, es posible que desee retirarse antes si se cancela su tarea. En ese caso, simplemente regrese prematuramente del cierre. Tenga en cuenta que su referencia a la tarea desde dentro del cierre debe ser débil o obtendrá un ciclo de retención. Aquí hay una ilustración artificial:
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)
En caso de que tenga que hacer una limpieza en caso de que la tarea en segundo plano se cancele prematuramente, proporcioné una cleanup
propiedad de controlador opcional (no se usa en los ejemplos anteriores). Algunas otras respuestas fueron criticadas por no incluir eso.