How can we know which Thread is used to execute Task?
UIViewController
is marked @MainActor
which means that tasks will be dispatched on the main thread. Your X
class is not marked @MainActor
so tasks are dispatched on any available thread.
asyncDetached falling back into main thread after MainActor call
The following formulation works, and solves the entire problem very elegantly, though I'm a little reluctant to post it because I don't really understand how it works:
override func viewDidLoad() {
super.viewDidLoad()
Task {
await self.test2()
}
}
nonisolated func test2() async {
print("test 1", Thread.isMainThread) // false
let bounds = await self.view.bounds // access on main thread!
print("test 2", bounds, Thread.isMainThread) // false
}
I've tested the await self.view.bounds
call up the wazoo, and both the view
access and the bounds
access are on the main thread. The nonisolated
designation here is essential to ensuring this. The need for this and the concomitant need for await
are very surprising to me, but it all seems to have to do with the nature of actors and the fact that a UIViewController is a MainActor.
Can I use actors in Swift to always call a function on the main thread?
Actors in Swift 5.5 ♀️
Actor isolation and re-entrancy are now implemented in the Swift stdlib. So, Apple recommends using the model for concurrent logic with many new concurrency features to avoid data races. Instead of lock-based synchronisation (lots of boilerplate), we now have a much cleaner alternative.
Some UIKit
classes, including UIViewController
and UILabel
, now have out of the box support for @MainActor
. So we only need to use the annotation in custom UI-related classes. For example, in the code above, myImageView.image
would automatically be dispatched on the main queue. However, the UIImage.init(named:)
call is not automatically dispatched on the main thread outside of a view controller.
In the general case, @MainActor
is useful for concurrent access to UI-related state, and is the easiest to do even though we can manually dispatch too. I've outlined potential solutions below:
Solution 1
The simplest possible. This attribute could be useful in UI-Related classes. Apple have made the process much cleaner using the @MainActor
method annotation:
@MainActor func setImage(thumbnailName: String) {
myImageView.image = UIImage(image: thumbnailName)
}
This code is equivalent to wrapping in DispatchQueue.main.async
, but the call site is now:
await setImage(thumbnailName: "thumbnail")
Solution 2
If you have Custom UI-related classes, we can consider applying @MainActor
to the type itself. This ensures that all methods and properties are dispatched on the main DispatchQueue
.
We can then manually opt out from the main thread using the nonisolated
keyword for non-UI logic.
@MainActor class ListViewModel: ObservableObject {
func onButtonTap(...) { ... }
nonisolated func fetchLatestAndDisplay() async { ... }
}
We don't need to specify await
explicitly when we call onButtonTap
within an actor
.
Solution 3 (Works for blocks, as well as functions)
We can also call functions on the main thread outside an actor
with:
func onButtonTap(...) async {
await MainActor.run {
....
}
}
Inside a different actor
:
func onButtonTap(...) {
await MainActor.run {
....
}
}
If we want to return from within a MainActor.run
, simply specify that in the signature:
func onButtonTap(...) async -> Int {
let result = await MainActor.run { () -> Int in
return 3012
}
return result
}
This solution is slightly less cleaner than the above two solutions which are most suited for wrapping an entire function on the MainActor
. However, actor.run
also allows for inter threaded code between actor
s in one func
(thx @Bill for the suggestion).
Solution 4 (Block solution that works within non-async functions)
An alternative way to schedule a block on the @MainActor
to Solution 3:
func onButtonTap(...) {
Task { @MainActor in
....
}
}
The advantage here over Solution 3 is that the enclosing func
doesn't need to be marked as async
. Do note however that this dispatches the block later rather than immediately as in Solution 3.
Summary
Actors make Swift code safer, cleaner and easier to write. Don't overuse them, but dispatching UI code to the main thread is a great use case. Note that since the feature is still in beta, the framework may change/improve further in the future.
Since we can easily use the actor
keyword interchangeably with class
or struct
, I want to advise limiting the keyword only to instances where concurrency is strictly needed. Using the keyword adds extra overhead to instance creation and so doesn't make sense when there is no shared state to manage.
If you don't need a shared state, then don't create it unnecessarily. struct
instance creation is so lightweight that it's better to create a new instance most of the time. e.g. SwiftUI
.
Swift - Not wait for async to return
You can wrap the function in a Task, Task
func doSomething() {
Task {
do {
try await a.doSomething()
} catch {
print(error)
}
}
}
Related Topics
Checking If an Array of Custom Objects Contain a Specific Custom Object
Listing All Files in a Folder Recursively with Swift
"Generic Parameter Could Not Be Inferred" in Swiftui Uiviewrepresentable
Why Does Swift Playground Shows Wrong Number of Executions
Call Completion Block When Two Other Completion Blocks Have Been Called
Swift, How to Play Sound When Press a Button
How to Run Xctest for a Swift Application from the Command Line
Turn Off Xcode's Unused Variable Warnings While Typing
How to Open Your App in Settings iOS 11
Swiftui Customized Text Field in Osx
Is This a Good Way to Display Asynchronous Data
Make Int Round Off to Nearest Value
How to Rotate Only One View Controller to Landscape Orientation in iOS Swift 3