如何让withCheckedContinuation实现的异步函数可重入或避免覆盖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
相关产品推荐
相关产品推荐

