Как добиться стабильного выполнения фоновых задач в iOS
Казалось бы, у вас в кармане суперкомпьютер. Он точно должен уметь выполнять рутинные задачи в фоне, пока вы занимаетесь своими делами. Как cron в Unix, который отлично работает с 1970 года. Но реальность iOS оказалась сложнее.
Автор статьи делится опытом работы с BGTaskScheduler - фреймворком для запуска фоновых задач. Методом проб и ошибок, работая с собственным устройством ему удалось добиться приемлемого уровня надежности фонового выполнения.
Типы фоновых задач в iOS:
Есть три основных типа фоновых задач:
🔹 BGAppRefreshTask: короткая задача для обновления. Предназначена для поддержания контента приложения в актуальном состоянии.
🔹 BGProcessingTask: для более тяжелой работы, которая может занимать минуты.
🔹 BGContinuedProcessingTask: появился в iOS 26. Запускается на переднем плане и может продолжать работу в фоне. Для автоматической синхронизации бесполезен.
Apple не публикует точных числовых ограничений. Реальные лимиты приходится выяснять вручную.
Как запланировать задачи:
Для этого необходимо отправить запрос и зарегистрировать обработчик:
В обработчике обязательно нужно задать expirationHandler. Если этого не сделать, iOS отметит задачу как завершенную с проблемой.
Точность планирования:
BGTaskScheduler не похож на cron. Задача может запуститься на несколько часов позже запрошенного времени. На устройстве автора processing задачи чаще запускались ночью на зарядке, а refresh днем, когда телефон активно использовался.
Полный цикл синхронизации укладывался в секунду. Это позволило зарегистрировать оба типа задач, чтобы дать iOS больше возможностей для запуска.
Ошибка: выполнение тяжелых операций в refresh задаче. После этого пробуждения стали менее надежными. Apple объясняет это энергетическим бюджетом приложения. Чем больше энергии и трафика потребляет задача, тем реже iOS ее запускает.
Подводные камни планирования:
BGTaskScheduler хранит только один ожидающий запрос для каждого идентификатора. Отправка нового запроса заменяет предыдущий. Если не проверять существующий запрос, можно бесконечно отодвигать задачу по времени.
Важно вычислять время от фиксированной точки, а не от текущего момента.
earliestBeginDate - это нижняя граница, а не расписание. iOS не гарантирует запуск в указанное время.
🔗 Читать подробнее
Вывод:
BGTaskScheduler не надежен как cron. Но добиться стабильного расписания можно. Планируйте задачи с фиксированной точки отсчета. Не пытайтесь бороться с iOS - она сама решает, когда запускать задачи. Используйте несколько типов фоновых задач, чтобы увеличить шансы на запуск. И помните: поведение пользователя и состояние устройства влияют на выполнение сильнее, чем ваш код.
Казалось бы, у вас в кармане суперкомпьютер. Он точно должен уметь выполнять рутинные задачи в фоне, пока вы занимаетесь своими делами. Как cron в Unix, который отлично работает с 1970 года. Но реальность iOS оказалась сложнее.
Автор статьи делится опытом работы с BGTaskScheduler - фреймворком для запуска фоновых задач. Методом проб и ошибок, работая с собственным устройством ему удалось добиться приемлемого уровня надежности фонового выполнения.
Типы фоновых задач в iOS:
Есть три основных типа фоновых задач:
🔹 BGAppRefreshTask: короткая задача для обновления. Предназначена для поддержания контента приложения в актуальном состоянии.
🔹 BGProcessingTask: для более тяжелой работы, которая может занимать минуты.
🔹 BGContinuedProcessingTask: появился в iOS 26. Запускается на переднем плане и может продолжать работу в фоне. Для автоматической синхронизации бесполезен.
Apple не публикует точных числовых ограничений. Реальные лимиты приходится выяснять вручную.
Как запланировать задачи:
Для этого необходимо отправить запрос и зарегистрировать обработчик:
let refresh = BGAppRefreshTaskRequest(identifier: "com.app.refresh")
refresh.earliestBeginDate = lastRun.addingTimeInterval(30 * 60)
try BGTaskScheduler.shared.submit(refresh)
let processing = BGProcessingTaskRequest(identifier: "com.app.process")
processing.earliestBeginDate = lastRun.addingTimeInterval(60 * 60)
try BGTaskScheduler.shared.submit(processing)
В обработчике обязательно нужно задать expirationHandler. Если этого не сделать, iOS отметит задачу как завершенную с проблемой.
BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.app.process") { task in
task.expirationHandler = {
cancelWork()
task.setTaskCompleted(success: false)
}
Task {
let ok = await doWork()
task.setTaskCompleted(success: ok)
}
}
Точность планирования:
BGTaskScheduler не похож на cron. Задача может запуститься на несколько часов позже запрошенного времени. На устройстве автора processing задачи чаще запускались ночью на зарядке, а refresh днем, когда телефон активно использовался.
Полный цикл синхронизации укладывался в секунду. Это позволило зарегистрировать оба типа задач, чтобы дать iOS больше возможностей для запуска.
Ошибка: выполнение тяжелых операций в refresh задаче. После этого пробуждения стали менее надежными. Apple объясняет это энергетическим бюджетом приложения. Чем больше энергии и трафика потребляет задача, тем реже iOS ее запускает.
Подводные камни планирования:
BGTaskScheduler хранит только один ожидающий запрос для каждого идентификатора. Отправка нового запроса заменяет предыдущий. Если не проверять существующий запрос, можно бесконечно отодвигать задачу по времени.
static func shouldSkipSubmit(existingEarliest: Date?, desiredEarliest: Date) -> Bool {
guard let existingEarliest else { return true }
return existingEarliest <= desiredEarliest.addingTimeInterval(5)
}
Важно вычислять время от фиксированной точки, а не от текущего момента.
let desiredEarliest = max(
now.addingTimeInterval(60),
lastRun.addingTimeInterval(interval)
)
earliestBeginDate - это нижняя граница, а не расписание. iOS не гарантирует запуск в указанное время.
🔗 Читать подробнее
Вывод:
BGTaskScheduler не надежен как cron. Но добиться стабильного расписания можно. Планируйте задачи с фиксированной точки отсчета. Не пытайтесь бороться с iOS - она сама решает, когда запускать задачи. Используйте несколько типов фоновых задач, чтобы увеличить шансы на запуск. И помните: поведение пользователя и состояние устройства влияют на выполнение сильнее, чем ваш код.