SwiftUI List搭配Firebase监听childAdded时出现内存泄漏问题求助
内存泄漏根因定位
- 未主动移除Firebase数据库监听:Firebase Realtime Database的
observe系列方法注册的事件监听会被SDK内部长期持有,必须显式调用移除方法才会释放。你当前的代码只注册了childAdded类型的监听,没有做任何移除操作,而DatabaseManager是全局单例,生命周期和应用一致,注册的监听会永久存在——哪怕ChatView、ChatViewModel已经被销毁,监听关联的闭包、闭包捕获的变量都会持续占用内存,这是泄漏的核心诱因。你在闭包中使用[weak self]只是避免了闭包持有ChatViewModel导致ViewModel无法释放的问题,并没有解决SDK持有监听本身带来的泄漏。 - 未处理重复注册场景:如果多次进入同一会话、或者切换会话,代码会重复注册多个监听,不仅会加剧内存泄漏,还会出现同一条消息被多次回调的异常。
修复方案
步骤1:改造DatabaseManager的监听逻辑
给监听方法新增返回值(返回Firebase的监听句柄),同时新增移除监听的接口:
// 监听方法新增返回UInt类型的监听句柄 func observeMessagesForConversation(conversationId id :String,completion : @escaping ([MessageModel]) -> Void ) -> UInt { return database.child(id).child("messages").observe(.childAdded) { snapshot in guard let value = snapshot.value as? [String:Any] else { completion([]) return } let decoder = JSONDecoder() guard let jsonData = try? JSONSerialization.data(withJSONObject:value), let message = try? decoder.decode(MessageModel.self, from: jsonData) else { completion([]) return } completion([message]) } } // 新增移除指定会话消息监听的方法 func removeMessageObserver(conversationId id: String, handle: UInt) { database.child(id).child("messages").removeObserver(withHandle: handle) }
步骤2:改造ChatViewModel逻辑
新增属性存储当前监听句柄,在切换会话、ViewModel销毁时主动移除监听:
class ChatViewModel : ObservableObject { @Published private(set) var messages : [MessageModel] = [] private(set) var conversationID : String? = nil // 存储当前消息监听的句柄 private var messageObserverHandle: UInt? func setConverationID(convesationID : String?){ // 切换会话前先移除旧的监听,避免重复注册 removeCurrentObserver() guard let convesationID = convesationID else { print("CONVERSATION ID DOES NOT EXIST") return } self.conversationID = convesationID startObservingConversation() } private func startObservingConversation(){ guard let conversationID = self.conversationID else { return } // 保存监听返回的句柄 messageObserverHandle = DatabaseManager.shared.observeMessagesForConversation(conversationId: conversationID) { [weak self] message in self?.messages += message } } // 移除当前已注册的监听 private func removeCurrentObserver() { if let handle = messageObserverHandle, let conversationID = conversationID { DatabaseManager.shared.removeMessageObserver(conversationId: conversationID, handle: handle) messageObserverHandle = nil } } // ViewModel销毁时自动移除监听 deinit { removeCurrentObserver() } }
改造完成后,当ChatView页面退出销毁时,@StateObject修饰的ChatViewModel会同步被销毁,触发deinit方法自动移除数据库监听,即可彻底解决本次内存泄漏问题。
内容的提问来源于stack exchange,提问作者keyvan yaghoubian
相关产品推荐
相关产品推荐

