Come indicato qui e nelle risposte ad altre domande SO, NON vuoi usare beginBackgroundTask
solo quando la tua app andrà in background; al contrario, è necessario utilizzare un compito sfondo per qualsiasi operazione in termini di tempo di cui si desidera assicurare, anche se l'applicazione di completamento non va in secondo piano.
Pertanto è probabile che il tuo codice finisca per essere costellato di ripetizioni dello stesso codice standard per chiamare beginBackgroundTask
e in modo endBackgroundTask
coerente. Per evitare questa ripetizione, è certamente ragionevole voler impacchettare il boilerplate in una singola entità incapsulata.
Mi piacciono alcune delle risposte esistenti per farlo, ma penso che il modo migliore sia usare una sottoclasse Operazione:
Puoi accodare l'operazione su qualsiasi OperationQueue e manipolare quella coda come meglio credi. Ad esempio, sei libero di annullare anticipatamente qualsiasi operazione esistente sulla coda.
Se hai più di una cosa da fare, puoi concatenare più operazioni in background. Dipendenze del supporto operativo.
La coda delle operazioni può (e dovrebbe) essere una coda in background; quindi, non è necessario preoccuparsi di eseguire codice asincrono all'interno dell'attività, poiché l'operazione è il codice asincrono. (In effetti, non ha senso eseguire un altro livello di codice asincrono all'interno di un'operazione, poiché l'operazione finirebbe prima che quel codice possa iniziare. Se fosse necessario, useresti un'altra operazione.)
Ecco una possibile sottoclasse di operazioni:
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
}
}
Dovrebbe essere ovvio come usarlo, ma nel caso in cui non lo sia, immagina di avere una OperationQueue globale:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Quindi, per un tipico batch di codice che richiede tempo, diremmo:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Se il tuo batch di codice che richiede tempo può essere suddiviso in fasi, potresti voler abbandonare presto se l'attività viene annullata. In tal caso è sufficiente rientrare prematuramente dalla chiusura. Nota che il tuo riferimento all'attività dall'interno della chiusura deve essere debole o otterrai un ciclo di conservazione. Ecco un'illustrazione artificiale:
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)
Nel caso in cui sia necessario eseguire la pulizia nel caso in cui l'attività in background stessa venga annullata prematuramente, ho fornito una cleanup
proprietà del gestore opzionale (non utilizzata negli esempi precedenti). Alcune altre risposte sono state criticate per non averlo incluso.