“未能在与其创建相同的运行循环上解除分配 CLLocationManager 可能会导致崩溃”

Posted

技术标签:

【中文标题】“未能在与其创建相同的运行循环上解除分配 CLLocationManager 可能会导致崩溃”【英文标题】:"Failure to deallocate CLLocationManager on the same runloop as its creation may result in a crash" 【发布时间】:2018-09-13 00:10:27 【问题描述】:

我在控制台中看到此错误,但不确定原因是什么?我正在请求用户位置一次。

2018-09-12 20:04:26.912292-0400 Watch Extension[40984:3250895] [Client] Failure to deallocate CLLocationManager on the same runloop as its creation may result in a crash

代码如下:

import CoreLocation


class WorkoutLocationManager: NSObject, CLLocationManagerDelegate 

    private var locationManager: CLLocationManager?
    public var formattedWorkoutAddress: String?

    public func getWorkoutLocation() 
        guard CLLocationManager.locationServicesEnabled() else 
            print("User does not have location services enabled")
            return
        

        locationManager = CLLocationManager()
        locationManager?.delegate = self
        locationManager?.desiredAccuracy = kCLLocationAccuracyNearestTenMeters

        let locationAuthorizationStatus = CLLocationManager.authorizationStatus()

        switch locationAuthorizationStatus 
        case .authorizedAlways:
            print("location authorized Always")
            locationManager?.requestLocation()
        case .authorizedWhenInUse:
            print("location authorized When in Use")
            locationManager?.requestLocation()
        case .denied:
            print("location authorization denied")
        case .notDetermined:
            print("location authorization not determined")

        case .restricted:
            print("location authorization restricted")

        

    


    // MARK: - CLLocationManagerDelegate

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) 

                guard let currentLocation = locations.first else  return 

                let geocoder = CLGeocoder()
                geocoder.reverseGeocodeLocation(currentLocation)  (placemarksArray, error) in

                    if let unwrappedError = error  
                        print("Geocoder error: \(unwrappedError)")
                    

                    guard let placemarksArrayUnwrapped = placemarksArray else   return 

                    if placemarksArrayUnwrapped.count > 0 

                        if let placemark = placemarksArray?.first 

                            let name = placemark.name ?? ""
                            let subLocality = placemark.subLocality ?? ""
                            let locality = placemark.locality ?? ""
                            let state = placemark.administrativeArea ?? ""

//                            print("address1:", placemark.thoroughfare ?? "")
//                            print("address2:", placemark.subThoroughfare ?? "")
//                            print("city:",     placemark.locality ?? "")
//                            print("state:",    placemark.administrativeArea ?? "")
//                            print("zip code:", placemark.postalCode ?? "")
//                            print("country:",  placemark.country ?? "")

                            let workoutLocationAsString = (name + " " + subLocality + " " + locality + " " + state)
                            print("workoutLocationAsString = \(workoutLocationAsString)")
                            self.formattedWorkoutAddress = workoutLocationAsString

                         else  print("no placemark")
                     else  print("placemark.count = 0")
                
    

    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) 
        print("location manager error = \(error)")
    


用法:

private func handleWorkoutSessionState(didChangeTo toState: HKWorkoutSessionState,
                                           from fromState: HKWorkoutSessionState) 
        switch (fromState, toState) 
        case (.notStarted, .running):
            workoutLocationManager.getWorkoutLocation() 

【问题讨论】:

你能给出错误吗? 编辑了我的问题 可能handleWorkoutSessionState 没有在主队列上被调用,但是持有对workoutLocationManager 的引用的对象的释放正在主队列上执行。尝试将 getWorkoutLocation 包装在 DispatchQueue.main.async 谢谢,这也是我的第一个想法,但不会阻止控制台消息。 您找到解决此问题的方法了吗? 【参考方案1】:

CLLocationManager 必须在运行循环中创建。如果您不这样做,您将收到来自CoreLocation 的以下警告:

在主线程以外的线程上执行的调度队列上创建了位置管理器。开发人员有责任确保在分配位置管理器对象的线程上运行运行循环。特别是,不支持在任意调度队列(未附加到主队列)中创建位置管理器,这将导致无法接收回调。

在您的情况下,看起来您的 locationManager 是在 main thread 中创建的,但 WorkoutLocationManager 的实例正在其他线程上使用,这导致它在该线程中被释放,因此,在同一个线程中释放locationManager

一种选择是确保您的WorkoutLocationManager 实例将被创建并仅在主线程中使用。

另一个我没有完全测试过的选项是尝试在主线程中强制创建和释放locationManager,如下所示:

class WorkoutLocationManager: NSObject 

    var locationManager: CLLocationManager!

    override init() 
        super.init()
        self.performSelector(onMainThread: #selector(initLocationManager), with: nil, waitUntilDone: true)
    

    @objc private func initLocationManager() 
        self.locationManager = CLLocationManager()
        self.locationManager.delegate = self
    

    @objc private func deinitLocationManager() 
        locationManager = nil
    

    deinit 
        self.performSelector(onMainThread: #selector(deinitLocationManager), with: nil, waitUntilDone: true)
    


让我知道你的想法。

【讨论】:

谢谢!这确实消除了控制台中的错误...但是这是否有任何潜在的意外后果? 好吧,我真的不知道。有一个关于performSelector(onMainThread:with:waitUntilDone:)developer.apple.com/documentation/objectivec/nsobject/…的特殊考虑部分。 我无法真正理解他们所说的罗德里戈。我的流程是我在 InterfaceController1 中实例化一个自定义管理器类,管理器启动一个 HKWorkout(healthKit 应用程序)并将自己设置为HKWorkoutSessionDelegate,然后触发一个调用self.workoutLocationManager.getWorkoutLocation() 的函数(当 HKWorkoutState = .running)我上面的代码。所以我没有看到我在使用 GCD 或调度队列的任何地方,看到任何问题吗? @GarySabo,没有发现任何问题。尝试用尽你的单元测试,也许,发布一个带有这个实现的测试版本,看看是否出现任何错误或危机。

以上是关于“未能在与其创建相同的运行循环上解除分配 CLLocationManager 可能会导致崩溃”的主要内容,如果未能解决你的问题,请参考以下文章