4

I need accurate location. To achieve the goal I have used like the below logic:

  1. I am trying to check updated location is to be accurate, if it is accurate then return it.
  2. But sometimes app does not receive accurate location(especially inside forest or between tall buildings, ...) for long time. To solve this I have put 5 second threshold time. If app doesn't receive accurate location for 5 seconds, app applies the latest tracked location even if it is not accurate.

So in my code timer will start/stop inside didUpdateLocations method multiple times.

But it is crashed randomly. Here is crash info:

libc++abi: terminating with uncaught exception of type std::bad_alloc: std::bad_alloc
dyld4 config: DYLD_LIBRARY_PATH=/usr/lib/system/introspection DYLD_INSERT_LIBRARIES=/Developer/usr/lib/libBacktraceRecording.dylib:/Developer/usr/lib/libMainThreadChecker.dylib:/usr/lib/libMTLCapture.dylib:/Developer/Library/PrivateFrameworks/DTDDISupport.framework/libViewDebuggerSupport.dylib
terminating with uncaught exception of type std::bad_alloc: std::bad_alloc

enter image description here

enter image description here

enter image description here

Here is my full code:

import UIKit
import CoreLocation

final class MapView: UIView {
    private lazy var locationImprover = TimerBasedLocationImprover(acceptInitialLocation: true) {[weak self] location in
        self?.updatedLocation(location)
    }
    
    private lazy var locationManager = CLLocationManager()
    
    init() {
        super.init(frame: .zero)
        setup()
    }
      
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    deinit {
        stopUpdating()
    }
    
    private func setup() {
        setupLocationManager()
    }
    
    private func setupLocationManager() {
        locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
        locationManager.distanceFilter = 1
        
        locationManager.delegate = self
        
        locationManager.requestWhenInUseAuthorization()
        startUpdating(manager: locationManager)
    }
    
    private func startUpdating(manager: CLLocationManager) {
        if CLLocationManager.locationServicesEnabled() {
          manager.startUpdatingLocation()
        }
        
        if CLLocationManager.headingAvailable() {
          manager.startUpdatingHeading()
        }
    }
      
    func stopUpdating() {
        locationManager.stopUpdatingLocation()
        locationManager.stopUpdatingHeading()
    }
        
    private func updatedLocation(_ location: CLLocation) {
        //Some UI update
    }

}

extension MapView: CLLocationManagerDelegate {
  func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
    startUpdating(manager: manager)
  }
  
  func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
    startUpdating(manager: manager)
  }
  
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
    guard let lastLocation = locations.last,
          let improvedLocation = locationImprover.improved(location: lastLocation) else { return }
    
    updatedLocation(improvedLocation)
  }
  
  func locationManager(_ manager: CLLocationManager, didUpdateHeading newHeading: CLHeading) {
    guard newHeading.headingAccuracy > 0 else { return }
  }
}


final class TimerBasedLocationImprover {
    private var _lastLocation: CLLocation?
    private let lastLocationLock = NSLock()
    private var lastLocation: CLLocation? {
        get {
            defer { lastLocationLock.unlock() }
            lastLocationLock.lock()
            return _lastLocation
        }
        set {
            lastLocationLock.lock()
            _lastLocation = newValue
            lastLocationLock.unlock()
        }
    }
    
    private var _workItem: DispatchWorkItem?
    private let workItemLock = NSLock()
    private var workItem: DispatchWorkItem? {
        get {
            defer { workItemLock.unlock() }
            workItemLock.lock()
            return _workItem
        }
        set {
            workItemLock.lock()
            _workItem = newValue
            workItemLock.unlock()
        }
    }
    
    private let timerThreashold: TimeInterval
    private let acceptInitialLocation: Bool
    private let onReachThreashold: (CLLocation) -> Void
    init(timerThreashold: TimeInterval = 5,
         acceptInitialLocation: Bool,
         onReachThreashold: @escaping (CLLocation) -> Void) {
        self.timerThreashold = timerThreashold
        self.acceptInitialLocation = acceptInitialLocation
        self.onReachThreashold = onReachThreashold
        
        startTimer()
    }
    
    deinit {
        stopTimer()
    }
    
    func improved(location: CLLocation) -> CLLocation? {
        debugPrint("improved thread is main: \(Thread.isMainThread)")
        
        if lastLocation == nil,
           acceptInitialLocation {
            startTimer()
            lastLocation = location
            startTimer()
            return location
        }
        
        lastLocation = location
        
        guard location.isAccurate else { return nil }
        
        startTimer()
        
        return location
    }
  
    private func startTimer() {
        debugPrint("startTimer thread is main: \(Thread.isMainThread)")
        stopTimer()
        
        let wi = DispatchWorkItem() {[weak self] in
            self?.timerDone()
        }
        
        DispatchQueue.global(qos: .background)
            .asyncAfter(deadline: .now() + timerThreashold,
                        execute: wi)
        
        workItem = wi
    }
    
    private func stopTimer() {
        workItem?.cancel()
        workItem = nil
    }
    
    private func timerDone() {
        if let lastLocation = lastLocation {
            DispatchQueue.main.async {
                self.onReachThreashold(lastLocation)
            }
        }
        startTimer()
    }
}

extension CLLocation {
    var isAccurate: Bool {
        horizontalAccuracy > 0 && horizontalAccuracy > 20
    }
}

Shohin
  • 519
  • 8
  • 11

0 Answers0