Http-запрос от действия push-уведомления iOS, когда приложение работает в фоновом режиме

#ios #swift #apple-push-notifications #background-process

#iOS #swift #apple -push-уведомления #фоновый процесс

Вопрос:

Мое приложение iOS может получать push-уведомления с помощью кнопок действий:

push-уведомления с кнопками действий

Нажатие кнопки отправляет post http-запрос через URLSession и data task. Он хорошо работает, когда приложение приостановлено или не запущено. Но это не работает, когда приложение находится в фоновом режиме, например, когда пользователь только что провел пальцем снизу вверх (или нажал кнопку «Домой»). Push-действие обрабатывает, но http-запрос не отправляет.

Из консоли os_logs моего iPhone я вижу, что URLSession не может установить соединение в фоновом режиме и выдает код ошибки =-1005 «Сетевое соединение было потеряно». Это может произойти, как только я убью приложение. Я пробовал разные конфигурации сеанса url, фоновый сеанс url, фоновые задачи, но ничего не работает. Единственное решение, которое я нашел, — это использовать exit(0) applicationDidEnterBackground метод from , но крайне не рекомендуется программно уничтожать приложение.

Вопрос: как я могу установить http-соединение из действия push-уведомления, когда приложение работает в фоновом режиме?

Мой код:

 class NotificationService: NSObject, UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        // notification processing if app is in foreground
        ...
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let requestId = String(describing: response.notification.request.content.userInfo["requestId"] ?? "")
        switch response.actionIdentifier {
        case "CONFIRM_ACTION":
            confirmAuthenticationRequest(requestId: requestId, isConfirmed: true)
        case "REJECT_ACTION":
            confirmAuthenticationRequest(requestId: requestId, isConfirmed: false)
        default:
            // on notification tap
            ...
        }
        completionHandler()
    }

private func confirmAuthenticationRequest(requestId: String, isConfirmed: Bool) {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        let json: [String: Any] = [
            "requestId": requestId,
            "deny": !isConfirmed
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: json)
        request.addValue("application/json", forHTTPHeaderField: "content-type")
        
        let dataTask = urlSession.dataTask(with: request)
        dataTask.resume()
    }
}
  

Ответ №1:

Наконец я нашел ответ. Причиной нестабильной работы действий push-уведомлений является неправильный способ вызова completionHandler userNotificationCenterDidReceiveResponse метода from. При выполнении асинхронной операции, такой как http-запрос, вы должны вызывать completionHandler сразу после завершения операции (не после вызова операции) и из основного потока. Вот рабочий пример:

 class NotificationService: NSObject, UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        let requestId = String(describing: response.notification.request.content.userInfo["requestId"] ?? "")
        switch response.actionIdentifier {
        case "CONFIRM_ACTION":
            confirmAuthenticationRequest(requestId: requestId, isConfirmed: true, completion: completionHandler)
        case "REJECT_ACTION":
            confirmAuthenticationRequest(requestId: requestId, isConfirmed: false, completion: completionHandler)
        default:
            // on notification tap
            ...
            completionHandler()
        }
    }

private func confirmAuthenticationRequest(requestId: String, isConfirmed: Bool, completion: @escaping () -> Void) {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        let json: [String: Any] = [
            "requestId": requestId,
            "deny": !isConfirmed
        ]
        request.httpBody = try? JSONSerialization.data(withJSONObject: json)
        request.addValue("application/json", forHTTPHeaderField: "content-type")
        
        let dataTask = urlSession.dataTask(with: request) { _, _, _ in
            DispatchQueue.main.async {
                completion()
            }
        }
        dataTask.resume()
    }
}