Какие легальные способы выполнения фоновой работы существуют в 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 и возможному отказу в публикации.

Ответ 18+ 🔞

А, слушай, смотри, этот ваш 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+) Это типа цивилизованный способ, мол, «дайте мне, пожалуйста, 30 секунд, я быстро». Но система, сука, может и не дать, или дать, а потом отобрать. Главное — не забыть сказать «спасибо, я всё», а то прибьёт.

// 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. Пуш-уведомления с content-available Хитрая жопа, блядь. Отправляешь пуш с флажком content-available: 1, и iOS такой: «О, ну ладно, запущу тебя ненадолго в фоне, обработай это».

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

А теперь, сука, самое важное — ограничения:

  • Время: Обычно дают секунд 30, не больше. Как на перекур. Для аудио или гео — побольше, но это спецпропуск.
  • Предсказуемость: Это не ты решаешь, когда работать. Это система решает, дать тебе время или послать нахуй.
  • Батарея: Если будешь слишком активно ебашить в фоне, Instruments покажет тебе красный Energy Impact, а ребята из App Store скажут: «Иди нахуй со своим приложением, оно батарею жрёт».