Skip to main content

Background State

Background State(Фоновое состояние) - это состояние iOS приложения, в котором оно неактивно (т.е оно не отображается в данный момент на экране), но при этом выполняет какую-либо работу.

Есть три основных способа перехода в это состояние:

  • Приложение было запущено сразу в фоне, например из-за вызова Background Fetch или другим способом, которые перечислены тут.
  • Приложение перешло в фон из состояния suspend, для выполнения определенной задач из того же списка, что и в первом пункте.
  • Приложение было свернуто пользователем и, перед тем как уйти в состояние suspend, переходит в фон, для завершения всех необходимых активностей.

В данном случае нас интересует именно третий вариант. Мы рассмотрим, каким образом можно дождаться выполнения задачи в фоновом состоянии и не дать приложению прервать ее уйдя в suspend.

Когда приложение переходит в фоновое состояние, iOS вызывает у AppDelegate метод applicationDidEnterBackground(_:). В этот момент у приложения есть несколько секунд, чтобы завершить активные задачи. В скоре после вызова этого метода, приложение перейдет в состояние suspend.

Предположим следующую ситуацию, пользователь добавляет фото для своего профиля в приложении, он сделал селфи и нажал кнопку отправить. Приложение начало отправку фото на сервер и показывает пользователю лоадер. Но пользователь не захотел ждать завершения загрузки, свернул приложение и открыл другое приложение. Проходит пара минут, пользователь возвращается в наше приложение ожидая, что фото успешно загружено, но вместо этого видит ошибку, что запрос завершился по таймауту.

Чтобы этого избежать, надо запросить у системы дополнительное время в фоне, для выполнения запроса. Для этого можно использовать метод beginBackgroundTask(withName:expirationHandler:). Подробное его описание можно найти в официальной документации.

Ниже приведен пример использование этого метода:

func applicationDidEnterBackground(_ application: UIApplication) { 
if isHardTaskInprogress {
// Запоминаем иденитификатор задачи, чтобы завершить ее, когда приложение готово к переходу в suspend
var taskId: UIBackgroundTaskIdentifier? = nil

// Запускаем фоновую задачу, это задержит приложение от ухода в suspend
taskId = application.beginBackgroundTask {
// Блок вызывается в случае, если мы не вызывали endBackgroundTask, за отведенное системой время
// Здесь у нас есть еще пара секунд, чтобы подготовиться к переходу в suspend
print("Я не успел закончить задачу")
application.endBackgroundTask(taskId!)
}
// Когда, мы выполнили все, что хотели, надо вызвать endBackgroundTask, чтобы приложение перешло в suspend
onHardTaskFinish = {
print("Я закончил, можно вырубать")
application.endBackgroundTask(taskId!)
}
}
}

Таким образом можно задержать переход в suspend примерно на 30 секунд. Когда я проводил замеры, в среднем, получалось 26 секунд между вызовом beginBackgroundTask(withName:expirationHandler:) и вызовом expirationHandler. Также можно узнать оставшееся выделенное время через свойство backgroundTimeRemaining.

Однако, необязательно ждать перехода в фон для вызова beginBackgroundTask, этот метод можно использовать в любом месте программы и в любое время, например, когда вы заранее знаете, что задача может занять продолжительное время (загрузка фото на сервер).

    @IBAction func onSubmitPressed() {
var taskId: UIBackgroundTaskIdentifier? = nil

taskId = UIApplication.shared.beginBackgroundTask {
print("Я не успел закончить задачу")
UIApplication.shared.endBackgroundTask(taskId!)
}

AF.request("https://cataas.com/api/cats").responseJSON { response in
print("Я закончил, можно вырубать")
UIApplication.shared.endBackgroundTask(taskId!)
}
}

В данном случае, если во время запроса приложение уйдет в фон, система не даст ему уйти в suspend до завершения задачи.

Использование данного метода не требует включения Background Modes в Capabilities.

Вы также можете вызвать этот метод множество раз, чтобы создать несколько фоновых задач, которые выполняются параллельно. Однако, для каждой задачи должна быть вызвана функция endBackgroundTask, иначе, вместо перехода в suspend, система может завершить работу приложения.

Автор: @Dorofeev