여러 기기에 메시지를 타겟팅하려면 주제 메시징을 사용합니다. 이 기능을 사용하면 특정 주제를 구독하는 여러 기기에 메시지를 보낼 수 있습니다.
이 튜토리얼에서는 앱 서버에서 FCM용 Admin SDK 또는 REST API를 사용하여 주제 메시지를 보내는 방법과 Apple 앱에서 주제 메시지를 수신하고 처리하는 방법을 중점적으로 설명합니다. 이 페이지에는 설정부터 검증까지 이 작업을 수행하는 모든 단계가 나와 있으므로 FCM용 Apple 클라이언트 앱 설정 또는 첫 번째 메시지 보내기 단계를 마쳤다면 일부 단계는 이미 완료된 상태일 수 있습니다.
Apple 프로젝트에 Firebase 추가
이 섹션에서 설명하는 작업은 앱에 다른 Firebase 기능을 이미 사용 설정한 경우 이미 완료된 상태일 수 있습니다. FCM에만 해당하는 작업으로는 APNs 인증 키 업로드와 원격 알림 등록이 있습니다.
기본 요건
다음을 설치합니다.
- Xcode 15.2 이상
프로젝트가 다음 요구사항을 충족하는지 확인합니다.
- 프로젝트에서 다음 플랫폼 버전 이상을 타겟팅해야 합니다.
- iOS 13
- macOS 10.15
- tvOS 13
- watchOS 7
- 프로젝트에서 다음 플랫폼 버전 이상을 타겟팅해야 합니다.
실제 Apple 기기를 설정하여 앱을 실행하고 다음 작업을 완료합니다.
- Apple 개발자 계정의 Apple 푸시 알림 인증 키를 가져옵니다.
- XCode의 App(앱) > Capabilities(기능)에서 푸시 알림을 사용 설정합니다.
- Google 계정을 사용하여 Firebase에 로그인합니다.
Xcode 프로젝트가 준비되지 않았지만 Firebase 제품을 사용해 보고자 하는 경우 빠른 시작 샘플 중 하나를 다운로드하세요.
Firebase 프로젝트 만들기
Firebase를 Apple 앱에 추가하려면 먼저 앱에 연결할 Firebase 프로젝트를 만드세요. Firebase 프로젝트에 대한 자세한 내용은 Firebase 프로젝트 이해를 참조하세요.
Firebase에 앱 등록
Apple 앱에서 Firebase를 사용하려면 Firebase 프로젝트에 앱을 등록해야 합니다. 앱 등록이란 보통 프로젝트에 앱을 '추가'하는 것을 의미합니다.
Firebase 콘솔로 이동
프로젝트 개요 페이지 중앙에 있는 iOS+ 아이콘을 클릭하여 설정 워크플로를 시작합니다.
Firebase 프로젝트에 앱을 이미 추가한 경우 앱 추가를 클릭하여 플랫폼 옵션을 표시합니다.
번들 ID 필드에 앱의 번들 ID를 입력합니다.
(선택사항) 다른 앱 정보(앱 닉네임 및 App Store ID)를 입력합니다.
앱 등록을 클릭합니다.
Firebase 구성 파일 추가
GoogleService-Info.plist 다운로드를 클릭하여 Firebase Apple 플랫폼 구성 파일(
GoogleService-Info.plist
)을 가져옵니다.구성 파일을 Xcode 프로젝트의 루트로 이동합니다. 메시지가 표시되면 모든 대상에 구성 파일을 추가하도록 선택합니다.
프로젝트에 번들 ID가 여러 개 있으면 각 앱에 자체 GoogleService-Info.plist
파일이 포함되도록 각 번들 ID를 Firebase 콘솔에서 등록된 앱과 연결해야 합니다.
앱에 Firebase SDK 추가
Swift Package Manager를 사용해 Firebase 종속 항목을 설치하고 관리하세요.
- 앱 프로젝트를 연 상태로 Xcode에서 File(파일) > Add Packages(패키지 추가)로 이동합니다.
- 메시지가 표시되면 Firebase Apple 플랫폼 SDK 저장소를 추가합니다.
- Firebase Cloud Messaging 라이브러리를 선택합니다.
- 타겟 빌드 설정의 Other Linker Flags(기타 링커 플래그) 섹션에
-ObjC
플래그를 추가합니다. - Firebase Cloud Messaging 사용 환경을 최적화하려면 Firebase 프로젝트에서 Google Analytics를 사용 설정하고 Google 애널리틱스용 Firebase SDK를 앱에 추가하는 것이 좋습니다. IDFA 수집이 없는 라이브러리 또는 IDFA 수집이 있는 라이브러리를 선택할 수 있습니다.
- 완료되면 Xcode가 백그라운드에서 자동으로 종속 항목을 확인하고 다운로드하기 시작합니다.
https://github.com/firebase/firebase-ios-sdk.git
APNs 인증 키 업로드
Firebase에 APNs 인증 키를 업로드합니다. 아직 APNs 인증 키가 없다면 Apple Developer Member Center에서 만드세요.
-
Firebase Console 프로젝트 내에서 톱니바퀴 아이콘을 선택하고 프로젝트 설정을 선택한 다음 클라우드 메시징 탭을 선택합니다.
-
iOS 앱 구성의 APNs 인증 키에서 업로드 버튼을 클릭합니다.
-
키를 저장한 위치로 이동하여 키를 선택하고 열기를 클릭합니다. 해당하는 키 ID(Apple Developer Member Center에서 확인 가능)를 추가하고 업로드를 클릭합니다.
앱에서 Firebase 초기화
애플리케이션에 Firebase 초기화 코드를 추가해야 합니다. 다음과 같이 Firebase 모듈을 가져오고 공유 인스턴스를 구성합니다.
UIApplicationDelegate
의FirebaseCore
모듈과 앱 대리자가 사용하는 다른 Firebase 모듈을 가져옵니다. 예를 들어 Cloud Firestore와 Authentication을 사용하려면 다음을 따르세요.SwiftUI
import SwiftUI import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
Swift
import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
Objective-C
@import FirebaseCore; @import FirebaseFirestore; @import FirebaseAuth; // ...
- 앱 대리자의
application(_:didFinishLaunchingWithOptions:)
메서드에서FirebaseApp
공유 인스턴스를 구성합니다.SwiftUI
// Use Firebase library to configure APIs FirebaseApp.configure()
Swift
// Use Firebase library to configure APIs FirebaseApp.configure()
Objective-C
// Use Firebase library to configure APIs [FIRApp configure];
- SwiftUI를 사용하는 경우 앱 대리자를 만들고
UIApplicationDelegateAdaptor
또는NSApplicationDelegateAdaptor
를 통해App
구조체에 연결해야 합니다. 앱 대리자 재구성도 중지해야 합니다. 자세한 내용은 SwiftUI 안내를 참조하세요.SwiftUI
@main struct YourApp: App { // register app delegate for Firebase setup @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { NavigationView { ContentView() } } } }
원격 알림 등록
애플리케이션이 시작될 때 또는 적절한 시점에 원격 알림에 앱을 등록합니다. 다음과 같이registerForRemoteNotifications
를 호출합니다.
Swift
UNUserNotificationCenter.current().delegate = self let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound] UNUserNotificationCenter.current().requestAuthorization( options: authOptions, completionHandler: { _, _ in } ) application.registerForRemoteNotifications()
Objective-C
[UNUserNotificationCenter currentNotificationCenter].delegate = self; UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge; [[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:authOptions completionHandler:^(BOOL granted, NSError * _Nullable error) { // ... }]; [application registerForRemoteNotifications];
클라이언트 앱에서 주제 구독
클라이언트 앱에서 기존 주제를 구독하거나 새 주제를 만들 수 있습니다. 클라이언트 앱에서 Firebase 프로젝트에 아직 없는 새 주제 이름을 구독하면 FCM에서 이 이름으로 새 주제가 만들어지고, 이후에 다른 클라이언트에서 그 주제를 구독할 수 있습니다.
주제를 구독하려면 애플리케이션의 기본 스레드에서 구독 메서드를 호출합니다. FCM은 스레드 안전을 지원하지 않습니다. 최초 구독 요청이 실패하면 FCM이 자동으로 다시 시도합니다. 구독을 완료할 수 없는 경우 구독에서 오류가 발생하며 아래 표시된 것처럼 완료 핸들러에서 확인할 수 있습니다.
Swift
Messaging.messaging().subscribe(toTopic: "weather") { error in print("Subscribed to weather topic") }
Objective-C
[[FIRMessaging messaging] subscribeToTopic:@"weather" completion:^(NSError * _Nullable error) { NSLog(@"Subscribed to weather topic"); }];
이 호출로 인해 FCM 백엔드로 비동기 요청이 전송되고 클라이언트가 지정된 주제를 구독하게 됩니다. subscribeToTopic:topic
을 호출하기 전에 클라이언트 앱 인스턴스에서 이미 didReceiveRegistrationToken
콜백을 통해 등록 토큰을 받은 상태인지 확인합니다.
앱이 시작될 때마다 FCM은 요청된 모든 주제가 구독되었는지 확인합니다. 구독을 취소하려면 unsubscribeFromTopic:topic
을 호출합니다. 그러면 FCM이 백그라운드에서 주제를 구독 취소합니다.
주제 메시지 수신 및 처리
FCM은 다른 다운스트림 메시지와 동일한 방식으로 주제 메시지를 전송합니다.
다음과 같이 application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
을 구현합니다.
Swift
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics // Messaging.messaging().appDidReceiveMessage(userInfo) // Print message ID. if let messageID = userInfo[gcmMessageIDKey] { print("Message ID: \(messageID)") } // Print full message. print(userInfo) return UIBackgroundFetchResult.newData }
Objective-C
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // With swizzling disabled you must let Messaging know about the message, for Analytics // [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; // ... // Print full message. NSLog(@"%@", userInfo); completionHandler(UIBackgroundFetchResultNewData); }
전송 요청 작성
주제를 만든 후에는 클라이언트 측의 클라이언트 앱 인스턴스에서 주제를 구독하거나 서버 API를 통해 주제에 메시지를 전송할 수 있습니다. FCM용 전송 요청을 처음 작성하는 경우 서버 환경 및 FCM 가이드에서 중요한 배경 및 설정 정보를 참조하세요.
백엔드의 전송 로직에서 아래와 같이 원하는 주제 이름을 지정합니다.
Node.js
// The topic name can be optionally prefixed with "/topics/".
const topic = 'highScores';
const message = {
data: {
score: '850',
time: '2:45'
},
topic: topic
};
// Send a message to devices subscribed to the provided topic.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// The topic name can be optionally prefixed with "/topics/".
String topic = "highScores";
// See documentation on defining a message payload.
Message message = Message.builder()
.putData("score", "850")
.putData("time", "2:45")
.setTopic(topic)
.build();
// Send a message to the devices subscribed to the provided topic.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# The topic name can be optionally prefixed with "/topics/".
topic = 'highScores'
# See documentation on defining a message payload.
message = messaging.Message(
data={
'score': '850',
'time': '2:45',
},
topic=topic,
)
# Send a message to the devices subscribed to the provided topic.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// The topic name can be optionally prefixed with "/topics/".
topic := "highScores"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Topic: topic,
}
// Send a message to the devices subscribed to the provided topic.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// The topic name can be optionally prefixed with "/topics/".
var topic = "highScores";
// See documentation on defining a message payload.
var message = new Message()
{
Data = new Dictionary<string, string>()
{
{ "score", "850" },
{ "time", "2:45" },
},
Topic = topic,
};
// Send a message to the devices subscribed to the provided topic.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"topic" : "foo-bar",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message"
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"message": {
"topic" : "foo-bar",
"notification": {
"body": "This is a Firebase Cloud Messaging Topic Message!",
"title": "FCM Message"
}
}
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
여러 주제를 조합하여 메시지를 보내려면 대상 주제를 지정하는 불리언 표현식인 조건을 지정합니다. 예를 들어 다음 조건은 TopicA
와 함께 TopicB
또는 TopicC
를 구독하는 기기로 메시지를 전송합니다.
"'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)"
FCM은 괄호 안의 조건부터 모두 판정한 후 왼쪽에서 오른쪽으로 표현식을 판정합니다. 위 표현식에서 주제를 하나만 구독한 사용자는 메시지를 수신하지 않습니다. TopicA
를 구독하지 않은 사용자도 메시지를 수신하지 않습니다. 다음과 같은 조합으로 구독해야 메시지를 수신합니다.
TopicA
,TopicB
TopicA
,TopicC
조건식에 최대 5개의 주제를 포함할 수 있습니다.
조건으로 보내는 방법은 다음과 같습니다.
Node.js
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
const condition = '\'stock-GOOG\' in topics || \'industry-tech\' in topics';
// See documentation on defining a message payload.
const message = {
notification: {
title: '$FooCorp up 1.43% on the day',
body: '$FooCorp gained 11.80 points to close at 835.67, up 1.43% on the day.'
},
condition: condition
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
getMessaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
자바
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
String condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
Message message = Message.builder()
.setNotification(Notification.builder()
.setTitle("$GOOG up 1.43% on the day")
.setBody("$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.")
.build())
.setCondition(condition)
.build();
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
String response = FirebaseMessaging.getInstance().send(message);
// Response is a message ID string.
System.out.println("Successfully sent message: " + response);
Python
# Define a condition which will send to devices which are subscribed
# to either the Google stock or the tech industry topics.
condition = "'stock-GOOG' in topics || 'industry-tech' in topics"
# See documentation on defining a message payload.
message = messaging.Message(
notification=messaging.Notification(
title='$GOOG up 1.43% on the day',
body='$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.',
),
condition=condition,
)
# Send a message to devices subscribed to the combination of topics
# specified by the provided condition.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
Go
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
condition := "'stock-GOOG' in topics || 'industry-tech' in topics"
// See documentation on defining a message payload.
message := &messaging.Message{
Data: map[string]string{
"score": "850",
"time": "2:45",
},
Condition: condition,
}
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
response, err := client.Send(ctx, message)
if err != nil {
log.Fatalln(err)
}
// Response is a message ID string.
fmt.Println("Successfully sent message:", response)
C#
// Define a condition which will send to devices which are subscribed
// to either the Google stock or the tech industry topics.
var condition = "'stock-GOOG' in topics || 'industry-tech' in topics";
// See documentation on defining a message payload.
var message = new Message()
{
Notification = new Notification()
{
Title = "$GOOG up 1.43% on the day",
Body = "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day.",
},
Condition = condition,
};
// Send a message to devices subscribed to the combination of topics
// specified by the provided condition.
string response = await FirebaseMessaging.DefaultInstance.SendAsync(message);
// Response is a message ID string.
Console.WriteLine("Successfully sent message: " + response);
REST
POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
Content-Type: application/json
Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA
{
"message":{
"condition": "'dogs' in topics || 'cats' in topics",
"notification" : {
"body" : "This is a Firebase Cloud Messaging Topic Message!",
"title" : "FCM Message",
}
}
}
cURL 명령어:
curl -X POST -H "Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA" -H "Content-Type: application/json" -d '{
"notification": {
"title": "FCM Message",
"body": "This is a Firebase Cloud Messaging Topic Message!",
},
"condition": "'dogs' in topics || 'cats' in topics"
}' https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1
다음 단계
- 서버를 사용하여 클라이언트 앱 인스턴스에서 주제를 구독하고 기타 관리 작업을 수행할 수 있습니다. 서버에서 주제 구독 관리를 참조하세요.