58. Получаем ответ сервера в свернутом приложении
В этой статье покажу как на iOS < 16 можно отправить запрос к серверу, пока приложение открыто (состояние foreground), и дождаться завершения запроса после сворачивания приложения (состояние background). Будем использовать background task (далее - фоновый таск).
Сценарий
- Нажимаем на кнопку авторизации в приложении
- Приложение запрашивает у сервера СМС со ссылкой для авторизации
- Дожидаемся СМС
- Переходим в "Сообщения" (приложение свернули)
- Нажимаем на ссылку в СМС
- Открывается safari с предложением вернуться обратно в приложение (по диплинку)
- Жмем "открыть" и возвращаемся в приложение
В чем проблема
Система (iOS) может приостановить работу приложения в свернутом состоянии: это может произойти даже через 1-2 секунды после сворачивания приложения.
Если не использовать фоновый таск, то на шаге 7 (возврат в приложение) мы можем повторить запрос к серверу для получения СМС, и это может привести к ошибке (слишком много запросов в короткий промежуток времени).
Повторный запрос консоль Xcode не покажет - там отобразится ответ сервера на второй запрос (скорее всего с ошибкой).
Решение
Настройка проекта
У apple есть несколько гайдов на такой случай, например такой и такой, и я рекомендую с ними ознакомиться после прочтения статьи.
<key>BGTaskSchedulerPermittedIdentifiers</key> <array> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> </array>
Код для iOS < 16
struct BackgroundTaskExample: View {
var body: some View {
VStack {
Text("Дождитесь СМС со ссылкой для авторизации")
Spacer()
}
.task { await requestSMS() }
}
/// Создает фоновый таск и возвращает его идентификатор
private func startBackgroundTask() -> UIBackgroundTaskIdentifier {
var identifier: UIBackgroundTaskIdentifier?
identifier = UIApplication.shared.beginBackgroundTask {
// Блок, выполняющийся при завершении таска, нужно вызвать `endBackgroundTask`
UIApplication.shared.endBackgroundTask(identifier ?? .invalid)
}
return identifier ?? .invalid
}
private func requestSMS() async {
// Создаем таск для фоновой работы, чтобы завершить его
// после успешной авторизации
let taskId = startBackgroundTask()
// Выполняем запрос
// await myService.requestSMSForAuth()
// После завершения авторизации завершаем фоновый таск
await UIApplication.shared.endBackgroundTask(taskId)
}
}
Код для iOS 16+
В SwiftUI на iOS 16 есть удобный модификатор backgroundTask, обзор на который уже есть у других ребят, например, тут и тут.
Заключение
Нужно помнить, что если приложение сворачивается, то по умолчанию нет гарантий завершения долгих запросов к серверу. В таких ситуациях нужно использовать фоновые таски 🙂
Код для этой статьи можно посмотреть тут, а другие статьи - тут.