CLMonitor singleton to workaround the “Monitor named is already in use” exception

Published by malhal on

If you attempt to init another instance of CLMonitor with the same name it crashes with:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Monitor named SampleMonitor is already in use'

Since I wanted to use the same monitor from different tasks I needed to turn it into a singleton and came up with this:

extension CLMonitor {
    @MainActor
    static var sampleMonitor: CLMonitor {
        get async {
            @MainActor
            struct Static {
                static var task: Task<CLMonitor, Never>?
            }
            if let task = Static.task {
                return await task.value
            }
            let task = Task { await CLMonitor("SampleMonitor") }
            Static.task = task
            return await task.value
        }
    }
}

This allows this kind of thing to work without crashing:

@main
struct LocationMonitorSampleApp: App {
    
    var body: some Scene {
        WindowGroup {
            Group {
                ContentView()
                    .task {
                        let monitor = await CLMonitor.sampleMonitor
                    }
                    .task {
                        let monitor = await CLMonitor.sampleMonitor
                    }
            }
        }
    }
}

Note you can only subscribe to the events stream from one task, however the singleton means at least you can now add new regions to monitor from different tasks.