Як зазначено тут та у відповідях на інші запитання щодо ТА, ви НЕ хочете використовувати їх beginBackgroundTask
лише тоді, коли ваш додаток відійде на другий план; навпаки, ви повинні використовувати фонову завдання для будь-якого трудомісткою операції поповнення якого ви хочете переконатися , навіть якщо додаток дійсно йти в фоновому режимі.
Тому ваш код, ймовірно, закінчиться повтореннями одного й того самого кодового коду для виклику beginBackgroundTask
та endBackgroundTask
узгоджено. Для запобігання цього повторення, безумовно, розумно хочете упакувати плиту котла в якесь одне капсульоване ціле.
Мені подобаються деякі з існуючих відповідей для цього, але я думаю, що найкращим способом є використання підкласу Операція:
Ви можете задіяти Операцію на будь-якій черзі операцій та маніпулювати цією чергою, як вважаєте за потрібне. Наприклад, ви можете передчасно скасувати будь-які існуючі операції в черзі.
Якщо у вас є кілька справ, ви можете зв'язати кілька операцій із фоновим завданням. Операції підтримують залежності.
Черга операцій може (і повинна) бути фоновою чергою; таким чином, не потрібно турбуватися про виконання асинхронного коду всередині вашого завдання, оскільки Операція - це асинхронний код. (Дійсно, не має сенсу виконувати інший рівень асинхронного коду всередині операції, оскільки Операція закінчиться до того, як цей код навіть міг би розпочатися. Якщо вам потрібно було це зробити, ви використовуєте іншу операцію.)
Ось можливий підклас Операція:
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
}
}
Повинно бути очевидним, як це використовувати, але, якщо це не так, уявіть, що у нас є глобальна операційна черга:
let backgroundTaskQueue : OperationQueue = {
let q = OperationQueue()
q.maxConcurrentOperationCount = 1
return q
}()
Отже, для типової трудомісткої партії коду ми б сказали:
let task = BackgroundTaskOperation()
task.whatToDo = {
// do something here
}
backgroundTaskQueue.addOperation(task)
Якщо ваш трудомісткий пакет коду можна розділити на етапи, ви, можливо, захочете поклонитися рано, якщо ваше завдання буде скасовано. У такому випадку просто повертайтеся передчасно із закриття. Зауважте, що посилання на завдання із закриття повинно бути слабким, або ви отримаєте цикл збереження. Ось штучна ілюстрація:
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)
Якщо у вас є очищення у випадку, якщо саме фонове завдання скасується передчасно, я надав необов'язкове cleanup
властивість обробника (не використовується в попередніх прикладах). Деякі інші відповіді критикували за те, що вони не включали.