Привет! Я Никита Пешков, iOS-разработчик в Effective. В этой статье я буду говорить об основных модах Background в iOS, но прежде напомню базу. Работа в Background – один из этапов жизненного цикла приложения, представляющего собой в iOS следующую структуру:
Когда пользователь сворачивает приложение или блокирует экран, система переводит приложение в фоновое состояние. Если приложение не имеет background-состояния, то фоновое состояние оказывается только короткой остановкой на пути к приостановке приложения, когда оперативная память ещё не освобождена, но код уже не выполняется.
Apple разрешила приложениям работать в Background вместе с релизом iOS 4. Cуществует 11 режимов фонового выполнения, которые может поддерживать приложение:
Audio, AirPlay, and Picture in Picture
Этот режим позволяет воспроизводить аудио, делиться видео через AirPlay и воспроизводить их в режиме Picture in picture в фоне. Для воспроизведения аудио и видео в нативном контроллере писать дополнительный код не нужно: эти функции будут работать из коробки.
Location Updates
Для отслеживания геолокации в iOS нужно предоставить разрешение для приложения. В момент такого запроса отображаются системные окна с текстом, указанным в info plist. Существует два типа разрешений – While in use и Always.
Авторизация While in use позволяет отслеживать геопозицию в Foreground и Background, когда включён индикатор её отслеживания. Приложение может использовать все службы определения местоположения и получать события, даже если пользователь не знает, что приложение запущено. Если приложение не запущено, система запустит его и доставит событие.
Если у приложения есть Always-авторизация, то через некоторое время на его главном экране высветится дополнительный алерт, в котором пользователю предложат переключиться на авторизацию While in use. Об этом необходимо помнить, чтобы ваше приложение внезапно не лишилось необходимой авторизации.
Запросить разрешение на геолокацию можно с помощью такого кода:
private func checkAuthorization() {
switch self.locationManager.authorizationStatus {
case .notDetermined:
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.requestAlwaysAuthorization()
case .restricted:
break
case .denied:
break
case .authorizedAlways:
self.locationManager.startUpdatingLocation()
case .authorizedWhenInUse:
self.locationManager.requestAlwaysAuthorization()
self.locationManager.startUpdatingLocation()
@unknown default:
break
}
}
Метод для обработки геолокации:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let currentLocation = locations.first {
print(currentLocation)
location = currentLocation
appendPin(location: currentLocation)
updateRegion(location: currentLocation)
}
}
Background fetch и Background processing
Background fetch нужен для обновления данных приложения, когда им не пользуются. Время его запуска полностью зависит от системы. На него влияет:
-
Критический низкий заряд батареи: если заряда < 20%, то фоновое выполнение будет приостановлено системой;
-
Режим низкого энергопотребления;
-
Включённая функция фонового обновления приложения;
-
Возможность запускать фоновые задачи есть только у приложений, отражаемых в app switcher;
-
Системные ограничение расхода батареи и времени выполнения для Background-задач;
-
Быстрое выполнение тасков: чем быстрее они выполняются, тем выше шанс на запуск.
C выходом iOS 13 Apple изменила механизм планирования фоновых задач. Система анализирует, как часто и когда пользователь запускает приложение, и планирует выполнение Background Fetch задач так, чтобы при запуске в приложении были свежие данные.
Также они ввели два вида задач: Background App Refresh Tasks для небольших задач (выполняется максимум 30 с) и Background Processing Tasks (выполняется дольше и только когда устройство не используется. Точная длительность неизвестна) для остальных. Для выполнения первых включается режим Background fetch, для вторых – Background processing.
Протестировать такие таски можно только на реальном устройстве, поставив приложение на паузу и прописав в консоль команду:
e -1 objc -- (void)[[BGTaskScheduler sharedScheduler] _simulateLaunchForTaskWithIdentifier:@"TASK_ID"]
Voice over IP
Опция позволяет принимать VOIP-звонки в фоне с помощью специальных push-уведомлений c типом VoIP.
При получении такого пуша приложение запускается в Background, подключается к службе связи и показывает UI из Callkit.
CallKit гарантирует: приложения, предоставляющие связанные с вызовами услуги, беспрепятственно работают вместе на устройстве пользователя и учитывают такие функции, как «Не беспокоить».
External accessory communication
Этот мод позволяет приложению работать с внешним устройством, зарегистрированным в программе MFi (Manufactured For iPhone) в Background-режиме.
Внешние устройства могут подключаться с помощью Bluetooth, Lighting и Type-C. За работу с такими устройствами отвечает фреймворк ExternalAccessory.
Using Bluetooth LE accessories и Acting as a Bluetooth LE accessory
Основная разница этих опций заключается в том, в какой роли Bluetooth-устройства приложение сможет принимать участие.
Using Bluetooth LE accessories позволяет приложению получать данные с Bluetooth-аксессуара. Acting as a Bluetooth LE accessory напротив – посылать данные, то есть быть в роли Bluetooth-аксессуара.
Реализация модов достигается с помощью фреймворка CoreBluetooth, который предоставляет набор классов и методов для обнаружения, установления соединения и обмена данными по Bluetooth.
Для работы с Bluetooth в приложении также требуется разрешение от пользователя!
Чтобы поиск устройства осуществлялся в Background, нужно указать необходимый идентификатор Bluetooth-сервиса.
let SERVICE_UUID = CBUUID(string: "0000ff0-0000-1000-8000-00805f9b34fb")
centralManager.scanForPeripherals(
withServices: [SERVICE_UUID],
options: [CBCentralManagerScanOptionAllowDuplicatesKey : true]
)
Без идентификатора Bluetooth-сервиса поиск не будет проводиться:
centralManager.scanForPeripherals(
withServices: nil,
options: [CBCentralManagerScanOptionAllowDuplicatesKey : true]
Remote notitifications
Remote notitifications позволяет получать различные уведомления для обновления данных приложения.
Получив такое уведомление, система удерживает его и в удобный момент запускает приложение в фоне, в котором обрабатывается уведомление. Такие пуши называют сайлент-пушами потому, что они не видны пользователю.
На их обработку даётся 30 секунд, и для получения данных этого достаточно. Однако у таких пушей есть ряд серьёзных недостатков:
-
Низкий приоритет (нет гарантии, что они обработаются);
-
Поступление пушей ограничено: Apple не советует посылать больше 2–3 в час;
-
Система задерживает поступающие сайлент-пуши, при этом новые замещают старые, перезаписывая их;
-
Не будут работать в режиме экономии энергии.
func application(
_ application: UlApplication,
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -› Void
){
do{
let data = try await fetchSomeData()
if data == nil {
completionHandler(.noData)
} else {
completionHandler(.newData)
}
}catch{
completionHandler(.failed)
}
}
Push to talk
В iOS 16 Apple представила новый фреймворк Push to Talk. Он позволяет пользователям отправлять голосовые сообщения в реальном времени, нажимая на кнопку и говоря в микрофон. Грубо говоря, это рация.
Данный фреймворк добавляет новый тип пушей, которые будят приложение и воспроизводят аудио. В заголовке таких пушей указывается тип pushtotalk, а их приоритет равен 10.
curl -v \
-d '{"activeSpeaker":"The name of the active speaker"}' \
-H "apns-push-type: pushtotalk" \
-Н "apns-topic: <The app bundle id>.voip-ptt" \
-Н "apns-priority: 10" \
-H "apns-expiration: 0" \
--http2 \
--cert <The certificate key name>.pem \
<https://api.sandbox.push.apple.com/3/device/><token>
Метод, который обрабатывает пуши:
func incomingPushResult(channelManager: PTChannelManager,
channelUUID: UUID,
pushPayload: [String: Any]) -> PTPushResult {
guard let activeSpeaker = pushPayload["activeSpeaker"] as? String else {
return .leaveChannel
}
let activeSpeakerImage = UIImage(named: "someImage")
let participant = PTParticipant(name: activeSpeaker,
image: activeSpeakerImage)
return .activeRemoteParticipant(participant)
}
func channelManager(_ channelManager: PTChannelManager,
didActivate audioSession: AVAudioSession) {
// some code
}
Этот метод возвращает результаты обработки пуша: leaveChannel, который обозначает освобождение канала, и activeRemoteParticipant, который указывает, что канал занят.
Uses nearby interactions
Nerby Interactions – это специальный фреймворк, который позволяет приложению работать с другими девайсами через UWB.
Ultra Wideband (UWB) – это беспроводная технология передачи данных, которая использует широкий диапазон частот. В связи с этим она обладает высокой точностью в определении расстояний и положения объектов, включая устройства и телефоны.
UWB поддерживает чип U1, имеющийся в ряде устройств Apple. Технология открывает широкий спектр возможностей использования. Например, в игровой индустрии:
В 2022 Apple добавила возможность использования NearbyInteractions в Background с аксессуарами, заранее сопряжёнными через Bluetooth.
Так кратко можно охарактеризовать моды Background в iOS, механизм работы которых необходимо учитывать разработчику. Если у вас есть дополнения или вопросы, буду рад обсудить их в комментариях.
Автор: peshkovnd