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, система может завершить работу приложения.