import CoreLocation /// CoreLocation 的极简 async 封装:请求一次"使用期间"定位。 /// 无权限/拒绝/失败都返 nil,调用方用默认坐标兜底(对齐安卓首页定位失败兜底天安门)。 @MainActor final class LocationManager: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() private var authContinuation: CheckedContinuation? private var locContinuation: CheckedContinuation? override init() { super.init() manager.delegate = self manager.desiredAccuracy = kCLLocationAccuracyHundredMeters } /// 请求一次定位。notDetermined 先弹权限,授权后取一次坐标;拒绝/失败返 nil。 func requestOnce() async -> CLLocationCoordinate2D? { // 防重入:Tab 切换会让 HomeView 的 .task 重启二次调用,避免覆盖 continuation 导致前一次永久挂起 guard authContinuation == nil, locContinuation == nil else { return nil } switch manager.authorizationStatus { case .denied, .restricted: return nil case .notDetermined: guard await requestAuthorization() else { return nil } default: break } return await fetchLocation() } private func requestAuthorization() async -> Bool { await withCheckedContinuation { cont in authContinuation = cont manager.requestWhenInUseAuthorization() } } private func fetchLocation() async -> CLLocationCoordinate2D? { await withCheckedContinuation { cont in locContinuation = cont manager.requestLocation() } } // MARK: - CLLocationManagerDelegate(回调可能非主线程,跳回 MainActor;continuation 置 nil 防重复 resume) nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { let status = manager.authorizationStatus Task { @MainActor in guard let cont = authContinuation else { return } // init 时的初始回调:cont 还没设,忽略 authContinuation = nil cont.resume(returning: status == .authorizedWhenInUse || status == .authorizedAlways) } } nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { let coord = locations.first?.coordinate Task { @MainActor in guard let cont = locContinuation else { return } locContinuation = nil cont.resume(returning: coord) } } nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { Task { @MainActor in guard let cont = locContinuation else { return } locContinuation = nil cont.resume(returning: nil) } } }