我有一个应用程序可以从 Firestore 获取和收听群聊。
我在我的 fetchGroups 函数中添加了 3 个侦听器,以根据组是否已保存或组所有者是否已阻止/已被当前用户阻止来过滤组。
过滤机制有效,但一旦加载视图并获取组就会导致内存泄漏。
仅当使用一个或多个侦听器进行过滤时才会发生泄漏,这意味着,例如,如果我删除
!message.isSaved
过滤器并保留剩余的2个过滤器并且没有人阻止我+我没有阻止任何人,我没有得到泄漏,否则我做...
正如预期的那样,当我卸下所有过滤器时
if !message.isSaved || message.hasBlocked || message.isBlocked
我一点也没有泄漏。
这是我使用 Instrument 时的泄漏结果:
这是我的代码:
群聊VC
private var lastDocumentSnapshot: DocumentSnapshot?
private var listener: [ListenerRegistration] = []
private var isListening = true
private var theyBlockedU: [String] = []
private var uBlockedThem: [String] = []
private var groups = [Group]()
override func viewDidLoad() {
super.viewDidLoad()
fetchGroups()
}
override func viewWillAppear(_ animated: Bool) {
self.isListening = true
}
override func viewDidDisappear(_ animated: Bool) {
self.isListening = false
}
// MARK: - API
func fetchGroups() {
self.fetchGroups() { [weak self] groups in
guard let self = self else{return}
groups.forEach { group in
self.groupsDictionary[group.recentMessage.groupID] = group
}
self.groups = Array(self.groupsDictionary.values)
self.groups.sort(by: { $0.recentMessage.timestamp.compare($1.recentMessage.timestamp) == .orderedDescending })
self.tableView.reloadData()
}
self.tableView.refreshControl?.endRefreshing()
}
func fetchGroups(completion: @escaping([Group]) -> Void) {
var query: Query!
tableView.refreshControl?.beginRefreshing()
if groups.isEmpty {
query = COLLECTION_GROUPS.order(by: "timestamp", descending: false)
} else {
if let lastDocSnapshot = lastDocumentSnapshot {
query = COLLECTION_GROUPS.order(by: "timestamp", descending: false).end(beforeDocument: lastDocSnapshot)
} else {
print("Error: lastDocumentSnapshot is nil")
return
}
}
let listener = query.addSnapshotListener { [weak self] (snapshot, err) in
guard let self = self else{return}
if let err = err {
print("\(err.localizedDescription)")
} else if snapshot!.isEmpty {
self.isLoading = false
self.stopActivityIndicator()
self.tableView.refreshControl?.endRefreshing()
return
}
guard let lastSnap = snapshot?.documents.first else {return}
self.lastDocumentSnapshot = lastSnap
snapshot?.documentChanges.forEach({ (change) in
let dictionary = change.document.data()
var message = GroupMessage(dictionary: dictionary)
UserService.fetchUser(withUid: message.senderID) { [weak self] user in
guard let self = self else { return }
self.isUserBlocked(ownerID: message.ownerID) { [weak self] isBlocked in
guard let self = self else { return }
message.isBlocked = isBlocked
self.isBlockedByUser(ownerID: message.ownerID) { [weak self] hasBlocked in
guard let self = self else { return }
message.hasBlocked = hasBlocked
self.listenForGroupSaved(groupID: message.groupID) { [weak self] isSaved in
guard let self = self else { return }
message.isSaved = isSaved
let group = Group(user: user, recentMessage: message)
if !message.isSaved || message.hasBlocked || message.isBlocked { // If one of those is used, i get leaks...
self.groups = self.groups.filter { $0.recentMessage.groupID != message.groupID }
self.tableView.reloadData()
return
} else {
if let index = self.groups.firstIndex(where: { $0.recentMessage.groupID == message.groupID }) {
self.groups[index] = group
} else {
self.groups.append(group)
self.groups.sort(by: { $0.recentMessage.timestamp.compare($1.recentMessage.timestamp) == .orderedDescending })
}
self.groups.sort(by: { $0.recentMessage.timestamp.compare($1.recentMessage.timestamp) == .orderedDescending })
self.tableView.reloadData()
}
}
}
}
}
})
self.tableView.refreshControl?.endRefreshing()
self.lastDocumentSnapshot = snapshot?.documents.first
}
if isListening == false { listener.remove()}
}
func listenForGroupSaved(groupID: String, completion: @escaping (Bool) -> Void) {
guard let currentUser = Auth.auth().currentUser else {return}
let docRef = COLLECTION_USERS.document(currentUser.uid).collection("isGroupSaved").document(groupID)
let listener = docRef.addSnapshotListener { (snapshot, error) in
if let error = error {
print("Error listening for savedGroup changes: \(error.localizedDescription)")
return
}
if let snapshot = snapshot, snapshot.exists {
guard let isSaved = snapshot.data()?["isSaved"] as? Bool else{return}
completion(isSaved)
} else {
// Provide a default value for defaultIsSaved if no document is found
let defaultIsSaved = false
completion(defaultIsSaved)
}
}
if isListening == false { listener.remove()}
}
func isUserBlocked(ownerID: String, completion: @escaping(Bool) -> Void) {
guard let currentUser = Auth.auth().currentUser else { return }
let listener = COLLECTION_USERS.document(currentUser.uid).collection("blocked").addSnapshotListener { [weak self] (querySnapshot, err) in
guard let self = self else { return }
if let err = err {
print("Error getting documents: \(err)")
} else {
self.uBlockedThem = querySnapshot!.documents.map({ $0.documentID })
let isBlocked = self.uBlockedThem.contains(ownerID)
completion(isBlocked)
}
}
if isListening == false { listener.remove()}
}
func isBlockedByUser(ownerID: String, completion: @escaping(Bool) -> Void) {
guard let currentUser = Auth.auth().currentUser else {return}
let listener = COLLECTION_USERS.document(currentUser.uid).collection("isBlockedBy").addSnapshotListener { [weak self] (querySnapshot, err) in
guard let self = self else { return }
if let err = err {
print("Error getting documents: \(err)")
} else {
self.theyBlockedU = querySnapshot!.documents.map({ $0.documentID })
let isBlocked = self.theyBlockedU.contains(ownerID)
completion(isBlocked)
}
}
if isListening == false { listener.remove()}
}
集团结构
struct Group {
var user: User
var recentMessage: GroupMessage
}
struct GroupMessage {
var user: User?
var text: String
var senderID: String
var groupID : String
var ownerID: String
var timestamp: Timestamp
var isSaved: Bool
var isBlocked: Bool
var hasBlocked: Bool
init(dictionary: [String: Any]) {
self.text = dictionary["text"] as? String ?? ""
self.senderID = dictionary["senderID"] as? String ?? ""
self.groupID = dictionary["groupID"] as? String ?? ""
self.ownerID = dictionary["ownerID"] as? String ?? ""
self.timestamp = dictionary["timestamp"] as? Timestamp ?? Timestamp(date: Date())
self.isSaved = dictionary["isSaved"] as? Bool ?? false
self.isBlocked = dictionary["isBlocked"] as? Bool ?? false
self.hasBlocked = dictionary["hasBlocked"] as? Bool ?? false
}
}