我的completion block为何失效?Alamofire同步请求未达预期
问题分析
从你给出的输出顺序 leaving viewDidLoad leaving loadData loading successfull 来看,你的请求实际上还是异步执行的——completion block并没有等待请求完成就继续往下走了,导致viewDidLoad和loadData都执行完毕后,请求的回调才触发。
这大概率是因为你实现"同步请求"的方式存在问题,比如没有正确使用信号量(Semaphore)或者调度组(DispatchGroup)来阻塞线程等待请求完成。
正确的Alamofire同步请求实现方式
要让Alamofire请求真正同步执行,你可以用DispatchSemaphore来阻塞当前线程,直到请求完成。但要注意:绝对不能在主线程调用阻塞操作,否则会导致UI卡死甚至死锁!
步骤1:封装同步请求的loadData方法
func loadData(completion: @escaping () -> Void) { // 创建信号量,初始值为0 let semaphore = DispatchSemaphore(value: 0) AF.request("你的接口URL").responseJSON { response in defer { // 请求完成后发送信号,唤醒等待的线程 semaphore.signal() } switch response.result { case .success(let value): print("loading successful") // 处理返回的数据 // ... completion() case .failure(let error): print("请求失败: \(error.localizedDescription)") completion() } } // 阻塞当前线程,直到收到信号 semaphore.wait() }
步骤2:在后台线程调用同步请求
override func viewDidLoad() { super.viewDidLoad() // 把同步请求放到后台线程执行,避免阻塞主线程 DispatchQueue.global(qos: .userInitiated).async { self.loadData { // 如果需要更新UI,必须切回主线程 DispatchQueue.main.async { self.tableView.reloadData() // 其他UI操作 } } print("leaving loadData") } print("leaving viewDidLoad") }
预期输出顺序
这样调整后,输出顺序会变成:leaving viewDidLoad loading successful leaving loadData
(因为viewDidLoad会先执行到print,然后后台线程开始处理请求,请求完成后打印loading successful,最后打印leaving loadData)
常见错误点排查
- 在主线程调用semaphore.wait():Alamofire的默认回调队列是主线程,如果你在主线程阻塞,回调永远无法执行,导致死锁。
- 信号量使用错误:比如忘记调用
signal(),或者调用时机不对(比如在completion之后才signal)。 - 混淆同步请求和completion顺序:即使是同步请求,如果你把loadData放到后台线程,viewDidLoad的print还是会先执行,这是正常的——因为后台线程的执行不会阻塞主线程。
内容的提问来源于stack exchange,提问作者Alexander Kozachenko
相关产品推荐
相关产品推荐

