[iOS] 사용자 알림(User Notifications) - (1) APNs - 1

Hyunndy·2023년 2월 26일
0

iOS-Push

목록 보기
1/4

🐸

안녕하세요 오늘은 2월 26일 일요일 주말..
오늘은 iOS 사용자 알림(User Notifications), 개발자들이 많이 부르기로는 Push에 대해 한 판 정리를 해보려고 합니다.

푸쉬 하면 떠오르는 APNs, NotificationCenter, FCM 등등이 있죠? 다 같이 보겠습니다.


iOS의 User Notifications Framework

앱 개발자는 사용자에게 다양한 알림을 보내고 싶어합니다.
주로 Alert, Sound, Badge, Silent 형식이 있는데요.

어떻게 보낼까요? 🤷🏼‍♀️

iOS 앱에서 사용자에게 알림을 보내는 방법은 2가지가 있습니다.

  • 서버에서 Push 알림을 생성하여 보내는 것
  • 앱 내에서 직접 알림을 생성하여 Push하는 것

서버에서 Push 알림을 생성하여 보내는 것

회사 서버에서 생성되며, Apple Push Notification Service(APNs) 를 통해 사용자의 기기로 전달됩니다.
이 방법은 앱을 사용하지 않을 때도 알림을 받을 수 있도록 하며, 알림을 받을 조건도 지정할 수 있습니다.

앱 내에서 직접 알림을 생성하여 Push 하는것

앱 내에서 알림 내용을 생성하고, 알림을 보내기 전 일정한 조건을 지정합니다.
예를 들어 특정 시간에 알림을 보내거나, 특정 장소에 도착했을 때 알림을 보내는 등의 조건이 있습니다.


간단한 앱말고는 거의 APNs를 사용해서 푸쉬알람을 보내겠죠? 그럼 APNs부터 보도록 합시다.

APNs

개요

원격 알림(Remote Notification, Push Notification)을 사용하면 앱이 실행 중 이지 않더라도 앱으로 적은 양의 데이터를 보낼 수 있습니다.
앱은 이런 Notification을 이용해 사용자에게 중요한 정보를 보낼 수 있습니다.
예를 들면 카톡을 사용하고 있지 않더라도 새 카톡이 오면 푸쉬가 뜨는 것 처럼요.

원격 알림 전송을 위해서는 몇 가지 중요한 구성요소가 필요합니다.

  • provider server라고 불리는 회사 서버(내가 회사면 내가 구축한 서버)
  • Apple Push Notification Service(APNs)
  • 사용자의 기기
  • 기기에 설치된 앱

APNs는 애플에서 개발한 플랫폼 알림 서비스 입니다.

Remote Notification의 작동 방식

1️⃣
Remote Notification은 회사 서버(Provider server)에서부터 시작 합니다.
Provider server에서

  • 어떤 Notiication을 보내고
  • 언제 보낼지

결정 합니다.

2️⃣
보낼 때가 오면, Provider server은 Notiication을 보내기 위해

  • Notification 데이터
  • 사용자의 디바이스의 unique Identifier(= device token)

을 포함한 요청(request)을 생성합니다.

3️⃣
그 다음 생성한 요청(request)을 APNs로 전달하여 Notification을 사용자의 기기에 전송합니다.

4️⃣
사용자의 기기에서 Notification을 수신하면 iOS가 사용자의 상호 작용(user Interaction)을 처리하고 앱으로 Notification을 전달합니다.


사용자의 기기에서 Notification을 처리하도록 App을 구성하고, Provider Server를 설정하고 구축하는것은 개발자의 몫입니다.
Apple은 Notification을 표시하는 것을 포함하여 중간에서 모든 것을 관리합니다.
또한 사용자의 기기에는 서버와 통신하고 필요한 정보를 제공할 수 있는 앱이 있어야 합니다.

Notification을 위한 Custom 인프라구조 만들기

Remote Notification 서버를 구축하는 것은 몇 가지 핵심 작업이 존재합니다.
회사의 인프라에 따라 작업을 구현하는 방법이 다르므로 적절한 기술을 사용해야 합니다.

  • 사용자 기기에서 실행되는 APNs로 부터 Device Token을 수신하고, 이 Token을 사용자 계정에 연결하는 코드를 작성해야 합니다. (Registering yout app with APNs)

  • 사용자에게 Notification을 보낼 시기를 결정하고, Notification payload를 생성하는 코드를 작성해야 합니다. (Generating a remote notification)

  • HTTP/2와 TLS를 사용해 APNs에 대한 연결을 관리해야 합니다. (Sending Notification Requests to APNs)

  • Notification payload를 포함하는 POST Request를 생성하는 코드 작성 및 해당 Request를 HTTP/2 연결로 보내야 합니다. (Sending Notification Requests to APNs)

  • token-based 인증을 위해 주기적으로 토큰을 재생성 해줘야 합니다. (Establishing a token-based connection to APNs)

