DEV Community

David Rickard
David Rickard

Posted on

Thread-safe async location fetching in Swift

The APIs to get the current location are a bit awkward. I've never liked callback APIs, so I wrap them in proper async calls whenever I find them.

Using a continuation is the first step, and it gives you an async method to call. However, this just hangs indefinitely if you have the misfortune of calling it on a non-UI thread.

To get a proper version, you have to ensure it's executed on the main thread. This is what I came up with:

import Foundation import CoreLocation @MainActor class OneTimeGeoLocationUtil: NSObject { static func getLocation() async -> CLLocationCoordinate2D? { let oneTimeLocationService = GeolocServiceInternal() return await oneTimeLocationService.getLocation() } } private class GeolocServiceInternal: NSObject, CLLocationManagerDelegate { private let manager = CLLocationManager() private var continuation: CheckedContinuation<CLLocationCoordinate2D?, Never>? override init() { super.init() manager.delegate = self } func getLocation() async -> CLLocationCoordinate2D? { if !CLLocationManager.locationServicesEnabled() { return nil } return await withCheckedContinuation { continuation2 in continuation = continuation2 manager.requestLocation() } } func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let location = locations.first { continuation?.resume(returning: location.coordinate) continuation = nil } } func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { // Log the error continuation?.resume(returning: nil) continuation = nil } } 
Enter fullscreen mode Exit fullscreen mode

A one-time use class instance holds the continuation and hosts the callback functions, exposing the raw async API. Then a wrapper static function with @MainActor makes it easier to call and ensures requestLocation() is executed on the main thread.

Top comments (0)