Printed 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 publish you’ll be taught a number of methods that you should utilize 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 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 advocate studying this publish which explores Swift Concurrency’s isolation options.
Alternatively, should you’re interested by a deep dive into Swift Concurrency and actors I extremely advocate that you just try my guide on Swift Concurrency or that you just try my video course on Swift Concurrency. Each of those sources gives you deeper insights and background data on actors.
Dispatching to the primary thread via the MainActor annotation
The quickest solution to get a operate 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 = 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 an alternative, 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 information.
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 @Printed
property so we should always at all times assign to it from the primary thread to keep away from important 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 can too annotate the homePageData
property as a substitute:
class HomePageViewModel: ObservableObject {
@MainActor @Printed var homePageData: HomePageData?
func loadHomePage() async throws {
self.homePageData = strive await networking.fetchHomePage()
}
}
Sadly, this code results in the next compiler error:
Predominant actor-isolated property ‘homePageData’ can’t 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
technique just isn’t operating on the primary actor which is information security downside in Swift Concurrency; we should mutate the homePageData
property from a context that’s remoted to the primary actor.
We will remedy this situation in one among 3 ways:
- Apply an
@MainActor
annotation to eachhomePageData
andloadHomePage
- Apply
@MainActor
to your entireHomePageViewModel
to isolate each thehomePageData
property and theloadHomePage
operate to the primary actor - Use
[MainActor.run](http://MainActor.run)
or an unstructured process that’s remoted to the primary actor withinloadHomePage
.
The quickest repair is to annotate our total 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 = strive await networking.fetchHomePage()
}
}
That is completely tremendous and can ensure 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
Should you don’t wish to annotate your total view mannequin with the primary actor, you’ll be able to isolate chunks of your code to the primary actor by calling the static run
technique on the MainActor
object:
class HomePageViewModel: ObservableObject {
@Printed var homePageData: HomePageData?
func loadHomePage() async throws {
let information = strive await networking.fetchHomePage()
await MainActor.run {
self.homePageData = information
}
}
}
Notice that the closure that you just move to run
just isn’t marked as async
. Which means any asynchronous work that you just wish 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 may be fairly handy should you don’t wish to annotate your total loadHomePage
technique with @MainActor
.
The final technique to dispatch to important that I’d like to indicate is thru an unstructured process.
Isolating an unstructured process to the primary actor
should you’re creating a brand new Process
and also you wish to ensure that your process runs on the primary actor, you’ll be able to apply an @MainActor
annotation to your process’s physique as follows:
class HomePageViewModel: ObservableObject {
@Printed var homePageData: HomePageData?
func loadHomePage() async throws {
Process { @MainActor in
self.homePageData = strive await networking.fetchHomePage()
}
}
}
On this case, we should always have simply annotated our loadHomePage
technique with @MainActor
as a result of we’re creating an unstructured process that we don’t want and we isolate our process to important.
Nonetheless, should you’d have to jot down loadHomePage
as a non-async technique creating a brand new main-actor remoted process may 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 change your calls to DispatchQueue.important.async
and with this publish you might have all of the code examples you want to have the ability to just do that.
Notice that a few of the examples supplied on this publish produce warnings underneath 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 should you’re eager on getting your codebase prepared for Swift 6.