APNs <-> Provider server의 trusted connection

APNs <-> Provider Server 간의 통신은 안전해야 하며, 이를 위해 Provider Server에 AAA Cerificate Service root 인증서를 등록해야 합니다.

Notification을 보내려면 Provider 서버는 HTTP2나 TLS를 사용하여 APNs와

  • Token-based 신뢰 관계
  • Certificate-based 신뢰 관계
    를 쌓아야 합니다.

회사에 맞게 알맞은걸 쓰도록 합니다~!

Understand what APNs Provides

APNs는 그럼.. 서버에서 알림을 받고! 디바이스에 그걸 띄워주고! 사용자의 인터렉션 관리! 해주는 것 말고 정확히 뭘해줄까요?

  • APNs는 사용자의 기기에 대한 accredited(공인된), encrypred(암호화된), persistent(지속적인) IP 연결을 관리합니다.
  • APNs는 오프라인(네트워크 비활성화) 기기를 위해 Notification을 저장할 수 있습니다. APNs는 기기가 다시 온라인 상태가 되면 저장된 Notification을 전달합니다.
  • APNs가 즉시 Notification을 전달하지 못하는 경우, (디바이스 배터리나 오프라인인 경우에) APNs는 동일한 번들 ID에 대한 Notification을 하나로 통합할 수 있습니다.

등록만해주면 정말 별걸 다해주는군요!

그럼 이제 위에서 넘어간 APNs를 위한 Custom InfraStructure에 관한 내용을 보도록 하겠습니다.


1️⃣ APNs에 앱 등록하기

개요

APNs는 디바이스의 주소를 알아야 Notification을 전송할 수 있습니다.
이 주소는 디바이스와 앱 모두에 고유한 device token의 형태를 갖습니다.

App을 launch 할 때, App은 APNs와 통신하여 device token을 받은 후, 해당 토큰을 provider server로 전달합니다.
그럼 Provider Server는 해당 device token을 Notification과 함께 전송합니다.

👩‍💻 Notification을 위한 Device Token을 APNs에서 전달 받는 거였군요!
여기서 Device Token은 해당 디바이스에서의 앱의 주소 역할을 합니다.

Push Notifications 활성화 하기

우선 Xcode 프로젝트에서 PushNotification을 활성화 해야 합니다.

XCode -> Target 에서 선택 -> Singing & Capablitities -> (+) Push Notificiations

추가했는데.. 엥 이게다야? 해서 검색해보니.. 이런 가이드가 있네요 Enable Push Notificaitons

XCode에서 Push Notification을 키면,
XCode가 Project 및 App ID에서 APNs 권한을 추가한다네요.
iOS에서는 앱에 APS 환경 권한이 주어진다고 합니다.

👩‍💻 APS 환경 권한이란..?
Push Notification을 위한 환경입니다.
개발 환경, Production 환경에서의 APNs를 구분짓기 위한 것 같네요.

XCode에서 프로젝트 및 App ID에 APNs 권한을 추가한다고 해서
제 Developer 계정 > 지금 테스트하고 있는 App의 App ID Configuration으로 가보니...

APNs가 활성화 되어있네요.

앱을 등록하고 앱의 Device Token을 검색하세요

앱을 APNs에 등록하고 해당 디바이스에서의 앱의 주소 역할을 하는 globally unique한 device token을 받아야 합니다.
Apple에서 제공하는 API로 할 수 있습니다.

iOS에서는 device token을 요청하기 위해 UIApplication.shared.registerForRemoteNotifications()을 호출해야 합니다.
등록에 성공해서 토큰을 받으면 AppDelegate의 application(_:didRegisterForRemoteNotificationsWithDeviceToken:) 함수가 불립니다.

실패할 경우도 대비해서 application(_:didFailToRegisterForRemoteNotificationsWithError:)도 준비해야 합니다.

실패할 경우는.. 네트워크에 연결되어있지 않거나, App의 code-signing이 잘못되어있거나 APNs 서버가 동작하지 않거나 이런 경우가 있습니다.

코드 블럭으로 보면 다음과 같습니다.

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // 여기선 launch 시에 했지만, launch가 끝나고 사용자에게 알림 허용 등의 권한 체크를 먼저 하고 수락하면 진행하는게 좀 더 깔끔합니다.
        UIApplication.shared.registerForRemoteNotifications()
        
        return true
    }
    
    // MARK: APNs
extension AppDelegate {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("APNs으로 부터 받은 디바이스 토큰:" + deviceToken.description)
    }
    
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        print("APNs 등록 및 디바이스 토큰 받기 실패:" + error.localizedDescription)
    }
}

2️⃣ Remote Notification 생성하기

