I haven't found any definitive documentation around this - so please point me at that if it's available!
Somehow, the compiler 'knows' that
DispatchQueue.background.async {
//this does not run on the main-actor
}
DispatchQueue.main.async {
//this does run on the main-actor
}
I can't see any clues in the headers as to how the two cases are annotated differently. They both just show as DispatchQueue.
I have a similar case in my own API where the user can set the queue for a notification to callback.
I'd like to mark that the block runs on @MainActor if the queue is .main - but I can't see how to do that.
e.g.
func register(on queue:DispatchQueue,callback:<if queue is .main @MainActor> ()->Void)
There's indeed some magic inside. It's not very deep/thorough though, probably some simple heuristic in there:
func foo() {
DispatchQueue.main.async {
_ = UIApplication.shared.applicationState // ok
}
let mainQueue = DispatchQueue.main
mainQueue.async {
_ = UIApplication.shared.applicationState // error
// Class property 'shared' isolated to global actor 'MainActor' can not be referenced from a non-isolated synchronous context
}
}
Edit:
Or even this:
enum DispatchQueue { // let's cheat
static let main = Foundation.DispatchQueue.global()
}
DispatchQueue.main.async { // not real main
_ = UIApplication.shared.applicationState // ok 😀
}
Looks so. I'd prefer some annotation system like @convention(mainActor) to make the check thorough but apparently it's much harder to do. You can still rely on runtime checks though.