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

Firebase GeoFire多查询如何等待所有回调完成并获取全量匹配数据

解决方案

方案1:使用DispatchGroup(兼容所有iOS版本)

这是最通用的实现方式,核心是通过计数信号量追踪所有异步回调的完成状态:

  • 首先修正现有逻辑问题:全局matchingDocs会导致多次定时触发的请求数据互相叠加,先将其移入方法内部,所有请求完成后再对外暴露结果
  • 用DispatchGroup管理所有查询请求,每个查询发起前调用enter(),回调执行完成后调用leave()
  • 调用notify()监听所有请求完成事件,拿到全量结果后再传递给外部使用
// 外部可以定义一个存储属性或者闭包接收最终结果
var finalMatchingDocs: [QueryDocumentSnapshot] = []

@objc func getLocations() {
    let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
    let radiusInM: Double = 10000
    let queryBounds = GFUtils.queryBounds(forLocation: center, withRadius: radiusInM)
    let queries = queryBounds.map { bound -> Query in
        return db.collection("geopoints")
            .order(by: "geohash")
            .start(at: [bound.startValue])
            .end(at: [bound.endValue]) // 原有代码漏了这两行,会导致查询全表数据,必须补上
    }
    
    var matchingDocs = [QueryDocumentSnapshot]()
    let group = DispatchGroup()
    
    func getDocumentsCompletion(snapshot: QuerySnapshot?, error: Error?) -> () {
        defer { group.leave() } // 无论回调成功失败都要执行leave,避免group永久等待
        guard let documents = snapshot?.documents else {
            print("Unable to fetch snapshot data. \(String(describing: error))")
            return
        }
        for document in documents {
            let lat = document.data()["lat"] as? Double ?? 0
            let lng = document.data()["lng"] as? Double ?? 0
            let coordinates = CLLocation(latitude: lat, longitude: lng)
            let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
            let distance = GFUtils.distance(from: centerPoint, to: coordinates)
            if distance <= radiusInM {
                matchingDocs.append(document)
            }
        }
    }

    for query in queries {
        group.enter() // 每个查询发起前计数+1
        query.getDocuments(completion: getDocumentsCompletion)
    }
    
    // 所有回调完成后会在主队列执行这个闭包
    group.notify(queue: .main) { [weak self] in
        guard let self = self else { return }
        // 这里已经拿到全量数据,可以赋值给外部属性或者触发后续逻辑
        self.finalMatchingDocs = matchingDocs
        // 可直接调用后续处理方法
        self.handleLocationResults(docs: matchingDocs)
    }
}

方案2:使用Swift Concurrency(iOS 15+ / macOS 12+ 支持)

如果你的App最低支持版本符合要求,用async/await的写法更简洁,不需要手动管理闭包和DispatchGroup:

func getLocationsAsync() async throws -> [QueryDocumentSnapshot] {
    let center = CLLocationCoordinate2D(latitude: lat, longitude: lon)
    let radiusInM: Double = 10000
    let queryBounds = GFUtils.queryBounds(forLocation: center, withRadius: radiusInM)
    
    var matchingDocs = [QueryDocumentSnapshot]()
    // 用task group并发执行所有查询
    try await withThrowingTaskGroup(of: QuerySnapshot?.self) { group in
        for bound in queryBounds {
            let query = db.collection("geopoints")
                .order(by: "geohash")
                .start(at: [bound.startValue])
                .end(at: [bound.endValue])
            group.addTask {
                return try await query.getDocuments()
            }
        }
        // 收集所有查询结果
        for try await snapshot in group {
            guard let documents = snapshot?.documents else { continue }
            for document in documents {
                let lat = document.data()["lat"] as? Double ?? 0
                let lng = document.data()["lng"] as? Double ?? 0
                let coordinates = CLLocation(latitude: lat, longitude: lng)
                let centerPoint = CLLocation(latitude: center.latitude, longitude: center.longitude)
                let distance = GFUtils.distance(from: centerPoint, to: coordinates)
                if distance <= radiusInM {
                    matchingDocs.append(document)
                }
            }
        }
    }
    return matchingDocs
}

定时任务的额外注意事项

  • 如果上一轮请求还没完成下一轮定时已经触发,会出现结果乱序的问题,可以加一个isRequesting标志位,前一轮请求完成前不执行新的请求
  • 用户离开页面前要记得销毁Timer,避免内存泄漏

内容的提问来源于stack exchange,提问作者Rhythmus

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.02 12:54:03