שליחת הודעות למספר מכשירים בפלטפורמות של Apple

כדי לשלוח הודעה למספר מכשירים, צריך להשתמש בהודעות בנושאים. התכונה הזו מאפשרת לשלוח הודעה לכמה מכשירים שהמשתמשים בהם הצטרפו לנושא מסוים.

המדריך הזה מתמקד בשליחת הודעות לנושא משרת האפליקציה באמצעות Admin SDK או REST API של FCM, וקבלתן וטיפול בהן באפליקציית אפל. בדף הזה מפורטים כל השלבים לביצוע הפעולות האלה, מההגדרה ועד האימות. יכול להיות שחלק מהשלבים כבר בוצעו אם הגדרתם אפליקציית לקוח של אפל ל-FCM או אם ביצעתם את השלבים לשליחת ההודעה הראשונה.

הוספת Firebase לפרויקט Apple

בקטע הזה מפורטות משימות שאולי כבר ביצעתם אם הפעלתם תכונות אחרות של Firebase באפליקציה. כדי להשתמש ב-FCM, תצטרכו להעלות את מפתח האימות של APNs ולהירשם לקבלת התראות מרחוק.

דרישות מוקדמות

  • מתקינים את הרכיבים הבאים:

    • ‫Xcode 16.2 ואילך
  • חשוב לוודא שהפרויקט עומד בדרישות הבאות:

    • הפרויקט שלכם צריך להיות מיועד לגרסאות הפלטפורמה האלה ומעלה:
      • iOS 13
      • macOS 10.15
      • tvOS 13
      • watchOS 7
  • מגדירים מכשיר Apple פיזי להרצת האפליקציה, ומבצעים את המשימות הבאות:

    • משיגים מפתח אימות של Apple Push Notification בשביל חשבון Apple Developer.
    • מפעילים את ההתראות בדחיפה ב-XCode בקטע App > Capabilities.

אם עדיין אין לכם פרויקט Xcode ואתם רק רוצים לנסות מוצר של Firebase, אתם יכולים להוריד אחת מדוגמאות ההפעלה המהירה שלנו.

יצירת פרויקט Firebase

לפני שמוסיפים את Firebase לאפליקציית Apple, צריך ליצור פרויקט Firebase כדי לקשר לאפליקציה. במאמר הסבר על פרויקטים ב-Firebase יש מידע נוסף על פרויקטים ב-Firebase.

רישום האפליקציה ב-Firebase

כדי להשתמש ב-Firebase באפליקציית Apple, צריך לרשום את האפליקציה בפרויקט Firebase. רישום האפליקציה נקרא לעיתים קרובות "הוספה" של האפליקציה לפרויקט.

  1. עוברים אל מסוף Firebase.

  2. במרכז הדף של סקירת הפרויקט, לוחצים על הסמל iOS+‎ כדי להפעיל את תהליך ההגדרה.

    אם כבר הוספתם אפליקציה לפרויקט Firebase, לוחצים על הוספת אפליקציה כדי להציג את אפשרויות הפלטפורמה.

  3. מזינים את מזהה החבילה של האפליקציה בשדה מזהה החבילה.

  4. (אופציונלי) מזינים פרטים נוספים על האפליקציה: כינוי האפליקציה ומזהה האפליקציה בחנות.

  5. לוחצים על רישום האפליקציה.

הוספת קובץ הגדרות של Firebase

  1. לוחצים על Download GoogleService-Info.plist (הורדה של GoogleService-Info.plist) כדי לקבל את קובץ ההגדרות של אפליקציית Firebase ‏(GoogleService-Info.plist).

  2. מעבירים את קובץ ההגדרות לרמה הבסיסית (root) של פרויקט Xcode. אם מוצגת בקשה, בוחרים להוסיף את קובץ ההגדרות לכל יעדי ההעברה.

אם יש לכם כמה מזהי חבילות בפרויקט, אתם צריכים לשייך כל מזהה חבילה לאפליקציה רשומה במסוף Firebase, כדי שלכל אפליקציה יהיה קובץ GoogleService-Info.plist משלה.

