在 iOS 上向多台设备发送消息

Firebase 云消息传递提供两种向多台设备发送消息的方法:

  • 主题消息传递,允许您将消息发送至已经选择订阅特定主题的多台设备。
  • 设备组消息传递,允许您将消息发送至属于同一组的设备上运行的多个应用实例。

本教程主要介绍如何使用 FCM 的 HTTP 协议或 XMPP 协议从应用服务器发送主题消息,以及如何在 iOS 应用中接收和处理此类消息。本页面列出了实现上述目标所需的从设置到验证的所有步骤。如果您已针对 FCM 设置 iOS 客户端应用,或已经完成发送第一条消息所需的步骤,则本页所列步骤中可能包括了您已经完成的步骤。

将 Firebase 添加至您的 iOS 项目

如果您已经为您的应用启用了其他 Firebase 功能,那么您可能已完成本部分将要介绍的一些任务。此外,您需要专门针对 FCM 上传您的 APNs 身份验证密钥注册接收远程通知

前提条件

开始之前,需要在您的环境中设置几项内容:

  • Xcode 9.2 或更高版本
  • 一个针对 iOS 8 或更高版本的 Xcode 项目
  • Swift 项目必须使用 Swift 3.0 或更高版本
  • 您的应用的软件包标识符
  • CocoaPods 1.4.0 或更高版本
  • 针对云消息传递:
    • 一台 iOS 真机
    • 一个 Apple 开发者帐号所对应的 Apple 推送通知身份验证密钥
    • 在 Xcode 中通过 App > Capabilities 启用推送通知功能

如果您还没有 Xcode 项目,只是想试用一下某项 Firebase 功能,可以下载我们的快速入门示例。如果使用快速入门示例,请不要忘记从项目设置中获取软件包标识符,下一步中会用到该标识符。

将 Firebase 添加到您的应用

现在需要将 Firebase 添加至您的应用。要执行此操作,您需要有一个 Firebase 项目和适用于您的应用的 Firebase 配置文件。

要创建 Firebase 项目,请执行以下操作:

  1. 访问 Firebase 控制台

  2. 点击添加项目,然后选择或输入项目名称

    • 如果您已经拥有一个与应用关联的 Google 项目,请从项目名称下拉菜单中选择该项目。
    • 如果您尚未创建 Google 项目,请输入新的项目名称
  3. (可选)修改项目 ID

    Firebase 会自动为您的 Firebase 项目分配一个独一无二的 ID。此 ID 会显示在公众可见的 Firebase 服务中,例如:

    • 默认实时数据库网址:your-project-id.firebaseio.com
    • 默认 Cloud Storage 存储分区名称:your-project-id.appspot.com
    • 默认托管子网域:your-project-id.firebaseapp.com
  4. 在 Firebase 控制台中完成剩下的设置步骤,然后点击创建项目(如果您使用的是现有 Google 项目,则点击添加 Firebase)。

Firebase 会自动为您的 Firebase 项目预配资源。此过程通常需要几分钟。完成此过程后,您将进入 Firebase 控制台中 Firebase 项目的概览页面。

创建项目之后,您就可以向其中添加 iOS 应用了:

  1. 点击将 Firebase 添加到您的 iOS 应用,然后按设置步骤操作。如果您是导入现有 Google 项目,系统可能会自动执行这些操作,您只需下载配置文件即可。

  2. 看到提示时,输入应用的软件包 ID。请务必输入应用在使用的软件包 ID;只有在将应用添加到 Firebase 项目时您才能进行此设置。

  3. 将 Firebase iOS 配置文件添加到您的应用:

    1. 点击下载 GoogleService-Info.plist 以获取 Firebase iOS 配置文件 (GoogleService-Info.plist)。

      您可以随时再次下载 Firebase iOS 配置文件

    2. 将配置文件移至 Xcode 项目的根目录中。如果出现提示,请选择将配置文件添加至所有目标。

  4. 添加初始化代码后,运行您的应用以便向 Firebase 控制台发送验证信息,证明您已成功安装 Firebase。

添加 SDK

如果您是设置一个新项目,则需要安装 SDK。您可能已经在创建 Firebase 项目的过程中完成此步操作。

我们建议使用 CocoaPods 安装相关的库。您可以按照安装说明来安装 Cocoapods。如果不想使用 CocoaPods,则可以直接集成 SDK 框架,而不使用 CocoaPods

如果您计划下载并运行某个快速入门示例,示例中会提供 Xcode 项目和 Podfile,不过您还是需要安装 Pod 并下载 GoogleService-Info.plist 文件。如果您希望将 Firebase 库集成至自己的某个项目中,则需要为想要使用的库添加 Pod。

  1. 如果还没有 Xcode 项目,请立即创建一个。

  2. 如果还没有 Podfile,请创建一个:

    $ cd your-project directory
    $ pod init
    
  3. 添加您想安装的 Pod。您可以按照以下方法在 Podfile 中纳入一个 Pod:

    pod 'Firebase/Core'
    pod 'Firebase/Messaging'
    

    这会在您的 iOS 应用中添加 Firebase 正常运行所需的必备库以及 Google Analytics for Firebase 功能。下面列出了目前可供使用的一系列 pod 和 subspec。在针对不同功能的设置指南中也对此给出了相应的链接。

  4. 安装 Pod 并打开 .xcworkspace 文件以便在 Xcode 中查看该项目。

    $ pod install
    $ open your-project.xcworkspace
    
  5. Firebase 控制台中下载一个 GoogleService-Info.plist 文件并将其添加到您的应用中。

