透過 Apple 平台將訊息傳送至多部裝置

如要將訊息指定給多部裝置,請使用主題訊息傳送功能。這項功能可讓你向已選擇加入特定主題的多部裝置傳送訊息。

本教學課程著重於使用 Admin SDKFCMREST API,從應用程式伺服器傳送主題訊息,以及在 Apple 應用程式中接收及處理這些訊息。本頁面列出從設定到驗證的所有步驟,因此如果您已為 FCM 設定 Apple 用戶端應用程式,或已完成傳送第一則訊息的步驟,這裡可能會涵蓋您已完成的步驟。

將 Firebase 新增至 Apple 專案

本節將說明如果您已為應用程式啟用其他 Firebase 功能,可能已完成的作業。具體來說,您需要上傳 APNs 驗證金鑰,並註冊遠端通知FCM

事前準備

  • 安裝下列項目:

    • Xcode 15.2 以上版本
  • 請確認您的專案符合下列規定:

    • 您的專案必須指定以下平台版本或以上版本:
      • iOS 13
      • macOS 10.15
      • tvOS 13
      • watchOS 7
  • 設定實體 Apple 裝置來執行應用程式,並完成下列工作:

    • 取得 Apple Developer 帳戶的 Apple 推播通知驗證金鑰。
    • 在 XCode 中依序前往「App」>「Capabilities」,啟用推播通知。

如果您還沒有 Xcode 專案,但想試用 Firebase 產品,可以下載我們的快速入門範例

建立 Firebase 專案

如要將 Firebase 新增至 Apple 應用程式,您必須先建立 Firebase 專案,才能連結至應用程式。請參閱「瞭解 Firebase 專案」,進一步瞭解 Firebase 專案。

透過 Firebase 註冊應用程式

如要在 Apple 應用程式中使用 Firebase,您必須透過 Firebase 專案註冊應用程式。註冊應用程式通常稱為將應用程式「新增」至專案。

  1. 前往 Firebase 控制台

  2. 在專案總覽頁面的中間,按一下 iOS+ 圖示,啟動設定工作流程。

    如果您已將應用程式新增至 Firebase 專案,請按一下「Add app」,顯示平台選項。

  3. 在「軟體包 ID」欄位中輸入應用程式的軟體包 ID。

  4. (選用) 輸入其他應用程式資訊:應用程式暱稱App Store ID

  5. 按一下 [Register app] (註冊應用程式)

新增 Firebase 設定檔

  1. 按一下「Download GoogleService-Info.plist」,取得 Firebase Apple 平台設定檔 (GoogleService-Info.plist)。

  2. 將設定檔移至 Xcode 專案的根目錄。如果系統提示,請選取將設定檔新增至所有目標。

如果專案中有多個套件 ID,您必須在 Firebase 主控台中將每個套件 ID 與已註冊的應用程式建立關聯,讓每個應用程式都能擁有自己的 GoogleService-Info.plist 檔案。

在應用程式中新增 Firebase SDK

使用 Swift Package Manager 安裝及管理 Firebase 依附元件。

  1. 在 Xcode 中保持開啟應用程式專案,然後依序點選「File」>「Add Packages」
  2. 系統顯示提示訊息時,請新增 Firebase Apple 平台 SDK 存放區:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. 選擇 Firebase Cloud Messaging 程式庫。
  5. -ObjC 標記新增至目標的建構設定「Other Linker Flags」部分。
  6. 為提供最佳的 Firebase Cloud Messaging 體驗,建議您在 Firebase 專案中啟用 Google Analytics,並將 Google Analytics 專用 Firebase SDK 新增至應用程式。您可以選擇不收集 IDFA 的程式庫,也可以選擇收集 IDFA 的程式庫。
  7. 完成後,Xcode 就會自動開始在背景中解析並下載依附元件。

上傳 APNs 驗證金鑰

將 APNs 驗證金鑰上傳至 Firebase。如果您尚未取得 APNs 驗證金鑰,請務必前往 Apple Developer Member Center 建立金鑰。

  1. Firebase 控制台的專案中,依序選取齒輪圖示、「Project Settings」(專案設定),然後選取「Cloud Messaging」分頁。

  2. 在「iOS 應用程式設定」下方的「APNs 驗證金鑰」中,按一下「上傳」按鈕。

  3. 瀏覽至儲存金鑰的位置,選取金鑰,然後按一下「Open」。新增金鑰的金鑰 ID (可在 Apple Developer Member Center 中找到),然後按一下「上傳」

在應用程式中初始化 Firebase

您必須在應用程式中加入 Firebase 初始化程式碼。匯入 Firebase 模組,並設定共用例項,如下所示:

  1. UIApplicationDelegate 中匯入 FirebaseCore 模組,以及應用程式委派程式使用的任何其他 Firebase 模組。例如,如要使用 Cloud FirestoreAuthentication

    SwiftUI

    import SwiftUI
    import FirebaseCore
    import FirebaseFirestore
    import FirebaseAuth
    // ...
          

    Swift

    import FirebaseCore
    import FirebaseFirestore
    import FirebaseAuth
    // ...
          

    Objective-C

    @import FirebaseCore;
    @import FirebaseFirestore;
    @import FirebaseAuth;
    // ...
          
  2. 在應用程式委派作業的 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];
  3. 如果您使用 SwiftUI,則必須建立應用程式委派程式,並透過 UIApplicationDelegateAdaptorNSApplicationDelegateAdaptor 將其附加至 App 結構體。您也必須停用應用程式委派程式 swizzling。詳情請參閱 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);
  });

Java

// 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

如要將訊息傳送至主題組合,請指定條件,這是指定目標主題的布林運算式。舉例來說,下列條件會將訊息傳送至已訂閱 TopicATopicBTopicC 的裝置:

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

FCM 會先評估括號中的任何條件,然後從左到右評估運算式。在上述運算式中,訂閱任何單一主題的使用者都不會收到訊息。同樣地,未訂閱 TopicA 的使用者也不會收到這則訊息。以下組合會收到這項通知:

  • TopicATopicB
  • TopicATopicC

您最多可以在條件式運算式中加入五個主題。

如要傳送至條件:

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

Java

// 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

後續步驟