SwiftUI中Firestore Geohash查询实现实时更新结果的问题
SwiftUI中Firestore地理查询的实时更新与结果同步问题
我正在SwiftUI中开发一款iOS应用,让用户查找当前位置附近的“Post”。Firestore的Posts子集合中存储了geohash,由于谷歌的geofire-objc库已归档,我使用了swift-geohash库。
我的实现逻辑是:先获取当前用户位置周边的所有相邻geohash,再针对每个geohash,在Firestore中执行以该geohash开头、以geohash+'~'结尾的查询。以下是我的代码:
// 引入swift-geohash库 class FirestorePosts: ObservableObject { @Published var items = [FirestorePost]() // 数据模型引用 func geoPointQuery(tag:String){ do { let db = Firestore.firestore().collection("tags") let docRef = db.document(tag).collection("posts") // 用户当前位置的geohash为"gcpu" let neighbors = try Geohash.neighbors(of: "gcpu", includingCenter: true) let queries = neighbors.map { bound -> Query in let end = "\(bound)~" return docRef .order(by: "geohash") .start(at: [bound]) .end(at: [end]) } func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () { guard let documents = snapshot?.documents else { print("无法获取快照数据. \(String(describing: error))") return } self.items += documents.compactMap { queryDocumentSnapshot -> FirestorePost? in return try? queryDocumentSnapshot.data(as: FirestorePost.self) } } for query in queries { print("执行地理查询") query.getDocuments(completion: getDocumentsCompletion) } } catch{ print(error.localizedDescription) } } }
目前查询能正常返回结果,但存在两个问题:
- 如何让该查询实现结果实时更新?我尝试使用
query.addSnapshotListener,但它不支持completion参数。 - 如何确保所有查询执行完成后再返回结果?
问题1:实现实时更新
addSnapshotListener不需要completion参数,它本身通过闭包实时返回数据更新。你可以为每个查询添加监听器,并在监听器闭包中处理数据,同时要注意管理监听器避免重复监听和内存泄漏。
修改后的代码示例:
class FirestorePosts: ObservableObject { @Published var items = [FirestorePost]() private var listeners = [ListenerRegistration]() // 存储监听器引用 func geoPointQuery(tag:String){ // 先清理旧监听器和旧数据 listeners.forEach { $0.remove() } listeners.removeAll() items.removeAll() do { let db = Firestore.firestore().collection("tags") let docRef = db.document(tag).collection("posts") let neighbors = try Geohash.neighbors(of: "gcpu", includingCenter: true) let queries = neighbors.map { bound -> Query in let end = "\(bound)~" return docRef .order(by: "geohash") .start(at: [bound]) .end(at: [end]) } for query in queries { let listener = query.addSnapshotListener { [weak self] snapshot, error in guard let self = self, let documents = snapshot?.documents else { print("无法获取快照数据. \(String(describing: error))") return } // 转换数据并去重(避免多个查询返回同一篇Post) let newPosts = documents.compactMap { try? $0.data(as: FirestorePost.self) } self.items = Array(Set(self.items + newPosts)) } listeners.append(listener) } } catch{ print(error.localizedDescription) } } // 销毁对象时移除所有监听器,避免内存泄漏 deinit { listeners.forEach { $0.remove() } } }
问题2:确保所有查询完成后返回结果
如果是单次查询场景,用DispatchGroup可以等待所有查询完成后统一处理结果:
func geoPointQuery(tag:String){ items.removeAll() do { let db = Firestore.firestore().collection("tags") let docRef = db.document(tag).collection("posts") let neighbors = try Geohash.neighbors(of: "gcpu", includingCenter: true) let queries = neighbors.map { bound -> Query in let end = "\(bound)~" return docRef .order(by: "geohash") .start(at: [bound]) .end(at: [end]) } let group = DispatchGroup() for query in queries { group.enter() query.getDocuments { [weak self] snapshot, error in defer { group.leave() } // 无论成功失败都标记查询完成 guard let self = self, let documents = snapshot?.documents else { print("无法获取快照数据. \(String(describing: error))") return } let newPosts = documents.compactMap { try? $0.data(as: FirestorePost.self) } self.items += newPosts } } // 所有查询完成后执行统一处理 group.notify(queue: .main) { [weak self] in self?.items = Array(Set(self?.items ?? [])) print("所有地理查询完成") // 这里可以添加UI通知或后续逻辑 } } catch{ print(error.localizedDescription) } }
说明:
DispatchGroup通过enter()和leave()跟踪每个查询的状态,所有查询完成后触发notify回调。- 实时监听场景下不存在“所有查询完成”的节点,因为监听器会持续触发,这个方案仅适用于单次查询。
内容的提问来源于stack exchange,提问作者CraigH
相关产品推荐
相关产品推荐