개요

Remote Notification은 JSON Payload 형식으로 사용자에게 중요한 정보를 전달합니다.
Payload에는

  • 유저 상호작용 유형(Alert, Sound, Badge, Silent)
  • 앱이 Notification에 respond 하기 위해 필요한 사용자 정의 데이터

가 포함됩니다.

여기서 alert,sound,badge는 기기에서 사용자가 볼 수 있게 표시해주는 반면,
silent notification은 사용자에게 표시되지 않고 기기에만 전달 됩니다.
silent notification을 사용하여 백그라운드 앱 업데이트 작업 등을 처리할 수 있습니다.

👩‍💻 Payload란?
전송되는 데이터를 의미합니다.
메타, 헤더데이터 같은 부가적인 것을 제외하고 딱 전달하고싶은 데이터입니다.

기본 Remote Notification Payload는 Apple이 정의한 key와 사용자 정의 value를 포함합니다.
물론 사용자 정의 key-value도 넣을 수 있습니다.

APNs는 Payload의 크기가 4KB(= 4096bytes)를 초과하면 알림을 보내는 것을 거부합니다.
(VoIP 알림은 5KB)

JSON Payload 생성

Remote Notification의 Playload를 지정하려면 JSON Dictionary를 사용해야 합니다.
Dictionary 안에는 한 개 이상의 Apple-defined Key를 포함하는 aps Key의 Dictionary 값을 포함해야 합니다.

시스템에서 Notification을 표시하도록 하는 alert, sound, badge 여부 그리고
Background에서 Notification을 처리하도록 지시할수 도 있습니다.

(👩‍💻 이건 저번 글에서 봤던 BackgroundMode에서 remote Notifications를 켜서 appDelegate에서 받았던 그걸 말하는것 같죠?)

{
   "aps" : {
      "alert" : {
         "title" : "Game Request",
         "subtitle" : "Five Card Draw",
         "body" : "Bob wants to play poker"
      },
      "category" : "GAME_INVITATION"
   },
   "gameID" : "12345678"
}

이렇게 Apple-defined key-value를 갖는 Dictionary는 aps라는 key의 value 안에 넣어야 합니다.

Apple-defined key 외에 추가로 사용자 정의 key를 추가할 수 있습니다.
사용자 정의 key는 반드시! Dictionary, String, array, number, boolean 같은 primitive 유형의 value가 있어야합니다.
사용자 정의 key를 aps 딕셔너리에 넣으면 APNs가 무시하기 때문에 따로 넣어야 합니다.
사용자 정의 key는 APNs로 부터 App에 전달되는 UNNnotificationContent 객체의 userInfo Dictionary에서 접근할 수 있습니다.

보통 사용자 정의 key는 나의 swift 코드가 Notification을 처리하는데 도움을 주기 위해 사용됩니다.
예를 들어, 이거 눌렀을 때 어디로 이동해라. 했을 때 필요한 id 값을 받을 수 있겠죠

👩‍💻 정리하자면 APNs JSON Payload에는 aps 딕셔너리, custom-key 가 들어갑니다.

예시를 볼까요?

{
   "aps" : {
      "alert" : {
         "title" : "Game Request",
         "subtitle" : "Five Card Draw",
         "body" : "Bob wants to play poker"
      },
      "category" : "GAME_INVITATION"
   },
   "gameID" : "12345678"
}

이 Notification payload는 게임에 초대하는 alert을 띄웁니다.
여기서 보면 payload의 aps 딕셔너리에 포함된 apple-defined key는 alert, category고
사용자 정의 키는 gameID네요.

"category" key가 예전에 등록된 UNNotificationCategory 객체를 식별하는 경우 시스템은 alert에 액션 버튼을 추가합니다.
예를 들어, 여기서의 category에는 게임을 즉시 시작하는 play action이 포함되어 있습니다.
사용자 정의 key인 "gameID"는 앱이 게임 초대를 검색할 수 있는데 사용할 수 있는 식별자를 포함합니다.

"액션" 이라는 개념이 등장하는데.. 이건 밑에서 보고 다음 예시 보겠습니다.

{
   "aps" : {
      "badge" : 9,
      "sound" : "bingbong.aiff"
   },
   "messageID" : "ABCDEFGHIJ"
}

이 Notification payload는 앱 아이콘에 뱃지를 띄우면서 사운드를 재생합니다.
sound에 지정된 파일은 유저의 기기 안 App의 Bundle Container나 Library/Sounds Container에 이미 로드되어 있어야 합니다.

