[iOS] Notification Push 사용 - 3 : Firebase 연동 (FCM)

Sean·2023년 5월 19일
0

iOS 앱 기능

목록 보기
3/4

누군가에게 알려주기 보다는 나 스스로 정리 하며 언젠가 다시 사용할 때를 대비하는 글을 작성할것이다.

참고자료 : Firebase - 클라우드 메시징 공식문서

시작

시작글

  • 시작하기 앞서 전에 작성했던 APNs 설정에 대해서 보지 않고 진행을 하게 된다면 몇몇 부분에서 맞지 않을 가능성이 있기에 해당 부분으로 진행을 하시고 이 포스트를 보시는걸 추천한다.

분석

저번에 만들었던 프로젝트에 Firebase의 클라우드 메시징 기능을 활용한 Push를 직접 보내볼 예정이다.

FCM 설정: 1. Firebase 설정

이 부분에 대해서는 다른 인터넷에 너무 자세한 자료들이 설명이 되어있기에 현재 프로젝트에서 어떠한 설정들만 사용을 하고 있는지만 간단하게 설명한다.

1. App 내부 + Firebase 기초 설정

cocoaPod이나 SPM이나 상관 없이 Firebase를 설치해준다.
단순히 메시징만 테스트 할 생각이기에 메시징만 받아준다. (해당 설정은 SPM용)

구글 Firebase 에서 프로젝트를 만들때 애널리틱스는 사용하지 않을 것이기에 따로 설정을 하지는 않는다.

프로젝트 생성 후 iOS 앱을 추가해주고 .plist 복사 붙여넣기 후 초기화 코드 까지 잘 입력해준다.


2. Firebase 프로젝트 설정

프로젝트 내부에 인증서를 입력하여 Firebase의 설정을 마무리 한다.
p8 인증서 를 사용할 예정이므로 해당 인증서가 뭔지 모른다면 위에 시작에 적어둔 링크로 이동해서 만들어두길 바란다.

순서
1. 위의 1에서 만들었던 프로젝트 진입
2. 좌상단 프로젝트 개요 옆 톱니바퀴로 프로젝트 설정 진입
3. 클라우드 메시징 항목으로 이동
4. 스크롤로 하단 이동 후 Apple 앱 구성 섹션에서 APN 인증키 업로드

ID 정보 확인

  • key ID == AppleDeveloper의 Certificates, Identifiers & Profiles에서 Keys에서 사용하는 key를 누르면 나오는 창에서 확인 가능

  • Team ID == AppleDevloper 홈페이지에서 Account(계정)으로 진입 후 멤버십 세부 사항 을 누르면 확인 가능


FCM 설정: 2. 코드 작성

코드 작성의 모든 동작은 AppDelegate 에서 진행한다.

이 포스트에서는 Firebase 를 활용한 푸시를 보낼 예정이기에 import 부분은 다음과 같다.

import UIKit
import FirebaseCore
import FirebaseMessaging

이전 포스트에서 사용자의 허가를 구하는 코드를 작성했었는데 해당 부분에 몇가지의 코드를 더 추가한다.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        
	FirebaseApp.configure()
        
	Messaging.messaging().delegate = self
	Messaging.messaging().isAutoInitEnabled = true
        
	UNUserNotificationCenter.current().delegate = self
    let authOptions: UNAuthorizationOptions = [.alert, .badge,. sound]
    UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
    application.registerForRemoteNotifications()
    
    UIApplication.shared.registerForRemoteNotifications()
    
    return true
}

해당 코드를 하나하나 따져보면 아래 코드는 firebase 공유 인스턴스 구성 하는 부분과 등록 토큰 수신을 위해 메시지 delegate를 설정하는 역할을 한다.

FirebaseApp.configure()	
Messaging.messaging().delegate = self
Messaging.messaging().isAutoInitEnabled = true

앱이 시작될 때 또는 적절한 시점에 알림에 앱을 등록하는 역할을 한다.
(이전 포스트에서 구현 했음)

UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge,. sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions) { _, _ in }
application.registerForRemoteNotifications()

APNs에 기기 토큰을 요청하는 부분이다.

UIApplication.shared.registerForRemoteNotifications()

토큰과 관련된 동작을 하는 함수들이다.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
	let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02.2hX", $1)})
    print(deviceTokenString)
    
    let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
    let token = tokenParts.joined()
    print("good: \(token)")
    
    Messaging.messaging().apnsToken = deviceToken
}


func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}

해당 함수는 기기토큰 요청시 등록이 성공적이면 토큰을 받는 함수이다.
print 문이 2개가 있는데 모두 동일한 토큰을 출력하므로 토큰 출력을 원한다면 둘 중 원하는 방법대로 사용하면 된다.

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
	let deviceTokenString = deviceToken.reduce("", {$0 + String(format: "%02.2hX", $1)})
    print("Token: \(deviceTokenString)")
    let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
    let token = tokenParts.joined()
    print("Token: \(token)") 
    Messaging.messaging().apnsToken = deviceToken
}

