Last active
December 25, 2022 16:38
-
-
Save robertmryan/e70aef8595fd79ad30f773c876d155a5 to your computer and use it in GitHub Desktop.
AsyncSequence/AsyncStream for CLLocationManager
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// LocationsManager.swift | |
// MyApp | |
// | |
// Created by Robert Ryan on 12/24/22. | |
// | |
import Foundation | |
import CoreLocation | |
// MARK: - Public interface | |
// As an aside, you wouldn't usually put a manager class on the main actor, but | |
// `CLLocationManager` uses the current `RunLoop`, so we want to make an exception | |
// in this case and put it on the `@MainActor`. | |
@MainActor | |
class LocationsManager: NSObject { | |
private let locationManager = CLLocationManager() | |
private var handlers: [Handler] = [] | |
/// The most recently retrieved user location | |
/// | |
/// The value of this property is `nil` if no location data has ever been retrieved. | |
/// | |
/// In iOS 4.0 and later, this property may contain a more recent location object at launch time. Specifically, if significant location updates are running and your app is terminated, this property is updated with the most recent location data when your app is relaunched (and you create a new location manager object). This location data may be more recent than the last location event processed by your app. | |
/// | |
/// It is always a good idea to check the `timestamp` of the location stored in this property. If the receiver is currently gathering location data, but the minimum distance filter is large, the returned location might be relatively old. If it is, you can stop the receiver and start it again to force an update. | |
var location: CLLocation? { locationManager.location } | |
init( | |
desiredAccuracy: CLLocationAccuracy = kCLLocationAccuracyBest, | |
distanceFilter: CLLocationDistance = kCLDistanceFilterNone | |
) { | |
super.init() | |
locationManager.delegate = self | |
locationManager.desiredAccuracy = desiredAccuracy | |
locationManager.distanceFilter = distanceFilter | |
} | |
func locations() -> AsyncThrowingStream<CLLocation, Error> { | |
let stream = AsyncThrowingStream<CLLocation, Error> { continuation in | |
let handler = Handler( | |
didUpdateLocation: { continuation.yield($0) }, | |
didFailWithError: { continuation.finish(throwing: $0) } | |
) | |
handlers.append(handler) | |
continuation.onTermination = { _ in | |
Task { @MainActor in | |
self.handlers.removeAll { $0.id == handler.id } | |
if self.handlers.isEmpty { | |
self.stop() | |
} | |
} | |
} | |
} | |
start() | |
return stream | |
} | |
func update(for location: CLLocation) async throws { | |
print("update 1") | |
try await Task.sleep(for: .seconds(10)) | |
print("update 2") | |
} | |
func start() { | |
if locationManager.authorizationStatus == .notDetermined { | |
locationManager.requestWhenInUseAuthorization() | |
} else { | |
locationManager.startUpdatingLocation() | |
} | |
} | |
func stop() { | |
locationManager.stopUpdatingLocation() | |
} | |
} | |
// MARK: - LocationSequenceManager.Handler | |
extension LocationsManager { | |
struct Handler: Identifiable { | |
let id = UUID() | |
let didUpdateLocation: (CLLocation) -> Void | |
let didFailWithError: (Error) -> Void | |
} | |
} | |
// MARK: - CLLocationManagerDelegate | |
extension LocationsManager: CLLocationManagerDelegate { | |
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { | |
locations | |
.forEach { location in | |
handlers.forEach { $0.didUpdateLocation(location) } | |
} | |
} | |
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { | |
handlers.forEach { $0.didFailWithError(error) } | |
} | |
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { | |
let status = manager.authorizationStatus | |
if status == .authorizedAlways || status == .authorizedWhenInUse { | |
manager.startUpdatingLocation() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See WWDC 2021 Meet AsyncSequence.