What’s new in Swift 5.3? – Hacking with Swift

Multiple trailing closures, massive package manager improvements, and more.

Swift 5.3 brings another set of improvements to Swift, including powerful new features such as multi-mode capture clauses and multiple trailing closures, as well as some important changes to Swift Package Manager.

In this article, I’ll cover each of the major changes, along with hands-on code examples so you can try them out for yourself. I encourage you to check out the Swift Evolution proposal link for more information, and if you missed my previous Swift 5.2 article ** new content, please check it out as well.

Sponsor with Swift Hacker and reach the world’s largest Swift community!

Multi-mode capture clause

**SE-0276** introduces the ability to catch multiple error cases within a single capture block, which allows us to remove some duplicates in error handling.

For example, we might have some code that defines two enumerations for a single error:

enum TemperatureError: Error {    case tooCold, tooHot}
Copy the code

When reading something’s temperature, we can throw one of the errors, or send back “ok” :

func getReactorTemperature() -> Int {    90}func checkReactorOperational() throws -> String {    let temp = getReactorTemperature()    if temp < 10 {        throw TemperatureError.tooCold    } else if temp > 90 {        throw TemperatureError.tooHot    } else {        return "OK"    }}
Copy the code

When it comes to catching thrown errors, SE-0276 lets us separate them with commas and handle too hot and too cold in the same way:

do { let result = try checkReactorOperational() print("Result: \(result)")} catch TemperatureError.tooHot, TemperatureError.tooCold { print("Shut down the reactor!" )} catch { print("An unknown error occurred.")}Copy the code

You can handle as many error cases as you need, and you can even bind values from errors if you want.

Multiple trailing closures

**SE-0279** introduces multiple trailing closures, thus providing an easier way to call functions with multiple closures.

This is especially popular in the Swift UI, where the code reads:

struct OldContentView: View {    @State private var showOptions = false    var body: some View {        Button(action: {            self.showOptions.toggle()        }) {            Image(systemName: "gear")        }    }}
Copy the code

Now you can write:

struct NewContentView: View {    @State private var showOptions = false    var body: some View {        Button {            self.showOptions.toggle()        } label: {            Image(systemName: "gear")        }    }}
Copy the code

Technically, the tag: doesn’t need to be on the same line as the preceding}, so you can even write this if you want:

struct BadContentView: View {    @State private var showOptions = false    var body: some View {        Button {            self.showOptions.toggle()        }        label: {            Image(systemName: "gear")        }    }}
Copy the code

However, FOR readability, I caution against doing this — floating code like this is never pleasant, and in Swift it looks like a block of marks rather than the second argument to the button initializer.

** Note: ** There’s been a lot of heated discussion on the Swift forums about multiple tracking shutdowns, and I’d like to take this opportunity to remind people to be civil when participating in our community. Noteworthy grammatical changes like this are always strange at first, but please give it time to see how you progress in practice.

Comprehensive comparable consistency of enumerations

**SE-0266** allows us to select comparable consistency for enumerations that have no associated values or whose associated values are themselves comparable. This allows us to compare two cases in the same enumeration using <, >, and similarity.

For example, if we had an enumeration describing clothing sizes, we could ask Swift to synthesize comparable consistency like this:

enum Size: Comparable {    case small    case medium    case large    case extraLarge}
Copy the code

We can now create two instances of this enumeration and compare them using <, as follows:

let shirtSize = Size.smalllet personSize = Size.largeif shirtSize < personSize {    print("That shirt is too small")}
Copy the code

This synthetical consistency works well for comparable correlation values. For example, if we had an enumeration describing a team winning the Football World Cup, we could write:

enum WorldCupResult: Comparable {    case neverWon    case winner(stars: Int)}
Copy the code

We can then create several instances of enumerations with different values and have Swift sort them:

let americanMen = WorldCupResult.neverWonlet americanWomen = WorldCupResult.winner(stars: 4)let japaneseMen = WorldCupResult.neverWonlet japaneseWomen = WorldCupResult.winner(stars: 1)let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]let sortedByWins = teams.sorted()print(sortedByWins)
Copy the code

This will rank the rankings so that the two teams that have not won the World Cup are ranked first, then the Japanese women’s team, then the U.S. women’s team — it rates two winners above two that have never won, and it rates the winner (stars: 4) above the winner (stars: 1).

The self is no longer needed in many places

**SE-0269** allows us to stop using ourselves in many unnecessary places. Before this change, we need to write about the self. In any closure that references the self, so we’ll capture semantics explicitly, but our closure usually can’t lead to a reference loop, which means the self is just messy.

For example, before this change, we would write the following code:

struct OldContentView: View { var body: some View { List(1.. <5) { number in self.cell(for: number) } } func cell(for number: Int) -> some View { Text("Cell \(number)") }}Copy the code

