Firebase Cloud Messaging推送通知实现
2026-02-04 04:53:29作者:庞眉杨Will
概述
Firebase Cloud Messaging(FCM)是Google提供的跨平台消息推送服务,支持iOS、Android和Web应用。它提供了可靠、高效的消息传递机制,让开发者能够向用户设备发送通知和数据消息。
本文将深入探讨如何在iOS应用中集成FCM,实现完整的推送通知功能。
核心概念
FCM架构
flowchart TD
A[开发者服务器] -->|发送消息| B[FCM服务器]
B -->|推送通知| C[iOS设备]
C -->|注册Token| D[开发者服务器]
D -->|存储Token| A
关键组件
| 组件 | 作用 | 说明 |
|---|---|---|
| FCM Token | 设备标识 | 唯一标识设备,用于定向推送 |
| APNs Token | Apple推送服务 | iOS设备与APNs的通信凭证 |
| Topics | 主题订阅 | 基于主题的消息广播机制 |
| Message Types | 消息类型 | 通知消息和数据消息 |
环境配置
1. 项目设置
首先需要在Firebase控制台创建项目并添加iOS应用:
- 访问 Firebase控制台
- 创建新项目或选择现有项目
- 添加iOS应用,填写Bundle ID
- 下载
GoogleService-Info.plist文件
2. 证书配置
为了接收推送通知,需要配置APNs证书:
- 在Apple Developer Center创建App ID
- 启用Push Notifications功能
- 生成APNs认证密钥或证书
- 在Firebase控制台上传APNs凭证
代码实现
1. 初始化配置
在 AppDelegate.swift 中配置Firebase和推送通知:
import FirebaseCore
import FirebaseMessaging
import UserNotifications
@main
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 配置Firebase
FirebaseApp.configure()
// 设置消息代理
Messaging.messaging().delegate = self
// 配置推送通知
configurePushNotifications(application)
return true
}
private func configurePushNotifications(_ application: UIApplication) {
let center = UNUserNotificationCenter.current()
center.delegate = self
// 请求通知权限
center.requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in
if let error = error {
print("通知权限请求失败: \(error)")
}
}
// 注册远程通知
application.registerForRemoteNotifications()
}
}
2. 处理APNs Token
extension AppDelegate {
// 成功注册APNs时调用
func application(_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
// 将APNs token设置到Messaging
Messaging.messaging().apnsToken = deviceToken
print("APNs token received: \(deviceToken.reduce("", {$0 + String(format: "%02X", $1)}))")
}
// 注册APNs失败时调用
func application(_ application: UIApplication,
didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("APNs注册失败: \(error)")
}
}
3. 实现MessagingDelegate
extension AppDelegate: MessagingDelegate {
// 接收FCM Token刷新
func messaging(_ messaging: Messaging,
didReceiveRegistrationToken fcmToken: String?) {
guard let token = fcmToken else { return }
print("FCM Token: \(token)")
// 将token发送到您的服务器
sendTokenToServer(token)
}
private func sendTokenToServer(_ token: String) {
// 实现将token发送到您的后端服务器
let url = URL(string: "https://your-server.com/register-device")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: Any] = [
"deviceToken": token,
"platform": "ios",
"appVersion": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body)
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Token发送失败: \(error)")
} else {
print("Token发送成功")
}
}.resume()
}
}
4. 处理接收到的消息
extension AppDelegate {
// 处理前台通知显示
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// 处理消息内容
handleMessage(userInfo)
// 即使应用在前台也显示通知
completionHandler([.banner, .sound, .badge])
}
// 处理通知点击
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
// 处理通知点击逻辑
handleNotificationTap(userInfo)
completionHandler()
}
// 处理静默推送和数据消息
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
// 记录消息传递指标
Messaging.serviceExtension().exportDeliveryMetricsToBigQuery(withMessageInfo: userInfo)
// 处理数据消息
handleDataMessage(userInfo)
completionHandler(.newData)
}
private func handleMessage(_ userInfo: [AnyHashable: Any]) {
// 解析消息内容
if let aps = userInfo["aps"] as? [String: Any] {
if let alert = aps["alert"] as? [String: String] {
let title = alert["title"] ?? "通知"
let body = alert["body"] ?? ""
print("收到通知: \(title) - \(body)")
}
}
// 处理自定义数据
if let customData = userInfo["customData"] as? [String: Any] {
print("自定义数据: \(customData)")
}
}
}
高级功能
1. 主题订阅
class NotificationManager {
static let shared = NotificationManager()
private init() {}
// 订阅主题
func subscribeToTopic(_ topic: String) {
Messaging.messaging().subscribe(toTopic: topic) { error in
if let error = error {
print("订阅主题失败: \(topic), 错误: \(error)")
} else {
print("成功订阅主题: \(topic)")
}
}
}
// 取消订阅
func unsubscribeFromTopic(_ topic: String) {
Messaging.messaging().unsubscribe(fromTopic: topic) { error in
if let error = error {
print("取消订阅失败: \(topic), 错误: \(error)")
} else {
print("成功取消订阅: \(topic)")
}
}
}
// 批量订阅管理
func manageTopics(subscriptions: [String], unsubscriptions: [String]) {
let group = DispatchGroup()
// 处理订阅
for topic in subscriptions {
group.enter()
subscribeToTopic(topic)
group.leave()
}
// 处理取消订阅
for topic in unsubscriptions {
group.enter()
unsubscribeFromTopic(topic)
group.leave()
}
}
}
2. Token管理
class TokenManager {
static let shared = TokenManager()
// 获取当前FCM Token
func getFCMToken(completion: @escaping (String?) -> Void) {
Messaging.messaging().token { token, error in
if let error = error {
print("获取Token失败: \(error)")
completion(nil)
} else {
completion(token)
}
}
}
// 删除Token
func deleteToken(completion: @escaping (Bool) -> Void) {
Messaging.messaging().deleteToken { error in
if let error = error {
print("删除Token失败: \(error)")
completion(false)
} else {
print("Token删除成功")
completion(true)
}
}
}
// 监控Token刷新
func monitorTokenRefresh() {
NotificationCenter.default.addObserver(
forName: Notification.Name.MessagingRegistrationTokenRefreshed,
object: nil,
queue: .main
) { notification in
self.getFCMToken { token in
if let token = token {
print("Token已刷新: \(token)")
// 更新服务器上的token
self.updateTokenOnServer(token)
}
}
}
}
}
3. 消息处理工具类
struct FCMMessage {
let title: String?
let body: String?
let sound: String?
let badge: Int?
let customData: [String: Any]
let messageType: MessageType
enum MessageType {
case notification
case data
case silent
}
init?(userInfo: [AnyHashable: Any]) {
guard let aps = userInfo["aps"] as? [String: Any] else {
return nil
}
// 解析通知内容
if let alert = aps["alert"] as? [String: String] {
self.title = alert["title"]
self.body = alert["body"]
} else if let alert = aps["alert"] as? String {
self.title = alert
self.body = nil
} else {
self.title = nil
self.body = nil
}
self.sound = aps["sound"] as? String
self.badge = aps["badge"] as? Int
// 解析自定义数据
var customData = userInfo
customData.removeValue(forKey: "aps")
self.customData = customData
// 判断消息类型
if aps["content-available"] as? Int == 1 {
self.messageType = .silent
} else if self.title != nil || self.body != nil {
self.messageType = .notification
} else {
self.messageType = .data
}
}
}
class MessageHandler {
static func handleMessage(_ userInfo: [AnyHashable: Any]) {
guard let message = FCMMessage(userInfo: userInfo) else {
print("无法解析消息内容")
return
}
switch message.messageType {
case .notification:
handleNotificationMessage(message)
case .data:
handleDataMessage(message)
case .silent:
handleSilentMessage(message)
}
}
private static func handleNotificationMessage(_ message: FCMMessage) {
print("处理通知消息: \(message.title ?? "无标题")")
// 这里可以添加具体的业务逻辑
if let deepLink = message.customData["deepLink"] as? String {
handleDeepLink(deepLink)
}
}
private static func handleDataMessage(_ message: FCMMessage) {
print("处理数据消息")
// 处理应用内数据更新
if let updateType = message.customData["updateType"] as? String {
switch updateType {
case "userProfile":
updateUserProfile(from: message.customData)
case "config":
updateAppConfig(from: message.customData)
default:
break
}
}
}
private static func handleSilentMessage(_ message: FCMMessage) {
print("处理静默消息")
// 后台数据同步
if let syncType = message.customData["syncType"] as? String {
BackgroundSyncManager.syncData(type: syncType)
}
}
}
错误处理与调试
1. 错误处理
enum FCMError: Error, LocalizedError {
case tokenFetchFailed(Error)
case tokenDeleteFailed(Error)
case subscriptionFailed(String, Error)
case unsubscriptionFailed(String, Error)
case permissionDenied
case apnsRegistrationFailed
var errorDescription: String? {
switch self {
case .tokenFetchFailed(let error):
return "Token获取失败: \(error.localizedDescription)"
case .tokenDeleteFailed(let error):
return "Token删除失败: \(error.localizedDescription)"
case .subscriptionFailed(let topic, let error):
return "订阅主题失败(\(topic)): \(error.localizedDescription)"
case .unsubscriptionFailed(let topic, let error):
return "取消订阅失败(\(topic)): \(error.localizedDescription)"
case .permissionDenied:
return "用户拒绝了通知权限"
case .apnsRegistrationFailed:
return "APNs注册失败"
}
}
}
class ErrorHandler {
static func handleFCMError(_ error: Error) {
if let fcmError = error as? FCMError {
print("FCM错误: \(fcmError.errorDescription ?? "未知错误")")
// 根据错误类型采取不同的处理策略
switch fcmError {
case .permissionDenied:
showPermissionAlert()
case .tokenFetchFailed:
scheduleTokenRetry()
default:
break
}
} else {
print("未知错误: \(error.localizedDescription)")
}
}
private static func showPermissionAlert() {
// 显示引导用户开启通知权限的提示
DispatchQueue.main.async {
if let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let rootVC = scene.windows.first?.rootViewController {
let alert = UIAlertController(
title: "通知权限被拒绝",
message: "请到设置中开启通知权限以接收重要消息",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "去设置", style: .default) { _ in
if let url = URL(string: UIApplication.openSettingsURLString) {
UIApplication.shared.open(url)
}
})
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
rootVC.present(alert, animated: true)
}
}
}
}
2. 调试工具
class FCMDebugger {
static var isDebugMode = false
static func log(_ message: String, level: LogLevel = .info) {
guard isDebugMode else { return }
let timestamp = DateFormatter.localizedString(
from: Date(),
dateStyle: .none,
timeStyle: .medium
)
let logMessage = "[FCM][\(timestamp)][\(level.rawValue)] \(message)"
switch level {
case .error:
print("❌ \(logMessage)")
case .warning:
print("⚠️ \(logMessage)")
case .info:
print("ℹ️ \(logMessage)")
case .debug:
print("🔍 \(logMessage)")
}
}
enum LogLevel: String {
case error = "ERROR"
case warning = "WARNING"
case info = "INFO"
case debug = "DEBUG"
}
static func dumpMessageInfo(_ userInfo: [AnyHashable: Any]) {
guard isDebugMode else { return }
log("收到消息内容:", level: .debug)
for (key, value) in userInfo {
log(" \(key): \(value)", level: .debug)
}
}
}
最佳实践
1. 性能优化
class FCMPerformanceOptimizer {
// 批量操作延迟
private static let batchDelay: TimeInterval = 0.5
private static var pendingOperations: [() -> Void] = []
private static var batchTimer: Timer?
// 批量订阅操作
static func batchSubscribe(to topics: [String]) {
topics.forEach { topic in
addOperation {
Messaging.messaging().subscribe(toTopic: topic)
}
}
scheduleBatchExecution()
}
// 批量取消订阅
static func batchUnsubscribe(from topics: [String]) {
topics.forEach { topic in
addOperation {
Messaging.messaging().unsubscribe(fromTopic: topic)
}
}
scheduleBatchExecution()
}
private static func addOperation(_ operation: @escaping () -> Void) {
pendingOperations.append(operation)
}
private static func scheduleBatchExecution() {
batchTimer?.invalidate()
batchTimer = Timer.scheduledTimer(
withTimeInterval: batchDelay,
repeats: false
) { _ in
executeBatch()
}
}
private static func executeBatch() {
let operations = pendingOperations
pendingOperations.removeAll()
operations.forEach { $0() }
}
}
2. 安全考虑
class FCMSecurityManager {
// 验证消息来源
static func verifyMessageSource(_ userInfo: [AnyHashable: Any]) -> Bool {
// 检查消息是否包含预期的签名或标识
// 这里可以添加自定义的验证逻辑
guard let from = userInfo["from"] as? String else {
return false
}
// 验证发送者是否为可信来源
let trustedSenders = ["your-firebase-project-id"]
return trustedSenders.contains(from)
}
// 加密敏感数据
static func encryptSensitiveData(_ data: [String: Any]) -> [String: Any] {
var encryptedData = data
// 对敏感字段进行加密处理
if let sensitiveInfo = data["sensitive"] as? String {
encryptedData["sensitive"] = encryptString(sensitiveInfo)
}
return encryptedData
}
private static func encryptString(_ string: String) -> String {
// 实现加密逻辑,这里使用Base64示例
return Data(string.utf8).base64EncodedString()
}
}
测试策略
1. 单元测试
import XCTest
@testable import YourApp
class FCMTests: XCTestCase {
var messaging: Messaging!
var notificationCenter: UNUserNotificationCenter!
override func setUp() {
super.setUp()
messaging = Messaging.messaging()
notificationCenter = UNUserNotificationCenter.current()
}
func testTokenRetrieval() {
let expectation = self.expectation(description: "Token retrieval")
messaging.token { token, error in
XCTAssertNil(error, "Token获取不应失败")
XCTAssertNotNil(token, "应返回有效的Token")
XCTAssertTrue(token?.isEmpty == false, "Token不应为空")
expectation.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
}
func testTopicSubscription() {
let testTopic = "testTopic"
let expectation = self.expectation(description: "Topic subscription")
messaging.subscribe(toTopic: testTopic) { error in
XCTAssertNil(error, "主题订阅不应失败")
// 验证订阅是否成功
// 这里可以添加验证逻辑
expectation.fulfill()
}
waitForExpectations(timeout: 10, handler: nil)
}
}
2. 集成测试
class FCMIntegrationTests: XCTestCase {
func testEndToEndMessageFlow() {
// 模拟接收推送消息
let testMessage: [AnyHashable: Any] = [
"aps": [
"alert": [
"title": "测试标题",
"body": "测试内容"
],
"sound": "default"
],
"customData": [
"deepLink": "app://test/page",
"timestamp": Date().timeIntervalSince1970
]
]
// 模拟应用收到消息
let appDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.application(
UIApplication.shared,
didReceiveRemoteNotification: testMessage,
fetchCompletionHandler: { result in
XCTAssertEqual(result, .newData, "应返回新数据")
}
)
// 验证消息处理结果
// 这里可以添加具体的验证逻辑
}
}
总结
Firebase Cloud Messaging为iOS应用提供了强大而灵活的推送通知解决方案。通过本文的详细指南,您应该能够:
- 正确配置 FCM环境和APNs证书
- 实现完整的 消息接收和处理流程
- 管理设备Token 和主题订阅
- 处理各种类型的 推送消息
- 实施最佳实践 确保性能和安全性
记住,良好的推送通知策略应该:
- 尊重用户的选择和隐私
- 提供有价值的内容
- 优化性能和电池使用
- 包含适当的错误处理和监控
通过遵循这些指导原则,您可以为用户提供出色的推送通知体验,同时确保应用的稳定性和可靠性。
登录后查看全文
热门项目推荐
相关项目推荐
GLM-5智谱 AI 正式发布 GLM-5,旨在应对复杂系统工程和长时域智能体任务。Jinja00
GLM-5-w4a8GLM-5-w4a8基于混合专家架构,专为复杂系统工程与长周期智能体任务设计。支持单/多节点部署,适配Atlas 800T A3,采用w4a8量化技术,结合vLLM推理优化,高效平衡性能与精度,助力智能应用开发Jinja00- QQwen3.5-397B-A17BQwen3.5 实现了重大飞跃,整合了多模态学习、架构效率、强化学习规模以及全球可访问性等方面的突破性进展,旨在为开发者和企业赋予前所未有的能力与效率。Jinja00
Kimi-K2.5Kimi K2.5 是一款开源的原生多模态智能体模型,它在 Kimi-K2-Base 的基础上,通过对约 15 万亿混合视觉和文本 tokens 进行持续预训练构建而成。该模型将视觉与语言理解、高级智能体能力、即时模式与思考模式,以及对话式与智能体范式无缝融合。Python00
MiniMax-M2.5MiniMax-M2.5开源模型,经数十万复杂环境强化训练,在代码生成、工具调用、办公自动化等经济价值任务中表现卓越。SWE-Bench Verified得分80.2%,Multi-SWE-Bench达51.3%,BrowseComp获76.3%。推理速度比M2.1快37%,与Claude Opus 4.6相当,每小时仅需0.3-1美元,成本仅为同类模型1/10-1/20,为智能应用开发提供高效经济选择。【此简介由AI生成】Python00
ruoyi-plus-soybeanRuoYi-Plus-Soybean 是一个现代化的企业级多租户管理系统,它结合了 RuoYi-Vue-Plus 的强大后端功能和 Soybean Admin 的现代化前端特性,为开发者提供了完整的企业管理解决方案。Vue06- RRing-2.5-1TRing-2.5-1T:全球首个基于混合线性注意力架构的开源万亿参数思考模型。Python00
Qwen3.5Qwen3.5 昇腾 vLLM 部署教程。Qwen3.5 是 Qwen 系列最新的旗舰多模态模型,采用 MoE(混合专家)架构,在保持强大模型能力的同时显著降低了推理成本。00
热门内容推荐
最新内容推荐
Degrees of Lewdity中文汉化终极指南:零基础玩家必看的完整教程Unity游戏翻译神器:XUnity Auto Translator 完整使用指南PythonWin7终极指南:在Windows 7上轻松安装Python 3.9+终极macOS键盘定制指南:用Karabiner-Elements提升10倍效率Pandas数据分析实战指南:从零基础到数据处理高手 Qwen3-235B-FP8震撼升级:256K上下文+22B激活参数7步搞定机械键盘PCB设计:从零开始打造你的专属键盘终极WeMod专业版解锁指南:3步免费获取完整高级功能DeepSeek-R1-Distill-Qwen-32B技术揭秘:小模型如何实现大模型性能突破音频修复终极指南:让每一段受损声音重获新生
项目优选
收起
deepin linux kernel
C
27
11
OpenHarmony documentation | OpenHarmony开发者文档
Dockerfile
574
3.85 K
Ascend Extension for PyTorch
Python
388
466
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
356
216
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
897
688
昇腾LLM分布式训练框架
Python
121
147
华为昇腾面向大规模分布式训练的多模态大模型套件,支撑多模态生成、多模态理解。
Python
120
156
🎉 (RuoYi)官方仓库 基于SpringBoot,Spring Security,JWT,Vue3 & Vite、Element Plus 的前后端分离权限管理系统
Vue
1.38 K
782
本项目是CANN开源社区的核心管理仓库,包含社区的治理章程、治理组织、通用操作指引及流程规范等基础信息
599
167
React Native鸿蒙化仓库
JavaScript
311
361