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

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)
        }
    }
}

目前查询能正常返回结果,但存在两个问题:

  1. 如何让该查询实现结果实时更新?我尝试使用query.addSnapshotListener,但它不支持completion参数。
  2. 如何确保所有查询执行完成后再返回结果?

问题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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 03:15:42