Friday, May 3, 2024
HomeIOS DevelopmentDispatching to the Predominant thread with MainActor in Swift – Donny Wals

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


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:

  1. Apply an @MainActor annotation to each homePageData and loadHomePage
  2. Apply @MainActor to your entire HomePageViewModel to isolate each the homePageData property and the loadHomePage operate to the primary actor
  3. Use [MainActor.run](http://MainActor.run) or an unstructured process that’s remoted to the primary actor within loadHomePage.

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.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments