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

Swift中借助Dispatch Group实现等待多轮回调完成后执行的方案咨询

Swift 串行队列等待多次异步回调收集元素最优实现方案

核心问题梳理

你当前的代码存在两个明显问题:

  1. 串行队列的异步任务会直接执行到return语句,不会等待getLocation的多次异步回调,最终返回的是空数组
  2. 定位回调的队列和你自定义的串行队列不一致,直接修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.27 14:36:02