Dispatching to the Foremost thread with MainActor in Swift – Donny Wals


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:

  1. Apply an @MainActor annotation to each homePageData and loadHomePage
  2. Apply @MainActor to your complete HomePageViewModel to isolate each the homePageData property and the loadHomePage operate to the primary actor
  3. Use MainActor.run or an unstructured activity that’s remoted to the primary actor within loadHomePage.

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.

Recent Articles

Related Stories

Leave A Reply

Please enter your comment!
Please enter your name here

Stay on op - Ge the daily news in your inbox