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

如何让withCheckedContinuation实现的异步函数可重入或避免覆盖Continuation?

多次调用checkAuthorizationStatus()导致Continuation被覆盖的问题分析与解决
class Locator : NSObject, ObservableObject
{
    private let locationManager: CLLocationManager
    private var authorizationContinuation: CheckedContinuation<CLAuthorizationStatus, Never>?

    @Published var authorizationStatus: CLAuthorizationStatus
    @Published var location: CLLocation?
    @Published var error: Error?

    override init()
    {
        locationManager = CLLocationManager()
        authorizationStatus = locationManager.authorizationStatus

        super.init()

        locationManager.delegate = self
    }

    @MainActor func checkAuthorizationStatus() async -> CLAuthorizationStatus
    {
        let status = locationManager.authorizationStatus
        if status == .notDetermined
        {
            return await withCheckedContinuation
            { continuation in
                authorizationContinuation = continuation

                locationManager.requestWhenInUseAuthorization()
            }
        }
        else
        {
            authorizationStatus = status

            return status
        }
    }
}

extension Locator : CLLocationManagerDelegate
{
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
    {
        authorizationStatus = manager.authorizationStatus

        authorizationContinuation?.resume(returning: authorizationStatus)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
    {
        print(error)
        
        self.error = error
        location = nil
    }
}

问题:多次调用是否会导致状态覆盖?

这种情况完全可能发生。比如当系统弹出权限请求弹窗后,用户还未点击允许/拒绝时,如果再次调用checkAuthorizationStatus(),新创建的CheckedContinuation会直接覆盖authorizationContinuation变量里存储的旧实例,导致第一次发起的异步调用永远无法被resume,一直处于挂起状态。

原因分析

authorizationContinuation是单个可选类型变量,每次进入.notDetermined分支时,都会把新的continuation赋值给它,直接覆盖之前的引用。而系统的权限状态变更回调只会处理最后一次存储的continuation,之前的实例就被永久遗弃了。

解决方法

有两种常见的修复思路:

思路1:维护等待的Continuation数组

把单个authorizationContinuation改成数组,存储所有等待授权结果的continuation。当授权状态变更或出现错误时,遍历数组逐个resume,然后清空数组。

修改后的代码示例:

class Locator : NSObject, ObservableObject
{
    private let locationManager: CLLocationManager
    // 用数组存储所有等待的continuation
    private var authorizationContinuations: [CheckedContinuation<CLAuthorizationStatus, Never>] = []

    @Published var authorizationStatus: CLAuthorizationStatus
    @Published var location: CLLocation?
    @Published var error: Error?

    override init()
    {
        locationManager = CLLocationManager()
        authorizationStatus = locationManager.authorizationStatus

        super.init()

        locationManager.delegate = self
    }

    @MainActor func checkAuthorizationStatus() async -> CLAuthorizationStatus
    {
        let status = locationManager.authorizationStatus
        if status == .notDetermined
        {
            return await withCheckedContinuation
            { continuation in
                authorizationContinuations.append(continuation)
                // 只在数组为空时发起请求,避免重复触发权限弹窗
                if authorizationContinuations.count == 1 {
                    locationManager.requestWhenInUseAuthorization()
                }
            }
        }
        else
        {
            authorizationStatus = status
            return status
        }
    }
}

extension Locator : CLLocationManagerDelegate
{
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
    {
        authorizationStatus = manager.authorizationStatus
        // 遍历所有等待的continuation并resume
        authorizationContinuations.forEach { $0.resume(returning: authorizationStatus) }
        authorizationContinuations.removeAll()
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
    {
        print(error)
        self.error = error
        location = nil
        // 错误时也要resume所有continuation,避免调用方永久挂起
        authorizationContinuations.forEach { $0.resume(returning: locationManager.authorizationStatus) }
        authorizationContinuations.removeAll()
    }
}

思路2:复用同一个请求任务

用一个Task?变量存储当前正在进行的授权请求任务,后续调用时直接await这个任务的结果,避免创建多个continuation。

修改后的代码示例:

class Locator : NSObject, ObservableObject
{
    private let locationManager: CLLocationManager
    // 存储当前正在进行的授权任务
    private var authorizationTask: Task<CLAuthorizationStatus, Never>?
    private var authorizationContinuation: CheckedContinuation<CLAuthorizationStatus, Never>?

    @Published var authorizationStatus: CLAuthorizationStatus
    @Published var location: CLLocation?
    @Published var error: Error?

    override init()
    {
        locationManager = CLLocationManager()
        authorizationStatus = locationManager.authorizationStatus

        super.init()

        locationManager.delegate = self
    }

    @MainActor func checkAuthorizationStatus() async -> CLAuthorizationStatus
    {
        let status = locationManager.authorizationStatus
        if status != .notDetermined
        {
            authorizationStatus = status
            return status
        }

        // 如果已有正在进行的任务,直接等待其结果
        if let existingTask = authorizationTask {
            return await existingTask.value
        }

        // 创建新任务并存储
        let task = Task {
            await withCheckedContinuation { continuation in
                authorizationContinuation = continuation
                locationManager.requestWhenInUseAuthorization()
            }
        }
        authorizationTask = task
        
        let result = await task.value
        authorizationTask = nil // 任务完成后清空引用
        return result
    }
}

extension Locator : CLLocationManagerDelegate
{
    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager)
    {
        authorizationStatus = manager.authorizationStatus
        authorizationContinuation?.resume(returning: authorizationStatus)
    }

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error)
    {
        print(error)
        self.error = error
        location = nil
        authorizationContinuation?.resume(returning: locationManager.authorizationStatus)
    }
}

总结

两种思路都能解决多次调用导致的continuation覆盖问题:数组方式适合处理批量等待的请求,任务复用方式更简洁,能避免重复发起授权请求。可以根据实际业务场景选择。

内容的提问来源于stack exchange,提问作者meaning-matters

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 02:25:48