Swift多线程问题:主线程异步调用先于全局队列任务完成
问题分析与解决方案
你的代码问题很典型——没等异步网络请求完成就提前执行了UI更新逻辑。
为什么原来的代码会失败?
requestAds()是耗时的异步API请求,它不会阻塞当前的全局队列。所以你写完requestAds()后立刻调用DispatchQueue.main.async,这部分UI更新代码会在requestAds()还没拿到数据、甚至还没完成请求的时候就跑起来了。这时候getAds()自然拿不到任何数据,ads数组为空,表格刷新后当然什么都没有。
正确的写法:用回调闭包等待请求完成
首先你需要改造requestAds(),给它加一个完成回调,让它在请求+解析完成后通知我们拿到的数据:
func requestAds(completion: @escaping ([Ads]) -> Void) { // 替换成你的实际API请求逻辑,这里用URLSession举例 let adRequestURL = URL(string: "你的广告API地址")! URLSession.shared.dataTask(with: adRequestURL) { data, response, error in // 处理请求错误 guard let data = data, error == nil else { print("请求失败: \(error?.localizedDescription ?? "未知错误")") completion([]) // 失败时返回空数组,也可以根据需求返回错误信息 return } // 解析数据为[Ads]数组 do { let fetchedAds = try JSONDecoder().decode([Ads].self, from: data) completion(fetchedAds) } catch { print("数据解析失败: \(error)") completion([]) } }.resume() }
然后在viewDidLoad里,把UI更新的逻辑放到回调闭包中,确保只有当数据真正拿到后才执行:
var ads = [Ads]() override func viewDidLoad() { super.viewDidLoad() // 发起广告请求,拿到数据后再更新UI requestAds { [weak self] fetchedAds in // 回到主线程更新UI(UI操作必须在主线程执行) DispatchQueue.main.async { guard let self = self else { return } // 避免循环引用 self.ads = fetchedAds print("获取到广告数量: \(self.ads.count)") self.adsTableView.reloadData() } } }
额外优化提示
- 避免多余的全局队列:
URLSession的dataTask本身就是在后台队列执行的,所以不需要额外套DispatchQueue.global(qos: .userInteractive).async,直接调用requestAds即可。 - 处理循环引用:用
[weak self]捕获self,避免ViewController因为闭包引用而无法释放。 - 错误状态反馈:如果请求或解析失败,除了打印日志,最好在UI上给用户一个提示(比如显示“暂无广告”的占位视图),提升用户体验。
内容的提问来源于stack exchange,提问作者Robihamanto
相关产品推荐
相关产品推荐

