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)