Firebase Cloud Messaging推送通知实现

Firebase Cloud Messaging推送通知实现

【免费下载链接】firebase-ios-sdk 适用于苹果应用开发的Firebase SDK。 【免费下载链接】firebase-ios-sdk 项目地址: https://gitcode.com/GitHub_Trending/fi/firebase-ios-sdk

概述

Firebase Cloud Messaging(FCM)是Google提供的跨平台消息推送服务,支持iOS、Android和Web应用。它提供了可靠、高效的消息传递机制,让开发者能够向用户设备发送通知和数据消息。

本文将深入探讨如何在iOS应用中集成FCM,实现完整的推送通知功能。

核心概念

FCM架构

mermaid

关键组件

组件作用说明
FCM Token设备标识唯一标识设备,用于定向推送
APNs TokenApple推送服务iOS设备与APNs的通信凭证
Topics主题订阅基于主题的消息广播机制
Message Types消息类型通知消息和数据消息

环境配置

1. 项目设置

首先需要在Firebase控制台创建项目并添加iOS应用:

  1. 访问 Firebase控制台
  2. 创建新项目或选择现有项目
  3. 添加iOS应用,填写Bundle ID
  4. 下载 GoogleService-Info.plist 文件

2. 证书配置

为了接收推送通知,需要配置APNs证书:

  1. 在Apple Developer Center创建App ID
  2. 启用Push Notifications功能
  3. 生成APNs认证密钥或证书
  4. 在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应用提供了强大而灵活的推送通知解决方案。通过本文的详细指南,您应该能够:

  1. 正确配置 FCM环境和APNs证书
  2. 实现完整的 消息接收和处理流程
  3. 管理设备Token 和主题订阅
  4. 处理各种类型的 推送消息
  5. 实施最佳实践 确保性能和安全性

记住,良好的推送通知策略应该:

  • 尊重用户的选择和隐私
  • 提供有价值的内容
  • 优化性能和电池使用
  • 包含适当的错误处理和监控

通过遵循这些指导原则,您可以为用户提供出色的推送通知体验,同时确保应用的稳定性和可靠性。

【免费下载链接】firebase-ios-sdk 适用于苹果应用开发的Firebase SDK。 【免费下载链接】firebase-ios-sdk 项目地址: https://gitcode.com/GitHub_Trending/fi/firebase-ios-sdk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值