관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] iOS remote push notification (서버 푸시) - Braze Appboy SDK 연동을 통한 이해 본문

iOS 응용 (swift)

[iOS - swift] iOS remote push notification (서버 푸시) - Braze Appboy SDK 연동을 통한 이해

jake-kim 2021. 8. 3. 23:37

iOS 푸시 원리

  • 앱 > 애플 서버에 토큰 요청 > 앱에서 토큰 수신 > push 서버에 토큰 등록 > push서버에서 가공된 내용 + token 앱에서 수신 > 애플서버에 다시 전송 > 애플서버에서 앱으로 최종 푸시 전송
    • 앱에서 push를 “p8 or .p12 certificate 파일” 인증서와 함께 애플서버에 HTTP로 토큰 달라고 요청
    • 애플 서버는 인증서, 프로파일을 받고 인증 후 토큰 응답
    • 받은 토큰값과 메시지를 앱에서 push 서버로 POST방식으로 요청
    • push서버에서 POST요청을 받아 내용을 가공 > 애플 서버에 요청 (push서버에서는 APNs키가 등록되어 있어야함)
    • 애플 서버는 토큰을 확인하고 토큰의 주인에 해당하는 아이폰 기기에 push 전송

앱 가동

  • APNs(Apple Push Notification service)에 사용자 기기를 등록 요청
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        center.requestAuthorization(options: options) { granted, _ in

            /// 사용자가 푸시 권한에 관해 선택한 결과를 Braze에 전달
            Appboy.sharedInstance()?.pushAuthorization(fromUserNotificationCenter: granted)
            guard granted else { return }
            DispatchQueue.main.async {

                /// APNs에서 push를 받기위해 등록 요청
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
  • APNs로부터 token 수신 (주의: simulator에서는 해당 메소드 실행되지 않는것을 주의)
    /// APNs에 해당 디바이스가 등록이 완료된 경우,  Braze에 토큰 등록
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Appboy.sharedInstance()?.registerDeviceToken(deviceToken)
    }

애플로부터 최종 Push 알림을 받은 경우

  • push 서버에 로깅을 위해 전송
    /// push 알림을 받았을때 Braze에 전달하여 push 분석과 로깅하는데 사용
    func application(_ application: UIApplication,
                     didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        Appboy.sharedInstance()?.register(application,
                                          didReceiveRemoteNotification: userInfo,
                                          fetchCompletionHandler: completionHandler)
    }

Push가 온 경우 처리

  • UNUserNotificationCenterDelegate 사용
extension AppDelegate: UNUserNotificationCenterDelegate {

    /// foreground에 있는 동안의 push 표출 옵션
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .badge, .sound])
    }

    /// push를 탭한 경우 처리
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        // TODO: Deeplink 처리

        /// 사용자가 push에 관해 상호작용한 후 알림 응답을 braze로 전달
        Appboy.sharedInstance()?.userNotificationCenter(center,
                                                        didReceive: response,
                                                        withCompletionHandler: completionHandler)
    }
}

* 전체 AppDelegate 코드

import UIKit
import AppboyKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

        /// Braze 웹사이트에서 등록한 key값을 이용하여 Braze SDK 연동
        Appboy.start(withApiKey: ServiceConfiguration.appboyAPIKey,
                     in: application,
                     withLaunchOptions: launchOptions,
                     withAppboyOptions: [ABKSessionTimeoutKey: 60])

        // TODO: 해당 코드는 테스트 용도이므로 user id를 AccessToken으로 교체가 되면 해당 코드 삭제
        /// Braze user id 등록
        let uuid: String
        if let storedUUID = UserDefaultsManager.AppConfiguration.uuid {
            uuid = storedUUID
        } else {
            uuid = UUID().uuidString
            UserDefaultsManager.AppConfiguration.uuid = uuid
        }
        Log.debug("UUID = \(uuid)")
        Appboy.sharedInstance()?.changeUser(uuid)

        registerRemoteNotification()

        return true
    }

    // MARK: - Notification

    /// 사용자의 기기를 APNs(Apple Push Notification service)에 등록
    private func registerRemoteNotification() {
        let center = UNUserNotificationCenter.current()
        center.delegate = self
        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        center.requestAuthorization(options: options) { granted, _ in

            /// 사용자가 푸시 권한에 관해 선택한 결과를 Braze에 전달
            Appboy.sharedInstance()?.pushAuthorization(fromUserNotificationCenter: granted)
            guard granted else { return }
            DispatchQueue.main.async {

                /// APNs에서 push를 받기위해 등록 요청
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }

    /// APNs에 해당 디바이스가 등록이 완료된 경우,  Braze에 토큰 등록
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Appboy.sharedInstance()?.registerDeviceToken(deviceToken)
    }

    /// push 알림을 받았을때 Braze에 전달하여 push 분석과 로깅하는데 사용
    func application(_ application: UIApplication,
                     didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        Appboy.sharedInstance()?.register(application,
                                          didReceiveRemoteNotification: userInfo,
                                          fetchCompletionHandler: completionHandler)
    }

    // MARK: UISceneSession Lifecycle

    func application(_ application: UIApplication,
                     configurationForConnecting connectingSceneSession: UISceneSession,
                     options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }

    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
    }
}

extension AppDelegate: UNUserNotificationCenterDelegate {

    /// foreground에 있는 동안의 push 표출 옵션
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                willPresent notification: UNNotification,
                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.alert, .badge, .sound])
    }

    /// push를 탭한 경우 처리
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {

        /// 사용자가 push에 관해 상호작용한 후 알림 응답을 braze로 전달
        Appboy.sharedInstance()?.userNotificationCenter(center,
                                                        didReceive: response,
                                                        withCompletionHandler: completionHandler)
    }
}

cf) deeplink를 처리할 때 url을 수신하는 델리게이트

  • SceneDelegate.swit 파일이 존재하면 scene(_:opeenURLContexts:)에서 url 수신
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    guard let url = URLContexts.first?.url else { return }
    urlHandler?.handle(url)
}
  • SceneDelegate.swift파일이 없고 AppDelegate.swift파일만 존재한다면, application(_:open:options:) -> Bool 메소드 이용
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    urlHandler?.handle(url)
    return true
}
Comments