해당 함수는 실패하였을때 호출된다.
지금은 구현을 하지 않았지만 만약에 APNs 서버가 어떠한 이유로 연결이 되지 않는 경우(네트워크 문제)나 앱이 자격을 갖지 못하는 경우에 실패하는데 이 때 설정한다.

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
}

코드를 작성하다보면 2개의 Delegate Protocol을 준수해야 된다.

UNUserNotificationCenterDelegate
MessagingDelegate

하나하나 확인해보자면 해당 프로토콜에 포함된 함수는 실제로 Noti가 왔을 경우에 동작한다.

extension AppDelegate: UNUserNotificationCenterDelegate {
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
        
        let userInfo = notification.request.content.userInfo

        if let messageID = userInfo[gcmMessageIDKey] {
            print("MessageId: \(messageID)")
        }
        
        Messaging.messaging().appDidReceiveMessage(userInfo)
        
        if #available(iOS 14.0, *) {
            return [[.list,.banner,.sound]]
        } else {
            return[[.alert,.sound]]
        }
        
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
        print("In_UserNotification")
        
        let userInfo = response.notification.request.content.userInfo
        print("Noti JSON: \(userInfo)")
    
    }
}

해당 함수는 Noti가 와서 push가 나타났을 경우에 동작하는 함수이다.

func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {        
	let userInfo = notification.request.content.userInfo
	if let messageID = userInfo[gcmMessageIDKey] {
    	print("MessageId: \(messageID)")
	}
    Messaging.messaging().appDidReceiveMessage(userInfo)
    if #available(iOS 14.0, *) {
    	return [[.list,.banner,.sound]]
	} else {
    	return[[.alert,.sound]]
	}
}

해당 함수는 등장한 Push를 누를 경우에 동작하는 함수로서 해당 부분에서 딥링크같은 부분을 구현한다거나 이벤트 페이지 이동 등의 방법으로 사용할 수 있다.

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
	print("In_UserNotification")        	
    let userInfo = response.notification.request.content.userInfo
    print("Noti JSON: \(userInfo)")
}

토큰이 업데이트 될 때마다 알림을 받기 위해 MessagingDelegate 프로토콜을 준수하는 delegate를 제공한다.

extension AppDelegate: MessagingDelegate {    
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        let dataDict: [String: String] = ["token": fcmToken ?? ""]
        
        NotificationCenter.default.post(
            name: Notification.Name("FCMToken"),
            object: nil,
            userInfo: dataDict
        )
    }
}

FCM 설정: 테스트

이제 모든 설정이 끝났으니 FCM으로 캠페인을 만들어 테스트를 진행해 제대로 동작이 되는지 확인한다.

순서
1. Firebase의 내 프로젝트의 Messaging 항목으로 이동
2. Firebase 알림 메시지 만들기
3. 알림 제목, 알림 텍스트 입력 그외 선택사항은 입력 원하면 입력
4. 타겟은 현재 해당 Push를 보낼 앱 선택
5. 예약에는 해당 푸시를 몇시에 보낼 것인지 지정하고 다음 선택
6. 그외에 필요한 사항이 있다면 입력
7. 검토 버튼을 누르고 게시를 하게 되면 우상단에 캠페인 시작 알림이 등장
8. Messaging 메인에 방금 만들었던 항목이 나타남
9. 수 분이 지난 후에 알림이 나타남


번외: FCM 너무 느려 - 방안

푸시 테스트 프로그램 이용

이건 광고도 아니고 내가 만든 프로그램도 아니지만 주변에서 사용을 하시는걸 보고 따라 쓰다보니 편해져서 이렇게 작성한다.

FCM으로 push를 보내보면 알겠지만 이게 push가 오는 시간이 너무 오래 걸린다.
테스트를 할 때 마다 매번 이시간을 기다리면 코드를 못 짤 것이다. 그렇기에 다른 방법을 찾아서 해결을 했다.

해당 프로그램은 앱 스토어에 푸시 알림-테스터 라는 이름으로 앱 검색해서 다운을 받으면 된다.

처음 받게 되면 중앙에 기본 JSON형식의 값만 적혀 있고 나머지는 입력을 해주면 된다.

해당 앱에서는 p12 인증서를 사용하기에 이전 포스트에서 p12 인증서를 받아온다.
APNS Certificate 버튼을 누르고 받은 인증서를 선택해준다.

위에 코드에서 Device Token을 print 해주는 코드가 있는데 해당 코드에서 토큰 값을 복사해서 번들ID와 함께 잘 넣어준다.

그 후 Send Push 를 누르게 되면 바로 BadDeviceToken 오류가 나타나게 되는데 이때 SandBox 항목을 선택 후 다시 Push 버튼을 누르게 되면 JSON 에 지정해둔 값대로 잘 Push가 날아오는것을 확인 할 수 있다.

참고자료

기타

당연 틀린 부분 지적은 감사하나 비난은 정중하게 사양하겠다.

profile
"잘 할 수 있을까?"를 고민하기보단 재밌어 보이는건 일단 하고, 잘하기 위해 그냥 계속합니다.

0개의 댓글