Wie hier und in den Antworten auf andere SO-Fragen erwähnt, möchten Sie NICHT beginBackgroundTask
nur verwenden, wenn Ihre App in den Hintergrund tritt. im Gegenteil, sollten Sie eine Hintergrundaufgabe für verwenden jederzeitaufwendiger Vorgang , dessen Abschluss Sie wollen auch , um sicherzustellen , wenn die App nicht in den Hintergrund rücken .
Daher wird Ihr Code wahrscheinlich mit Wiederholungen desselben Boilerplate-Codes zum Aufrufen beginBackgroundTask
und endBackgroundTask
kohärent gespickt sein . Um diese Wiederholung zu verhindern, ist es sicherlich vernünftig, die Boilerplate in eine einzelne gekapselte Einheit zu packen.
Ich mag einige der vorhandenen Antworten dafür, aber ich denke, der beste Weg ist, eine Operations-Unterklasse zu verwenden:
Sie können die Operation in eine beliebige OperationQueue einreihen und diese Warteschlange nach Belieben bearbeiten. Sie können beispielsweise vorhandene Vorgänge in der Warteschlange vorzeitig abbrechen.
Wenn Sie mehr als eine Aufgabe haben, können Sie mehrere Vorgänge für Hintergrundaufgaben verketten. Operationen unterstützen Abhängigkeiten.
Die Operationswarteschlange kann (und sollte) eine Hintergrundwarteschlange sein. somit gibt es keine Notwendigkeit , über die Durchführung asynchrone Codes innerhalb Ihrer Aufgabe zu kümmern, weil der Betrieb ist der asynchrone Code. (In der Tat ist es nicht sinnvoll, eine andere Ebene von asynchronem Code innerhalb einer Operation auszuführen , da die Operation beendet würde, bevor dieser Code überhaupt gestartet werden könnte. Wenn Sie dies tun müssten, würden Sie eine andere Operation verwenden.)
Hier ist eine mögliche Operations-Unterklasse:
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
}
}
Es sollte offensichtlich sein, wie dies verwendet wird, aber falls dies nicht der Fall ist, stellen Sie sich vor, wir haben eine globale OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Für einen typischen zeitaufwändigen Code-Stapel würden wir also sagen:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Wenn Ihr zeitaufwändiger Codestapel in Phasen unterteilt werden kann, möchten Sie sich möglicherweise frühzeitig zurückziehen, wenn Ihre Aufgabe abgebrochen wird. In diesem Fall kehren Sie einfach vorzeitig vom Verschluss zurück. Beachten Sie, dass Ihr Verweis auf die Aufgabe innerhalb des Abschlusses schwach sein muss, sonst erhalten Sie einen Aufbewahrungszyklus. Hier ist eine künstliche Illustration:
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)
Falls Sie eine Bereinigung durchführen müssen, falls die Hintergrundaufgabe selbst vorzeitig abgebrochen wird, habe ich eine optionale cleanup
Handler-Eigenschaft bereitgestellt (in den vorhergehenden Beispielen nicht verwendet). Einige andere Antworten wurden dafür kritisiert, dass sie dies nicht beinhalteten.