[iOS] Firebase Cloud Messaging - APN
iOS/Swift

[iOS] Firebase Cloud Messaging - APN

๊ธฐ๋ก์šฉ

 

 

1. firebase project ๋งŒ๋“ค๊ธฐ

https://console.firebase.google.com/u/3/?hl=ko



2. iOS ์•ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ

 

iOS๋ฅผ ๋ˆŒ๋Ÿฌ ์•ฑ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค

๊ทธ๋Ÿฌ๋ฉด ์•ฑ ๋“ฑ๋ก ํ™”๋ฉด์ด ๋‚˜์˜ค๋Š”๋ฐ ์ด๋•Œ Apple ๋ฒˆ๋“ค ID๋ฅผ ์ž…๋ ฅํ•ด์•ผ ํ•จ

์š”๊ฑด ์–ด๋Š ๊ฒƒ์ด๋ƒ๋ฉด ์˜ค๋ฅธ์ชฝ ์—‘์Šค์ฝ”๋“œ ์ฐฝ์—์„œ!! Signing & Capabilities ์•ˆ์— ์žˆ๋Š” Bundle Identifier ์ž…๋‹ˆ๋‹ค

 

๋‚˜๋จธ์ง€ ์•ฑ ๋‹‰๋„ค์ž„, App Store ID๋Š” ์„ ํƒ์‚ฌํ•ญ์ด๋‹ˆ ํŒจ์Šคํ• ๊ฒŒ์š”

 

 

๊ทธ๋Ÿฌ๋ฉด 2๋ฒˆ ๋‹จ๊ณ„ ๊ตฌ์„ฑ ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ๊ฐ€ ๋‚˜์˜ค๊ณ , GoogleService-Info.plist ๋‹ค์šด๋กœ๋“œ ๋ฐ›์•„์„œ Xcode ํ”„๋กœ์ ํŠธ ์•ˆ์— ์ถ”๊ฐ€ํ•ด ์ฃผ์„ธ์š”

 

 

์ €๋Š” ์œ„ ์‚ฌ์ง„์ฒ˜๋Ÿผ Plists๋ผ๋Š” ํด๋”๋ฅผ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด ์—ฌ๊ธฐ์— Info.plist์™€ ํ•จ๊ป˜ ๋„ฃ์–ด ๋‘์—ˆ์Šต๋‹ˆ๋‹ค

 

์ฐธ๊ณ !!!! GoogleService-Info.plist ์•ˆ์—๋Š” ์•ฑ์— ๊ด€๋ จ๋œ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ์–ด์„œ gitignore์— ์ถ”๊ฐ€ํ•˜์—ฌ ๋”ฐ๋กœ ๋กœ์ปฌ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„.. ๊ถŒ์žฅ๋“œ๋ฆฝ๋‹ˆ๋‹ค

 

3. Apple Developer > Identifiers ์„ค์ •

์ด์ œ Firebase Console์€ ์ž ์‹œ.. ๋ฉˆ์ถฐ ๋‘๊ณ  Apple Developer๋กœ ๊ฐ‘๋‹ˆ๋‹ค

https://developer.apple.com/account

 

Identifiers ํƒญ์—์„œ, App ID๋ฅผ ๋งŒ๋“ค์—ˆ๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค (Push Notifications ๋ง๊ณ ๋„ ๋‹ค๋ฅธ ๊ธฐ๋Šฅ (Sign in with Apple ๋“ฑ..)์„ ์ด์šฉํ•˜์…จ๋‹ค๋ฉด ์ด๋ฏธ ๋งŒ๋“ค์–ด์ ธ ์žˆ์„ ๊ฒ๋‹ˆ๋‹ค !)

๋งŒ์•ฝ, App ID๊ฐ€ ์—†๋‹ค๋ฉด ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค

 

์ €๋Š” ์ด๋ฏธ ์žˆ์œผ๋‹ˆ ์ด๊ฑฐ ์“ธ๊ฒŒ์š”

์‚ฌ์šฉํ•  App ID๋ฅผ ํด๋ฆญํ•ด์„œ ๋“ค์–ด๊ฐ„ ํ›„, Push Notifications๋ฅผ ํ™œ์„ฑํ™”์‹œํ‚ต๋‹ˆ๋‹ค

4. Apple Developer > Keys ์„ค์ •

