tokizo
(tokizo)
1
I was experimenting with writing Global Actor code on hand while watching SE-0316. At that time, even though @MainActor was given, it was not executed on the main thread. Here is the code:
import UIKit
class ViewController: UIViewController {
var callback: () -> Void = { @MainActor in
print(Thread.isMainThread) // false
}
override func viewDidLoad() {
super.viewDidLoad()
Task.detached {
await self.callback()
}
}
}
Adding async to the closure's type annotation executed it in the main thread as intended. Is the earlier code a bug? Please let me know if there is any knowledge that is missing, not a bug.
import UIKit
class ViewController: UIViewController {
var callback: () async -> Void = { @MainActor in
print(Thread.isMainThread) // true
}
override func viewDidLoad() {
super.viewDidLoad()
Task.detached {
await self.callback()
}
}
}
- Swift version: 5.10
- Xcode: 15.3
1 Like
tera
2
Looks like a bug.
Making it a function also fixes it.
class ViewController: UIViewController {
func callback() {
...
}
...
}
tokizo
(tokizo)
3
Oh, thanks.
In that example, it would be true. It's ok to understand.
Detatched Task does not inherit actor context, but since UIViewContrller is implicitly isolated to MainActor, the callback is isolated to MainActor, so it is executed on the main thread.
import UIKit
class ViewController: UIViewController {
func callback() {
print(Thread.isMainThread) // true
}
override func viewDidLoad() {
super.viewDidLoad()
Task.detached {
await self.callback()
}
}
}
You could also explicitly call over to the MainActor if thatâs easier to read:
Task {
MainActor.run {
await self.callback()
}
}
I sometimes find this easier for me to remember what I was aiming for. Kind of the equivalent to âmain.async { }â from the DispatchQueue technique.
1 Like
tokizo
(tokizo)
5
That's for sure.
I checked additionally, and when I removed the type annotation as shown below, it was executed on the main thread. It seems that the type annotation I wrote was incorrect.
import UIKit
class ViewController: UIViewController {
var callback = { @MainActor in
print(Thread.isMainThread) // true
}
override func viewDidLoad() {
super.viewDidLoad()
Task.detached {
await self.callback()
}
}
}
1 Like
tera
6
Hmm, perhaps the type of callback is not "() -> Void" but "() async -> Void" in this case, as ViewController is implicitly an actor by virtue of UIViewController?
tokizo
(tokizo)
7
Yes, it seems so. It is probably no longer isolated to MainActor due to the () -> Void type annotation.
mbrandonw
(Brandon Williams)
8
If you turn on strict concurrency warnings you will see that there is a problem with this code:
let callback: () -> Void = { @MainActor in ⌠}
Converting function value of type '@MainActor () -> Void' to '() -> Void' loses global actor 'MainActor'; this is an error in Swift 6
10 Likes
pyrtsa
(Pyry Jahkola)
9
The correct type annotation would be var callback: @MainActor () -> Void. (âŚAs seen in the above compiler diagnostic.)
1 Like
Right. Actor isolation has to be represented in the type of a sync function, or else it just canât be automatically honored â itâs sync, so only the caller can make sure it runs with the right isolation. In non-strict mode, Swift has to assume that youâre getting it right dynamically somehow, but strict mode makes the compiler diagnose it immediately. SE-0423, if accepted, will at least make Swift diagnose the failure dynamically.
8 Likes
Sorry for jumping with my topic but I have a slightly different yet similar problem with understanding how @MainActor annotation is preserved while passing around a closure.
Could someone look at why it is not respected even if the closure type is annotated with @MainActor?