Они же пуши, remote notifications, push notifications, pushes.
Краткая статья о пуш-уведомлениях и их поддержки на сервере и в приложении.
Изначально приложения не умели работать в фоне, в дальнейшем Apple добавляла и добавляет разные режимы когда приложение может работать в фоне, в iOS нет возможности реализовать полноценных фоновых сервисов.
В 2009 году Apple запустили службу push-уведомлений (APNs, Apple Push Notification service), позволяющая с серверов (провайдеров) разработчиков приложений отправлять уведомления на устройства пользователей через APNs, в свою очередь iOS берет на себя получение уведомлений от APNs и их отображение.
Глобально програмнные интерфейсы службы и работа с пушами из приложения не менялись. Apple только добавляет новый функционал, кардинально не изменяя базовый, в отличии от других платформ, таких как Android, где уже сменилось несколько несовместимых поколений механизмов push-умедомлений.
Главная задача push-уведомлений всячески привлекать внимание пользователя к приложению или сервису, существует два основных сценария:
- уведомить о каких-либо конкретных действиях связанных с жизненным циклом прилжения или сервиса: новый контент, лайки, посты, списание денег в банке,
- массовая рассылка по базе пользователей.
Подробное описание:
В механизме push-умедомлений существуют 4 агента:
- APNs, служба-уведомлений
- Provider (провайдер)
- iOS-устройство
- Приложение
APNs, служба push-уведомлений, отвечающая за прием уведомлений от провайдеров, короткосрочное хранение и отправку на iOS-устройства.
Provider (провайдер) — сервер разработчика приложений, отвечающий за прием push-токенов (device token) от приложений, инициацию отправки умедомлений по каким-либо внутренним событиям, отправка умедомлений в APNs.
iOS-устройство отвечает за генерацию уникального push-токена, приемку умедомлений от APNs, отображение их в системе, и передаче умедомлений приложению.
Приложение получает от iOS-устройства уникальный для приложения и устройства push-токен далее который передает провайдеру, так же обрабатывает пришедшии push-уведомления от iOS.
Push-токен или device token это уникальный токен идентифицирующий связку приложения и устройствя на котором оно установлено, запрашивается каждый раз на старте приложения, чаще всего не изменяется от запроса к запросу, но Apple не гарантирует этого, существуют разные случаи, когда этот токен может быть обновлен. Push-токен служит для индентификации конкретного получателя уведомлений.
Push-токен отвечает за доставку уведомлений на устройство, он не отвечает за отображение их на устройстве.
Для отображения уведомлений на устройстве приложению необходимо получить разрешения на отображения уведомлений от пользователя, в дальнейшем iOS учитывая пользовательские разрешения и текущии найстройки устройства (режим не беспокоить, выключенный звук и т.д.) решает как отобразить уведомления.
Push-уведомление представляет собой JSON-payload состоящий из системной информации (aps), регулирующей отображение и поведение уведомления на устройстве и информации переданной провайдером. Payload ограничен размеров в 4 Кб. Дополнительное поведение регулируется через загаловки HTTP/2 запроса на отправку уведомления от провайдера.
Пример JSON-payload:
{
"aps" : {
"alert" : {
"title" : "Game Request",
"body" : "Bob wants to play poker",
"action-loc-key" : "PLAY"
},
"badge" : 5
},
"acme1" : "bar",
"acme2" : [ "bang", "whiz" ]
}Информация переданная от провайдера, может быть обработана в приложении при получении пуша приложением.
Немного про отображение:
https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/notifications/
В зависимости от конфигурации JSON-payload и заголовков запроса отправки пуша, отображение и поведение уведомлений может различаться.
Изначальная задача уведомления отобразится на iOS-устройстве, по этому большая часть конфигурации JSON-payload отвечает за отображение пуша: заголовок, текст, звук, бейдж на иконке приложения.
В дальнейшем Apple развивал функциональность уведомлений и отображения их на устройстве.
Появился Notification Center в котором складируются уведомления от всех приложений, пуши могут собираться в группы в рамках одного приложения, предыдущее уведомления может быть заменено новым.
Приложение может изменять содержимое уведомления до его отображения или отображать кастомный интерфейс для них.
Приложения может определять категории быстрых действий уведомления, в свою очередь iOS отображает эти действия в пушах, например как кнопки или поле ввода для быстрого ответа на сообщения.
Приложение может не запрашивать разрешения на отображение уведомлений, а запросить ненавязчивые уведомления, которые будут только отображены в Notification Center ибо люди часто отказывают в разрешении уведомлять их, что позволяет все равно привлекать внимание к приложению.
Пуши могут быть фоновыми, с отображением контента или без, в любом случае iOS попытается разбудить приложение и в фоне позволить ему немного порабоать, через этот механизм раньше можно было реализовать отчеты о доставке пушей.
Так же пуши бывают VoIP, служат только для инициации VoIP звонка.
Все разнообразие отображения и поведения уведомлений конфигурируется как написано выше через JSON-payload и заголовки запроса отправки пуша провайдером в свою очередь финальное поведение является пересечением хотелок провайдера и настроек iOS-устройства (разрешения и текущая конфигурация).
Базовая реализация push-уведомлений — получение push-токена приложением и передача его провайдеру, которую лучше производить на каждом старте приложения. Так как push-токен не гарантирует отображение пушей, хорошей практикой является передача текущих разрешений на отображения провайдеру.
Так же при массовой рассылке полезно знать больше о пользователя для качественной сегментации и комфортной доставки пушей.
Для сегментации:
- модель устройства
- версия iOS
- локаль
- гео-позиция
- сотовый оператор
- тип аппстора
Для комфорта доставки:
- часовой пояс
- язык интерфейса iOS и приложения
- типы полученных разрешений отображения уведомлений
Реализация на сервере может сильно варьироваться. Push-токены могут быть анонимными либо привязаны к пользователю, устройству или сессии. Информация об устройстве может быть переданна ранее или склеиваться из сервисов аналитики.
Пользователь в сервисе может иметь несколько устройств, в свою очередь каждое устройство может иметь несколько push-токенов. В разное время жизни приложения на устройстве в нем могут быть зарегистрированы разные пользователи. Приложение внезапно может иметь несколько рабочих push-токенов, токены в принципе склоны засорять базу сервера.
По этому на сервере следует выработать политику работы с push-токенами, нужно подчищать устаревшие токены, отслеживать связку push-токен и устройства для пользователя, ни в коем случае нельзя допускать чтобы несколько пользователей делили один и тот же push-токен.
Устройство обычно отслеживаем несколькими способами:
- Идентификатор устройства (Device ID, idfv) уникальный идентификатор устройства, общий для приложений конкретного разработчика, при удалении всех приложений разработчика — сбрасывается, имеет проблему получения, иногда его невозможно моментально запросить у iOS.
- Идентификатор установки (Install ID) — после установки приложения при первом запуске генерируется случайная строка (например UUID) и сохраняется локально.
- Случайный идентификатор сохраненный в Keychain, информация в Keychain может сохранятся в iCloud, что позволяет отслеживать пользователя между переустановками приложения.
- Сессия авторизации пользователя — push-token привязывается к сессии авторизации в сервисе
Пример отправляемых данных о push-токене.
struct PushToken: Codable {
let token: String
let settings: PushSettings
let device: Device
}
struct PushSettings: Codable {
enum AuthorizationStatus: String, Codable {
case notDetermined
case denied
case authorized
case provisional
case unknown
}
enum ShowPreviewsSetting: String, Codable {
case always
case whenAuthenticated
case never
case unknown
}
enum Setting: String, Codable {
case notSupported
case disabled
case enabled
case unknown
}
enum AlertStyle: String, Codable {
case none
case banner
case alert
case unknown
}
let authorizationStatus: AuthorizationStatus
let soundSetting: Setting
let badgeSetting: Setting
let alertSetting: Setting
let notificationCenterSetting: Setting
let lockScreenSetting: Setting
let carPlaySetting: Setting
let alertStyle: AlertStyle
let showPreviewsSetting: ShowPreviewsSetting
let criticalAlertSetting: Setting
let providesAppNotificationSettings: Bool
let announcementSetting: Setting
}
struct Device: Codable {
let appVersion: String?
let installId: String
let keychainId: String?
let vendorId: String?
let advertisingId: String?
let deviceModel: String
let systemName: String
let systemVersion: String
let locale: String
let language: String?
let languages: [String]
let region: String?
let timeZoneId: String
let timeZoneOffset: Int
}Так же полезно отслеживать жизненный цикл push-уведомлений, в особенности для массовых рассылок. Для отслеживания необходимо дополнительная идентификация уведомлений либо попробовать использовать один из системных идентификатор, что не всегда удобно.
Для начала отслеживаем открытие уведомления на устройстве. При нажатии на уведомление iOS передает управление приложению, приложение в этот момент может отправить на сервер запрос обозначить уведомление прочитанным, запрос можно делать без авторизации.
Доставка уведомлений — факт того что уведомление было доставлено и отображено на устройстве, отслеживать можно несколькими способами:
- Помечать все уведомления как фоновые (
content-available), iOS разбудит приложение и даст возможность уведомить сервер, есть свои ньюансы, в документацииBackground Modesподробно расписано в каких случаях iOS так делать не будет, был первым механизмом отслеживания доставки, сейчас не является предпочтительным. - Через
Notification Service Extensionизначально сервис служит для изменения уведомления до его отображения, но в нем так же можно отслеживать доставку. Для его активации все уведомления следуют помечать как изменяемые (mutable-content), iOS дает больше гарантий его активации чем первый способ. - Приложение видит уведомления, которые находятся в Notification Center и все эти уведомления может помечать как доставленные, но пользователь может почистить Notification Center раньше чем будет открыто приложение.
Второй способ является самый лучший, достаточно сделать запрос к серверу с идентификатором пуша, имеет смысл делать без авторизации, потому что extension работает как отдельное небольшое приложение, вызывающиеся iOS на короткий срок по необходимости, по этому необходим дополнительный механизм синхронизации информации из основного приложение с extension (например API-token).
Итого взаимодействие приложения с сервером можно свести к трем основным шагам, убывающим по мере важности:
- передать push-токен и дополнительную информацию серверу
- отследить открытие уведомления
- уведомить сервер о доставки пуша