A call to self.cell (for 🙂 cannot cause a reference cycle because it is used in a structure. Thanks to SE-0269, we can now write the same code like this:

struct NewContentView: View { var body: some View { List(1.. <5) { number in cell(for: number) } } func cell(for number: Int) -> some View { Text("Cell \(number)") }}Copy the code

This can be very popular in any framework that makes heavy use of closures, including Swift UI and merge.

Type-based program entry points

**SE-0281** introduces a new @main attribute that allows us to declare where the entry point of the program is. This allows us to control exactly which parts of the code should start running, which is especially useful for command-line programs.

For example, when creating a terminal application in the past, we needed to create a file called main.swift that would guide our code:

struct OldApp { func run() { print("Running!" ) }}let app = OldApp()app.run()Copy the code

Swift automatically treats the code in main. Swift as top-level code, so it creates an application instance and runs it. This is still the case even after SE-0281, but now if you wish, you can remove main.swift and instead use the @main attribute to mark the structure or base class that contains the static main () method, which will be used as the entry point for the program:

@mainstruct NewApp {    static func main() {        print("Running!")    }}
Copy the code

At runtime, Swift will automatically call newapp.main () to launch the code.

UI Kit and App Kit developers will be familiar with the new @main property, where we use @UI Application Main and @NS Application Main to mark our Application delegate.

However, there are a few caveats you should be aware of when using @main:

  • You cannot use this property in an application that already has a main.swift file.

  • You can’t have more than one @main attribute

  • The @main attribute applies only to the base class – it is not inherited by any subclasses.

Clause of a context generic declaration

**SE-0267** introduces the ability to append here clauses to functions in generic types and extensions.

For example, we can start with a simple Stack structure that allows us to push and pop values from private arrays:

struct Stack<Element> {    private var array = [Element]()    mutating func push(_ obj: Element) {        array.append(obj)    }    mutating func pop() -> Element? {        array.popLast()    }}
Copy the code

Using SE-0267, we can add a new sort () method to the stack, but only if the elements in the stack are comparable:

extension Stack {    func sorted() -> [Element] where Element: Comparable {        array.sorted()    }}
Copy the code

Cite cases as witnesses for agreement

Se-0280 allows enumerations to participate in protocol witness matching, which is a technical way of saying that they can now more easily match protocol requirements.

For example, you can write code to handle various types of data, but what if that data is lost? Of course, you could use something like nil coalesc ing to provide defaults every time, but you could also create a protocol that requires defaults, and then make the various types fit the defaults you want:

protocol Defaultable {    static var defaultValue: Self { get }}extension Int: Defaultable {    static var defaultValue: Int { 0 }}extension Array: Defaultable {    static var defaultValue: Array { [] }}extension Dictionary: Defaultable {    static var defaultValue: Dictionary { [:] }}
Copy the code

Se-0280 allows us to do exactly the same as enumeration. For example, if you want to create a fill enumeration, it can take a certain number of pixels, a certain number of centimeters, or a default value determined by the system:

enum Padding: Defaultable {    case pixels(Int)    case cm(Int)    case defaultValue}
Copy the code

Prior to the SE-0280, such code was impossible — Swift would say padding didn’t meet the protocol. However, if you think it’s really good with the protocol: we say it needs a static default that returns Self, any specific type that matches the protocol, which is exactly what padding.default Value does.

Refine didSet semantics

Se-0268 adjusts the way didSet attribute observers work to make it more efficient. No code changes are required unless you rely in some way on previous bad behavior; You’ll only get a small performance improvement for free.

Internally, this change makes Swift not retrieve the previous Value whenever you Set a new Value without using the old one, and if you don’t reference the old Value and don’t want to Set Swift to change your data in place.

If you happen to rely on old behavior, you can fix it by referencing old Value to trigger your custom getter, like this:

didSet {    _ = oldValue}
Copy the code

A new Float 16 type

The SE-0277 introduces a new semi-precision floating point type called Float 16, which is commonly used for graphical programming and machine learning.

This new floating-point type is the same as other Swift equivalents:

let first: Float16 = 5let second: Float32 = 11let third: Float64 = 7let fourth: Float80 = 13
Copy the code

Swift Package Manager gets binary dependencies, resources, and so on

Swift 5.3 introduces many improvements to Swift Package Manager (SPM). While it’s impossible to give examples here, we can at least discuss what has changed and why.

First, SE-0271 (Package Manager Resources) allows SPM to contain Resources such as images, audio, JSON, etc. It’s not just copying files into a finished application package — for example, we can apply custom processing steps to our assets, such as optimizing images for iOS. This also adds a new bundle. module property for accessing these assets at run time. Se-0278 (Package Manager Localization Resource) builds on this to allow localized versions of resources, such as French images.

Second, SE-0272 (Package Manager binary dependency) allows SPM to use binary packages and its existing support for source code packages. This means that common closed source SDKS such as Firebase can now be integrated using SPM.

Third, SE-0273 (Package Manager conditional target dependencies) allows us to configure the target to have only platform – and configuration-specific dependencies. For example, we might say that we need some specific extra framework when compiling for Linux, or that we should build some debug code when compiling for local tests.

It’s worth adding that the “Future Directions” section of SE-0271 mentions the possibility of type-safe access to individual resource files — SPM is able to generate specific declarations for our resource files as Swift codes, which means things like images (” avatars “) become images (module.avatar).

What’s next?

We expect to see the first beta of Swift 5.3 released at WWDC 20 along with Xcode Next, but in the meantime, you can get it from Swift.org. Download the nightly toolchain snapshot

I also recommend you check out the Swift Standard Library Preview – an earlier version of this article featured SE-0270, which added a new collection method on discontinuous elements, but has since been moved to the library preview. So go ahead and give it a try and see what you think!

Sponsor with Swift Hacker and reach the world’s largest Swift community!