You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift+Firestore:如何实现跨ViewController接收应用内通知?

解决方案

一、高效管理Firestore快照监听器

直接在AppDelegate挂载监听器会导致长期存活、浪费资源,推荐用单例类统一管控,灵活控制监听器生命周期:

  • 新建NotificationManager单例,专门负责Firestore监听和全局通知分发
  • 用户登录时启动监听器,登出时立即移除所有监听,避免内存泄漏与无效请求
  • 监听器触发数据变化时,通过NotificationCenter发送全局通知,所有ViewController均可订阅接收

示例代码:

class NotificationManager {
    static let shared = NotificationManager()
    private var listeners: [ListenerRegistration] = []
    
    private init() {}
    
    func startListeningForUpdates() {
        // 监听好友请求状态变更
        let friendRequestListener = Firestore.firestore()
            .collection("friendRequests")
            .whereField("recipientId", isEqualTo: CurrentUser.id)
            .addSnapshotListener { snapshot, error in
                guard let snapshot = snapshot else { return }
                for change in snapshot.documentChanges where change.type == .modified {
                    let requestData = change.document.data()
                    if let status = requestData["status"] as? String, status == "rejected" {
                        NotificationCenter.default.post(
                            name: .friendRequestRejected,
                            object: nil,
                            userInfo: ["request": requestData]
                        )
                    }
                }
            }
        listeners.append(friendRequestListener)
        
        // 监听新消息
        let messageListener = Firestore.firestore()
            .collection("messages")
            .whereField("recipientId", isEqualTo: CurrentUser.id)
            .addSnapshotListener { snapshot, error in
                guard let snapshot = snapshot else { return }
                for change in snapshot.documentChanges where change.type == .added {
                    let messageData = change.document.data()
                    NotificationCenter.default.post(
                        name: .newMessageReceived,
                        object: nil,
                        userInfo: ["message": messageData]
                    )
                }
            }
        listeners.append(messageListener)
    }
    
    func stopAllListeners() {
        listeners.forEach { $0.remove() }
        listeners.removeAll()
    }
}

// 定义全局通知名
extension Notification.Name {
    static let friendRequestRejected = Notification.Name("friendRequestRejected")
    static let newMessageReceived = Notification.Name("newMessageReceived")
}

在ViewController中订阅通知:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleFriendRequestRejected(_:)),
        name: .friendRequestRejected,
        object: nil
    )
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleNewMessage(_:)),
        name: .newMessageReceived,
        object: nil
    )
}

@objc private func handleFriendRequestRejected(_ notification: Notification) {
    let alert = UIAlertController(title: "提示", message: "好友请求被拒绝", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "确定", style: .default))
    present(alert, animated: true)
}

@objc private func handleNewMessage(_ notification: Notification) {
    guard let message = notification.userInfo?["message"] as? [String: Any] else { return }
    let senderName = message["senderName"] as? String ?? "未知用户"
    let content = message["content"] as? String ?? ""
    let alert = UIAlertController(title: "新消息", message: "\(senderName): \(content)", preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "查看", style: .default) { _ in
        // 跳转至聊天界面逻辑
    })
    alert.addAction(UIAlertAction(title: "忽略", style: .cancel))
    present(alert, animated: true)
}

// 销毁时移除观察者
deinit {
    NotificationCenter.default.removeObserver(self)
}

二、推送通知的补充方案

若用户禁用系统推送,可通过静默推送触发应用内逻辑(需用户开启远程通知权限,完全禁用则无法生效):

  • 配置FCM静默推送,payload包含content-available: 1,不会显示系统通知,但可唤醒后台应用
  • 应用接收静默推送后,查询Firestore获取最新数据,通过UNUserNotificationCenter发送本地应用内通知

示例FCM payload:

{
  "to": "用户FCM令牌",
  "content_available": true,
  "data": {
    "type": "friend_request_rejected",
    "requestId": "xxx"
  }
}

在AppDelegate中处理静默推送:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
    guard let type = userInfo["type"] as? String else {
        completionHandler(.noData)
        return
    }
    
    switch type {
    case "friend_request_rejected":
        guard let requestId = userInfo["requestId"] as? String else {
            completionHandler(.failed)
            return
        }
        Firestore.firestore().collection("friendRequests").document(requestId).getDocument { snapshot, error in
            guard let data = snapshot?.data() else {
                completionHandler(.failed)
                return
            }
            // 发送本地通知
            let content = UNMutableNotificationContent()
            content.title = "提示"
            content.body = "好友请求被拒绝"
            content.sound = .default
            
            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
            UNUserNotificationCenter.current().add(request)
            
            completionHandler(.newData)
        }
    case "new_message":
        // 同理处理新消息逻辑
        completionHandler(.newData)
    default:
        completionHandler(.noData)
    }
}

三、最优组合方案

  • 前台活跃状态:依赖单例管理的Firestore快照监听器,实时推送应用内通知,响应最快
  • 后台/未启动状态:优先使用FCM静默推送唤醒应用,同步数据后发送本地通知;若用户允许系统通知,可同时发送普通推送
  • 用户完全禁用推送:只能在用户下次打开应用时,通过监听器批量查询未读消息/请求,显示应用内通知

内容的提问来源于stack exchange,提问作者Ozgun

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 02:26:15