Как отмечено здесь и в ответах на другие вопросы SO, вы НЕ хотите использовать beginBackgroundTask
только тогда, когда ваше приложение перейдет в фоновый режим; напротив, вы должны использовать фоновую задачу для любой трудоемкой операции, выполнение которой вы хотите гарантировать, даже если приложение действительно перейдет в фоновый режим.
Поэтому ваш код, скорее всего, в конечном итоге приправлены повторений одного и того же шаблонного кода для вызова beginBackgroundTask
и endBackgroundTask
слаженно. Чтобы предотвратить это повторение, безусловно, разумно захотеть упаковать шаблон в некую единую инкапсулированную сущность.
Мне нравятся некоторые из существующих ответов для этого, но я думаю, что лучший способ - использовать подкласс Operation:
Вы можете поставить операцию в очередь на любую OperationQueue и управлять этой очередью по своему усмотрению. Например, вы можете досрочно отменить любые существующие операции в очереди.
Если у вас есть несколько дел, вы можете связать несколько операций фоновых задач. Зависимости поддержки операций.
Очередь операций может (и должна) быть фоновой очередью; таким образом, нет необходимости беспокоиться о выполнении асинхронного кода внутри вашей задачи, потому что операция - это асинхронный код. (Действительно, нет смысла выполнять другой уровень асинхронного кода внутри операции, поскольку операция завершится до того, как этот код сможет даже начаться. Если бы вам нужно было это сделать, вы бы использовали другую операцию.)
Вот возможный подкласс 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
}
}
Должно быть очевидно, как это использовать, но если это не так, представьте, что у нас есть глобальная OperationQueue:
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
свойство обработчика (не используется в предыдущих примерах). Некоторые другие ответы подверглись критике за отсутствие этого.