使用Dispatch Group从闭包返回值导致UI冻结问题求助
问题分析与解决方案
你的核心问题在于:用DispatchGroup.wait()阻塞了主线程,导致UI冻结;而如果不用等待,异步网络请求还没完成函数就返回了,所以拿到nil。本质上是iOS异步编程的常见误区——试图让异步操作同步返回值,这和iOS的运行机制冲突。
下面给你几种可行的方案,按推荐程度排序:
1. 首选:使用Completion Handler(异步回调)
这是iOS异步编程的标准做法,放弃同步返回,改为在请求完成后通过回调传递结果,完全不会阻塞UI:
func retrieveAccessToken(completion: @escaping (String) -> Void) { if !accessTokenExpired() { // 异步请求完成后,通过回调返回token Network.instance.networkRequest() { value in completion(value) } } else { // 直接返回本地缓存的token let localToken = KeychainHandler.shared[ACCESS_TOKEN]! completion(localToken) } }
调用的时候,在回调里处理后续逻辑(如果要更新UI,记得切回主线程):
retrieveAccessToken { token in // 在这里使用token,比如发起后续请求 DispatchQueue.main.async { // 更新UI的操作放在这里,比如设置按钮状态、显示信息 } }
2. 更简洁:使用Async/Await(Swift 5.5+)
如果你的项目已经适配Swift 5.5及以上,用async/await可以让异步代码看起来像同步代码,同时不会阻塞线程:
首先把你的网络请求包装成异步函数:
// 先给Network扩展一个async版本的请求方法 extension Network { func networkRequestAsync() async throws -> String { return try await withCheckedThrowingContinuation { continuation in self.networkRequest { value in if let validValue = value { continuation.resume(returning: validValue) } else { // 这里可以定义自定义错误,比如NetworkError.failed continuation.resume(throwing: NSError(domain: "Network", code: -1, userInfo: [NSLocalizedDescriptionKey: "请求失败"])) } } } } } // 重写你的token获取函数为async函数 func retrieveAccessToken() async -> String { if !accessTokenExpired() { do { return try await Network.instance.networkRequestAsync() } catch { // 请求失败时, fallback到本地token(根据你的需求调整错误处理) return KeychainHandler.shared[ACCESS_TOKEN]! } } else { return KeychainHandler.shared[ACCESS_TOKEN]! } }
调用的时候需要在Task异步上下文中:
Task { let token = await retrieveAccessToken() // 使用token处理业务逻辑 DispatchQueue.main.async { // 切回主线程更新UI } }
3. 不推荐:后台线程同步等待(仅适合非UI线程调用)
如果你非常坚持要同步返回值,可以把整个逻辑放到后台队列,避免阻塞主线程,但这种方式依然会阻塞调用它的线程,所以绝对不能在主线程使用:
func retrieveAccessToken() -> String { var accessToken: String? let group = DispatchGroup() // 用后台队列执行异步逻辑 let backgroundQueue = DispatchQueue.global(qos: .background) backgroundQueue.async { if !accessTokenExpired() { group.enter() Network.instance.networkRequest() { value in accessToken = value group.leave() } } else { accessToken = KeychainHandler.shared[ACCESS_TOKEN]! } } // 等待后台队列的操作完成 group.wait() // 强制解包这里要注意安全,最好加个默认值或者错误处理 return accessToken ?? "" }
⚠️ 重要提醒:不要在主线程调用这个函数,否则依然会导致UI冻结,仅适合在后台任务中使用。
总结
iOS的异步设计就是为了避免阻塞主线程,给用户流畅的体验。同步阻塞主线程是反模式,强烈建议你选择前两种异步方案(Completion Handler或Async/Await),这才是符合iOS开发规范的做法。
内容的提问来源于stack exchange,提问作者Abhinav Jha
相关产品推荐
相关产品推荐