๊ณ ๋‹ค์Œ... Keys๋กœ ๊ฐ€์„œ

์‚ฌ์šฉํ•  Key๊ฐ€ ์—†์œผ๋ฉด ์ƒˆ Key๋ฅผ ๋งŒ๋“ค์–ด ์ฃผ์„ธ์š”... ์ „ ์ด๋ฏธ ์žˆ์œผ๋‹ˆ ์žˆ๋Š” ๊ฑธ ์“ฐ๊ฒ ์Šต๋‹ˆ๋‹ค (์ฐธ๊ณ ๋กœ Key๋Š” ํ•œ ๊ณ„์ •๋‹น 3๊ฐœ์ธ.. ๊ฐ€? ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ฑธ๋กœ ์•Œ์•„์š”. ๊ทธ๋ƒฅ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•˜์‹œ๋Š” ๋ถ„๋“ค์€ ์žˆ๋˜ Key๋ฅผ ๊ณ„์† ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์— ์‚ฌ์šฉํ•˜์…”๋„ ๊ดœ์ฐฎ์Šต๋‹ˆ๋‹ค.. ์•„๋‹ ์ˆ˜ ์žˆ์Œ)

 

 

 

Key๋ฅผ ๋งŒ๋“ค ๋•Œ ์ฃผ์˜ํ•  ์ ์€ ... ์ € Download๊ฐ€ ํ•œ ๋ฒˆ๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ์ ์ž…๋‹ˆ๋‹ค!!!

๊ทธ๋ž˜์„œ ๊ผญ Key๋ฅผ ๋‹ค์šด๋กœ๋“œ๋ฐ›๊ณ , (.p8 ํ™•์žฅ์ž๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํŒŒ์ผ์ž…๋‹ˆ๋‹ค) ๊ฐœ์ธ์ ์œผ๋กœ ๊ผญ ๋”ฐ๋กœ ์ €์žฅํ•˜์—ฌ ๊ด€๋ฆฌํ•ด ์ฃผ์„ธ์š”~ ์žƒ์–ด๋ฒ„๋ฆฌ๋ฉด ๊ณค๋ž€~

.p8 Key ํŒŒ์ผ๊นŒ์ง€ ๋ฐ›์•˜๋‹ค๋ฉด Apple Developer์—์„œ ํ•ด์•ผ ํ•  ์ผ์€ ๋์ž…๋‹ˆ๋‹ค

 

 

5. Firebase Console ํ”„๋กœ์ ํŠธ ์„ค์ •

 

๋‹ค์‹œ Firebase Project Console๋กœ ๋Œ์•„์™€์„œ... ์„ค์ • > ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง•์œผ๋กœ ๋“ค์–ด์™€ ์ฃผ์„ธ์š”

 

์•„๋ž˜๋กœ ์ข€ ๋‚ด๋ ค๊ฐ€ ๋ณด๋ฉด ์•„๊นŒ ์ถ”๊ฐ€ํ–ˆ๋˜ iOS ์•ฑ์ด ๋ณด์ž…๋‹ˆ๋‹ค

"APN ์ธ์ฆ ํ‚ค" ๋ถ€๋ถ„์— ์ข€ ์ „์— ๋ฐ›์•˜๋˜ .p8 ํŒŒ์ผ์„ ์—…๋กœ๋“œํ•˜์—ฌ ์ฃผ์„ธ์š”

 

๊ทธ๋Ÿฌ๋ฉด ํ‚ค ID์™€ ํŒ€ ID๋ฅผ ์ž…๋ ฅํ•˜๋ผ๊ณ  ๋œฐ ํ…๋ฐ,

ํ‚ค ID๋Š” Apple Developers > Keys์—์„œ ์ €~ ๊ธฐ Key ID๋ฅผ ์“ฐ์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค!

 

ํŒ€ ID๋Š” Apple Developers ์šฐ์ธก ์ƒ๋‹จ ๊ณ„์ • ์ •๋ณด์— ๋‚˜์™€ ์žˆ๋Š”
์ € ๊ฐ€๋ฆฐ ๋ถ€๋ถ„!! ์ €๊ธฐ์˜ˆ์š”~! ์œ„ ์‚ฌ์ง„์—์„œ Key ID ๋ฐ‘์— CONFIGURATION์—๋„ ๋˜‘๊ฐ™์€ ๋ถ€๋ถ„์ด ์žˆ์„ ๊ฑฐ์˜ˆ์š”!

