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
相关产品推荐
相关产品推荐

