Printed on: April 23, 2024
Swift 5.5 launched a great deal of new concurrency associated options. One in all these options is the MainActor
annotation that we are able to apply to courses, features, and properties.
On this publish 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.
Should you’d prefer 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 publish which explores Swift Concurrency’s isolation options.
Alternatively, in case you’re thinking about a deep dive into Swift Concurrency and actors I extremely suggest that you just try my e-book on Swift Concurrency or that you just try my video course on Swift Concurrency. Each of those sources offers you deeper insights and background data on actors.
Dispatching to the primary thread by means of the MainActor annotation
The quickest option to get a perform to run on the primary thread in Swift Concurrency is to use the @MainActor
annotation to it:
class HomePageViewModel: ObservableObject {
@Printed var homePageData: HomePageData?
@MainActor
func loadHomePage() async throws {
self.homePageData = attempt await networking.fetchHomePage()
}
}
The code above will run your loadHomePage
perform on the primary thread. The cool factor about that is that the await
on this perform isn’t blocking the primary thread. As a substitute, it permits our perform to be suspended in order that the primary thread can do another work whereas we anticipate fetchHomePage()
to return again with some information.
The impact that making use of @MainActor
to this perform has is that the project of self.homePageData
occurs on the primary thread which is nice as a result of it’s an @Printed
property so we should always at all times assign to it from the primary thread to keep away from foremost thread associated warnings from SwiftUI at runtime.
Should you don’t like the concept of getting all of loadHomePage
run on the primary actor, you may also annotate the homePageData
property as a substitute:
class HomePageViewModel: ObservableObject {
@MainActor @Printed var homePageData: HomePageData?
func loadHomePage() async throws {
self.homePageData = attempt await networking.fetchHomePage()
}
}
Sadly, this code results in the next compiler error:
Important actor-isolated property ‘homePageData’ cannot be mutated from a non-isolated context
This tells us that we’re attempting to mutate a property, homePageData
on the primary actor whereas our loadHomePage
methodology isn’t operating on the primary actor which is information security drawback in Swift Concurrency; we should mutate the homePageData
property from a context that’s remoted to the primary actor.
We will remedy this challenge in one among 3 ways:
- Apply an
@MainActor
annotation to eachhomePageData
andloadHomePage
- Apply
@MainActor
to the completeHomePageViewModel
to isolate each thehomePageData
property and theloadHomePage
perform to the primary actor - Use
[MainActor.run](http://MainActor.run)
or an unstructured job that’s remoted to the primary actor withinloadHomePage
.
The quickest repair is to annotate our whole class with @MainActor
to run every thing that our view mannequin does on the primary actor:
@MainActor
class HomePageViewModel: ObservableObject {
@Printed var homePageData: HomePageData?
func loadHomePage() async throws {
self.homePageData = attempt await networking.fetchHomePage()
}
}
That is completely tremendous 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 in case 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 possibility three from the checklist above subsequent.
Dispatching to the primary thread with MainActor.run
Should you don’t need to annotate your whole 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 {
@Printed var homePageData: HomePageData?
func loadHomePage() async throws {
let information = attempt await networking.fetchHomePage()
await MainActor.run {
self.homePageData = information
}
}
}
Notice that the closure that you just move to run
isn’t marked as async
. Which means any asynchronous work that you just need to do must occur earlier than your name to MainActor.run
. The entire work that you just put within the closure that you just move to MainActor.run
is executed on the primary thread which could be fairly handy in case you don’t need to annotate your whole loadHomePage
methodology with @MainActor
.
The final methodology to dispatch to foremost that I’d like to indicate is thru an unstructured job.
Isolating an unstructured job to the primary actor
in case you’re creating a brand new Process
and also you need to be sure that your job runs on the primary actor, you may apply an @MainActor
annotation to your job’s physique as follows:
class HomePageViewModel: ObservableObject {
@Printed var homePageData: HomePageData?
func loadHomePage() async throws {
Process { @MainActor in
self.homePageData = attempt await networking.fetchHomePage()
}
}
}
On this case, we should always have simply annotated our loadHomePage
methodology with @MainActor
as a result of we’re creating an unstructured job that we don’t want and we isolate our job to foremost.
Nevertheless, in case you’d have to put in writing loadHomePage
as a non-async methodology creating a brand new main-actor remoted job could be fairly helpful.
In Abstract
On this publish 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.foremost.async
and with this publish you may have all of the code examples you want to have the ability to do exactly that.
Notice that a few of the examples supplied on this publish produce warnings below strict concurrency checking. That’s as a result of the HomePageViewModel
I’m utilizing on this publish 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 in case you’re eager on getting your codebase prepared for Swift 6.