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
相关产品推荐
相关产品推荐