上传您的 APNs 身份验证密钥

将您的 APNs 身份验证密钥上传到 Firebase。如果您还没有 APNs 身份验证密钥,请参阅配置 FCM APNs

  1. 在 Firebase 控制台中,在您的项目内依次选择齿轮图标、项目设置以及云消息传递标签。

  2. iOS 应用配置下的 APNs 身份验证密钥中,点击上传按钮。

  3. 转到您保存密钥的位置,选择该密钥,然后点击打开。添加该密钥的密钥 ID(可在 Apple 开发者会员中心Certificates, Identifiers & Profiles 中找到),然后点击上传

在您的应用中初始化 Firebase

您需要为应用添加 Firebase 初始化代码。导入 Firebase 模块并配置一个共享实例,具体操作如下所示:

  1. UIApplicationDelegate 中导入 Firebase 模块:

    Swift

    import Firebase
    

    Objective-C

    @import Firebase;
    
  2. 配置一个 FirebaseApp 共享实例(通常在应用的 application:didFinishLaunchingWithOptions: 方法中配置):

    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);
}

构建发送请求

向 Firebase 云消息传递主题发送消息与向单台设备或一个用户组发送消息十分类似。应用服务器会将 to 键设为类似 /topics/yourTopic 这样的值。开发者可以选择符合以下正则表达式的任何主题名称:"/topics/[a-zA-Z0-9-_.~%]+"

要向多个主题的组合发送消息,应用服务器必须将 condition 键(而非 to 键)设为用于指定目标主题的布尔型条件。例如,要向已订阅 TopicATopicBTopicC 的设备发送消息,则设置如下条件:

'TopicA' in topics && ('TopicB' in topics || 'TopicC' in topics)

FCM 首先对括号中的所有条件求值,然后从左至右对表达式求值。在上述表达式中,只订阅某个单一主题的用户将不会接收到消息。同样地,未订阅 TopicA 的用户也不会接收到消息。下列组合将会接收到消息:

  • TopicA 和 TopicB
  • TopicA 和 TopicC

您最多可以在条件表达式中添加五个主题,支持使用括号。支持的运算符包括 &&||!。请注意 ! 的用法:

!('TopicA' in topics)

使用此表达式,任何未订阅 TopicA 的应用实例(包括未订阅任何主题的应用实例)都会收到消息。

如需详细了解应用服务器密钥,请参阅所选的连接服务器协议(HTTPXMPP)的参考信息。本页面中的示例说明了如何使用 HTTP 和 XMPP 协议将消息发送至主题。

主题 HTTP POST 请求

发送至一个主题:

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
  "to" : /topics/foo-bar",
  "priority" : "high",
  "notification" : {
    "body" : "This is a Firebase Cloud Messaging Topic Message!",
    "title" : "FCM Message",
  }
}

发送至已订阅“dogs”或“cats”主题的设备:

https://fcm.googleapis.com/fcm/send
Content-Type:application/json
Authorization:key=AIzaSyZ-1u...0GBYzPu7Udno5aA

{
  "condition": "'dogs' in topics || 'cats' in topics",
  "priority" : "high",
  "notification" : {
    "body" : "This is a Firebase Cloud Messaging Topic Message!",
    "title" : "FCM Message",
  }
}

主题 HTTP 响应

//Success example:
{
  "message_id": "1023456"
}

//failure example:
{
  "error": "TopicsMessageRateExceeded"
}

主题 XMPP 消息

发送至一个主题:

<message id="">
  <gcm xmlns="google:mobile:data">

{
  "to" : /topics/foo-bar",
  "priority" : "high",
  "notification" : {
    "body" : "This is a Firebase Cloud Messaging Topic Message!",
    "title" : "FCM Message",
  }
}
  </gcm>
</message>

发送至已订阅“dogs”或“cats”主题的设备:

<message id="">
  <gcm xmlns="google:mobile:data">

{
  "condition": "'dogs' in topics || 'cats' in topics",
  "priority" : "high",
  "notification" : {
    "body" : "This is a Firebase Cloud Messaging Topic Message!",
    "title" : "FCM Message",
  }
}
  </gcm>
</message>

主题 XMPP 响应

//Success example:
{
  "message_id": "1023456"
}

//failure example:
{
  "error": "TopicsMessageRateExceeded"
}

FCM 服务器对主题发送请求返回成功或失败响应前,可能会有最长 30 秒的延迟。请务必在请求中相应地设置应用服务器的超时值。

如需消息选项的完整列表,请参阅您所选的连接服务器协议(HTTPXMPP)的参考信息。

后续步骤

发送以下问题的反馈:

此网页
需要帮助?请访问我们的支持页面