הוספת ערכות SDK של Firebase לאפליקציה

משתמשים ב-Swift Package Manager כדי להתקין ולנהל יחסי תלות ב-Firebase.

  1. ב-Xcode, כשהפרויקט של האפליקציה פתוח, עוברים אל File > Add Packages (קובץ > הוספת חבילות).
  2. כשמוצגת בקשה, מוסיפים את מאגר Firebase Apple platforms SDK:
  3.   https://github.com/firebase/firebase-ios-sdk.git
  4. בוחרים את הספרייה Firebase Cloud Messaging.
  5. מוסיפים את הדגל -ObjC לקטע Other Linker Flags בהגדרות הבנייה של היעד.
  6. כדי ליהנות מחוויה אופטימלית עם Firebase Cloud Messaging, מומלץ להפעיל את Google Analytics בפרויקט Firebase ולהוסיף את Firebase SDK for Google Analytics לאפליקציה. אפשר לבחור בספרייה ללא איסוף של IDFA או עם איסוף של IDFA. אפשר לעיין בשאלות הנפוצות בנושא הארגון העדכני של מודולים ב-Google Analytics עבור Firebase SDK.
  7. אחרי שתסיימו, פלטפורמת Xcode תתחיל באופן אוטומטי לטפל ביחסי התלות ולהוריד אותם ברקע.

העלאת מפתח אימות של APNs

מעלים את מפתח האימות של APNs ל-Firebase. אם עדיין אין לכם מפתח אימות של APNs, הקפידו ליצור אותו ב-Apple Developer Member Center.

  1. בפרויקט במסוף Firebase, לוחצים על סמל גלגל השיניים, בוחרים באפשרות הגדרות הפרויקט ואז בוחרים בכרטיסייה Cloud Messaging.

  2. בקטע הגדרת אפליקציה ל-iOS, בשדה מפתח אימות של APNs, לוחצים על לחצן העלאה.

  3. מדפדפים למיקום שבו שמרתם את המפתח, בוחרים אותו ולוחצים על פתיחה. מוסיפים את מזהה המפתח (שזמין ב- Apple Developer Member Center) ולוחצים על העלאה.

הפעלת Firebase באפליקציה

תצטרכו להוסיף קוד אתחול של Firebase לאפליקציה. מייבאים את מודול Firebase ומגדירים מופע משותף כמו שמוצג:

  1. מייבאים את המודול FirebaseCore אל UIApplicationDelegate, וגם את כל מודולי 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;
    // ...
          
  2. מגדירים מופע משותף של FirebaseApp בשיטה application(_:didFinishLaunchingWithOptions:) של נציג האפליקציה:

    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, אתם צריכים ליצור נציג אפליקציה ולצרף אותו למבנה App באמצעות UIApplicationDelegateAdaptor או NSApplicationDelegateAdaptor. צריך גם להשבית את החלפת השיטות של נציג האפליקציה. מידע נוסף זמין בהוראות ל-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 מנסה שוב באופן אוטומטי. במקרים שבהם אי אפשר להשלים את המינוי, המינוי מחזיר שגיאה שאפשר לטפל בה באמצעות handler להשלמה, כמו שמוצג כאן:

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

כדי לשלוח הודעה לשילוב של נושאים, צריך לציין תנאי, שהוא ביטוי בוליאני שמציין את נושאי היעד. לדוגמה, התנאי הבא ישלח הודעות למכשירים שמנויים ל-TopicA ול-TopicB או ל-TopicC:

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

הפונקציה FCM בודקת קודם את התנאים שבסוגריים, ואז בודקת את הביטוי משמאל לימין. בביטוי שלמעלה, משתמש שנרשם לנושא יחיד לא יקבל את ההודעה. באופן דומה, משתמש שלא נרשם למינוי ל-TopicA לא יקבל את ההודעה. השילובים האלה מקבלים את הנתונים:

  • TopicA וגם TopicB
  • TopicA וגם TopicC

אפשר לכלול עד חמישה נושאים בביטוי המותנה.

כדי לשלוח למישהו בתנאי:

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

השלבים הבאים