Какие легальные способы выполнения фоновой работы существуют в iOS?

«Какие легальные способы выполнения фоновой работы существуют в iOS?» — вопрос из категории UIKit, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

iOS предоставляет строго ограниченные механизмы для фонового выполнения задач, чтобы сохранять заряд батареи. Вот основные разрешённые способы:

1. Фоновые режимы (Background Modes) Указываются в Info.plist (UIBackgroundModes). Каждый режим требует обоснования при ревью App Store.

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>      <!-- Воспроизведение или запись аудио -->
    <string>location</string>   <!-- Получение обновлений геолокации -->
    <string>fetch</string>      <!-- Периодическое обновление контента -->
    <string>processing</string> <!-- Завершение длительных задач -->
    <string>remote-notification</string> <!-- Обработка пуш-уведомлений с контентом -->
</array>

2. Background Tasks Framework (iOS 13+) Предпочтительный современный API для коротких фоновых задач (например, синхронизация, очистка кэша).

// 1. Запросить идентификатор задачи у системы
var backgroundTaskID: UIBackgroundTaskIdentifier?
backgroundTaskID = UIApplication.shared.beginBackgroundTask(
    withName: "Network Sync") {
    // Этот блок вызовется, если время истекло. Необходима быстрая очистка.
    if let taskID = backgroundTaskID {
        UIApplication.shared.endBackgroundTask(taskID)
        backgroundTaskID = .invalid
    }
}

// 2. Выполнить асинхронную задачу (например, сетевой запрос)
DispatchQueue.global().async {
    // Ваша фоновая работа
    performSync()

    // 3. ЯВНО завершить задачу по её окончании
    DispatchQueue.main.async {
        if let taskID = backgroundTaskID {
            UIApplication.shared.endBackgroundTask(taskID)
            backgroundTaskID = .invalid
        }
    }
}

3. Background Fetch Система сама предоставляет приложению периодическое время для предварительной загрузки данных.

// В AppDelegate:
func application(_ application: UIApplication,
                 performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

    fetchNewData { newData in
        if newData {
            completionHandler(.newData)
        } else {
            completionHandler(.noData)
        }
    }
}

// Также нужно установить интервал (реальный интервал определяет система):
UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)

4. Push-уведомления с контентом (Background Notifications) Можно отправить пуш с content-available: 1, и система запустит приложение в фоне для обработки.

// В AppDelegate:
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // Обработать данные из пуша
    completionHandler(.newData)
}

Ключевые ограничения:

  • Время выполнения: Для beginBackgroundTask обычно даётся ~30 секунд. Для специальных режимов (аудио, локация) — больше.
  • Предсказуемость: Система решает, когда и как часто предоставлять фоновое время.
  • Энергоэффективность: Чрезмерная фоновая активность приведёт к низкой оценке в Energy Impact в Instruments и возможному отказу в публикации.