Conforme observado aqui e em respostas a outras perguntas do SO, você NÃO deseja usar beginBackgroundTask
apenas quando seu aplicativo ficará em segundo plano; pelo contrário, você deve usar uma tarefa de fundo para qualquer operação demorada cuja conclusão você quiser garantir, mesmo que o aplicativo faz ir para o fundo.
Portanto, é provável que seu código acabe salpicado de repetições do mesmo código clichê para chamar beginBackgroundTask
eendBackgroundTask
coerente. Para evitar essa repetição, é certamente razoável querer empacotar o clichê em uma única entidade encapsulada.
Gosto de algumas das respostas existentes para fazer isso, mas acho que a melhor maneira é usar uma subclasse de Operação:
Você pode enfileirar a operação em qualquer OperationQueue e manipular essa fila como achar melhor. Por exemplo, você está livre para cancelar prematuramente qualquer operação existente na fila.
Se você tem mais de uma coisa a fazer, pode encadear várias operações de tarefas em segundo plano. Dependências de suporte de operações.
A fila de operações pode (e deve) ser uma fila de segundo plano; assim, não há necessidade de se preocupar em realizar código assíncrono dentro de sua tarefa, pois a Operação é o código assíncrono. (Na verdade, não faz sentido executar outro nível de código assíncrono dentro de uma operação, pois a operação terminaria antes mesmo que o código pudesse começar. Se você precisasse fazer isso, usaria outra operação.)
Aqui está uma possível subclasse de Operação:
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
}
}
Deve ser óbvio como usar isso, mas caso não seja, imagine que temos uma OperationQueue global:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Portanto, para um lote típico de código demorado, diríamos:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Se seu lote de código demorado pode ser dividido em estágios, convém encerrar mais cedo se a tarefa for cancelada. Nesse caso, basta retornar prematuramente do fechamento. Observe que sua referência à tarefa de dentro do fechamento precisa ser fraca ou você obterá um ciclo de retenção. Aqui está uma ilustração 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)
Caso você tenha que fazer uma limpeza, caso a própria tarefa em segundo plano seja cancelada prematuramente, forneci uma cleanup
propriedade de manipulador opcional (não usada nos exemplos anteriores). Algumas outras respostas foram criticadas por não incluir isso.