콘솔로 이동

iOS에서 여러 기기에 메시지 보내기

Firebase 클라우드 메시징은 메시지를 여러 기기로 타겟팅하는 2가지 방법을 제공합니다.

  • 주제 메시징: 특정 주제를 구독하는 여러 기기에 메시지를 보낼 수 있습니다.
  • 기기 그룹 메시징: 그룹에 속한 기기에서 실행되는 여러 앱 인스턴스에 단일 메시지를 보낼 수 있습니다.

이 가이드에서는 앱 서버에서 FCM용 HTTP 또는 XMPP 프로토콜을 사용하여 주제 메시지를 보내는 방법 및 iOS 앱에서 메시지를 수신하고 처리하는 방법을 중점적으로 설명합니다. 이 페이지에는 설정부터 검증까지 이 작업을 수행하는 모든 단계가 나와 있으므로 FCM용 iOS 클라이언트 앱 설정 또는 첫 번째 메시지 보내기 단계를 마쳤다면 일부 단계는 이미 완료된 상태일 수 있습니다.

iOS 프로젝트에 Firebase 추가

이 섹션에서 설명하는 작업은 앱에서 다른 Firebase 기능을 사용 설정한 경우 이미 완료된 상태일 수 있습니다. 특히 FCM의 경우 APN 인증 키를 업로드하고 원격 알림을 등록해야 합니다.

기본 요건

  • Xcode 10.1 이상을 설치합니다.

  • CocoaPods 1.4.0 이상을 설치합니다.

  • Xcode에서 프로젝트를 엽니다.

    • 프로젝트에서 iOS 8 이상을 타겟팅해야 합니다.

    • Swift 프로젝트에서 Swift 3.0 이상을 사용해야 합니다.

  • 앱을 실행할 실제 iOS 기기 또는 iOS 시뮬레이터를 설정합니다.

    • 클라우드 메시징에는 다음이 필요합니다.

      • 실제 iOS 기기
      • Apple 개발자 계정의 Apple 푸시 알림 인증 키
      • Xcode의 App(앱) > Capabilities(기능)에서 푸시 알림 사용 설정
    • 다른 모든 Firebase 제품의 경우 실제 iOS 기기 또는 iOS 시뮬레이터를 사용할 수 있습니다.

  • Google 계정을 사용하여 Firebase에 로그인합니다.

Xcode 프로젝트가 준비되지 않았다면 빠른 시작 샘플 중 하나를 다운로드하여 Firebase 제품을 사용해 볼 수 있습니다.

Firebase 프로젝트 만들기

Firebase를 iOS 앱에 추가하려면 먼저 iOS 앱에 연결할 Firebase 프로젝트를 만드세요. Firebase 프로젝트에 대한 자세한 내용은 Firebase 프로젝트 이해를 참조하세요.

Firebase에 앱 등록

Firebase 프로젝트가 준비되었으면 iOS 앱을 추가할 수 있습니다.

여러 빌드 변형을 처리하는 방법을 비롯하여 앱을 Firebase 프로젝트에 추가할 때의 권장사항 및 고려사항을 자세히 알아보려면 Firebase 프로젝트 이해를 참조하세요.

  1. Firebase Console의 프로젝트 개요 페이지 중앙에 있는 iOS 아이콘을 클릭하여 설정 워크플로를 시작합니다.

    Firebase 프로젝트에 앱을 이미 추가한 경우 앱 추가를 클릭하여 플랫폼 옵션을 표시합니다.

  2. iOS 번들 ID 필드에 앱의 번들 ID를 입력합니다.

    • XCode에서 앱을 열고 최상위 Runner 디렉터리의 General(일반) 탭에 액세스하여 이 번들 ID를 찾습니다. Bundle Identifier(번들 식별자) 필드의 값이 iOS 번들 ID입니다(예: com.yourcompany.yourproject).
  3. (선택사항) 설정 워크플로의 안내를 따라 기타 앱 정보를 입력합니다.

  4. 앱 등록을 클릭합니다.

Firebase 구성 파일 추가

  1. Download GoogleService-Info.plist(GoogleService-Info.plist 다운로드)를 클릭하여 Firebase iOS 구성 파일(GoogleService-Info.plist)을 가져옵니다.

    • 언제든지 다시 Firebase iOS 구성 파일을 다운로드할 수 있습니다.

    • 구성 파일에 (2)와 같은 추가 문자가 포함되지 않았는지 확인합니다.

  2. 구성 파일을 Xcode 프로젝트의 루트로 이동합니다. 메시지가 표시되면 모든 대상에 구성 파일을 추가하도록 선택합니다.

프로젝트에 번들 ID가 여러 개 있으면 각각 자체 GoogleService-Info.plist 파일이 포함되도록 각 번들 ID를 Firebase Console의 등록된 앱에 연결해야 합니다.

앱에 Firebase SDK 추가

CocoaPods를 사용하여 Firebase 라이브러리를 설치하는 것이 좋습니다. 하지만 CocoaPods를 사용하지 않으려는 경우 SDK 프레임워크를 직접 통합할 수 있습니다.

빠른 시작 샘플 중 하나를 사용할 경우 Xcode 프로젝트 및 Podfile(pod 포함)이 이미 있어도 Firebase 구성 파일을 추가하고 pod를 설치해야 합니다.

