Swift:如何从completion block获取多城市天气加载完成后的完整数组
多接口请求聚合统一回调实现方案
核心改造逻辑
- 调整completion闭包入参类型,从单个
Weather实例改为[Weather]类型的数组 - 预先过滤无效请求URL,统计实际需要发起的请求总数,避免因原逻辑中跳过无效URL的分支导致“全部完成”的判断永远无法触发
- 引入轻量串行同步队列,保护共享数据(结果收集数组、请求完成计数器)的线程安全,解决多线程并发回调下的数据竞争崩溃问题
- 当已完成的请求数和总有效请求数相等时,切回主线程触发统一结果回调
改造后的代码实现
1. 重构getCityWeather方法
public func getCityWeather(completion: @escaping ([Weather]) -> Void) { // 存储所有收集到的天气数据 var weatherResult: [Weather] = [] // 串行队列保证多线程下的数据操作安全 let syncQueue = DispatchQueue(label: "weather.collect.sync") // 预先过滤出所有有效的请求URL let validURLs = citiesGeo.compactMap { geo in let urlString = "https://api.weather.yandex.ru/v2/forecast?lat=\(geo.latitude)&lon=\(geo.longitude)" return URL(string: urlString) } // 无有效请求时直接回调空数组 guard !validURLs.isEmpty else { DispatchQueue.main.async { completion([]) } return } // 记录已完成的请求数量 var completedCount = 0 // 遍历所有有效URL发起请求 validURLs.forEach { url in self.loadManager.getWeather(url: url) { weather in syncQueue.async { weatherResult.append(weather) completedCount += 1 // 所有请求完成时触发统一回调 if completedCount == validURLs.count { DispatchQueue.main.async { completion(weatherResult) } } } } } }
2. 调整调用处的逻辑
原来viewDidLoad中的调用无需额外做线程切换,直接接收完整数组即可:
override func viewDidLoad() { super.viewDidLoad() weatherLoader.getCityWeather { weatherList in // 这里拿到的是加载完成的全量城市天气数据 print("共获取\(weatherList.count)条天气数据:\(weatherList)") } }
注意事项
- 线程安全保护不能省略:网络请求的回调通常在不同子线程并发触发,没有同步队列保护的情况下,同时修改数组和计数器会引发数据错乱、数组越界甚至崩溃
- 回调线程提前切换:拿到数据后通常需要刷新UI,在函数内部提前切回主线程,能避免调用方重复处理线程跳转
- 兼容异常场景:如果
loadManager.getWeather接口存在请求失败/超时无回调的情况,需要先调整该接口逻辑,保证无论请求成功、失败都一定会触发收尾回调,否则一旦有请求异常,整个聚合流程会永久阻塞
内容的提问来源于stack exchange,提问作者IlyaNester
相关产品推荐
相关产品推荐

