I’ve been trying to figure out a way to call corelocation’s requestLocation on the main thread (which apparently is required).
Consider this MRE
import CoreLocation
import MapKit
import SwiftUI
struct ContentView: View {
var locationManager = LocationManager()
var body: some View {
Button {
Task {
let location = try await locationManager.currentLocation // works
print(location)
let location2 = try await locationManager.work() // works, no mainactor needed
print(location2)
let location3 = try await APIService.shared.test() // doesnt work
print(location3)
let location4 = try await APIService.shared.test2() // works, mainactor needed
print(location4)
let location5 = try await APIService.shared.test3() // doesnt work even with mainactor
print(location5)
}
} label: {
Text("Get Location")
}.task {
// 1. Check if the app is authorized to access the location services of the device
locationManager.checkAuthorization()
}
}
}
class LocationManager: NSObject, CLLocationManagerDelegate {
// MARK: Object to Access Location Services
private let locationManager = CLLocationManager()
// MARK: Set up the Location Manager Delegate
override init() {
super.init()
locationManager.delegate = self
}
// MARK: Request Authorization to access the User Location
func checkAuthorization() {
switch locationManager.authorizationStatus {
case .notDetermined:
locationManager.requestWhenInUseAuthorization()
default:
return
}
}
// MARK: Continuation Object for the User Location
private var continuation: CheckedContinuation<CLLocation, Error>?
// MARK: Async Request the Current Location
var currentLocation: CLLocation {
get async throws {
return try await withCheckedThrowingContinuation { continuation in
// 1. Set up the continuation object
self.continuation = continuation
// 2. Triggers the update of the current location
locationManager.requestLocation()
}
}
}
@MainActor
var currentLocation2: CLLocation {
get async throws {
return try await withCheckedThrowingContinuation { continuation in
// 1. Set up the continuation object
self.continuation = continuation
// 2. Triggers the update of the current location
locationManager.requestLocation()
}
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
// 4. If there is a location available
if let lastLocation = locations.last {
// 5. Resumes the continuation object with the user location as result
continuation?.resume(returning: lastLocation)
// Resets the continuation object
continuation = nil
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
// 6. If not possible to retrieve a location, resumes with an error
continuation?.resume(throwing: error)
// Resets the continuation object
continuation = nil
}
func work() async throws -> CLLocation {
return try await currentLocation
}
}
class APIService {
static let shared = APIService()
// Private initializer to prevent the creation of additional instances
private init() {
}
func test() async throws -> String {
return try await String(describing: LocationManager().currentLocation)
}
@MainActor
func test2() async throws -> String {
return try await String(describing: LocationManager().currentLocation)
}
func test3() async throws -> String {
return try await String(describing: LocationManager().currentLocation2)
}
}
Test1 works as expected because Task from the view is inherited as mainactor
Test2 works for the same reason I assume
Test3 doesnt work not sure why when test2 worked? I guess if it goes to another class, you lose the actor?
Test4 works as expected because you force it to be mainactor
Test5 doesnt work mysteriously even though you force it to be mainactor again
So what is the rule for main thread in swift concurrency?