Jak wspomniano tutaj i w odpowiedziach na inne pytania SO, NIE chcesz używać beginBackgroundTask
tylko wtedy, gdy Twoja aplikacja przejdzie w tło; Przeciwnie, należy użyć zadania tła dla każdej operacji czasochłonnego którego ukończenie chcesz mieć pewność, nawet jeśli aplikacja ma iść w tle.
Dlatego twój kod prawdopodobnie zostanie usiany powtórzeniami tego samego standardowego kodu do wywoływania beginBackgroundTask
i endBackgroundTask
spójnie. Aby zapobiec takim powtórzeniom, z pewnością rozsądne jest zapakowanie schematu w jakąś pojedynczą zamkniętą całość.
Podoba mi się niektóre z istniejących odpowiedzi za to, ale myślę, że najlepszym sposobem jest użycie podklasy Operation:
Możesz umieścić Operację w kolejce do dowolnej kolejki OperationQueue i manipulować tą kolejką według własnego uznania. Na przykład możesz przedwcześnie anulować wszelkie istniejące operacje w kolejce.
Jeśli masz więcej niż jedną rzecz do zrobienia, możesz połączyć wiele operacji zadań w tle. Operacje obsługują zależności.
Kolejka operacji może (i powinna) być kolejką w tle; w związku z tym nie ma potrzeby martwić się wykonywaniem kodu asynchronicznego wewnątrz zadania, ponieważ operacja jest kodem asynchronicznym. (Rzeczywiście, nie ma sensu wykonywanie innego poziomu kodu asynchronicznego wewnątrz Operacji, ponieważ Operacja zakończyłaby się, zanim ten kod mógłby się nawet rozpocząć. Gdybyś musiał to zrobić, użyłbyś innej Operacji.)
Oto możliwa podklasa Operation:
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
}
}
Powinno być oczywiste, jak tego użyć, ale jeśli tak nie jest, wyobraź sobie, że mamy globalną OperationQueue:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Tak więc w przypadku typowej, czasochłonnej partii kodu powiedzielibyśmy:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Jeśli twoją czasochłonną partię kodu można podzielić na etapy, możesz chcieć się wycofać wcześniej, jeśli zadanie zostanie anulowane. W takim przypadku po prostu wróć przedwcześnie z zamknięcia. Zwróć uwagę, że odniesienie do zadania od wewnątrz musi być słabe, w przeciwnym razie otrzymasz cykl utrzymania. Oto sztuczna ilustracja:
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)
W przypadku konieczności wyczyszczenia w przypadku przedwczesnego anulowania samego zadania w tle, zapewniam opcjonalną cleanup
właściwość obsługi (nie używana w poprzednich przykładach). Niektóre inne odpowiedzi były krytykowane za nieuwzględnianie tego.