Instruction

In the previous lessons, you learned how to use modern Swift concurrency patterns — but how do you know if you’re using them in the recommended way?

Swift 6 introduces a new language mode, designed to enforce stricter concurrency checks. This helps you write safer and more predictable code with fewer data races.

Swift 6 language mode aims to achieve:

  • Stricter Concurrency Enforcement: To prevent data races — by ensuring stricter access rules for shared mutable state.
  • Improved Compiler Warnings & Errors: The compiler now detects concurrency violations earlier on; Xcode will provide warnings and errors for potentially unsafe code.
  • Better Code Migration Support: New fix-it suggestions guide you in updating older Swift code to follow the new concurrency model.

Fix-it Suggestions

When migrating to Swift 6, you might encounter compiler warnings related to concurrency.

The following tools help in updating your code:

  • Fix-it Suggestions:

    • The compiler provides automatic fix-it suggestions. These include adding @MainActor annotations, or modifying actor isolation.
    • Example warning: “Call to global function must be isolated to the main actor.”
  • Stricter Actor Isolation:

    • In Swift 6, actors enforce stricter access control. This ensures functions marked within an actor get invoked with await.

Migrating the Weather Sample App to Swift 6

Up until this point, your weather app project has used Swift 5. However, you’ll now update the project to use Swift 6. Awesome!

How to update to Swift 6: Open the weather sample app starter project. Select WeatherAppSample in the left column.

Navigate to Build Settings. Search for Swift Language Version.

Finally, select Swift 6 from the dropdown list. After this, your build settings should look like this:

Once enabled, the compiler will begin enforcing Swift 6’s new concurrency rules — this will help you identify unsafe code.

Reviewing the Errors

Looking back on the previous lessons, you built a weather sample app with async/await and used actor types. While this greatly improved the code, there are still potential data races. Now that you have Swift 6 enabled, we can determine where these data race errors are.

Attempt to build and run your project. Notice that the following errors show up now that you’re using Swift 6:

Fixing the Errors

You’ll start with the first error in HomeViewModel.

Click on the error, or open up HomeViewModel.swift. Look for the line in the Task block that calls weatherRepo.fetchWeather(for: query). The error is Sending 'query' risks causing data races. If you click on the error next to the line to expand it, you get the following explanation:

Task-isolated ‘query’ is captured by a main actor-isolated closure. main actor-isolated uses in closure may race against later nonisolated uses

This means you’re using query in an unsafe way. query gets created outside of the @MainActor Task block that it’s referenced in — access to this variable from the main thread can cause a data race with unexpected behavior.

To fix this error, replace the HomeViewModel code with:

// 1
@MainActor class HomeViewModel: ObservableObject {
  @Published var state: HomeState = .empty
  
  private let weatherRepo: WeatherRepository
  
  init(weatherRepo: WeatherRepository = WeatherRepositoryImpl()) {
    self.weatherRepo = weatherRepo
  }

  func getWeather(query: String) {
    state = .loading

    // 2
    Task {
      do {
        let weatherData = try await weatherRepo.fetchWeather(for: query)
        state = .ready(weatherData)
      } catch (_) {
        state = .error
      }
    }
  }
}

Breaking down the changes, you:

  1. Added @MainActor to the HomeViewModel declaration.
  2. Removed the @MainActor in context of the Task.

Try building and running the app. Notice there’s still an error on the weatherRepo.fetchWeather(for: query) line! But why, you ask? Notice the error has changed:

Sending main actor-isolated ‘self.weatherRepo’ to nonisolated instance method ‘fetchWeather(for:)’ risks causing data races between nonisolated and main actor-isolated uses

This is progress! Now we know that there’s a direct issue with accessing weatherRepo within our main actor. If you open up the WeatherRepository protocol, it does not conform to the Actor protocol, so the compiler throws the error above.

The Actor protocol generalizes over all actor types; any actor types conform to this protocol. When you use an actor type (rather than class or struct), it will conform to Actor. Making the WeatherRepository protocol conform to Actor enforces all implementations to be actors. This will help ensure thread safety of actor-isolated variables.

To fix the error, open WeatherRepository.swift and change this line from:

protocol WeatherRepository {

to:

protocol WeatherRepository: Actor {

Now, WeatherRepository conforms to the Actor protocol — the error on the HomeViewModel goes away!

Still, after building and attempting to run the app once more, you run into one more error. And, this time in WeatherRepositoryImpl.swift, when it attempts to call weatherService.getWeather(for: query):

Sending ‘self’-isolated ‘self.weatherService’ to nonisolated instance method ‘getWeather(for:)’ risks causing data races between nonisolated and ‘self’-isolated uses

As you may have guessed, WeatherService and WAPIWeatherService need to become actor-conforming.

To do this, first open WeatherService.swift and replace:

protocol WeatherService {

with:

protocol WeatherService: Actor {

Last, open WAPIWeatherService.swift and make it an actor by replacing:

class WAPIWeatherService: WeatherService {

with:

actor WAPIWeatherService: WeatherService {

That should do the trick! Make sure that your API key is added to the WAPIWeatherService class. Build and run the app and the errors should go away. The app should run and function as it normally does.

See forum comments
Download course materials from Github
Previous: Introduction Next: Conclusion