지원되는 Firebase 제품을 iOS 앱에 추가할 수 있습니다.

  1. Podfile이 없으면 만듭니다.

    cd your-project-directory
    pod init
  2. 앱에 사용할 Firebase pod를 Podfile에 추가합니다.

    Firebase 클라우드 메시징 사용 환경을 최적화하려면 프로젝트에 Google 애널리틱스를 사용 설정하는 것이 좋습니다. Google 애널리틱스를 설정하는 과정에서 앱에 Google 애널리틱스용 Firebase SDK를 추가해야 합니다.

    애널리틱스를 사용 설정한 경우

    # Add the Firebase pod for Google Analytics
    pod 'Firebase/Analytics'
    # Add the pod for Firebase Cloud Messaging pod 'Firebase/Messaging'

    애널리틱스를 사용 설정하지 않은 경우

    # Add the pod for Firebase Cloud Messaging
    pod 'Firebase/Messaging'

  3. pod를 설치하고 .xcworkspace 파일을 열어 Xcode에서 프로젝트를 확인합니다.

    pod install
    open your-project.xcworkspace

APN 인증 키 업로드

Firebase에 APN 인증 키를 업로드합니다. 아직 APN 인증 키가 없으면 FCM에서 APN 구성을 참조하세요.

  1. Firebase Console의 프로젝트 내에서 톱니바퀴 아이콘을 선택하고 프로젝트 설정을 선택한 다음 클라우드 메시징 탭을 선택합니다.

  2. iOS 앱 구성APN 인증 키에서 업로드 버튼을 클릭합니다.

  3. 키를 저장한 위치로 이동하여 키를 선택하고 열기를 클릭합니다. 해당하는 키 ID(Apple Developer Member CenterCertificates, Identifiers & Profiles(인증서, 식별자, 프로필)에서 확인 가능)를 추가하고 업로드를 클릭합니다.

앱에서 Firebase 초기화

애플리케이션에 Firebase 초기화 코드를 추가해야 합니다. 다음과 같이 Firebase 모듈을 가져오고 공유 인스턴스를 구성합니다.

  1. UIApplicationDelegate에서 Firebase 모듈을 가져옵니다.

    Swift

    import Firebase

    Objective-C

    @import Firebase;
  2. 일반적으로 앱의 application:didFinishLaunchingWithOptions: 메서드에서 FirebaseApp 공유 인스턴스를 구성합니다.

    Swift

    // Use Firebase library to configure APIs
    FirebaseApp.configure()

    Objective-C

    // Use Firebase library to configure APIs
    [FIRApp configure];

원격 알림 등록

애플리케이션이 시작될 때 또는 적절한 시점에 원격 알림에 앱을 등록합니다. 다음과 같이 registerForRemoteNotifications를 호출합니다.

Swift

if #available(iOS 10.0, *) {
  // For iOS 10 display notification (sent via APNS)
  UNUserNotificationCenter.current().delegate = self

  let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
  UNUserNotificationCenter.current().requestAuthorization(
    options: authOptions,
    completionHandler: {_, _ in })
} else {
  let settings: UIUserNotificationSettings =
  UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
  application.registerUserNotificationSettings(settings)
}

application.registerForRemoteNotifications()

Objective-C

if ([UNUserNotificationCenter class] != nil) {
  // iOS 10 or later
  // For iOS 10 display notification (sent via APNS)
  [UNUserNotificationCenter currentNotificationCenter].delegate = self;
  UNAuthorizationOptions authOptions = UNAuthorizationOptionAlert |
      UNAuthorizationOptionSound | UNAuthorizationOptionBadge;
  [[UNUserNotificationCenter currentNotificationCenter]
      requestAuthorizationWithOptions:authOptions
      completionHandler:^(BOOL granted, NSError * _Nullable error) {
        // ...
      }];
} else {
  // iOS 10 notifications aren't available; fall back to iOS 8-9 notifications.
  UIUserNotificationType allNotificationTypes =
  (UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge);
  UIUserNotificationSettings *settings =
  [UIUserNotificationSettings settingsForTypes:allNotificationTypes categories:nil];
  [application registerUserNotificationSettings:settings];
}

[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은 다른 다운스트림 메시지와 동일한 방식으로 주제 메시지를 전송합니다.

다음과 같이 AppDelegate application:didReceiveRemoteNotification:을 구현합니다.

Swift

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
  // 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)
}

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  // 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)

  completionHandler(UIBackgroundFetchResult.newData)
}

Objective-C

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
  // 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 message ID.
  if (userInfo[kGCMMessageIDKey]) {
    NSLog(@"Message ID: %@", userInfo[kGCMMessageIDKey]);
  }

  // Print full message.
  NSLog(@"%@", userInfo);
}

- (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 message ID.
  if (userInfo[kGCMMessageIDKey]) {
    NSLog(@"Message ID: %@", userInfo[kGCMMessageIDKey]);
  }

  // Print full message.
  NSLog(@"%@", userInfo);

  completionHandler(UIBackgroundFetchResultNewData);
}

보내기 요청 작성

주제를 만든 후에는 클라이언트 측의 클라이언트 앱 인스턴스에서 주제를 구독하거나 서버 API를 통해 주제에 메시지를 전송할 수 있습니다. 백엔드로 로직을 전송하면서 아래와 같이 원하는 주제 이름을 지정합니다.

Node.js

// The topic name can be optionally prefixed with "/topics/".
var topic = 'highScores';

var message = {
  data: {
    score: '850',
    time: '2:45'
  },
  topic: topic
};

// Send a message to devices subscribed to the provided topic.
admin.messaging().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를 구독하지 않은 사용자도 메시지를 수신하지 않습니다. 다음과 같이 조합되어야 메시지를 수신합니다.

  • TopicATopicB
  • TopicATopicC

조건식에 최대 5개의 주제를 포함할 수 있습니다.

조건으로 보내는 방법은 다음과 같습니다.

Node.js

// 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 = {
  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.
admin.messaging().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(new Notification(
        "$GOOG up 1.43% on the day",
        "$GOOG gained 11.80 points to close at 835.67, up 1.43% on the day."))
    .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

다음 단계