@MainActor does not always mean main thread

Published by malhal on

Seems to me some developers think that @MainActor means “always main thread” and has the same behaviour as DispatchQueue.main.async which I don’t think is try. For example, the code below outputs:

before true
inside 1 false
after true
class Database {
    func func1() async {
        print("inside 1", Thread.isMainThread)
    }
}

@main
@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
    let db = Database()
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        
        Task {
            print("before", Thread.isMainThread)
            await db.func1()
            print("after", Thread.isMainThread)
        }
        
    }
}

Because the Database class is not marked as @MainActor then loadModel will in fact be on a background thread, despite being invoked from an @MainActor context. It seems what matters is where the func is defined not the callsite. Sometimes class Database is implemented as actor Database instead. Another way to await something executed on a background thread is to use nonisolated, e.g.

@main
@MainActor
class AppDelegate: NSObject, NSApplicationDelegate {
    
    func applicationDidFinishLaunching(_ notification: Notification) {
        
        Task {
            print("before", Thread.isMainThread)
            await func1()
            print("after", Thread.isMainThread)
        }
    }
    
    nonisolated func func1() async {
        print("inside 1", Thread.isMainThread)
    }
}

The output is the same as the first example.