使用Alamofire获取数据时函数执行顺序异常导致返回数组为空
Alamofire 5.0 请求后数组为空、执行顺序异常问题
问题表现
初学Alamofire 5.0时遇到代码执行顺序不符合预期的问题:数据拉取、变量赋值流程未按书写顺序执行,最终获取到的天气预报数组为空。
相关代码实现如下:
数据拉取函数
func fetchForecast() { let url = "http://api.weatherapi.com/v1/current.json" for city in cities { let parameters: [String: String] = ["key": apiKey, "q": city, "aqi": "no"] AF.request(url, parameters: parameters) .validate() .responseDecodable(of: CurrentForecast.self) { (response) in switch response.result { case .success(let value): print("Before appending") self.currentForecasts.append(value) print("After appending") case .failure(let error): print("Failed with error: \(error)") } print("Before printing self.currentForecasts") print(self.currentForecasts) print("After printing self.currentForecasts") } } }
控制器存储属性
var currentForecasts = [CurrentForecast]() var cities: [String] = ["Moscow", "London"]
生命周期调用逻辑
override func viewDidLoad() { super.viewDidLoad() print("Before fetch") fetchForecast() print("After fetch") print("Before printing currentForecasts") print(currentForecasts) print("After printing currentForecasts") }
实际运行控制台输出和预期不符:调用fetchForecast后立刻打印天气预报数组得到空值,所有网络请求的响应回调都在fetchForecast执行结束后才触发,输出如下:
Before fetch After fetch Before printing currentForecasts [] After printing currentForecasts Before appending After appending Before printing self.currentForecasts [Forecast for London] After printing self.currentForecasts Before appending After appending Before printing self.currentForecasts [Forecast for London, Forecast for Moscow] After printing self.currentForecasts
问题根因
这是异步编程的通用特性,和Alamofire本身逻辑无关:
网络请求属于异步任务,发起请求后不会阻塞当前线程等待结果返回。
调用AF.request时,系统仅会将网络任务提交给后台线程调度,当前主线程会继续向下执行:首先跑完fetchForecast内的循环(循环逻辑仅完成请求发起动作,不会等待响应),接着执行viewDidLoad中fetchForecast()调用后的打印代码——此时网络请求仍在后台运行,尚未拿到返回数据,currentForecasts自然为空。
等后台网络任务拿到服务器响应后,才会执行responseDecodable传入的闭包逻辑,向数组中追加数据,这也是闭包内所有打印内容都出现在日志末尾的原因。
额外注意:并发发起的多个网络请求返回顺序不固定,不会严格遵循循环内的城市排列顺序,日志中先返回London结果、后返回Moscow结果就是这个特性的体现,业务逻辑不要依赖请求返回顺序。
修复方案
不要在发起请求的代码行之后立刻读取currentForecasts的值,等请求完成后再执行数据处理、UI刷新逻辑,两种常用实现方式:
- 方式一:用
DispatchGroup监听所有请求完成
适合需要等所有城市数据全部加载完再统一刷新UI的场景,修改fetchForecast方法加入分组调度逻辑,所有请求结束后触发回调:
调用时在回调内处理后续逻辑:func fetchForecast(completion: @escaping () -> Void) { let url = "http://api.weatherapi.com/v1/current.json" let requestGroup = DispatchGroup() for city in cities { requestGroup.enter() // 每发起一个请求,分组计数+1 let parameters: [String: String] = ["key": apiKey, "q": city, "aqi": "no"] AF.request(url, parameters: parameters) .validate() .responseDecodable(of: CurrentForecast.self) { (response) in switch response.result { case .success(let value): self.currentForecasts.append(value) case .failure(let error): print("Request failed: \(error)") } requestGroup.leave() // 单个请求结束(无论成功失败),分组计数-1 } } // 所有请求全部执行完后,在主线程触发回调 requestGroup.notify(queue: .main) { completion() } }override func viewDidLoad() { super.viewDidLoad() fetchForecast { [weak self] in // 走到这里所有请求已经完成,数组有值 print(self?.currentForecasts ?? []) // 执行刷新列表、更新UI等后续逻辑 } } - 方式二:单请求返回后即时处理
不需要等所有数据加载完的场景,可以直接在单个请求的成功闭包内,将拿到的单条天气预报数据同步更新到对应UI位置,不用等待全部请求结束。
内容的提问来源于stack exchange,提问作者Nikita
相关产品推荐
相关产品推荐

