I need accurate location. To achieve the goal I have used like the below logic:
- I am trying to check updated location is to be accurate, if it is accurate then return it.
- 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
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
}
}