You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:32:57