이런 custom-key는 Notification 객체의 userInfo 딕셔너리에서 접근할 수 있습니다.
우선 Notification으로 인해 앱이 실행되면, userNotificationCenter(_:didReceive:withCompletionHandler:) 메서드가 호출됩니다.

    // 사용자가 앱 내부나 앱 외부에서 알림을 클릭하여 앱을 실행할 때 호출된다.
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
        
        // Remote Notification received
        let userInfo = response.notification.request.content.userInfo
        
        // custom-key 뽑기
        if let gameId = userInfo["gameId"] as? String {
            print("gameId = \(gameId)")
        }
        
        if let messageId = userInfo["messageId"] as? String {
            print("messageId = \(messageId)")
        }
    }

첫번째 예시라면 gameId, 두번째 예시라면 messageId가 나오겠죠!

👩‍💻 Notification의 Payload엔 절대 사용자의 민감한 정보를 넣으면 안됩니다.
만약 꼭 넣어야 한다면 payload에 추가하기전에 암호화 해야 합니다.
사용자의 기기에서 데이터를 복호화 하기 위해 notification service app extension을 사용할 수 있습니다.

Payload Key

KeyValue typeDescription
alertDictionary(or String)Dictionary안에 content를 만들 수 있다.(title 등)
badgeNumber앱 아이콘 뱃지의 숫자를 세팅한다. 0넣으면 현재 뱃지 삭제됨
soundStringApp의 Bundle이나 Library/Sounds Container에 있는 사운드 파일 재생한다.
default 넣으면 시스템 사운드가 나온다.
긴급 알람 울리려면 dictionary 로 넣으면됨.
thread-idStringiOS14부터 제공된 기능으로, Notification Grouping을 위해 사용된다.
하나의 앱에 수신되는 여러 Notification이 있을 경우,
이걸 지정하면 해당 알림들이 하나의 그룹으로 묶여서 사용자가 놓치지 않도록 돕는데 도움이된다.
categoryStringNotification 카테고리를 식별하는 데 사용되는 문자열 입니다.
사용자가 알림을 탭했을 때 실행할 동작과 관련있습니다.
UNNotificationCategory 객체를 사용해서 Notification 카테고리를 정의&등록할 수 있습니다.
Notification 카테고리를 사용하면 사용자가 알림을 탭할 때 미리 정의된 동작을 수행할 수 있습니다.
content-availableNumber백그라운드 앱 업데이트를 위한 silent remote notification에서 사용됩니다.
이 값을 1로 하면 silent noti를 보낼 수 있습니다.
이러면 기기가 알림을 수신하면 백그라운드에서 앱을 실행시키고, application(_:didReceiveRemoteNotification:fetchCompletionHandler:) 을 호출합니다.
여기서 데이터 업데이트를 수행할 수 있습니다.

👩‍💻 Background Task랑 유사한데 이게 훨씬 좋아보이네요. 서버에서 슉 쏘면 Delegate 에서 슉 하면 수행하면 되니까요. Background Task에서도 iOS13 이상에서는 Noti 쓰고, 12 이하에서는 Background Task를 사용하라고 적었던게 생각나네요.

Localize your alert Message

Remote Notification의 content를 로컬라이징 하는 방법이 2개 있습니다.

  • playload에 직접 localized String을 포함시킨다.
  • app Bunlde에 localized message를 추가하고, 시스템에 어떤 문자열을 표시할지 선택하게 하는 것

첫번째 방법은 사용자가 선택한 언어가 무엇인지 알아야 하는 단점이 있습니다.
주로 NSLocale의 preferredLanguages 속성을 검사하여 사용자의 선호 언어를 갖고와서 서버에 전달합니다.

두번째 방법은 Localizable.strings 파일에 문자열을 저장하고, title-loc-key, subtitle-loc-keyloc-key 페이로드 키를 사용하여 어떤 문자열을 표시할지 지정할 수 있습니다.


마무리

여기까지

  • APNs의 개념
  • APNs 사용을 위한 XCode Push Notifications 세팅, Device Token 받고 등록
  • Remote Notification을 위한 Json Payload 구성(aps, custom key)

를 봤는데요.

더 정리하면 너무 길어질 것 같아 글을 나누겠습니다.
(너무 힘들어요 머리가 터질 것 같네요 🥹)

더 정리해야할 개념으로는

  • HTTP/2와 TLS를 사용해 APNs에 대한 연결 관리
  • Notification payload를 포함하는 POST Request를 생성하는 코드 작성 및 해당 Request를 HTTP2/TLS 연결로 APNs에 보내기
  • APNs <-> Provider server 간의 신뢰 연결을 위한 token-based, certificate-based 인증
  • 위 payload 예시에서 봤던 "Action"
  • payload에서 데이터를 암호화/복호화 할 때 나왔던 notification service app extension
  • (가장 중요한) APNs 를 받을 때 호출되는 UNUserNotificationCenterDelegate.

이 있겠네요.

profile
https://hyunndyblog.tistory.com/163 티스토리에서 이사 중

0개의 댓글