Comme indiqué ici et dans les réponses à d'autres questions SO, vous ne souhaitez PAS utiliser beginBackgroundTask
uniquement lorsque votre application passera en arrière-plan; au contraire, vous devez utiliser une tâche d'arrière - plan pour toute opération de temps dont l' achèvement vous voulez vous assurer même si l'application ne va dans l'arrière - plan.
Par conséquent, votre code est susceptible de se retrouver parsemé de répétitions du même code standard pour appeler beginBackgroundTask
et de manière endBackgroundTask
cohérente. Pour éviter cette répétition, il est certainement raisonnable de vouloir emballer le passe-partout dans une seule entité encapsulée.
J'aime certaines des réponses existantes pour ce faire, mais je pense que le meilleur moyen est d'utiliser une sous-classe d'opération:
Vous pouvez mettre l'opération en file d'attente sur n'importe quelle file d'attente et manipuler cette file d'attente comme bon vous semble. Par exemple, vous êtes libre d'annuler prématurément toutes les opérations existantes sur la file d'attente.
Si vous avez plus d'une chose à faire, vous pouvez enchaîner plusieurs opérations de tâche d'arrière-plan. Les opérations prennent en charge les dépendances.
La file d'attente des opérations peut (et devrait) être une file d'attente d'arrière-plan; ainsi, il n'est pas nécessaire de s'inquiéter de l'exécution de code asynchrone à l'intérieur de votre tâche, car l'opération est le code asynchrone. (En effet, cela n'a aucun sens d'exécuter un autre niveau de code asynchrone dans une opération, car l'opération se terminerait avant que ce code ne puisse même démarrer. Si vous deviez faire cela, vous utiliseriez une autre opération.)
Voici une sous-classe d'opération possible:
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
}
}
Il devrait être évident de savoir comment l'utiliser, mais au cas où ce ne serait pas le cas, imaginez que nous ayons une OperationQueue globale:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Donc, pour un lot de code qui prend du temps, nous dirions:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Si votre lot de code chronophage peut être divisé en étapes, vous souhaiterez peut-être vous retirer plus tôt si votre tâche est annulée. Dans ce cas, revenez prématurément de la fermeture. Notez que votre référence à la tâche à partir de la fermeture doit être faible ou vous obtiendrez un cycle de rétention. Voici une illustration artificielle:
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)
Si vous avez un nettoyage à faire au cas où la tâche d'arrière-plan elle-même serait annulée prématurément, j'ai fourni une option cleanup
propriété de gestionnaire (non utilisée dans les exemples précédents). Certaines autres réponses ont été critiquées pour ne pas avoir inclus cela.