Swift使用DispatchSemaphore循环调用依赖型API导致应用冻结如何解决
问题根因
- 你的代码直接在网络请求的回调队列(绝大多数iOS网络库默认会把请求回调切到主队列)执行
DispatchSemaphore.wait(),该方法会直接阻塞当前队列,而后续的signal()需要等待网络请求完成后同样在该队列执行回调才能触发,直接造成死锁。主队列被阻塞后所有UI响应、队列任务都无法执行,就会出现应用无响应的问题。 - 同时你混用了DispatchGroup和DispatchSemaphore,逻辑冗余,循环内每次请求都调用
completion(indexValue)也不符合常规的全量请求完成后统一回调的需求。
解决方案
方案1:调整Semaphore执行队列,避免阻塞主队列
把循环执行的逻辑丢到全局后台队列,不在主队列执行wait操作即可解决死锁问题:
func DownloadInformation(urlString: String, device: String, indexValue: Int, completion: @escaping (_ requestIndex: Int?) -> (Void)) { ApiService.sharedInstance.DownloadHealthInsightDetails(ID: self.equipmentAnalaysisArr[indexValue].id) { [self] (serverResponse, error, cardID) -> (Void) in if let serverResponse = serverResponse, error == nil { let insightCycleList = serverResponse as? NSDictionary if insightCycleList?["error"] == nil { if let cycle = insightCycleList?["cycles"] as? [AnyObject], !cycle.isEmpty { // 将串行执行逻辑放到后台队列,避免阻塞主队列 DispatchQueue.global().async { let dispatchSemaphore = DispatchSemaphore(value: 0) let validCount = min(cycle.count, 5) for i in 0..<validCount { let element = cycle[i] self.DownloadCycleData(urlString: blob_url , cycleDownloadCompletion: {_ in dispatchSemaphore.signal() }) dispatchSemaphore.wait() } // 所有请求完成后切回主队列执行回调 DispatchQueue.main.async { completion(indexValue) print("done") } } } else { self.ShowMessage(index: indexValue, message: self.message) } } else { self.ShowMessage(index: indexValue, message: self.message) } } } }
方案2:递归实现串行请求(更推荐,无死锁风险)
不需要依赖信号量,在上一个请求的回调里触发下一个请求,逻辑更清晰,不存在队列阻塞风险:
func DownloadInformation(urlString: String, device: String, indexValue: Int, completion: @escaping (_ requestIndex: Int?) -> (Void)) { ApiService.sharedInstance.DownloadHealthInsightDetails(ID: self.equipmentAnalaysisArr[indexValue].id) { [self] (serverResponse, error, cardID) -> (Void) in if let serverResponse = serverResponse, error == nil { let insightCycleList = serverResponse as? NSDictionary if insightCycleList?["error"] == nil { if let cycle = insightCycleList?["cycles"] as? [AnyObject], !cycle.isEmpty { let validCount = min(cycle.count, 5) // 递归请求函数 func requestCycle(index: Int) { guard index < validCount else { // 所有请求完成后回调 DispatchQueue.main.async { completion(indexValue) print("done") } return } let element = cycle[index] self.DownloadCycleData(urlString: blob_url, cycleDownloadCompletion: { _ in // 上一个请求完成,触发下一个请求 requestCycle(index: index + 1) }) } requestCycle(index: 0) } else { self.ShowMessage(index: indexValue, message: self.message) } } else { self.ShowMessage(index: indexValue, message: self.message) } } } }
方案3:iOS 15+ 用Swift Concurrency实现
如果项目最低支持iOS 15及以上版本,直接用async/await改写网络请求,串行逻辑写起来和同步代码一样简洁,不需要依赖信号量或者递归:
只需先把
DownloadHealthInsightDetails和DownloadCycleData方法改为async版本,或者用withCheckedThrowingContinuation包装原有闭包版本的方法即可。
内容的提问来源于stack exchange,提问作者Vignesh
相关产品推荐
相关产品推荐

