Как iOS-приложение обрабатывает входящий Push Notification в разных состояниях?

«Как iOS-приложение обрабатывает входящий Push Notification в разных состояниях?» — вопрос из категории Сети, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Обработка зависит от состояния приложения в момент доставки уведомления (APNs payload).

1. Приложение запущено и активно (Foreground):

  • Уведомление не показывается в системном центре уведомлений по умолчанию.
  • Вызывается метод userNotificationCenter(_:willPresent:withCompletionHandler:) делегата UNUserNotificationCenter.
  • Решение на стороне приложения: Вы можете решить, показать ли алерт, воспроизвести звук или обработать уведомление тихо.

2. Приложение запущено в фоне (Background) или свернуто (Suspended):

  • С payload content-available: 1 (Silent Push):
    • Система пробуждает приложение в фоне.
    • Вызывается application(_:didReceiveRemoteNotification:fetchCompletionHandler:) в AppDelegate.
    • У вас есть ~30 секунд на выполнение фоновой задачи (обновление данных, загрузка контента).
    • Вы обязаны вызвать completionHandler с результатом (.newData, .noData, .failed).
  • Без content-available или с ним, но с alert, sound, badge:
    • Система показывает уведомление пользователю.
    • При тапе по уведомлению приложение переходит в активное состояние, и вызывается userNotificationCenter(_:didReceive:withCompletionHandler:).

3. Приложение не запущено (Not Running / Terminated):

  • Система показывает уведомление.
  • При тапе по уведомлению приложение запускается заново.
  • Полезная нагрузка (payload) уведомления передается в параметре launchOptions метода application(_:didFinishLaunchingWithOptions:) по ключу .remoteNotification.

Пример обработки Silent Push в фоне:

func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    // 1. Проверяем флаг content-available
    guard let aps = userInfo["aps"] as? [String: Any],
          aps["content-available"] as? Int == 1 else {
        // Это не silent push, возможно, просто показ уведомления
        completionHandler(.noData)
        return
    }
    // 2. Выполняем фоновую задачу (например, загрузку данных)
    fetchNewData { success in
        // 3. Обязательно сообщаем системе о результате
        completionHandler(success ? .newData : .failed)
    }
}