Как в iOS реализовать выполнение задачи приложением в фоновом режиме?

Ответ

В iOS для фонового выполнения используются Background Modes и API beginBackgroundTask. Почему это важно: Система приостанавливает приложение в фоне для экономии заряда, поэтому для завершения критичных операций (например, сохранения данных или завершения сетевого запроса) требуется явно запросить дополнительное время.

Пример: Завершение сетевого запроса при уходе в фон

  1. Объявите capability в Xcode (например, Background fetch) или добавьте ключ в Info.plist для специальных режимов (аудио, локация, VOIP).
  2. Запросите фоновое время в AppDelegate или SceneDelegate:
func applicationDidEnterBackground(_ application: UIApplication) {
    // Объявляем идентификатор фоновой задачи
    var backgroundTask: UIBackgroundTaskIdentifier = .invalid

    // Запрашиваем у системы дополнительное время
    backgroundTask = application.beginBackgroundTask(withName: "Finish Network Call") {
        // Этот блок выполнится, если время истечёт. Нужно завершить операцию и очистить ресурсы.
        application.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
    }

    // Запускаем асинхронную операцию, которую нужно завершить
    DispatchQueue.global().async {
        // Выполняем долгую операцию (например, отправку логов на сервер)
        self.sendLogsToServer()

        // По завершении ОБЯЗАТЕЛЬНО сообщаем системе
        application.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
    }
}

Ключевые моменты:

  • Лимит времени: Обычно ~30 секунд. Точное время доступно через application.backgroundTimeRemaining.
  • Обязательная очистка: Всегда вызывайте endBackgroundTask, иначе приложение будет убито системой.
  • Для периодических задач (iOS 13+) используйте BackgroundTasks.framework для BGAppRefreshTask или BGProcessingTask.

Ответ 18+ 🔞

А, ну вот, подъехали к самому сокровенному — как заставить эту суку-систему не убивать твой код, когда юзер тыкает в домашнюю кнопку. Слушай сюда, это важно, как дважды два, блядь.

iOS, она же хитрая жопа, считает, что раз приложение не на экране — значит ему батарейку жрать незачем. И просто приостанавливает все процессы, накрывает их медным тазом. А если ты там сетевое рукопожатие не закончил или данные не сохранил — всё, пиздец, операция в говне. Поэтому надо ей явно сказать: «Э, подожди, сука, дай мне минуточку, я тут дельце одно доделаю».

Как это работает, на пальцах:

  1. Ты должен легализоваться. Иди в Xcode, в настройках таргета, найди там Background Modes и галочку поставь. Ну, или в Info.plist ключик нужный впиши, если у тебя там аудио плеер, навигация или ещё какая специальная хрень. Без этого система на тебя посмотрит как на идиота и времени не даст.
  2. Просишь время у системы. Это делается через beginBackgroundTask. Это как сказать: «Система, я ща быстро, на пять минут, честно-честно».

Вот смотри, как это в коде выглядит, ёпта:

func applicationDidEnterBackground(_ application: UIApplication) {
    // Заводим себе «пропуск» на фоновую деятельность
    var backgroundTask: UIBackgroundTaskIdentifier = .invalid

    // Идём к системе и говорим: «Начальница, можно мне фон? Называться будет "Закончить запрос"»
    backgroundTask = application.beginBackgroundTask(withName: "Finish Network Call") {
        // А это блок, который вызовется, если ты проёбываешься и не успеваешь.
        // Типа последний шанс всё похерить и прибраться, чтобы тебя не прибили.
        application.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
    }

    // Получил пропуск — теперь делай что хотел, но в отдельной очереди, не в главной!
    DispatchQueue.global().async {
        // Допустим, шлёшь последние логи перед смертью
        self.sendLogsToServer()

        // КАК ТОЛЬКО ЗАКОНЧИЛ — БЛЯДЬ, СРАЗУ СДАЙ ПРОПУСК НАЗАД!
        // Не будь мудаком, который систему обманывает.
        application.endBackgroundTask(backgroundTask)
        backgroundTask = .invalid
    }
}

А теперь главное, что ты должен вынести, чтобы не обосраться:

  • Время — не резиновое. Обычно это около 30 секунд, не больше. Узнать, сколько осталось, можно через application.backgroundTimeRemaining. Не расслабляйся.
  • Обязательно сдай пропуск! Вызов endBackgroundTask — это святое. Не вызовешь — система тебя возненавидит, сожрёт всю батарею и в итоге всё равно прибьёт, но уже с позором.
  • Если тебе надо что-то делать периодически (типа раз в час данные подтягивать), то для этого с iOS 13 есть отдельная, более сложная бюрократия — BackgroundTasks.framework. Там уже задачи BGAppRefreshTask и BGProcessingTask. Это уже для серьёзных пацанов, которые планируют свои дела заранее, а не в последний момент, как мы тут.

Короче, суть в том, чтобы не надеяться на авось, а чётко договориться с системой. Сделал дело — гуляй смело, а иначе получишь пизды.