๊ทธ๋Ÿผ Firebase Console๋„ ์ง„์งœ ๋๋‚ฌ์Šต๋‹ˆ๋‹ค

 

6. Xcode๋กœ ๊ฐ€์„œ ํ”„๋กœ์ ํŠธ Capability ์„ค์ •

 

ํ”„๋กœ์ ํŠธ ์„ค์ •์—์„œ, ์ขŒ์ธก ์ƒ๋‹จ +Capability๋ฅผ ๋ˆŒ๋Ÿฌ Push Notifications์™€ Background Modes๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

Background Modes์—๋Š” Background fetch์™€ Remote notifications๋ฅผ ์ฒดํฌํ•ฉ๋‹ˆ๋‹ค

 

๊ทธ ๋‹ค์Œ์—, Info.plist ํŒŒ์ผ๋กœ ์ด๋™ํ•˜์—ฌ FirebaseAppDelegateProxyEnabled ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ’์€ NO

 

 

7. AppDelegate.swift์—์„œ ํ† ํฐ ๊ด€๋ จ ์ฝ”๋“œ ์ž‘์„ฑ

 

๋งˆ์ง€๋ง‰!!

์ œ๊ฐ€ ํ‘ธ์‹œ์•Œ๋ฆผ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ๊ฐ€์ ธ์™”์Šต๋‹ˆ๋‹ค...

์ฝ”๋“œ์— ๋Œ€ํ•œ ํ”ผ๋“œ๋ฐฑ์€.. ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค

 

import UIKit
import Firebase
import FirebaseCore

@main
class AppDelegate: UIResponder, UIApplicationDelegate, MessagingDelegate {
    
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        FirebaseApp.configure()
        PushNotificationHelper.shared.setAuthorization()
        // ์•ฑ ์‹คํ–‰ ์‹œ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ํ—ˆ์šฉ ๊ถŒํ•œ ๋ฐ›๊ธฐ
        UNUserNotificationCenter.current().delegate = self
        
        // ์›๊ฒฉ ์•Œ๋ฆผ ๋“ฑ๋ก
        application.registerForRemoteNotifications()

        /// ๋ฉ”์‹œ์ง€ ๋Œ€๋ฆฌ์ž ์„ค์ •
        Messaging.messaging().delegate = self
        
        /// ์ž๋™ ์ดˆ๊ธฐํ™” ๋ฐฉ์ง€
        Messaging.messaging().isAutoInitEnabled = true
        
        /// ํ˜„์žฌ ๋“ฑ๋ก ํ† ํฐ ๊ฐ€์ ธ์˜ค๊ธฐ
        Messaging.messaging().token { token, error in
            if let error = error {
                print("Error fetching FCM registration token: \(error)")
            } else if let token = token {
                print("FCM registration token: \(token)")
                UserDefaultsManager.fcmDeviceToken = token
                UserInfo.shared.deviceToken = token 
            }
        }
        
        return true
    }
    
    // MARK: UISceneSession Lifecycle
    
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
    }
    
    func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
        // Called when the user discards a scene session.
        // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
        // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
    }
    
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        if (AuthApi.isKakaoTalkLoginUrl(url)) {
            return AuthController.handleOpenUrl(url: url)
        }
        
        return false
    }
    
    /// APN ํ† ํฐ๊ณผ ๋“ฑ๋ก ํ† ํฐ ๋งคํ•‘
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken
    }
    
    /// APN ํ† ํฐ๊ณผ ๋“ฑ๋ก ํ† ํฐ ๋งคํ•‘ ์‹คํŒจ
    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
        debugPrint("APN ํ† ํฐ ๋“ฑ๋ก ์‹คํŒจ", "fail")
    }
    
    /// ๋””๋ฐ”์ด์Šค ์„ธ๋กœ๋ฐฉํ–ฅ์œผ๋กœ ๊ณ ์ •
    func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
        return UIInterfaceOrientationMask.portrait
    }
    
    /// ํ† ํฐ ๊ฐฑ์‹  ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฉ”์„œ๋“œ
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
        print("Firebase registration token: \(String(describing: fcmToken))")
        UserDefaultsManager.fcmDeviceToken = fcmToken
        UserInfo.shared.deviceToken = fcmToken ?? "non-token"
        let dataDict: [String: String] = ["token": fcmToken ?? ""]
        NotificationCenter.default.post(
            name: Notification.Name("FCMToken"),
            object: nil,
            userInfo: dataDict
        )
    }
}

