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
@MainActorannotations, or modifying actor isolation. - Example warning: “Call to global function must be isolated to the main actor.”
- The compiler provides automatic fix-it suggestions. These include adding
-
Stricter Actor Isolation:
- In Swift 6, actors enforce stricter access control. This ensures functions marked within an
actorget invoked withawait.
- In Swift 6, actors enforce stricter access control. This ensures functions marked within an
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:
- Added
@MainActorto the HomeViewModel declaration. - Removed the
@MainActor incontext of theTask.
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.