Swift中借助Dispatch Group实现等待多轮回调完成后执行的方案咨询
Swift 串行队列等待多次异步回调收集元素最优实现方案
核心问题梳理
你当前的代码存在两个明显问题:
- 串行队列的异步任务会直接执行到return语句,不会等待
getLocation的多次异步回调,最终返回的是空数组 - 定位回调的队列和你自定义的串行队列不一致,直接修改
locationCollected数组会存在线程安全问题
方案1:DispatchSemaphore + 超时控制(兼容iOS所有版本)
适合需要支持旧系统版本的场景,核心是用信号量控制任务等待,同时加超时逻辑终止收集流程:
// 首先修正getLocation的回调定义 func getLocation(completion: @escaping (Location) -> Void) { self.manager?.location(completion: { location in completion(location) }) } let customQueue = DispatchQueue(label: "com.example.serialQueue") customQueue.async { var locationCollected = [Location]() let semaphore = DispatchSemaphore(value: 0) let collectDuration: TimeInterval = 10 // 收集10秒 // 超时任务,到点直接释放信号量终止等待 DispatchQueue.global().asyncAfter(deadline: .now() + collectDuration) { semaphore.signal() } // 启动定位收集 getLocation { location in // 数组修改同步到自定义串行队列,保证线程安全 customQueue.async { locationCollected.append(location) } } // 阻塞当前串行队列等待收集完成,不会影响主线程 semaphore.wait() // 到这里已经收集完成,执行后续逻辑 print("收集到的定位数据:\(locationCollected)") }
注意:不要把该逻辑放到主线程执行,否则会阻塞主线程导致UI卡顿
方案2:Combine 实现(iOS 13+)
更简洁的响应式实现,无需手动处理线程同步和信号量:
import Combine var cancellables = Set<AnyCancellable>() // 封装定位回调为Publisher func locationPublisher() -> AnyPublisher<Location, Never> { let subject = PassthroughSubject<Location, Never>() getLocation { location in subject.send(location) } return subject.eraseToAnyPublisher() } let customQueue = DispatchQueue(label: "com.example.serialQueue") customQueue.async { locationPublisher() // 收集10秒内的所有元素 .collect(.byTime(DispatchQueue.global(), .seconds(10))) .receive(on: customQueue) .sink { locations in // 收集完成,执行后续逻辑 print("收集到的定位数据:\(locations)") } .store(in: &cancellables) }
方案3:Swift Concurrency 实现(iOS 15+)
现在苹果官方推荐的异步逻辑写法,代码最简洁易读:
// 封装定位回调为AsyncStream func locationStream() -> AsyncStream<Location> { AsyncStream { continuation in getLocation { location in continuation.yield(location) } } } Task { var locationCollected = [Location]() let stream = locationStream() // 启动超时任务 let timeoutTask = Task { try await Task.sleep(nanoseconds: 10 * 1_000_000_000) // 10秒 } // 异步收集定位数据 let collectTask = Task { for await location in stream { locationCollected.append(location) } } // 等待超时完成 _ = await timeoutTask.result collectTask.cancel() // 收集完成,执行后续逻辑 print("收集到的定位数据:\(locationCollected)") }
如果你的项目最小支持版本在iOS15及以上,优先选该方案,维护成本最低
内容的提问来源于stack exchange,提问作者nikiizvorski
相关产品推荐
相关产品推荐

