Background Task — це механізм iOS, який дозволяє застосунку завершити розпочату операцію після переходу у фоновий режим. Система виділяє обмежений час (до 30 секунд) на виконання завдання, після чого застосунок примусово призупиняється. За даними Apple, 2026, використання expiration handler є обов'язковою вимогою для коректного завершення фонового завдання.
Головне
Background Task — це програмний механізм iOS, що дозволяє застосунку продовжити час виконання після переходу у фоновий режим. Коли користувач згортає застосунок, система викликає UIApplicationDelegate.applicationDidEnterBackground. Якщо в цей момент виконується критична операція (збереження даних, надсилання запиту), застосунок може запросити додатковий час через beginBackgroundTask.
Без використання Background Task застосунок примусово призупиняється через 3–5 секунд після переходу в фон. Усі незавершені операції перериваються: дані не зберігаються, мережеві запити обриваються, стани втрачаються. Background Task дає застосунку до 30 секунд для коректного завершення цих операцій.
beginBackgroundTask з'явився в iOS 4.0 — першому релізі, що підтримує багатозадачність. До iOS 4 застосунок повністю завершувався при натисканні кнопки Home. З iOS 7 з'явилися Background Fetch та URLSession background configuration. Починаючи з iOS 13, Apple рекомендує BGTaskScheduler для нових проєктів, але beginBackgroundTask залишається актуальним для короткострокових операцій.
beginBackgroundTask — метод UIApplication, який реєструє завдання та повертає унікальний ідентифікатор UIBackgroundTaskIdentifier. Система збільшує таймер фонового виконання. Після вичерпання часу викликається expiration handler, після чого застосунок має викликати endBackgroundTask(identifier:) для коректного завершення.
Крок 1 — застосунок отримує сповіщення didEnterBackground. Крок 2 — викликається beginBackgroundTask(expirationHandler:). Крок 3 — виконується критична операція (збереження, мережевий запит). Крок 4 — після завершення викликається endBackgroundTask(identifier:). Якщо операція не завершилася за 30 секунд — система викликає expiration handler, і застосунок має негайно завершити завдання.
var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
func startBackgroundTask() {
backgroundTaskID = UIApplication.shared.beginBackgroundTask {
// Expiration handler
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
}
func finishBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
Властивість backgroundTimeRemaining повертає кількість секунд, що залишилися до примусового завершення фонового завдання. Значення зменшується в реальному часі. Якщо застосунок не у фоні — повертає DBL_MAX. Використовуйте цю властивість для адаптації поведінки: при залишку менше 5 секунд переривайте тривалі операції та зберігайте прогрес.
Застосунок може зареєструвати кілька Background Tasks одночасно. Кожне завдання отримує свій ідентифікатор. Загальний час виконання підсумовується — якщо зареєстровано 3 завдання, застосунок може отримати до 90 секунд. Однак система може завершити застосунок раніше, якщо вичерпано ресурси або перевищено ліміт батареї.
Expiration handler — це блок коду, який система викликає при вичерпанні виділеного часу на фонове завдання. Наявність expiration handler є обов'язковою вимогою Apple. Без нього застосунок може бути примусово завершений системою, а дані — втрачені.
Expiration handler має виконати мінімальні дії для збереження стану застосунку: зберегти поточні дані у постійне сховище, викликати endBackgroundTask з відповідним ідентифікатором та встановити ідентифікатор у .invalid. Всередині expiration handler заборонено запускати нові тривалі операції — час виконання обмежений 1–2 секундами.
func handleExpiration() {
// Зберігаємо прогрес виконання
saveProgressToUserDefaults()
// Скасовуємо активні мережеві запити
currentTask?.cancel()
// Завершуємо Background Task
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
// Реєстрація з expiration handler
backgroundTaskID = UIApplication.shared.beginBackgroundTask(
withName: "SaveDocument",
expirationHandler: handleExpiration
)
Найпоширеніша помилка — не викликати endBackgroundTask всередині expiration handler. У цьому випадку система продовжує вважати завдання активним, застосунок не переходить у стан Suspended, і батарея розряджається. Друга помилка — запуск тривалих операцій всередині expiration handler. Система може завершити застосунок до їх завершення, і дані будуть втрачені.
В expiration handler час виконання критично малий — зазвичай менше 1 секунди. Тому всередині handler не можна виконувати: складні обчислення, мережеві запити, запис великих обсягів даних у CoreData. Лише атомарні операції: запис одного ключа в UserDefaults, встановлення прапорця стану, виклик endBackgroundTask.
Розглянемо повну реалізацію Background Task для збереження документа при переході застосунку в фон. Приклад включає реєстрацію завдання, виконання операції з перевіркою часу, що залишився, та коректне завершення через endBackgroundTask.
При переході в фон застосунок ініціює збереження великого документа. Background Task дає до 30 секунд на операцію. Якщо час вичерпується — expiration handler зберігає проміжні результати. Після завершення збереження викликається endBackgroundTask для звільнення ресурсів.
class DocumentManager {
private var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
func saveDocumentInBackground(document: Document) {
backgroundTaskID = UIApplication.shared.beginBackgroundTask {
self.saveProgress(document)
self.endBackgroundTask()
}
DispatchQueue.global().async {
document.save()
self.endBackgroundTask()
}
}
private func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
backgroundTaskID = .invalid
}
}
Мережеві запити у фоні потребують особливої уваги — expiration handler може перервати запит до отримання відповіді. Використовуйте URLSession з dataTask та перевіряйте backgroundTimeRemaining перед надсиланням. Якщо залишилося менше 5 секунд — відкладіть надсилання до наступного пробудження застосунку.
func sendCriticalData(data: Data) {
backgroundTaskID = UIApplication.shared.beginBackgroundTask {
// Зберігаємо дані для повторного надсилання
saveForRetry(data)
self.endBackgroundTask()
}
let task = URLSession.shared.dataTask(with: request) { _, _, error in
if error != nil {
saveForRetry(data)
}
self.endBackgroundTask()
}
task.resume()
}
Для тривалих операцій відстежуйте UIApplication.shared.backgroundTimeRemaining. Якщо значення падає нижче порогу (10 секунд), перервіть поточну операцію та запустіть збереження проміжних результатів. Це дозволяє коректно завершити роботу до примусового виклику expiration handler.
Background Task — потужний механізм, але його неправильне використання призводить до проблем із батареєю та продуктивністю. Дотримання рекомендацій Apple та перевірених практик спільноти допоможе уникнути типових помилок і забезпечити стабільну роботу застосунку.
Завершуйте Background Task якомога швидше. Кожна секунда фонового виконання витрачає заряд батареї. Оптимальний час — менше 5 секунд. Якщо операція займає більше — розгляньте використання BGTaskScheduler або Background Modes. Не продовжуйте завдання штучно через виклики beginBackgroundTask без реальної необхідності.
Зберігайте UIBackgroundTaskIdentifier як властивість класу або структури. Ніколи не передавайте його як глобальну змінну — це призводить до конфліктів при множинних завданнях. Встановлюйте ідентифікатор у .invalid після виклику endBackgroundTask для запобігання подвійному завершенню.
У Xcode Simulator перемкніть застосунок у фон через кнопку Home (Command + Shift + H). Використовуйте Debug — Simulate Background Fetch для тестування фонового пробудження. Для емуляції вичерпання часу встановіть змінну середовища BACKGROUND_TASK_SIMULATE_EXPIRATION = YES — expiration handler буде викликано через 5 секунд.
// Перевірка часу, що залишився, перед початком операції
let remaining = UIApplication.shared.backgroundTimeRemaining
guard remaining > 10.0 else {
// Недостатньо часу — відкласти завдання
scheduleForNextLaunch()
return
}
// Виконання операції з контролем часу
backgroundTaskID = UIApplication.shared.beginBackgroundTask {
saveProgress()
self.endBackgroundTask()
}
Expiration handler захоплює self — це може створити retain cycle, якщо backgroundTaskID зберігається як властивість того ж об'єкта. Використовуйте [weak self] у замиканні або зберігайте ідентифікатор окремо. Retain cycle призведе до витоку пам'яті — об'єкт не буде звільнено до завершення застосунку.
З виходом iOS 13 Apple представила BGTaskScheduler — сучасну заміну старого API Background Task. Обидва механізми вирішують схожі завдання, але BGTaskScheduler надає більше контролю та гнучкості. Розуміння відмінностей допоможе вибрати правильний інструмент для конкретного сценарію.
| Характеристика | Background Task | BGTaskScheduler |
|---|---|---|
| Версія iOS | 4.0+ | 13.0+ |
| Макс. час | 30 секунд | 30 с / 10 хв (processing) |
| Ініціація | didEnterBackground | Планування + система |
| Гарантія виконання | Тільки при переході в фон | У будь-який зручний для системи час |
| Expiration handler | Так, обов'язковий | Так, через task.expirationHandler |
| Інтернет | Потрібен при запуску | Доступний через requiresNetworkConnectivity |
| Заряджання | Не потрібне | Опціонально для processing |
beginBackgroundTask підходить для короткострокових операцій, які потрібно виконати негайно при переході в фон: збереження стану, завершення мережевого запиту, кешування даних. API простий і не вимагає налаштування Info.plist або реєстрації ідентифікаторів. Ідеальний для операцій, що займають менше 10 секунд.
BGTaskScheduler підходить для завдань, які можуть бути виконані в будь-який зручний час: періодична синхронізація, очищення кешу, оновлення віджетів. Система сама обирає оптимальний час з урахуванням поведінки користувача та стану батареї. Для тривалих операцій (до 10 хвилин) використовуйте BGProcessingTask.
Обидва API можна використовувати в одному застосунку. Background Task — для негайних операцій при переході в фон (збереження чернетки). BGTaskScheduler — для планових оновлень (синхронізація кожні 6 годин). Розділення відповідальності дозволяє гарантувати коректне збереження даних та енергоефективне фонове обслуговування.
Часті запитання
Так, beginBackgroundTask можна викликати в будь-який момент роботи застосунку. Однак системне виділення часу почнеться тільки після переходу в фон. Якщо застосунок на передньому плані, виклик beginBackgroundTask не має ефекту — backgroundTimeRemaining повертає DBL_MAX, а завдання буде активовано при переході в фон.
Якщо не викликати endBackgroundTask, система продовжить вважати застосунок активним у фоні. Через 30 секунд буде викликано expiration handler, але якщо і в ньому немає endBackgroundTask — застосунок залишиться в пам'яті, витрачаючи батарею. В iOS 13+ система примусово завершує такий застосунок через 3 хвилини.
Стандартний час — 30 секунд. Збільшити його можна тільки через Background Modes: Audio (відтворення), Location (геолокація), Bluetooth (BLE). Або через BGProcessingTask (iOS 13+) — до 10 хвилин за умови заряджання та Wi-Fi. beginBackgroundTask сам по собі не надає способу збільшити ліміт.
Так, iPadOS повністю підтримує beginBackgroundTask з тими ж обмеженнями, що й iOS. На iPad з Stage Manager застосунок може залишатися в пам'яті довше — система рідше призупиняє застосунки через більший обсяг RAM. Але ліміт 30 секунд на фонове завдання зберігається.
Підключіть пристрій до Xcode, запустіть застосунок, згорніть його — у консолі з'являться логи системи. Використовуйте sysdiagnose для збору детальних логів: примусово викличте sysdiagnose з пристрою (Гучність + + Гучність − + Живлення). У Xcode Debug Navigator відстежуйте активність фонових завдань.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також