WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

What's new in Swift 5.10?

Important concurrency clean ups ahead of Swift 6.

Paul Hudson       @twostraws

A huge swathe of features, changes, and adjustments are planned Swift 6, but before then we have Swift 5.10: an interim release mostly focused on fixing up data race checking at compile time, hopefully clearing the decks for Swift 6.

Let's take a look at what's changing…

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

Data races are now clearly diagnosed

Swift concurrency was introduced back in Swift 5.5, but had a bit of a rocky adoption both in Apple's own frameworks and our own projects. However, with Swift 5.10 the team made a rather dramatic statement: "Swift 5.10 closes all known static data-race safety holes in complete strict concurrency checking."

Concurrency checking is what allows the compiler to verify our use of concurrent code is safe – that we aren't accidentally sharing mutable state in a way that can cause race conditions. Of course, the key word here is "known": everything they know about has been resolved.

Apple's work here is not only hugely innovative, but hugely complex: similar to how type inference requires the Swift compiler to be able to reason about how various parts of our code are used, in concurrency the compiler is effectively running a series of algorithms that attempt to determine conclusively that our code is concurrency-safe.

To give you a concrete example, this code generated a warning in Swift 5.9:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Tap Me", action: doWork)
    }

    func doWork() {
        print("Hello")
    }
}

That would throw up the rather unhelpful warning, "Converting function value of type '@MainActor () -> ()' to '() -> Void' loses global actor MainActor".

The problem here is that SwiftUI's Button view doesn't use @MainActor for its action, so Swift was throwing up a warning that we were calling a main actor-method from somewhere that isn't isolated to the main actor. This warning has been removed by the concurrency checking improvements in Swift 5.10: the compiler can now see the button action exists in side the body property, which is isolated to the main actor, and therefore is safe.

Allow Protocols to be Nested in Non-Generic Contexts

SE-0404 allows us to create nested protocols, meaning that we can place protocols inside structs, enums, classes, actors, and even functions, with the sole restriction that whatever we're nesting the protocol in can't use generics.

This is particularly helpful when common names are given to protocols. For example, the word "transaction" could feasibly be used to mean an animation transaction, a bank transaction, and a database transaction all in the same app.

One way to resolve this is by using compound names – we add more words to protocol names to clarify what we mean, like this:

protocol AnimationTransaction {
}

protocol BankTransaction {
}

protocol DatabaseTransaction {
}

Another common problem also occurs when several similar protocols exist. For example, in SwiftUI we have protocols for ButtonStyle, LabelStyle, ListStyle, and more, all encapsulating the idea that a view can be styled in various ways.

Both of these can be resolved with this change. In the case of transactions, we could nest each transaction type inside whatever type it operated on:

struct Animation {
    protocol Transaction {
    }
}

struct Bank {
    protocol Transaction {
    }
}

struct Database {
    protocol Transaction {       
    }
}

Where those protocols are used externally, they would now be written Animation.Transaction, Bank.Transaction, and Database.Transaction, but inside their respective structs they can just be referred to as Transaction.

In theory, SwiftUI could also move to Button.Style, List.Style, and so on, but that feels like a big change at this point.

Deprecate @UIApplicationMain and @NSApplicationMain

SE-0383 formally deprecates the @UIApplicationMain and @NSApplicationMain attributes, encouraging folks to switch across to the general-purpose @main attribute that was introduced back in Swift 5.3.

Adopting this change is trivial. You should be able to change this code:

import SwiftUI 

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    // your code here
}

To this:

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    // your code here        
}

Or if you're using SwiftUI, just this:

@main
struct SandboxApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Refined actor initialization and deinitialization

SE-0327 adds some clarifications about how state inside actors is created and destroyed, and also relaxes some rules that were overly restrictive.

This proposal contains a number of small, specific changes to actors. For example, Swift will now automatically make actors with an async initializer move to the actor's executor when all its properties are initialized.

In code, it means the two print() calls shown below might execute on entirely different threads:

actor Actor {
    var name: String

    init(name: String) async {
        print(name)
        self.name = name
        print(name)
    }
}

let actor = await Actor(name: "Margot")

This means the code has a potential suspension directly after the name property is set.

Onwards to Swift 6

Swift 6 introduces a gigantic batch of changes to the language, including enabling various that were implemented in earlier Swift versions including 5.10, but disabled by default. Although strictly speaking these features were implemented in 5.10 and earlier, they weren't enabled and so I'll be discussing those in a separate What's New in Swift 6 article.

When enabled fully, I have no doubt Swift 6 will cause compile errors for a great many projects out there.

We still have a few months until the final version of Swift 6 ships, so I suggest you try enabling features from there one by one while remaining in Swift 5 language mode as long as you can. This should allow you to update your code incrementally, opting in to feature flags individually to make sure your code will work well when you finally enable Swift 6 language mode.

You might be tempted to enable complete concurrency checking in Swift 5.10 as a lead-up to Swift 6. While I do think it's important to at least try enabling the feature, you shouldn't panic if you see warnings and errors everywhere – Swift 6 introduces a lot of other refinements to concurrency that will help smooth things over, and I also fully expect Apple to ship refinements in their APIs to the same effect.

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.0/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.