Revealed on: April 23, 2024
Swift 5.5 launched a great deal of new concurrency associated options. Considered one of these options is the MainActor
annotation that we are able to apply to lessons, features, and properties.
On this submit you’ll study a number of methods that you should use to dispatch your code to the primary thread from inside Swift Concurrency’s duties or by making use of the primary actor annotation.
In case you’d wish to take a deep dive into studying how one can work out whether or not your code runs on the primary actor I extremely suggest studying this submit which explores Swift Concurrency’s isolation options.
Alternatively, should you’re curious about a deep dive into Swift Concurrency and actors I extremely suggest that you simply take a look at my ebook on Swift Concurrency or that you simply take a look at my video course on Swift Concurrency. Each of those sources will provide you with deeper insights and background info on actors.
Dispatching to the primary thread by the MainActor annotation
The quickest option to get a operate to run on the primary thread in Swift Concurrency is to use the @MainActor
annotation to it:
class HomePageViewModel: ObservableObject {
@Revealed var homePageData: HomePageData?
@MainActor
func loadHomePage() async throws {
self.homePageData = strive await networking.fetchHomePage()
}
}
The code above will run your loadHomePage
operate on the primary thread. The cool factor about that is that the await
on this operate isn’t blocking the primary thread. As a substitute, it permits our operate to be suspended in order that the primary thread can do another work whereas we await fetchHomePage()
to come back again with some knowledge.
The impact that making use of @MainActor
to this operate has is that the project of self.homePageData
occurs on the primary thread which is sweet as a result of it’s an @Revealed
property so we must always at all times assign to it from the primary thread to keep away from major thread associated warnings from SwiftUI at runtime.
In case you don’t like the thought of getting all of loadHomePage
run on the primary actor, you too can annotate the homePageData
property as a substitute:
class HomePageViewModel: ObservableObject {
@MainActor @Revealed var homePageData: HomePageData?
func loadHomePage() async throws {
self.homePageData = strive await networking.fetchHomePage()
}
}
Sadly, this code results in the next compiler error:
Foremost actor-isolated property ‘homePageData’ cannot be mutated from a non-isolated context
This tells us that we’re making an attempt to mutate a property, homePageData
on the primary actor whereas our loadHomePage
methodology isn’t operating on the primary actor which is knowledge security drawback in Swift Concurrency; we should mutate the homePageData
property from a context that’s remoted to the primary actor.
We will resolve this situation in one in every of 3 ways:
- Apply an
@MainActor
annotation to eachhomePageData
andloadHomePage
- Apply
@MainActor
to your completeHomePageViewModel
to isolate each thehomePageData
property and theloadHomePage
operate to the primary actor - Use
MainActor.run
or an unstructured activity that’s remoted to the primary actor withinloadHomePage
.
The quickest repair is to annotate our complete class with @MainActor
to run the whole lot that our view mannequin does on the primary actor:
@MainActor
class HomePageViewModel: ObservableObject {
@Revealed var homePageData: HomePageData?
func loadHomePage() async throws {
self.homePageData = strive await networking.fetchHomePage()
}
}
That is completely nice and can be sure that your whole view mannequin work is carried out on the primary actor. That is truly actually near how your view mannequin would work should you didn’t use Swift Concurrency because you usually name all view mannequin strategies and properties from inside your view anyway.
Let’s see how we are able to leverage choice three from the listing above subsequent.
Dispatching to the primary thread with MainActor.run
In case you don’t wish to annotate your complete view mannequin with the primary actor, you may isolate chunks of your code to the primary actor by calling the static run
methodology on the MainActor
object:
class HomePageViewModel: ObservableObject {
@Revealed var homePageData: HomePageData?
func loadHomePage() async throws {
let knowledge = strive await networking.fetchHomePage()
await MainActor.run {
self.homePageData = knowledge
}
}
}
Observe that the closure that you simply go to run
isn’t marked as async
. Which means that any asynchronous work that you simply wish to do must occur earlier than your name to MainActor.run
. The entire work that you simply put within the closure that you simply go to MainActor.run
is executed on the primary thread which might be fairly handy should you don’t wish to annotate your complete loadHomePage
methodology with @MainActor
.
The final methodology to dispatch to major that I’d like to indicate is thru an unstructured activity.
Isolating an unstructured activity to the primary actor
should you’re creating a brand new Process
and also you wish to be sure that your activity runs on the primary actor, you may apply an @MainActor
annotation to your activity’s physique as follows:
class HomePageViewModel: ObservableObject {
@Revealed var homePageData: HomePageData?
func loadHomePage() async throws {
Process { @MainActor in
self.homePageData = strive await networking.fetchHomePage()
}
}
}
On this case, we must always have simply annotated our loadHomePage
methodology with @MainActor
as a result of we’re creating an unstructured activity that we don’t want and we isolate our activity to major.
Nevertheless, should you’d have to jot down loadHomePage
as a non-async methodology creating a brand new main-actor remoted activity might be fairly helpful.
In Abstract
On this submit you’ve seen a number of methods to dispatch your code to the primary actor utilizing @MainActor
and MainActor.run
. The primary actor is meant to substitute your calls to DispatchQueue.major.async
and with this submit you have got all of the code examples you want to have the ability to just do that.
Observe that among the examples offered on this submit produce warnings beneath strict concurrency checking. That’s as a result of the HomePageViewModel
I’m utilizing on this submit isn’t Sendable
. Making it conform to Sendable
would do away with all warnings so it’s a good suggestion to brush up in your data of Sendability should you’re eager on getting your codebase prepared for Swift 6.