主线程调用CLLocationManager.locationServicesEnabled()致UI卡顿求助
解决CLLocationManager主线程阻塞警告及天气查询失败问题
问题原因
你遇到的警告是因为CLLocationManager.locationServicesEnabled()是同步阻塞方法,在主线程调用可能导致UI卡顿。更关键的是,当前代码的授权和位置获取逻辑存在流程错误:
- 调用
requestWhenInUseAuthorization()后立刻检查服务状态、获取位置,但授权是异步操作,用户尚未完成授权选择时,状态判断和位置获取都是无效的,这也是页面无法加载的核心原因。 - 错误类型映射错误:
locationServicesEnabled()返回false是系统位置服务整体禁用,而非用户拒绝授权,两者需要区分。
解决方案
核心思路是依赖CLLocationManager的回调处理授权和位置更新,而非同步阻塞检查,具体步骤:
- 让
WeatherService遵守CLLocationManagerDelegate,通过回调处理授权状态变化和位置更新 - 用Combine的
Subject管理位置状态,实现异步的位置获取与API请求联动 - 移除主线程的同步检查逻辑,在授权回调中处理服务启用状态和位置更新触发
- 获取到有效位置后立即停止更新,减少不必要的耗电
修改后的完整代码
import Foundation import CoreLocation import Combine typealias LocationNameResultType = Result<String, Error> // 修正错误类型,区分不同场景 enum WeatherServiceErrors: Error { case locationNil case placeMarkNil case locationServicesDisabled case userDeniedAuthorization case authorizationNotDetermined } class WeatherService: WeatherServiceProtocol, CLLocationManagerDelegate { private let apiProvider = APIProvider<WeatherEndpoint>() private let locationManager = CLLocationManager() private var locationSubject = PassthroughSubject<CLLocation, Error>() private var cancellables = Set<AnyCancellable>() private var currentLocation: CLLocation? init() { locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters // 初始化时请求授权,后续逻辑交给回调处理 locationManager.requestWhenInUseAuthorization() } func getCityName(completion: @escaping (LocationNameResultType) -> Void) { guard let location = currentLocation else { completion(.failure(WeatherServiceErrors.locationNil)) return } let geocoder = CLGeocoder() // 反向地理编码建议放在后台线程执行,避免阻塞主线程 DispatchQueue.global().async { geocoder.reverseGeocodeLocation(location) { (placemarks, error) in DispatchQueue.main.async { if let error = error { completion(.failure(error)) return } guard let placemark = placemarks?.first, let cityName = placemark.locality else { completion(.failure(WeatherServiceErrors.placeMarkNil)) return } completion(.success(cityName)) } } } } func requestCurrentWeather() -> AnyPublisher<Data, Error> { // 如果已有位置,直接发起请求;否则等待位置Subject的回调 if let location = currentLocation { return fetchWeather(for: location) } else { return locationSubject .first() .flatMap { [weak self] location in guard let self = self else { return Fail(error: WeatherServiceErrors.locationNil).eraseToAnyPublisher() } return self.fetchWeather(for: location) } .eraseToAnyPublisher() } } deinit { locationManager.stopUpdatingLocation() cancellables.forEach { $0.cancel() } } // MARK: - Private Methods private func fetchWeather(for location: CLLocation) -> AnyPublisher<Data, Error> { apiProvider.getData( from: .getCurrentWeather(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) ) .eraseToAnyPublisher() } // MARK: - CLLocationManagerDelegate func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { switch manager.authorizationStatus { case .authorizedWhenInUse, .authorizedAlways: // 授权通过,检查系统位置服务是否启用 guard CLLocationManager.locationServicesEnabled() else { locationSubject.send(completion: .failure(WeatherServiceErrors.locationServicesDisabled)) return } manager.startUpdatingLocation() case .denied, .restricted: locationSubject.send(completion: .failure(WeatherServiceErrors.userDeniedAuthorization)) case .notDetermined: // 初始状态,等待用户选择 break @unknown default: locationSubject.send(completion: .failure(WeatherServiceErrors.authorizationNotDetermined)) } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { guard let latestLocation = locations.last else { locationSubject.send(completion: .failure(WeatherServiceErrors.locationNil)) return } // 保存位置并停止更新 currentLocation = latestLocation locationSubject.send(latestLocation) manager.stopUpdatingLocation() } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { locationSubject.send(completion: .failure(error)) } }
关键修改点说明
- 授权与位置回调:通过
locationManagerDidChangeAuthorization处理授权状态,只有授权通过且服务启用时才开始更新位置 - 异步位置传递:用
PassthroughSubject实现位置的异步传递,requestCurrentWeather可以等待位置就绪后再发起API请求 - 主线程优化:反向地理编码放在后台线程执行,避免阻塞UI;移除了主线程的同步
locationServicesEnabled()检查,改在授权回调中处理(此时用户已完成授权选择,操作更安全) - 错误类型修正:区分了系统位置服务禁用、用户拒绝授权等不同错误场景,便于后续处理
- 资源优化:获取到位置后立即停止更新位置,减少耗电
内容的提问来源于stack exchange,提问作者skyline1801
相关产品推荐
相关产品推荐