// MARK: - UNUserNotificationCenterDelegate
extension AppDelegate: UNUserNotificationCenterDelegate {

    /// foreGround์— ํ‘ธ์‹œ์•Œ๋ฆผ์ด ์˜ฌ ๋•Œ ์‹คํ–‰๋˜๋Š” ๋ฉ”์„œ๋“œ
    func userNotificationCenter(_ center: UNUserNotificationCenter,willPresent notification: UNNotification,withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.badge, .sound, .list, .banner])
    }
}

 

//
//  PushNotificationHelper.swift
//  GAM
//
//  Created by Jungbin on 1/24/24.
//

import Foundation
import Firebase

class PushNotificationHelper {
    static let shared = PushNotificationHelper()

    private init() { }

    func setAuthorization() {
        let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] // ํ•„์š”ํ•œ ์•Œ๋ฆผ ๊ถŒํ•œ์„ ์„ค์ •
        UNUserNotificationCenter.current().requestAuthorization(
            options: authOptions,
            completionHandler: { _, _ in }
        )
    }
}

 

 

8. ํ…Œ์ŠคํŠธํ•˜๊ธฐ

 

์ด์ œ ์‹คํ–‰์„ ์‹œ์ผœ ๋ด…๋‹ˆ๋‹ค!

๋ณ„ ๋ฌธ์ œ๊ฐ€ ์—†๋‹ค๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ฝ˜์†”์— device token์ด ์ถœ๋ ฅ๋  ํ…๋ฐ์š”, ์ด ํ† ํฐ์„ ๋ณต์‚ฌํ•ด ๋‘ก๋‹ˆ๋‹ค.

 

Firebase Console > Messaging์œผ๋กœ ๋“ค์–ด๊ฐ€์„œ, Firebase ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ ์บ ํŽ˜์ธ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์•Œ๋ฆผ ์ œ๋ชฉ, ์•Œ๋ฆผ ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜๊ณ , ์˜ค๋ฅธ์ชฝ์— "ํ…Œ์ŠคํŠธ ๋ฉ”์‹œ์ง€ ์ „์†ก" ๋ฒ„ํŠผ ํด๋ฆญ

 

 

 

Xcode ์ฝ˜์†”์—์„œ ๋ณต์‚ฌํ•ด ๋’€๋˜ FCM ๋“ฑ๋ก ํ† ํฐ์„ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค!


๊ทธ๋ฆฌ๊ณ  ํ…Œ์ŠคํŠธ๋ฅผ ๋ˆ„๋ฅด๋ฉด~

 

์งœ์ž”

 

 

์ด์ƒ์ž…๋‹ˆ๋‹ค ^_^

 

FCM - APN, FCM ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ์  ๋“ฑ์— ๋Œ€ํ•ด์„œ๋Š”

https://seungwoolog.tistory.com/88

 

[Firebase] FCM์„ ๋„์ž…ํ•  ๋•Œ ๊ณ ๋ คํ•  ๊ฒƒ๋“ค

์ตœ๊ทผ ์ง„ํ–‰ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์— FCM์„ ์•ฑ ํ‘ธ์‹œ ์•Œ๋ฆผ ์„œ๋น„์Šค๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ๋„์ž…ํ•  ๋•Œ ์„œ๋ฒ„ ๊ฐœ๋ฐœ์ž๋กœ์„œ ๊ณ ๋ คํ•  ๊ฒƒ๋“ค์„ ์ •๋ฆฌํ•˜์˜€๋‹ค. Firebase Cloud Messaging (FCM) Firebase ํด๋ผ์šฐ๋“œ ๋ฉ”์‹œ์ง•(FCM)์€ ๋ฌด๋ฃŒ๋กœ ๋ฉ”

seungwoolog.tistory.com

 

์ด ๋ถ„์˜ ๋ธ”๋กœ๊ทธ๋ฅผ ์ฐธ๊ณ ํ•˜์…”์š”.. ์ •๋ฆฌ๊ฐ€ ์ž˜ ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค