Crash: Call must be on main thread

As Swift give as async await functions, I tried to use them for push notifications. In my case if I use

extension TMNotificationCenter: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        completionHandler([.sound, .banner, .list, .badge])
    }

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        completionHandler()
    }
}

There are no error, erevything works fine, but when I change to this:

extension TMNotificationCenter: UNUserNotificationCenterDelegate {
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {
        print(#function)
        return [.sound, .banner, .list, .badge]
    }
    
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {
        print(#function)
    }
}

I got crash:

What I am doing wrong?

The callback-based APIs do not declare their callbacks as @MainActor. This doesn't matter when you explicitly use the callback-based API, because they were actually called on the main thread, and you so your code runs in the right place.

When you use the async versions, the functions are not declared or assumed to be @MainActor, so they run in a default non-actor concurrency context, which runs on the global concurrent executor. As a result, when these functions run, they hop to that executor — which is to say, the hop off the main thread. This leads to your crash, obviously.

There are a couple of possible ways around this:

  1. You can continue to use the callback-based APIs.
  2. Whichever version of the APIs you use, if there's no return value needed, you can embed your code within a Task { @MainActor in … } block inside each function.
  3. If you have a recent version of the Swift compiler, you might be able to use MainActor.assumeIsolated { … } to get the Swift compiler to make the correct assumption about where the function is already running. This is something fairly new in SE-0392, so I'd recommend you verify its behavior before relying on it.
Crash: Call must be on main thread
 
 
Q