What's new in Swift?

Learn what's changed in Swift with hands-on code examples, all in one place and with no clutter.

The Hacking with Swift logo.

Questions? Feedback? Tweet me @twostraws.

New in Swift 1.1

countElements() is now count()

In Swift 1.0 you would count an array like this:

let items = [1, 2, 3]
println(countElements(items))

The countElements() function has been renamed to count() in Swift 1.1, so the new code is this:

let items = [1, 2, 3]
println(count(items))

Note: This has changed in later versions of Swift – count is now a property of strings and collections.

macOS apps can now use @NSApplicationMain

iOS apps have a @UIApplicationMain attribute that automatically generates a UIApplicationMain() func to bootstrap the app. This is now also available to macOS developers using @NSApplicationMain, and this attribute will automatically be added to Cocoa app delegates in all new projects.

New in Swift 1.2

The zip() function joins two sequences

If you have two sequences that you'd like to join together, the zip() function will do just that and return an array of tuples. For example:

let names = ["Sophie", "Charlotte", "John"]
let scores = [90, 92, 95]
let zipped = zip(names, scores)

That will output an array of the tuples ("Sophie", 90), ("Charlotte", 92), and ("John", 95).

The flatMap() method transforms optionals and arrays

The flatMap() method is designed to allow you to transform optionals and elements inside a collection while also decreasing the amount of containment that happens. For example, if you transform an optional in a way that will also return an optional, using map() would give you an optional optional (a double optional), whereas flatMap() is able to combine those two optionals into a single optional.

let lengthOfFirst = names.first.flatMap { count($0) }

Decreasing the amount of containment also makes flatMap() a simple way of converting multi-dimensional arrays into single-dimensional arrays:

[[1, 2], [3, 4], [5, 6]].flatMap { $0 }

There is no "map" operation there, so we're just left with the flattening behavior – that will result in a single array containing the value [1, 2, 3, 4, 5, 6].

Closures can now be marked @noescape

Closures are reference types, which means Swift must quietly add memory management calls when they are passed into functions. To avoid adding unwanted work, you can now mark closure parameters with the @noescape keyword, which tells Swift the closure will be used before the function returns – it doesn't need to retain or release the closure.

As an example, this function checks whether a password that we have stored matches a password the user just entered, but it does this using a closure so that you can give it any encryption code you like. This closure is used immediately inside the function, so @noescape may be used as a performance optimization:

func checkPassword(encryption: @noescape (String) -> ()) -> Bool {
    if closure(enteredPassword) == storedPassword {
        return true
    } else {
        return false
    }
}

Note: This has changed in later versions of Swift – all closures are considered to be non-escaping by default.

Classes can now have static methods and properties

Swift 1.2 gives us an alias for class final properties: static. While class variables may be overridden in subclasses, static variables may not. For example:

class Student: Person {
    // THIS ISN'T ALLOWED
    override static var count: Int {
        return 150
    }

    // THIS IS ALLOWED
    override class var averageAge: Double {
        return 19.5
    }
}

In this usage, static var is merely an alias for final class var.

Constants no longer require immediate initialization

Constants may be set only once, but Swift 1.2 allows us to create constants without initializing them immediately. For example:

let username: String

if authenticated {
    username = fetchUsername()
} else {
    username = "Anonymous"
}

A new Set data structure

Swift 1.2 introduced a new Set type that works similarly to NSSet except with value semantics. Sets work similarly to arrays except they are not ordered and do not store any element more than once. For example:

var starships = Set<String>()
starships.insert("Serenity")
starships.insert("Enterprise")
starships.insert("Executor")
starships.insert("Serenity")
starships.insert("Serenity")

Even though that code tries to insert Serenity three times, it will only be stored in the set once.

Implicit bridging has been reduced

Prior to Swift 1.2 bridging from Objective-C types to Swift types happened implicitly, meaning that you could use the two interchangeable. From Swift 1.2 onwards you must now use as to typecast these yourself. For example:

authenticateUser(yourNSString as String)

Note: This has changed in later versions of Swift – implicit bridging never happens now.

Multiple if let bindings

You may now place multiple if let bindings on a single line separated by a comma, rather than embed them in increasingly indented pyramids.

For example, previously you would write code like this:

if let user = loadUsername() {
    if let password = decryptPassword() {
        authenticate(user, password)
    }
}

As of Swift 1.2 you can write this:

if let user = loadUsername(), let password = decryptPassword() {
    authenticate(user, password)
}

Typecasting now includes as!

From Swift 1.2 onwards we have three ways of performing typecasts: as is used for typecasts that will always succeed (e.g. someString as NSString), as? is used for typecasts that might fail (e.g. someView as? UIImageView), and as! is used to force typecasts. If you use as! and you're wrong, your code will crash.

For example:

let submitButton = vw.viewWithTag(10) as! UIButton

New in Swift 2.0

Throwing errors

try/catch is a way of programming that means "try this thing, but if it fails do this other thing instead." Swift uses enums for error types so that it can ensure your error catching is exhaustive, just like with switch statements. So for example, you might define your error list something like this:

enum MyError: ErrorType {
    case UserError
    case NetworkError
    case DiscoverydError
}

Notice how my error type builds on the built-in ErrorType protocol; this is required.

Once you've defined the various errors you want to work with, it's time to introduce three new keywords: throws, try, do and catch.

First up, throws is a simple keyword that you add to your method to tell Swift it might fail. You put it right before where you put your method's return type, like this:

func doStuff() throws -> String {

Once that's done, you cannot call that method unless your code is written to handle any errors it throws – Xcode simply won't compile. When you want to throw an error from inside your methods, you just write throw followed by the type of error you want to throw, like this:

func doStuff() throws -> String {
    print("Do stuff 1")
    print("Do stuff 2")
    throw MyError.NetworkError

    return "Some return value"
}

The dummy print() calls are there so you can follow the program flow, as you'll see in a moment.

But first, on to the next keyword: try. This is placed before any call to a method that throws an error, like this:

try doStuff()

This literally writes into your code "I acknowledge that this code might fail," so it's effectively syntactic sugar to ensure safety. But even with that your code still won't compile, because you don't catch the errors: you need to use do and catch.

Catching errors has two forms: catching specific errors and catching all errors. You can mix and match, meaning your code can say "if the error is X, I want to handle it like this; all other errors should be handled this other way."

Here's a very basic example showing how to catch all errors:

do {
    try doStuff()
    print("Success")
} catch {
    print("An error occurred.")
}

If you remember, we made the doStuff() method print "Do stuff 1" then "Do stuff 2" before throwing a network error. So, what will happen is:

  • "Do stuff 1" will be printed
  • "Do stuff 2" will be printed
  • The NetworkError error will be thrown, immediately exiting the doStuff() method – its return statement will never be reached
  • Control will jump to the catch block
  • "An error occurred" will be printed

To be clear: in the code above, "Success" will never be printed – as soon as any try methods throw an error, execution stops and jumps to the catch block.

As I said, you can mix and match generic and specific catch blocks, but you do need to be sure that all possible errors are caught. For example, this will execute one chunk of code for NetworkError errors, and another chunk for all other errors:

do {
    try doStuff()
    print("Success")
} catch MyError.NetworkError {
    print("A network error occurred")
} catch {
    print("An error occurred")
}

Use the guard keyword for early returns

It's very common to place some conditional checks at the start of a method to ensure that various data is configured ready to go. For example, if a Submit button is tapped, you might want to check that the user has entered a username in your user interface. To do this, you'd use this code:

func submitTapped() {
    guard username.text.characters.count > 0 else {
        return
    }

    print("All good")
}

Using guard might not seem much different to using if, but with guard your intention is clearer: execution should not continue if your conditions are not met. Plus it has the advantage of being shorter and more readable, so guard is a real improvement, and I'm sure it will be adopted quickly.

There is one bonus to using guard that might make it even more useful to you: if you use it to unwrap any optionals, those unwrapped values stay around for you to use in the rest of your code block. For example:

guard let unwrappedName = userName else {
    return
}

print("Your username is \(unwrappedName)")

This is in comparison to a straight if statement, where the unwrapped value would be available only inside the if block, like this:

if let unwrappedName = userName {
    print("Your username is \(unwrappedName)")
} else {
    return
}

// this won't work – unwrappedName doesn't exist here!
print("Your username is \(unwrappedName)")

Measure strings using their character count

In Swift 2.2 the way strings are measured changed yet again. What was countElements() became count() in Swift 1.1, and in Swift 2.0 was removed entirely.

Instead, you should access the characters property of your String, then call count on that, like this:

let string = "Hello, Swift!"
let count = string.characters.count
print(count)

Note: This has changed in later versions of Swift – you should access the count property of strings directly.

Use the defer keyword to delay work until your scope exits

Some languages have a concept of try/finally which lets you tell your app "no matter what happens, I want this code to be executed." Swift 2 introduced its own take on this requirement using the defer keyword: it means "I want this work to take place, but not just yet." In practice, this usually means the work will happen just before your method ends, but here's the cool thing: this will still happen if you throw an error.

First, a simple example:

override func viewDidLoad() {
    super.viewDidLoad()

    print("Checkpoint 1")
    doStuff()
    print("Checkpoint 4")
}

func doStuff() {
    print("Checkpoint 2")
    defer { print("Do clean up here") }
    print("Checkpoint 3")
}

If you run that, you'll see "Checkpoint 1", "Checkpoint 2", "Checkpoint 3", "Do clean up here", then "Checkpoint 4". So, even though the defer line appears before checkpoint 3, it gets executed after – it gets deferred until the method is about to end.

I put "Do clean up code here" in there because that's exactly what defer is good at: when you know you need to flush a cache, write out a file or whatever, and you want to make sure that code gets executed regardless of what path is taken through your method.

As I said, work you schedule with defer will execute no matter what route your code takes through your method, and that includes if you throw any errors. For example:

override func viewDidLoad() {
    super.viewDidLoad()

    print("Checkpoint 1")

    do {
        try doStuff()
    } catch {
        print("Error!")
    }

    print("Checkpoint 4")
}

func doStuff() throws {
    print("Checkpoint 2")
    defer { print("Do clean up here") }
    throw MyError.UserError
    print("Checkpoint 3")
}

As soon as doStuff() throws its error, the method is exited and at that point the deferred code is called.

Mutability warnings

This is a simple change that is going to go a long way to help code readability. As you know, Swift developers prefer declaring things as constants (using let) rather than variables (using var). But what if you made something a variable by accident? Or if you thought you might need to change it, then never do?

As of Xcode 7 and Swift 2, you'll get warnings in your code whenever you declare variables that never change – Xcode literally examines the way you use the variable and knows if you never change it.

Checking API availability

One regular problem that iOS developers hit is that we need to be careful when using new APIs – if you try and use UIStackView on iOS 8, for example, your app will crash. In the olden days, Objective C developers would write code like this:

NSClassFromString(@"UIAlertController") != nil

That means, "if the UIAlertController class exists," which was a way of checking if we were running on iOS 8 or later. But because Xcode didn't know that was our goal, it couldn't ensure we got things right. Well, this is fixed with Swift 2, because you can now write code like this:

if #available(iOS 9, *) {
    let stackView = UIStackView()
    // do stuff
}

The magic happens with #available: it will automatically check whether we are running on iOS 9 or later, and, if so, will run the code with the UIStackView. The * after "iOS 9" is there as a catch all for any future platforms that Apple introduces, and it's required.

So, #available is cool, but even better is the fact that you can give it an else block and, because Xcode now knows this block will only execute if the device is iOS 8 or earlier, it can warn you if you new APIs. For example, if you wrote something like this:

if #available(iOS 9, *) {
    // do cool iOS 9 stuff
} else {
    let stackView = UIStackView()
}

New in Swift 2.1

String interpolation can now include string literals

Using quote marks inside strings caused problems before Swift 2.1, which meant that using string literals inside string interpolation was also difficult. This has been fixed in Swift 2.1, so this kind of code works fine now:

print("Hello, \(username ?? "Anonymous")")

This means it's also possible to read dictionary keys using string interpolation, like this:

print("Hello, \(user["name"]!)")

New in Swift 2.2

++ and -- are deprecated

Swift 2.2 formally deprecates the ++ and -- operators, which means they still work but you'll get a warning when you use them. Deprecation is usually a first step towards removing something entirely, and in this case both of these operators will be removed in Swift 3.0.

In their place, you need to use += 1 and -= 1 instead. These operators have been there all along, and are not going away.

You might wonder why two long-standing operators are being removed, particularly when they exist in C, C#, Java, and – critically to its "joke" – C++. There are several answers, not least:

  1. Writing ++ rather than += 1 is hardly a dramatic time saving
  2. Although it's easy once you know it, ++ doesn't have an obvious meaning to people learning Swift, whereas += at least reads as "add and assign."
  3. C-style loops – one of the most common situations where ++ and -- were used – have also been deprecated, which brings me on to my next point…

Traditional C-style for loops are deprecated

This changed outlawed the following syntax in Swift:

for var i = 1; i <= 10; i += 1 {
    print("\(i) green bottles")
}

These are called C-style for loops because they have long been a feature of C-like languages, and conceptually even pre-date C by quite a long way.

Although Swift is (just about!) a C-like language, it has a number of newer, smarter alternatives to the traditional for loop. The result: this construct was deprecated in Swift 2.2 and will be removed "in a future version of Swift."

To replace these old for loops, use one of the many alternatives. For example, the "green bottles" code above could be rewritten to loop over a range, like this:

for i in 1...10 {
    print("\(i) green bottles")
}

Remember, though, that it's a bad idea to create a range where the start is higher than the end: your code will compile, but it will crash at runtime. So, rather than writing this:

for i in 10...1 {
    print("\(i) green bottles")
}

…you should write this instead:

for i in (1...10).reverse() {
    print("\(i) green bottles")
}

Another alternative is just to use regular fast enumeration over an array of items, like this:

var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

for number in array {
    print("\(number) green bottles")
}

Although if you want to be technically correct (also known as "the best kind of correct") you would write such a beast like this:

var array = Array(1...10)

for number in array {
    print("\(number) green bottles")
}

Comparing tuples

A tuple is simply a comma-separated list of values, where each value may or may not be named. For example:

let singer = ("Taylor", "Swift")
let alien = ("Justin", "Bieber")

In older versions of Swift, you couldn't compare two tuples without writing some unwieldy code like this:

func ==  (t1: (T, T), t2: (T, T)) -> Bool {
    return t1.0 == t2.0 && t1.1 == t2.1
}

It's not very user-friendly to require that kind of boilerplate code, and of course it would only work for tuples that have exactly two elements. In Swift 2.2, you no longer need to write that code because tuples can be compared directly:

let singer = ("Taylor", "Swift")
let alien = ("Justin", "Bieber")

if singer == alien {
    print("Matching tuples!")
} else {
    print("Non-matching tuples!")
}

Swift 2.2's automatic tuple comparison works with tuples with two elements just like the function we wrote, but it also works with tuples of other sizes – up to arity 6, which means a tuple that contains six elements.

(In case you were wondering: "arity" is pronounced like "arrity", but "tuple" is pronounced any number of ways: "toople", "tyoople" and "tupple" are all common.)

You can see how tuple comparison works by changing our two tuples like this:

let singer = ("Taylor", 26)
let alien = ("Justin", "Bieber")

Be prepared for a very long error message from Xcode, but the interesting part comes near the end:

note: overloads for '==' exist with these partially matching parameter lists: ......
((A, B), (A, B)), ((A, B, C), (A, B, C)), ((A, B, C, D), (A, B, C, D)), ((A, B, C, D, E), (A, B, C, D, E)), ((A, B, C, D, E, F), (A, B, C, D, E, F))

As you can see, Swift literally has functions to compare tuples all the way up to (A, B, C, D, E, F), which ought to be more than enough.

Tuple splat syntax is deprecated

Another feature that has been deprecated is one that has been part of Swift since 2010 (yes, years before it launched). It's been named "the tuple splat", and not many people were using it. It's partly for that reason – although mainly because it introduces all sorts of ambiguities when reading code – that this syntax is being deprecated.

In case you were curious – and let's face it, you probably are – here's an example of tuple splat syntax in action:

func describePerson(name: String, age: Int) {
    print("\(name) is \(age) years old")
}

let person = ("Taylor Swift", age: 26)
describePerson(person)

But remember: don't grow too fond of your new knowledge, because tuple splats are deprecated in Swift 2.2 and will be removed entirely in a later version.

More keywords can be used as argument labels

Argument labels are a core feature of Swift, and let us write code like this:

for i in 1.stride(through: 9, by: 2) {
    print(i)
}

Without the through or by labels, this code would lose its self-documenting nature: what do the 9 and 2 do in 1.stride(9, 2)? In this example, Swift also uses the argument labels to distinguish 1.stride(through: 9, by: 2) from 1.stride(to: 9, by: 2), which produces different results.

As of Swift 2.2, you can now use a variety of language keywords as these argument labels. You might wonder why this would be a good thing, but consider this code:

func printGreeting(name: String, repeat repeatCount: Int) {
    for _ in 0 ..< repeatCount {
        print(name)
    }
}

printGreeting("Taylor", repeat: 5)

That uses repeat as an argument label, which makes sense because the function will print a string a number of times. Because repeat is a keyword, this code would not work before Swift 2.2 – you would need to write repeat instead, which is unpleasant.

Note that there are still some keywords that may not be used, specifically var, let and inout.

Variable parameters have been deprecated

Another deprecation, but again with good reason: var parameters are deprecated because they offer only marginal usefulness, and are frequently confused with inout.

To give you an example, here is the printGreeting() function modified to use var:

func printGreeting(var name: String, repeat repeatCount: Int) {
    name = name.uppercaseString

    for _ in 0 ..< repeatCount {
        print(name)
    }
}

printGreeting("Taylor", repeat: 5)

The differences there are in the first two lines: name is now var name, and name gets converted to uppercase so that "TAYLOR" is printed out five times.

Without the var keyword, name would have been a constant and so the uppercaseString line would have failed.

The difference between var and inout is subtle: using var lets you modify a parameter inside the function, whereas inout causes your changes to persist even after the function ends.

As of Swift 2.2, var is deprecated, and it's slated for removal in Swift 3.0. If this is something you were using, just create a variable copy of the parameter inside the method, like this:

func printGreeting(name: String, repeat repeatCount: Int) {
    let upperName = name.uppercaseString

    for _ in 0 ..< repeatCount {
        print(upperName)
    }
}

printGreeting("Taylor", repeat: 5)

Renamed debug identifiers: #line, #function, #file

Swift 2.1 and earlier used the "screaming snake case" symbols __FILE__, __LINE__, __COLUMN__, and __FUNCTION__, which automatically get replaced the compiler by the filename, line number, column number and function name where they appear.

From Swift 2.2, those old symbols have been replaced with #file, #line, #column and #function, which will be familiar to you if you've already used Swift 2.0's #available to check for iOS features. As the official Swift review says, it also introduced "a convention where # means invoke compiler substitution logic here."

Here’s how the debug identifiers in action from Swift 2.2 and later:

func printGreeting(name: String, repeat repeatCount: Int) {
    print("This is on line \(#line) of \(#function)")

    let upperName = name.uppercaseString

    for _ in 0 ..< repeatCount {
        print(upperName)
    }
}

printGreeting("Taylor", repeat: 5)

Stringified selectors are deprecated

One unwelcome quirk of Swift before 2.2 was that selectors could be written as strings, like this:

navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Tap!", style: .Plain, target: self, action: "buttonTaped")

If you look closely, I wrote "buttonTaped" rather than "buttonTapped", but Xcode wasn't able to notify me of my mistake if either of those methods didn't exist.

This has been resolved as of Swift 2.2: using strings for selectors has been deprecated, and you should now write #selector(buttonTapped) in that code above. If the buttonTapped() method doesn't exist, you'll get a compile error – another whole class of bugs eliminated at compile time!

Compile-time Swift version checking

Swift 2.2 adds a new build configuration option that makes it easy to combine code code written in versions of Swift into a single file. This might seem unnecessary, but spare a thought to people who write libraries in Swift: do they target Swift 2.2 and hope everyone is using it, or target Swift 2.0 and hope users can upgrade using Xcode?

Using the new build option lets you write two different flavours of Swift, and the correct one will be compiled depending on the version of the Swift compiler.

For example:

#if swift(>=2.2)
print("Running Swift 2.2 or later")
#else
print("Running Swift 2.1 or earlier")
#endif

Just like the existing #if os() build option, this adjusts what code is produced by the compiler: if you're using a Swift 2.2 compiler, the second print() line won't even be seen. This means you can use utter gibberish if you want:

#if swift(>=2.2)
print("Running Swift 2.2 or later")
#else
THIS WILL COMPILE JUST FINE IF YOU'RE
USING A SWIFT 2.2 COMPILER BECAUSE
THIS BIT IS COMPLETELY IGNORED!
#endif

New in Swift 3.0

All function parameters have labels unless you request otherwise

The way we call functions and methods already changed in Swift 2.0, but it's changing again and this time it's going to break everything. In Swift 2.x and earlier, method names did not require a label for their first parameter, so the name of the first parameter was usually built into the method name. For example:

names.indexOf("Taylor")
"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
override func numberOfSectionsInTableView(tableView: UITableView) -> Int
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

Swift 3 makes all labels required unless you specify otherwise, which means the method names no longer detail their parameters. In practice, this often means the last part of the method name gets moved to be the name of the first parameter.

To show you how that looks, here is that Swift 2.2 code followed by its equivalent in Swift 3:

names.indexOf("Taylor")
names.index(of: "Taylor")

"Taylor".writeToFile("filename", atomically: true, encoding: NSUTF8StringEncoding)
"Taylor".write(toFile: "somefile", atomically: true, encoding: String.Encoding.utf8)

SKAction.rotateByAngle(CGFloat(M_PI_2), duration: 10)
SKAction.rotate(byAngle: CGFloat(M_PI_2), duration: 10)

UIFont.preferredFontForTextStyle(UIFontTextStyleSubheadline)
UIFont.preferredFont(forTextStyle: UIFontTextStyle.subheadline)

override func numberOfSectionsInTableView(tableView: UITableView) -> Int
override func numberOfSections(in tableView: UITableView) -> Int

func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView?
func viewForZooming(in scrollView: UIScrollView) -> UIView?

NSTimer.scheduledTimerWithTimeInterval(0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)
Timer.scheduledTimer(timeInterval: 0.35, target: self, selector: #selector(createEnemy), userInfo: nil, repeats: true)

In that last call, notice how NSTimer is now just called Timer. Several other basic types have also dropped the "NS" prefix, so you'll now see UserDefaults, FileManager, Data, Date, URL URLRequest, UUID, NotificationCenter, and more.

Those are methods you call, but this has a knock-on effect for many methods that get called too: when you're connecting to frameworks such as UIKit, they expect to follow the old-style "no first parameter name" rule even in Swift 3.

Here are some example signatures from Swift 2.2:

override func viewWillAppear(animated: Bool)
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(view: SKView)
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(textField: UITextField) -> Bool

In Swift 3, they all need an underscore before the first parameter, to signal that the caller (Objective-C code) won't be using a parameter label:

override func viewWillAppear(_ animated: Bool)
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
override func didMoveToView(_ view: SKView)
override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?)
func textFieldShouldReturn(_ textField: UITextField) -> Bool

Omit needless words

When Swift went open source in December 2015, its shiny new API guideliness contained three fateful words: "omit needless words." This introduced another huge raft of breaking changes in Swift 3, because it means that method names that contain self-evident words now have those words removed.

Let's look at some simple examples first. First, Swift 2.2:

let blue = UIColor.blueColor()
let min = numbers.minElement()
attributedString.appendAttributedString(anotherString)
names.insert("Jane", atIndex: 0)
UIDevice.currentDevice()

Can you identify the needless words? When you're working with UIColor, of course blue is going to be a color, so saying blueColor() is needless. When you append one attributed string to another, do you really need to specify that it's an attributed string you're appending as opposed to an elephant? And why should it be a method – surely a color should be a property!

Here is that same code in Swift 3:

let blue = UIColor.blue
let min = numbers.min()
attributedString.append(anotherString)
names.insert("Jane", at: 0)
UIDevice.current

As you can see, this makes method names significantly shorter!

This change has particularly affected strings, which had repetition all over the place. The best way to demonstrate this is to show before and after code side-by-side, so in the code below the first line of each pair is Swift 2.2 and the second is Swift 3.0:

"  Hello  ".stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet())
"  Hello  ".trimmingCharacters(in: .whitespacesAndNewlines)

"Taylor".containsString("ayl")
"Taylor".contains("ayl")

"1,2,3,4,5".componentsSeparatedByString(",")
"1,2,3,4,5".components(separatedBy: ",")

myPath.stringByAppendingPathComponent("file.txt")
myPath.appendingPathComponent("file.txt")

"Hello, world".stringByReplacingOccurrencesOfString("Hello", withString: "Goodbye")
"Hello, world".replacingOccurrences(of: "Hello", with: "Goodbye")

"Hello, world".substringFromIndex(7)
"Hello, world".substring(from: 7)

"Hello, world".capitalizedString
"Hello, world".capitalized

Warning: capitalized is still a property, but lowercaseString and uppercaseString have been transmogrified into the methods lowercased() and uppercased().

I've chosen the examples so far because the jump to Swift 3 isn't vast, but there are quite a few changes that were significant enough to make my brain hit a speedbump – usually when the resulting method is so short that it wasn't immediately obvious what it was.

For example, look at this code:

dismiss(animated: true, completion: nil)

When I first saw that, I blanked: "dismiss what?" That's partly a result of the Stockholm syndrome that's inevitable having programmed for iOS for so long, but once you learn to reverse the parameter label change and re-add the needless words, you can see it's equivalent to this code in Swift 2.2:

dismissViewControllerAnimated(true, completion: nil)

In fact, the completion: nil part is optional now, so you could even write this:

dismiss(animated: true)

A similar change happened to prepareForSegue(), which now looks like this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?)

UpperCamelCase has been replaced with lowerCamelCase for enums and properties

Although syntactically irrelevant, the capital letters we use to name classes and structs, properties, enums, and more have always followed a convention fairly closely: classes, structs, and enums use UpperCamelCase (MyStruct, WeatherType.Cloudy), properties and parameter names use lowerCamelCase (emailAddress, requestString).

I say "fairly closely" because there are some exceptions that are going to stop being exceptions in Swift 3: properties and parameters that started with initials in Swift 2.2 will now used lowerCamelCase in Swift 3.

Sometimes this isn't too strange: Swift 2.2 created NSURLRequest objects using NSURLRequest(URL: someURL) – note the capital "URL". Swift 3 rewrites that to URLRequest(url: someURL), and also means you'll use things like webView.request?.url?.absoluteString for reading the URL of a web view.

Where it's a bit more jarring is when only part of the property name is in caps, e.g. CGColor or CIColor. Yes, you've guessed it: they become cgColor and ciColor in Swift 3, so you'll be writing code like this:

let red = UIColor.red.cgColor

This change does help drive consistency: all properties and parameters should start with a lowercase letter, no exceptions.

At the same time enum cases are also changing, moving from UpperCamelCase to lowerCamelCase. This makes sense: an enum is a data type (like a struct), but enum values are closer to properties. However, it does mean that wherever you've used an Apple enum, it will now be lowercase. So:

UIInterfaceOrientationMask.Portrait // old
UIInterfaceOrientationMask.portrait // new

NSTextAlignment.Left // old
NSTextAlignment.left // new

SKBlendMode.Replace // old
SKBlendMode.replace // new

You get the idea. However, this tiny change brings something much bigger because Swift's optionals are actually just an enum under the hood, like this:

enum Optional {
    case None
    case Some(Wrapped)
}

This means if you use .Some to work with optionals, you'll need to switch to .some instead. Of course, you could always take this opportunity to ditch .some entirely – these two pieces of code are identical:

for case let .some(datum) in data {
    print(datum)
}

for case let datum? in data {
    print(datum)
}

Swifty importing of C functions

Swift 3 introduced attributes for C functions that allow library authors to specify new and beautiful ways their code should be imported into Swift. For example, all those functions that start with "CGContext" now get mapped to properties methods on a CGContext object, which makes for much more idiomatic Swift. Yes, this means the hideous wart that is CGContextSetFillColorWithColor() has finally been excised.

To demonstrate this, here's an example in Swift 2.2:

let ctx = UIGraphicsGetCurrentContext()

let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
CGContextSetFillColorWithColor(ctx, UIColor.redColor().CGColor)
CGContextSetStrokeColorWithColor(ctx, UIColor.blackColor().CGColor)
CGContextSetLineWidth(ctx, 10)
CGContextAddRect(ctx, rectangle)
CGContextDrawPath(ctx, .FillStroke)

UIGraphicsEndImageContext()

In Swift 3 the CGContext can be treated as an object that you can call methods on rather than repeating CGContext again and again. So, we can rewrite that code like this:

if let ctx = UIGraphicsGetCurrentContext() {
    let rectangle = CGRect(x: 0, y: 0, width: 512, height: 512)
    ctx.setFillColor(UIColor.red.cgColor)
    ctx.setStrokeColor(UIColor.black.cgColor)
    ctx.setLineWidth(10)
    ctx.addRect(rectangle)
    ctx.drawPath(using: .fillStroke)

    UIGraphicsEndImageContext()
}

Note: in both Swift 2.2 and Swift 3.0 UIGraphicsGetCurrentContext() returns an optional CGContext, but because Swift 3 uses method calls we need to safely unwrap before it's used.

This mapping of C functions exists elsewhere, for example you can now read the numberOfPages property of a CGPDFDocument, and CGAffineTransform has been souped up quite dramatically. Here are some examples showing old and new:

CGAffineTransformIdentity
CGAffineTransform.identity

CGAffineTransformMakeScale(2, 2)
CGAffineTransform(scaleX: 2, y: 2)

CGAffineTransformMakeTranslation(128, 128)
CGAffineTransform(translationX: 128, y: 128)

CGAffineTransformMakeRotation(CGFloat(M_PI))
CGAffineTransform(rotationAngle: CGFloat(M_PI))

Verbs and nouns

This is the part where some people will start to drift off in confusion, which is a shame because it's important.

Here's are some quotes from the Swift API guidelines:

  • "When the operation is naturally described by a verb, use the verb’s imperative for the mutating method and apply the “ed” or “ing” suffix to name its nonmutating counterpart"
  • "Prefer to name the nonmutating variant using the verb’s past participle"
  • "When adding “ed” is not grammatical because the verb has a direct object, name the nonmutating variant using the verb’s present participle"
  • "When the operation is naturally described by a noun, use the noun for the nonmutating method and apply the “form” prefix to name its mutating counterpart"

Got that? It's no surprise that Swift's rules are expressed using lingustic terminology – it is after all a language! – but this at least gives me a chance to feel smug that I did a second degree in English. What it means is that many methods are changing names in subtle and sometimes confusing ways.

Let's start with a couple of simple examples:

myArray.enumerate()
myArray.enumerated()

myArray.reverse()
myArray.reversed()

Each time Swift 3 modifies the method by adding a "d" to the end: this is a value that's being returned.

These rules are mostly innocent enough, but it causes confusion when it comes to array sorting. Swift 2.2 used sort() to return a sorted array, and sortInPlace() to sort an array in place. In Swift 3.0, sort() is renamed to sorted() (following the examples above), and sortInPlace() is renamed to sort().

TL;DR: This means you need to be careful because in Swift 2.2 sort() returned a sorted array, but in Swift 3.0 sort() sorts the array in place.

New in Swift 3.1

Concrete constrained extensions

Swift lets us extend types using constraints, which is a powerful and expressive way to add functionality. To demonstrate this, let's look at a worked example in Swift 3.0 that modifies collections to do something trivial:

extension Collection where Iterator.Element: Comparable {
    func lessThanFirst() -> [Iterator.Element] {
        guard let first = self.first else { return [] }
        return self.filter { $0 < first }
    }
}

let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)

That adds a new method called lessThanFirst(), which returns all items in a collection that are less than the first item. So, using it with the array [5, 6, 10, 4, 110, 3] will return [4, 3].

That code extends a protocol (Collection) only where it matches a constraint: elements in the collection must conform to another protocol, Comparable. This alone is powerful stuff, but let's take it back a step: what if we wanted something a bit more specific? Swift 3.0 lets us extend a concrete type rather than the protocol Collection, so instead we could write this:

extension Array where Element: Comparable {
    func lessThanFirst() -> [Element] {
        guard let first = self.first else { return [] }
        return self.filter { $0 < first }
    }
}

let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)

That extends a concrete type (only Array) but still using a protocol for its constraint. What if we wanted to go even more specific – extend a concrete type with a concrete constraint, for example only arrays that contains integers? Well, it turns out that isn't possible in Swift 3.0, which usually strikes people as odd: if Swift 3.0 can handle extending protocols with another protocol as a constraint, then surely extending a specific type with a specific constraint should be a cinch?

Fortunately, this discrepancy has been removed in Swift 3.1, which means we can now write code like this:

extension Array where Element == Int {
    func lessThanFirst() -> [Int] {
        guard let first = self.first else { return [] }
        return self.filter { $0 < first }
    }
}

let items = [5, 6, 10, 4, 110, 3].lessThanFirst()
print(items)

That extends a concrete type (only Array) and uses a concrete constraint (only where the elements are Int).

Now, obviously we're using a trivial example here – in your own code this is going to be significantly more useful when you want to extend arrays containing your own custom structs.

Generics with nested types

Swift 3.0's support for nested types is useful to help you organize your data and increase encapsulation, but Swift 3.1 takes them to the next level by adding support for generics. Let's look at a simple example again, just to start with:

struct Message {
    struct Attachment {
        var contents: String
    }

    var title: String
    var attachment: Attachment
}

That creates a Message struct that has an Attachment struct inside it – a nested type. I've added two String properties, because messages will have some text and attachments will hold some text.

Now, what if we wanted either Message or Attachment to have different kinds of data – perhaps Int or Data? Well, that requires generics, so you might have found yourself writing something like this:

struct Message<T> {
    struct Attachment {
        var contents: String
    }

    var title: T
    var attachment: Attachment
}

That tells Swift we want Message to work across several data types, and whatever data type gets used to create the struct should also be used for the title property. Or at least that's what it would tell Swift, if such code were actually legal – Swift 3.0 does not allow you to mix nested type with generics. Fortunately, this is exactly what Swift 3.1 allows, because nested types can now appear inside generic types.

Not content to stop there, Swift 3.1 takes this a step further: nested types can also be generic, either using their own generic type or by inheriting the generic type of their parent. For example:

struct Message<T> {
    struct Attachment<T> {
        var contents: T
    }

    var title: T
    var attachment: Attachment<T>
}

With that code, the Message struct will have a specific type assigned to it, and the Attachment struct will always have the same type – you can't use String for one and Int for the other. So, this code will work fine:

let msg = Message(title: "Hello", attachment: Message.Attachment(contents: "World"))

Helpfully, if your goal is to make the nested type and its container use the same generic type, you don't even need to declare the nested type as generic – Swift makes the outer type available to the nested type, so in fact you can just write this:

struct Message<T> {
    struct Attachment {
        var contents: T
    }

    var title: T
    var attachment: Attachment
}

Generics are great and so are nested types, so I'm really pleased to see Swift 3.1 bring them together at last.

Sequences get prefix(while:) and drop(while:) methods

Two useful new methods have been added to the Sequence protocol: prefix(while:) and drop(while:). The former returns the longest subsequence that satisfies a predicate, which is a fancy way of saying that you give it a closure to run on every item, and it will go through all the elements in the sequence and return those that match the closure – but will stop as soon as it finds a non-matching element.

Let's take a look at a code example:

let names = ["Michael Jackson", "Michael Jordan", "Michael Caine", "Taylor Swift", "Adele Adkins", "Michael Douglas"]
let prefixed = names.prefix { $0.hasPrefix("Michael") }
print(prefixed)

That uses the hasPrefix() method to return the subsequence ["Michael Jackson", "Michael Jordan", "Michael Caine" – the first three elements in the sequence. It won't include "Michael Douglas", because that comes after the first non-Michael. If you wanted all the Michaels regardless of their position, you should use filter() instead.

The second new method, drop(while:) is effectively the opposite: it finds the longest subsequence that satisfies your predicate, then returns everything after it. For example:

let names = ["Michael Jackson", "Michael Jordan", "Michael Caine", "Taylor Swift", "Adele Adkins", "Michael Douglas"]
let dropped = names.drop { $0.hasPrefix("Michael") }
print(dropped)

That will return the subsequence ["Taylor Swift", "Adele Adkins", "Michael Douglas"] – everything after the initial Michaels.

New in Swift 4.0

Encoding and decoding data using Codable

We know value types are great, but we also know they interact terribly with Objective-C APIs such as NSCoding – you either need to write a shim layer or give in and use classes, both of which are unpleasant. Worse, even if you give in and switch to classes, you still need to write your encoding and decoding methods by hand, which is painful and error-prone.

Swift 4 introduced a new Codable protocol that lets you serialize and deserialize custom data types without writing any special code – and without having to worry about losing your value types. Even better, you can choose how you want the data to be serialized: you can use classic property list format or even JSON.

Let's take a look at how beautiful this is. First, here's a custom data type and some instances of it:

struct Language: Codable {
    var name: String
    var version: Int
}

let swift = Language(name: "Swift", version: 4)
let php = Language(name: "PHP", version: 7)
let perl = Language(name: "Perl", version: 6)

You can see I've marked the Language struct as conforming to the Codable protocol. With that one tiny addition, we can convert it to a Data representation of JSON like this:

let encoder = JSONEncoder()
if let encoded = try? encoder.encode(swift) {
    // save `encoded` somewhere
}

Swift will automatically encode all properties inside your data type – you don't need to do anything.

Now, if you're like me and have a long history of using NSCoding, you're probably somewhat doubtful: is that really all it takes, and how can we be sure it's working? Well, let's add some more code to try converting the Data object into a string so we can print it out, then decode it back into a new Language instance that we can read from:

if let encoded = try? encoder.encode(swift) {
    if let json = String(data: encoded, encoding: .utf8) {
        print(json)
    }

    let decoder = JSONDecoder()
    if let decoded = try? decoder.decode(Language.self, from: encoded) {
        print(decoded.name)
    }
}

Notice how decoding doesn't require a typecast – you provide the data type name as its first parameter, so Swift infers the return type from there.

Both JSONEncoder and its property list counterpart PropertyListEncoder have lots of options for customizing how they work: do you want compact JSON or pretty-printed JSON? Do you want to use ISO8601 dates or Unix epoch dates? Do you want to use binary property lists or XML? For more information on these and other options, see the Swift Evolution proposal for this new feature.

Multi-line string literals

Writing multi-line strings in Swift has always meant adding \n inside your strings to add line breaks wherever you want them. This doesn't look good in code, but at least it displays correctly for users. Fortunately, Swift 4 introduced new multi-line string literal syntax that lets you add line breaks freely and use quote marks without escaping, while still benefiting from functionality like string interpolation.

To start a string literal, you need to write three double quotation marks: """ then press return. You can then go ahead and write a string as long as you want, including variables and line breaks, before ending your string by pressing return then writing three more double quotation marks.

String literals have two important rules: when you open a string using """ the content of your string must begin on a new line, and when you end a multi-line string using """ that must also begin on a new line.

Here it is in action:

let longString = """
When you write a string that spans multiple
lines make sure you start its content on a
line all of its own, and end it with three
quotes also on a line of their own.
Multi-line strings also let you write "quote marks"
freely inside your strings, which is great!
"""

That creates a new string with several line breaks right there in the definition – much easier to read and write.

For more information see the Swift Evolution proposal for this new feature.

Improved keypaths for key-value coding

One of the most loved features of Objective-C is its ability to reference a property dynamically rather than directly – that is, to be able to say "given object X, here is the property I'd like to read" without actually reading it. These references, called keypaths, are distinct from direct property accesses because they don't actually read or write the value, they just stash it away for use later on.

If you've never used keypaths before, let me show you an analogy of how they work using regular Swift methods. We're going to define a struct called Starship and a struct called Crew, then create one instance of each:

// an example struct
struct Crew {
    var name: String
    var rank: String
}

// another example struct, this time with a method
struct Starship {
    var name: String
    var maxWarp: Double
    var captain: Crew

    func goToMaximumWarp() {
        print("\(name) is now travelling at warp \(maxWarp)")
    }
}

// create instances of those two structs
let janeway = Crew(name: "Kathryn Janeway", rank: "Captain")
let voyager = Starship(name: "Voyager", maxWarp: 9.975, captain: janeway)

// grab a reference to the `goToMaximumWarp()` method
let enterWarp = voyager.goToMaximumWarp

// call that reference
enterWarp()

Because functions are first-class types in Swift, the last two lines are able to create a reference to the goToMaximumWarp() method called enterWarp, then call that later on whenever we want to. The problem is, you can't do the same thing for properties – you can't say "create a reference to the captain's name property that I can check when the inevitable mutiny happens," because Swift will just read the property directly and you'll just get its original value.

This is fixed with keypaths: they are uninvoked references to properties just like our enterWarp() code. If you invoke the reference now you get the current value, but if you invoke the reference later you get the latest value. You can dig through any number of properties, and Swift uses its type inference to ensure you get the correct type back.

The Swift Evolution community spent quite a while discussing the correct syntax for keypaths because it needed to be something visually different from other Swift code, and the syntax they ended up with uses backslashes: \Starship.name, \Starship.maxWarp, and \Starship.captain.name. You can assign those two to a variable then use them whenever you want, on any Starship instance. For example:

let nameKeyPath = \Starship.name
let maxWarpKeyPath = \Starship.maxWarp
let captainName = \Starship.captain.name

let starshipName = voyager[keyPath: nameKeyPath]
let starshipMaxWarp = voyager[keyPath: maxWarpKeyPath]
let starshipCaptain = voyager[keyPath: captainName]

That will make starshipName a string and starshipMaxWarp a double, because Swift is able to infer the types correctly. The third example there even goes into the property of a property, and Swift still figures it out correctly.

Future plans for this include being able to access array indexes and to create keypaths from strings at runtime – for more information see the Swift Evolution proposal for this new feature.

Improved dictionary functionality

One of the most intriguing proposals for Swift 4 was to add some new functionality to dictionaries to make them more powerful, and also to make them behave more like you would expect in certain situations.

Let's start with a simple example: filtering dictionaries in Swift 3 does not return a new dictionary. Instead, it returns an array of tuples with key/value labels. For example:

let cities = ["Shanghai": 24_256_800, "Karachi": 23_500_000, "Beijing": 21_516_000, "Seoul": 9_995_000];
let massiveCities = cities.filter { $0.value > 10_000_000 }

After that code runs you can't read massiveCities["Shanghai"] because it is no longer a dictionary. Instead, you need to use massiveCities[0].value, which isn't great.

As of Swift 4 this behaves more like you would expect: you get back a new dictionary. Obviously this will break any existing code that relies on the tuple-array return type.

Similarly, the map() method on dictionaries never quite worked the way many people hoped: you got a key-value tuple passed in, and could return a single value to be added to an array. For example:

let populations = cities.map { $0.value * 2 }

That hasn't changed in Swift 4, but there is a new method called mapValues() that is going to be much more useful because it lets you transform the values and place them back into a dictionary using the original keys.

For example, this code will round and stringify all city populations, then put them back into a new dictionary with the same keys of Shanghai, Karachi, and Seoul:

let roundedCities = cities.mapValues { "\($0 / 1_000_000) million people" }

(In case you were wondering, it's not safe to map dictionary keys because you might create duplicates by accident.)

Easily my favorite new dictionary addition is a grouping initializer, which converts a sequence into a dictionary of sequences that are grouped by whatever you want. Continuing our cities example, we could use cities.keys to get back an array of city names, then group them by their first letter, like this:

let groupedCities = Dictionary(grouping: cities.keys) { $0.characters.first! }
print(groupedCities)

That will output the following:

["B": ["Beijing"], "S": ["Shanghai", "Seoul"], "K": ["Karachi"]]

Alternatively, we could group the cities based on the length of their names like this:

let groupedCities = Dictionary(grouping: cities.keys) { $0.count }
print(groupedCities)

That will output the following:

[5: ["Seoul"], 7: ["Karachi", "Beijing"], 8: ["Shanghai"]]

Finally, it's now possible to access a dictionary key and provide a default value to use if the key is missing:

let person = ["name": "Taylor", "city": "Nashville"]
let name = person["name", default: "Anonymous"]

Now, any experienced developer will probably argue that's better written using nil coalescing, and I agree. You could write this line instead using the current version of Swift:

let name = person["name"] ?? "Anonymous"

However, that doesn't work when you're modifying the dictionary value rather than just reading it. You can't modify a dictionary value in place because accessing its key returns an optional – the key might not exist, after all. With Swift 4's default dictionary values you can write much more succinct code, such as this:

var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"]
var favoriteCounts = [String: Int]()

for show in favoriteTVShows {
    favoriteCounts[show, default: 0] += 1
}

That loops over every string in favoriteTVShows, and uses a dictionary called favoriteCounts to keep track of how often each item appears. We can modify the dictionary in one line of code because we know it will always have a value: either the default value of 0, or some higher number based on previous counting.

For more information see the Swift Evolution proposal for these new features.

Strings are collections again

This is a small change, but one guaranteed to make a lot of people happy: strings are collections again. This means you can reverse them, loop over them character-by-character, map() and flatMap() them, and more. For example:

let quote = "It is a truth universally acknowledged that new Swift versions bring new features."
let reversed = quote.reversed()

for letter in quote {
    print(letter)
}

This change was introduced as part of a broad set of amendments called the String Manifesto.

One-sided ranges

Last but not least, Swift 4 introduced Python-like one-sided collection slicing, where the missing side is automatically inferred to be the start or end of the collection. This has no effect on existing code because it's a new use for the existing operator, so you don't need to worry about potential breakage.

Here's an example:

let characters = ["Dr Horrible", "Captain Hammer", "Penny", "Bad Horse", "Moist"]
let bigParts = characters[..<3]
let smallParts = characters[3...]
print(bigParts)
print(smallParts)

That code will print out ["Dr Horrible", "Captain Hammer", "Penny"] then ["Bad Horse", "Moist"].

For more information see the Swift Evolution proposal for this new feature.

New in Swift 4.1

Synthesized Equatable and Hashable

The Equatable protocol allows Swift to compare one instance of a type against another. When we say 5 == 5, Swift understands what that means because Int conforms to Equatable, which means it implements a function describing what == means for two instances of Int.

Implementing Equatable in our own value types allows them to work like Swift’s strings, arrays, numbers, and more, and it’s usually a good idea to make your structs conform to Equatable just so they fit the concept of value types better.

However, implementing Equatable can be annoying. Consider this code:

struct Person {
    var firstName: String
    var lastName: String
    var age: Int
    var city: String
}

If you have two instances of Person and want to make sure they are identical, you need to compare all four properties, like this:

struct Person: Equatable {
    var firstName: String
    var lastName: String
    var age: Int
    var city: String

    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.firstName == rhs.firstName && lhs.lastName == rhs.lastName && lhs.age == rhs.age && lhs.city == rhs.city
    }
}

Even reading that is tiring, never mind writing it.

Fortunately, Swift 4.1 can synthesize conformance for Equatable – it can generate an == method automatically, which will compare all properties in one value with all properties in another, just like above. So, all you have to do now is add Equatable as a protocol for your type, and Swift will do the rest.

Of course, if you want you can implement == yourself. For example, if your type has an id field that identifies it uniquely, you would write == to compare that single value rather than letting Swift do all the extra work.

Swift 4.1 also introduced synthesized support for the Hashable protocol, which means it will generate a hashValue property for conforming types automatically. Hashable was always annoying to implement because you need to return a unique (or at least mostly unique) hash for every object. It’s important, though, because it lets you use your objects as dictionary keys and store them in sets.

Previously we’d need to write code like this:

var hashValue: Int {
    return firstName.hashValue ^ lastName.hashValue &* 16777619
}

For the most part that’s no longer needed in Swift 4.1, although as with Equatable you might still want to write your own method if there’s something specific you need.

Note: You still need to opt in to these protocols by adding a conformance to your type, and using the synthesized code does require that all properties in your type conform to Equatable or Hashable respectively.

For more information, see Swift Evolution proposal SE-0185.

Key decoding strategies for Codable

In Swift 4.0 a common problem was trying to use Codable with JSON that utilized snake_case for its key names rather than the camelCase we normally use in Swift. Codable was unable to understand how the two different name types were mapped, so you had to create a custom CodingKeys enum helping it out.

This is where Swift 4.1's new keyDecodingStrategy property comes in: it’s set to .useDefaultKeys by default, which does a direct mapping of JSON names to Swift properties. However, if you change it to .convertFromSnakeCase then Codable handles the name conversion for us.

For example:

let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase

do {
    let macs = try decoder.decode([Mac].self, from: jsonData)
    print(macs)
} catch {
    print(error.localizedDescription)
}

When you want to go back the other way – to convert a Codable struct with camelCase properties back to JSON with snake_case keys, set the keyEncodingStrategy to .convertToSnakeCase like this:

let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
let encoded = try encoder.encode(someObject)

Note: At the time of writing keyDecodingStrategy and keyEncodingStrategy are not available on Linux.

Conditional conformances

Swift 4.1 implements SE-0143, which introduced proposed conditional conformances into the language. This allows types to conform to a protocol only when certain conditions are met.

To demonstrate conditional conformances, let's create a Purchaseable protocol that we can use to buy things:

protocol Purchaseable {
   func buy()
}

We can now define a Book struct that conforms to the protocol, and prints a message when a book is bought:

struct Book: Purchaseable {
   func buy() {
      print("You bought a book")
   }
}

So far this is easy enough, but let's take it one step further: what if the user has a basket full of books, and wants to buy them all? We could loop over all books in the array by hand, calling buy() on each one. But a better approach is to write an extension on Array to make it conform to Purchaseable, then give it a buy() method that in turn calls buy() on each of its elements.

This is where conditional conformances come in: if we tried to extend all arrays, we'd be adding functionality where it wouldn't make sense – we'd be adding buy() to arrays of strings, for example, even though those strings don't have a buy() method we can call.

Swift 4.1 lets us make arrays conform to Purchaseable only if their elements also conform to Purchaseable, like this:

extension Array: Purchaseable where Element: Purchaseable {
   func buy() {
      for item in self {
         item.buy()
      }
   }
}

As you can see, conditional conformances let us constrain the way our extensions are applied more precisely than was possible before.

Conditional conformances also make large parts of Swift code easier and safer, even if you don't do any extra work yourself. For example, this code creates two arrays of optional strings and checks whether they are equal:

var left: [String?] = ["Andrew", "Lizzie", "Sophie"]
var right: [String?] = ["Charlotte", "Paul", "John"]
left == right

That might seem trivial, but that code wouldn't even compile in Swift 4.0 – both String and [String] were equatable, but [String?] was not.

The introduction of conditional conformance in Swift 4.1 means that it’s now possible to add protocol conformance to a type as long as it satisfies a condition. In this case, if the elements of the array are equatable, that means the whole thing is equatable. So, the above code now compiles in Swift 4.1

Conditional conformance has been extended to the Codable protocol in a way that will definitely make things safer. For example:

struct Person {
   var name = "Taylor"
}

var people = [Person()]
var encoder = JSONEncoder()
// try encoder.encode(people)

If you uncomment the encoder.encode(people) line, Swift will refuse to build your code because you're trying to encode a struct that doesn't conform to Codable. However, that code compiled cleanly with Swift 4.0, then threw a fatal error at runtime because Person doesn’t conform to Codable.

Obviously no one wants a fatal error at runtime, because it means your app crashes. Fortunately, Swift 4.1 cleans this up using conditional conformances: Optional, Array, Dictionary, and Set now only conform to Codable if their contents also conform to Codable, so the above code will refuse to compile.

Recursive constraints on associated types

Swift 4.1 implements SE-0157, which lifts restrictions on the way we use associated types inside protocols. As a result, we can now create recursive constraints for our associated types: associated types that are constrained by the protocol they are defined in.

To demonstrate this, let's consider a simple team hierarchy in a tech company. In this company, every employee has a manager – someone more senior to them that they report to. Each manager must also be an employee of the company, because it would be weird if they weren't.

We can express this relationship in a simple Employee protocol:

protocol Employee {
   associatedtype Manager: Employee
   var manager: Manager? { get set }
}

Note: I've used an optional Manager? because ultimately one person (presumably the CEO) has no manager.

Even though that's a fairly self-evident relationship, it wasn't possible to compile that code in Swift 4.0 because we're using the Employee protocol inside itself. However, this is fixed in Swift 4.1 because of the new ability to use recursive constraints on associated types.

Thanks to this new feature, we can model a simple tech company that has three kinds of team members: junior developers, senior developers, and board members. The reporting structure is also simple: junior developers are managed by senior developers, senior developers are managed by board members, and board members may be managed by another board member – e.g. the CTO reporting to the CEO.

That looks exactly as you would imagine thanks to Swift 4.1:

class BoardMember: Employee {
   var manager: BoardMember?
}

class SeniorDeveloper: Employee {
   var manager: BoardMember?
}

class JuniorDeveloper: Employee {
   var manager: SeniorDeveloper?
}

Note: I've used classes here rather than structs because BoardMember itself contains a BoardMember property and that would result in an infinitely sized struct. If one of these has to be a class I personally would prefer to make all three classes just for consistency, but if you preferred you could leave BoardMember as a class and make both SeniorDeveloper and JuniorDeveloper into structs.

Build configuration import testing

Swift 4.1 implemented SE-0075, which introduced a new canImport condition that lets us check whether a specific module can be imported when our code is compiled.

This is particularly important for cross-platform code: if you had a Swift file that implemented one behavior on macOS and another on iOS, or if you needed specific functionality for Linux. For example:

#if canImport(SpriteKit)
   // this will be true for iOS, macOS, tvOS, and watchOS
#else
   // this will be true for other platforms, such as Linux
#endif

Previously you would have had to use inclusive or exclusive tests by operating system, like this:

#if !os(Linux)
   // Matches macOS, iOS, watchOS, tvOS, and any other future platforms
#endif

#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
   // Matches only Apple platforms, but needs to be kept up to date as new platforms are added
#endif

The new canImport condition lets us focus on the functionality we care about rather than what platform we're compiling for, thus avoiding a variety of problems.

Target environment testing

Swift 4.1 implemented SE-0190, which introduced a new targetEnvironment condition that lets us differentiate between builds that are for physical devices and those that are for a simulated environment.

At this time targetEnvironment has only one value, simulator, which will be true if your build is targeting a simulated device such as the iOS Simulator. For example:

#if targetEnvironment(simulator)
   // code for the simulator here
#else
   // code for real devices here
#endif

This is useful when writing code to deal with functionality the simulator doesn't support, such as capturing photos from a camera or reading the accelerometer.

As an example, let's look at processing a photo from the camera. If we're running on a real device we'll create and configure a UIImagePickerController() to take photos using the camera, but if we're in the simulator we'll just load a sample image from our app bundle:

import UIKit

class TestViewController: UIViewController, UIImagePickerControllerDelegate {
   // a method that does some sort of image processing
   func processPhoto(_ img: UIImage) {
       // process photo here
   }

   // a method that loads a photo either using the camera or using a sample
   func takePhoto() {
      #if targetEnvironment(simulator)
         // we're building for the simulator; use the sample photo
         if let img = UIImage(named: "sample") {
            processPhoto(img)
         } else {
            fatalError("Sample image failed to load")
         }
      #else
         // we're building for a real device; take an actual photo
         let picker = UIImagePickerController()
         picker.sourceType = .camera
         vc.allowsEditing = true
         picker.delegate = self
         present(picker, animated: true)
      #endif
   }

   // this is called if the photo was taken successfully
   func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
      // hide the camera
      picker.dismiss(animated: true)

      // attempt to retrieve the photo they took
      guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else {
         // that failed; bail out
         return
      }

      // we have an image, so we can process it
      processPhoto(image)
   }
}

flatMap is now (partly) compactMap()

The flatMap() method was useful for a variety of things in Swift 4.0, but one was particularly useful: the ability to transform each object in a collection, then remove any items that were nil.

Swift Evolution proposal SE-0187 suggested changing this, and as of Swift 4.1 this flatMap() variant has been renamed to compactMap() to make its meaning clearer.

For example:

let array = ["1", "2", "Fish"]
let numbers = array.compactMap { Int($0) }

That will create an Int array containing the numbers 1 and 2, because "Fish" will fail conversion to Int, return nil, and be ignored.

New in Swift 4.2

Derived collections of enum cases

SE-0194 introduced a new CaseIterable protocol that automatically generates an array property of all cases in an enum.

Prior to Swift 4.2 this either took hacks, hand-coding, or Sourcery code generation to accomplish, but now all you need to do is make your enum conform to the CaseIterable protocol. At compile time, Swift will automatically generate an allCases property that is an array of all your enum’s cases, in the order you defined them.

For example, this creates an enum of pasta shapes and asks Swift to automatically generate an allCases array for it:

enum Pasta: CaseIterable {
    case cannelloni, fusilli, linguine, tagliatelle
}

You can then go ahead and use that property as a regular array – it will be a [Pasta] given the code above, so we could print it like this:

for shape in Pasta.allCases {
    print("I like eating \(shape).")
}

This automatic synthesis of allCases will only take place for enums that do not use associated values. Adding those automatically wouldn’t make sense, however if you want you can add it yourself:

enum Car: CaseIterable {
    static var allCases: [Car] {
        return [.ford, .toyota, .jaguar, .bmw, .porsche(convertible: false), .porsche(convertible: true)]
    }

    case ford, toyota, jaguar, bmw
    case porsche(convertible: Bool)
}

At this time, Swift is unable to synthesize the allCases property if any of your enum cases are marked unavailable. So, if you need allCases then you’ll need to add it yourself, like this:

enum Direction: CaseIterable {
    static var allCases: [Direction] {
        return [.north, .south, .east, .west]
    }

    case north, south, east, west

    @available(*, unavailable)
    case all
}

Important: You need to add CaseIterable to the original declaration of your enum rather than an extension in order for the allCases array to be synthesized. This means you can’t use extensions to retroactively make existing enums conform to the protocol.

Warning and error diagnostic directives

SE-0196 introduced new compiler directives that help us mark issues in our code. These will be familiar to any developers who had used Objective-C previously, but as of Swift 4.2 we can enjoy them in Swift too.

The two new directives are #warning and #error: the former will force Xcode to issue a warning when building your code, and the latter will issue a compile error so your code won’t build at all. Both of these are useful for different reasons:

  • #warning is mainly useful as a reminder to yourself or others that some work is incomplete. Xcode templates often use #warning to mark method stubs that you should replace with your own code.
  • #error is mainly useful if you ship a library that requires other developers to provide some data. For example, an authentication key for a web API – you want users to include their own key, so using #error will force them to change that code before continuing.

Both of these work in the same way: #warning("Some message") and #error("Some message"). For example:

func encrypt(_ string: String, with password: String) -> String {
    #warning("This is terrible method of encryption")
    return password + String(string.reversed()) + password
}

struct Configuration {
    var apiKey: String {
        #error("Please enter your API key below then delete this line.")
        return "Enter your key here"
    }
}    

Both #warning and #error work alongside the existing #if compiler directive, and will only be triggered if the condition being evaluated is true. For example:

#if os(macOS)
#error("MyLibrary is not supported on macOS.")
#endif

Dynamic member look up

SE-0195 introduced a way to bring Swift closer to scripting languages such as Python, but in a type-safe way – you don’t lose any of Swift’s safety, but you do gain the ability to write the kind of code you’re more likely to see in PHP and Python.

At the core of this feature is a new attribute called @dynamicMemberLookup, which instructs Swift to call a subscript method when accessing properties. This subscript method, subscript(dynamicMember:), is required: you’ll get passed the string name of the property that was requested, and can return any value you like.

Let’s look at a trivial example so you can understand the basics. We could create a Person struct that reads its values from a dictionary like this:

@dynamicMemberLookup
struct Person {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "Taylor Swift", "city": "Nashville"]
        return properties[member, default: ""]
    }
}

The @dynamicMemberLookup attribute requires the type to implement a subscript(dynamicMember:) method to handle the actual work of dynamic member lookup. As you can see, I’ve written one that accepts the member name as string and returns a string, and internally it just looks up the member name in a dictionary and returns its value.

That struct allows us to write code like this:

let person = Person()
print(person.name)
print(person.city)
print(person.favoriteIceCream)

That will compile cleanly and run, even though name, city, and favoriteIceCream do not exist as properties on the Person type. Instead, they are all looked up at runtime: that code will print “Taylor Swift” and “Nashville” for the first two calls to print(), then an empty string for the final one because our dictionary doesn’t store anything for favoriteIceCream.

My subscript(dynamicMember:) method must return a string, which is where Swift’s type safety comes in: even though you’re dealing with dynamic data, Swift will still ensure you get back what you expected. And if you want multiple different types, just implement different subscript(dynamicMember:) methods, like this:

@dynamicMemberLookup
struct Employee {
    subscript(dynamicMember member: String) -> String {
        let properties = ["name": "Taylor Swift", "city": "Nashville"]
        return properties[member, default: ""]
    }

    subscript(dynamicMember member: String) -> Int {
        let properties = ["age": 26, "height": 178]
        return properties[member, default: 0]
    }
}

Now that any property can be accessed in more than one way, Swift requires you to be clear which one should be run. That might be implicit, for example if you send the return value into a function that accepts only strings, or it might be explicit, like this:

let employee = Employee()
let age: Int = employee.age

Either way, Swift must know for sure which subscript will be called.

You can even overload subscript to return closures:

@dynamicMemberLookup
struct User {
    subscript(dynamicMember member: String) -> (_ input: String) -> Void {
        return {
            print("Hello! I live at the address \($0).")
        }
    }
}

let user = User()
user.printAddress("555 Taylor Swift Avenue")

When that’s run, user.printAddress returns a closure that prints out a string, and the ("555 Taylor Swift Avenue") part immediately calls it with that input.

If you use dynamic member subscripting in a type that has also some regular properties and methods, those properties and methods will always be used in place of the dynamic member. For example, we could define a Singer struct with a built-in name property alongside a dynamic member subscript:

struct Singer {
    public var name = "Justin Bieber"

    subscript(dynamicMember member: String) -> String {
        return "Taylor Swift"
    }
}

let singer = Singer()
print(singer.name)

That code will print “Justin Bieber”, because the name property will be used rather than the dynamic member subscript.

@dynamicMemberLookup plays a full part in Swift’s type system, which means you can assign them to protocols, structs, enums, and classes – even classes that are marked @objc.

In practice, this means two things. First, you can create a class using @dynamicMemberLookup, and any classes that inherit from it are also automatically @dynamicMemberLookup. So, this will print “I’m a sandwich” because HotDog inherits from Sandwich:

@dynamicMemberLookup
class Sandwich {
    subscript(dynamicMember member: String) -> String {
        return "I'm a sandwich!"
    }
}

class HotDog: Sandwich { }

let chiliDog = HotDog()
print(chiliDog.description)

Second, you can retroactively make other types use @dynamicMemberLookup by defining it on a protocol, adding a default implementation of subscript(dynamicMember:) using a protocol extension, then making other types conform to your protocol however you want.

For example, this creates a new Subscripting protocol, provides a default subscript(dynamicMember:) implementation that returns a message, then extends Swift’s String to use that protocol:

@dynamicMemberLookup
protocol Subscripting { }

extension Subscripting {
    subscript(dynamicMember member: String) -> String {
        return "This is coming from the subscript"
    }
}

extension String: Subscripting { }
let str = "Hello, Swift"
print(str.username)

Enhanced conditional conformances

Conditional conformances were introduced in Swift 4.1, allowing types to conform to a protocol only when certain conditions are met.

For example, if we had a Purchaseable protocol:

protocol Purchaseable {
    func buy()
}

And a simple type that conforms to that protocol:

struct Book: Purchaseable {
    func buy() {
        print("You bought a book")
    }
}

Then we could make Array conform to Purchaseable if all the elements inside the array were also Purchasable:

extension Array: Purchaseable where Element: Purchaseable {
    func buy() {
        for item in self {
            item.buy()
        }
    }
}

This worked great at compile time, but there was a problem: if you needed to query a conditional conformance at runtime, your code would crash because it wasn’t supported in Swift 4.1

In Swift 4.2 that’s now fixed, so if you receive data of one type and want to check if it can be converted to a conditionally conformed protocol, it works great.

For example:

let items: Any = [Book(), Book(), Book()]

if let books = items as? Purchaseable {
    books.buy()
}

In addition, support for automatic synthesis of Hashable conformance has improved greatly in Swift 4.2. Several built-in types from the Swift standard library – including optionals, arrays, dictionaries, and ranges – now automatically conform to the Hashable protocol when their elements conform to Hashable.

For example:

struct User: Hashable {
    var name: String
    var pets: [String]
}

Swift 4.2 can automatically synthesize Hashable conformance for that struct, but Swift 4.1 could not.

Random number generation and shuffling

SE-0202 introduced a new random API that’s native to Swift. This means you can for the most part stop using arc4random_uniform() and GameplayKit to get randomness, and instead rely on a cryptographically secure randomizer that’s baked right into the core of the language.

You can generate random numbers by calling the random() method on whatever numeric type you want, providing the range you want to work with. For example, this generates a random number in the range 1 through 4, inclusive on both sides:

let randomInt = Int.random(in: 1..<5)

Similar methods exist for Float, Double, and CGFloat:

let randomFloat = Float.random(in: 1..<10)
let randomDouble = Double.random(in: 1...100)
let randomCGFloat = CGFloat.random(in: 1...1000)

There’s also one for booleans, generating either true or false randomly:

let randomBool = Bool.random()

Checking a random boolean is effectively the same as checking Int.random(in: 0...1) == 1, but it expresses your intent more clearly.

SE-0202 also includes support for shuffling arrays using new shuffle() and shuffled() methods depending on whether you want in-place shuffling or not. For example:

var albums = ["Red", "1989", "Reputation"]

// shuffle in place
albums.shuffle()

// get a shuffled array back
let shuffled = albums.shuffled()

It also adds a new randomElement() method to arrays, which returns one random element from the array if it isn’t empty, or nil otherwise:

if let random = albums.randomElement() {
    print("The random album is \(random).")
}

Simpler, more secure hashing

Swift 4.2 implemented SE-0206, which simplifies the way we make custom types conform to the Hashable protocol.

From Swift 4.1 onwards conformance to Hashable can be synthesized by the compiler. However, if you want your own hashing implementation – for example, if your type has many properties but you know that one of them was enough to identify it uniquely – you still need to write your own code using whatever algorithm you thought was best.

Swift 4.2 introduced a new Hasher struct that provides a randomly seeded, universal hash function to make this process easier:

struct iPad: Hashable {
    var serialNumber: String
    var capacity: Int

    func hash(into hasher: inout Hasher) {
        hasher.combine(serialNumber)
    }
}

You can add more properties to your hash by calling combine() repeatedly, and the order in which you add properties affects the finished hash value.

You can also use Hasher as a standalone hash generator: just provide it with whatever values you want to hash, then call finalize() to generate the final value. For example:

let first = iPad(serialNumber: "12345", capacity: 256)
let second = iPad(serialNumber: "54321", capacity: 512)

var hasher = Hasher()
hasher.combine(first)
hasher.combine(second)
let hash = hasher.finalize()

Hasher uses a random seed every time it hashes an object, which means the hash value for any object is effectively guaranteed to be different between runs of your app.

This in turn means that elements you add to a set or a dictionary are highly likely to have a different order each time you run your app.

Checking sequence elements match a condition

SE-0207 provides a new allSatisfy() method that checks whether all items in a sequence pass a condition.

For example, if we had an array of exam results like this:

let scores = [85, 88, 95, 92]

We could decide whether a student passed their course by checking whether all their exam results were 85 or higher:

let passed = scores.allSatisfy { $0 >= 85 }

In-place collection element removal

SE-0197 introduced a new removeAll(where:) method that performs a high-performance, in-place filter for collections. You give it a closure condition to run, and it will strip out all objects that match the condition.

For example, if you have a collection of names and want to remove people called “Terry”, you’d use this:

var pythons = ["John", "Michael", "Graham", "Terry", "Eric", "Terry"]
pythons.removeAll { $0.hasPrefix("Terry") }
print(pythons)

Now, you might very well think that you could accomplish that by using filter() like this:

pythons = pythons.filter { !$0.hasPrefix("Terry") }

However, that doesn’t use memory very efficiently, it specifies what you don’t want rather than what you want, and more advanced in-place solutions come with a range of complexities that are off-putting to novices. Ben Cohen, the author of SE-0197, gave a talk at dotSwift 2018 where he discussed the implementation of this proposal in more detail – if you’re keen to learn why it’s so efficient, you should start there!

Boolean toggling

SE-0199 introduced a new toggle() method to booleans that flip them between true and false..

The entire code to implement proposal is only a handful of lines of Swift:

extension Bool {
   mutating func toggle() {
      self = !self
   }
}

However, the end result makes for much more natural Swift code:

var loggedIn = false
loggedIn.toggle()

As noted in the proposal, this is particularly useful in more complex data structures: myVar.prop1.prop2.enabled.toggle() avoids the potential typing errors that could be caused using manual negation.

The proposal makes Swift easier and safer to write, and is purely additive, so I think most folks will switch to using it quickly enough.

New in Swift 5.0

Raw strings

SE-0200 added the ability to create raw strings, where backslashes and quote marks are interpreted as those literal symbols rather than escapes characters or string terminators. This makes a number of use cases more easy, but regular expressions in particular will benefit.

To use raw strings, place one or more # symbols before your strings, like this:

let rain = #"The "rain" in "Spain" falls mainly on the Spaniards."#

The # symbols at the start and end of the string become part of the string delimiter, so Swift understands that the standalone quote marks around “rain” and “Spain” should be treated as literal quote marks rather than ending the string.

Raw strings allow you to use backslashes too:

let keypaths = #"Swift keypaths such as \Person.name hold uninvoked references to properties."#

That treats the backslash as being a literal character in the string, rather than an escape character. This in turn means that string interpolation works differently:

let answer = 42
let dontpanic = #"The answer to life, the universe, and everything is \#(answer)."#

Notice how I’ve used \#(answer) to use string interpolation – a regular \(answer) will be interpreted as characters in the string, so when you want string interpolation to happen in a raw string you must add the extra #.

One of the interesting features of Swift’s raw strings is the use of hash symbols at the start and end, because you can use more than one in the unlikely event you’ll need to. It’s hard to provide a good example here because it really ought to be extremely rare, but consider this string: My dog said "woof"#gooddog. Because there’s no space before the hash, Swift will see "# and immediately interpret it as the string terminator. In this situation we need to change our delimiter from #" to ##", like this:

let str = ##"My dog said "woof"#gooddog"##

Notice how the number of hashes at the end must match the number at the start.

Raw strings are fully compatible with Swift’s multi-line string system – just use #""" to start, then """# to end, like this:

let multiline = #"""
The answer to life,
the universe,
and everything is \#(answer).
"""#

Being able to do without lots of backslashes will prove particularly useful in regular expressions. For example, writing a simple regex to find keypaths such as \Person.name used to look like this:

let regex1 = "\\\\[A-Z]+[A-Za-z]+\\.[a-z]+"

Thanks to raw strings we can write the same thing with half the number of backslashes:

let regex2 = #"\\[A-Z]+[A-Za-z]+\.[a-z]+"#

We still need some, because regular expressions use them too.

A standard Result type

SE-0235 introduced a Result type into the standard library, giving us a simpler, clearer way of handling errors in complex code such as asynchronous APIs.

Swift’s Result type is implemented as an enum that has two cases: success and failure. Both are implemented using generics so they can have an associated value of your choosing, but failure must be something that conforms to Swift’s Error type.

To demonstrate Result, we could write a function that connects to a server to figure out how many unread messages are waiting for the user. In this example code we’re going to have just one possible error, which is that the requested URL string isn’t a valid URL:

enum NetworkError: Error {
    case badURL
}

The fetching function will accept a URL string as its first parameter, and a completion handler as its second parameter. That completion handler will itself accept a Result, where the success case will store an integer, and the failure case will be some sort of NetworkError. We’re not actually going to connect to a server here, but using a completion handler at least lets us simulate asynchronous code.

Here’s the code:

import Foundation

func fetchUnreadCount1(from urlString: String, completionHandler: @escaping (Result<Int, NetworkError>) -> Void)  {
    guard let url = URL(string: urlString) else {
        completionHandler(.failure(.badURL))
        return
    }

    // complicated networking code here
    print("Fetching \(url.absoluteString)...")
    completionHandler(.success(5))
}

To use that code we need to check the value inside our Result to see whether our call succeeded or failed, like this:

fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
    switch result {
    case .success(let count):
        print("\(count) unread messages.")
    case .failure(let error):
        print(error.localizedDescription)
    }
}

There are three more things you ought to know before you start using Result in your own code.

First, Result has a get() method that either returns the successful value if it exists, or throws its error otherwise. This allows you to convert Result into a regular throwing call, like this:

fetchUnreadCount1(from: "https://www.hackingwithswift.com") { result in
    if let count = try? result.get() {
        print("\(count) unread messages.")
    }
}

Second, Result has an initializer that accepts a throwing closure: if the closure returns a value successfully that gets used for the success case, otherwise the thrown error is placed into the failure case.

For example:

let result = Result { try String(contentsOfFile: someFile) }

Third, rather than using a specific error enum that you’ve created, you can also use the general Error protocol. In fact, the Swift Evolution proposal says “it's expected that most uses of Result will use Swift.Error as the Error type argument.”

So, rather than using Result<Int, NetworkError> you could use Result<Int, Error>. Although this means you lose the safety of typed throws, you gain the ability to throw a variety of different error enums – which you prefer really depends on your coding style.

Customizing string interpolation

SE-0228 dramatically revamped Swift’s string interpolation system so that it’s more efficient and more flexible, and it’s creating a whole new range of features that were previously impossible.

In its most basic form, the new string interpolation system lets us control how objects appear in strings. Swift has default behavior for structs that is helpful for debugging, because it prints the struct name followed by all its properties. But if you were working with classes (that don’t have this behavior), or wanted to format that output so it could be user-facing, then you could use the new string interpolation system.

For example, if we had a struct like this:

struct User {
    var name: String
    var age: Int
}

If we wanted to add a special string interpolation for that so that we printed users neatly, we would add an extension to String.StringInterpolation with a new appendInterpolation() method. Swift already has several of these built in, and uses the interpolation type – in this case User to figure out which method to call.

In this case, we’re going to add an implementation that puts the user’s name and age into a single string, then calls one of the built-in appendInterpolation() methods to add that to our string, like this:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ value: User) {
        appendInterpolation("My name is \(value.name) and I'm \(value.age)")
    }
}

Now we can create a user and print out their data:

let user = User(name: "Guybrush Threepwood", age: 33)
print("User details: \(user)")

That will print User details: My name is Guybrush Threepwood and I'm 33, whereas with the custom string interpolation it would have printed User details: User(name: "Guybrush Threepwood", age: 33). Of course, that functionality is no different from just implementing the CustomStringConvertible protocol, so let’s move on to more advanced usages.

Your custom interpolation method can take as many parameters as you need, labeled or unlabeled. For example, we could add an interpolation to print numbers using various styles, like this:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ number: Int, style: NumberFormatter.Style) {
        let formatter = NumberFormatter()
        formatter.numberStyle = style

        if let result = formatter.string(from: number as NSNumber) {
            appendLiteral(result)
        }
    }
}

The NumberFormatter class has a number of styles, including currency ($72.83), ordinal (1st, 12th), and spell out (five, forty-three). So, we could create a random number and have it spelled out into a string like this:

let number = Int.random(in: 0...100)
let lucky = "The lucky number this week is \(number, style: .spellOut)."
print(lucky)

You can call appendLiteral() as many times as you need, or even not at all if necessary. For example, we could add a string interpolation to repeat a string multiple times, like this:

extension String.StringInterpolation {
    mutating func appendInterpolation(repeat str: String, _ count: Int) {
        for _ in 0 ..< count {
            appendLiteral(str)
        }
    }
}

print("Baby shark \(repeat: "doo ", 6)")

And, as these are just regular methods, you can use Swift’s full range of functionality. For example, we might add an interpolation that joins an array of strings together, but if that array is empty execute a closure that returns a string instead:

extension String.StringInterpolation {
    mutating func appendInterpolation(_ values: [String], empty defaultValue: @autoclosure () -> String) {
        if values.count == 0 {
            appendLiteral(defaultValue())
        } else {
            appendLiteral(values.joined(separator: ", "))
        }
    }
}

let names = ["Harry", "Ron", "Hermione"]
print("List of students: \(names, empty: "No one").")

Using @autoclosure means that we can use simple values or call complex functions for the default value, but none of that work will be done unless values.count is zero.

With a combination of the ExpressibleByStringLiteral and ExpressibleByStringInterpolation protocols it’s now possible to create whole types using string interpolation, and if we add CustomStringConvertible we can even make those types print as strings however we want.

To make this work, we need to fulfill some specific criteria:

  • Whatever type we create should conform to ExpressibleByStringLiteral, ExpressibleByStringInterpolation, and CustomStringConvertible. The latter is only needed if you want to customize the way the type is printed.
  • Inside your type needs to be a nested struct called StringInterpolation that conforms to StringInterpolationProtocol.
  • The nested struct needs to have an initializer that accepts two integers telling us roughly how much data it can expect.
  • It also needs to implement an appendLiteral() method, as well as one or more appendInterpolation() methods.
  • Your main type needs to have two initializers that allow it to be created from string literals and string interpolations.

We can put all that together into an example type that can construct HTML from various common elements. The “scratchpad” inside the nested StringInterpolation struct will be a string: each time a new literal or interpolation is added, we’ll append it to the string. To help you see exactly what’s going on, I’ve added some print() calls inside the various append methods.

Here’s the code.

struct HTMLComponent: ExpressibleByStringLiteral, ExpressibleByStringInterpolation, CustomStringConvertible {
    struct StringInterpolation: StringInterpolationProtocol {
        // start with an empty string
        var output = ""

        // allocate enough space to hold twice the amount of literal text
        init(literalCapacity: Int, interpolationCount: Int) {
            output.reserveCapacity(literalCapacity * 2)
        }

        // a hard-coded piece of text – just add it
        mutating func appendLiteral(_ literal: String) {
            print("Appending \(literal)")
            output.append(literal)
        }

        // a Twitter username – add it as a link
        mutating func appendInterpolation(twitter: String) {
            print("Appending \(twitter)")
            output.append("<a href=\"https://twitter/\(twitter)\">@\(twitter)</a>")
        }

        // an email address – add it using mailto
        mutating func appendInterpolation(email: String) {
            print("Appending \(email)")
            output.append("<a href=\"mailto:\(email)\">\(email)</a>")
        }
    }

    // the finished text for this whole component
    let description: String

    // create an instance from a literal string
    init(stringLiteral value: String) {
        description = value
    }

    // create an instance from an interpolated string
    init(stringInterpolation: StringInterpolation) {
        description = stringInterpolation.output
    }
}

We can now create and use an instance of HTMLComponent using string interpolation like this:

let text: HTMLComponent = "You should follow me on Twitter \(twitter: "twostraws"), or you can email me at \(email: "paul@hackingwithswift.com")."
 print(text)

Thanks to the print() calls that were scattered inside, you’ll see exactly how the string interpolation functionality works: you’ll see “Appending You should follow me on Twitter”, “Appending twostraws”, “Appending , or you can email me at “, “Appending paul@hackingwithswift.com”, and finally “Appending .” – each part triggers a method call, and is added to our string.

Dynamically callable types

SE-0216 adds a new @dynamicCallable attribute to Swift, which brings with it the ability to mark a type as being directly callable. It’s syntactic sugar rather than any sort of compiler magic, effectively transforming this code:

let result = random(numberOfZeroes: 3)

Into this:

let result = random.dynamicallyCall(withKeywordArguments: ["numberOfZeroes": 3])

@dynamicCallable is the natural extension of Swift 4.2's @dynamicMemberLookup, and serves the same purpose: to make it easier for Swift code to work alongside dynamic languages such as Python and JavaScript.

To add this functionality to your own types, you need to add the @dynamicCallable attribute plus one or both of these methods:

func dynamicallyCall(withArguments args: [Int]) -> Double

func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double

The first of those is used when you call the type without parameter labels (e.g. a(b, c)), and the second is used when you do provide labels (e.g. a(b: cat, c: dog)).

@dynamicCallable is really flexible about which data types its methods accept and return, allowing you to benefit from all of Swift’s type safety while still having some wriggle room for advanced usage. So, for the first method (no parameter labels) you can use anything that conforms to ExpressibleByArrayLiteral such as arrays, array slices, and sets, and for the second method (with parameter labels) you can use anything that conforms to ExpressibleByDictionaryLiteral such as dictionaries and key value pairs.

As well as accepting a variety of inputs, you can also provide multiple overloads for a variety of outputs – one might return a string, one an integer, and so on. As long as Swift is able to resolve which one is used, you can mix and match all you want.

Let’s look at an example. First, here’s a RandomNumberGenerator struct that generates numbers between 0 and a certain maximum, depending on what input was passed in:

struct RandomNumberGenerator {
    func generate(numberOfZeroes: Int) -> Double {
        let maximum = pow(10, Double(numberOfZeroes))
        return Double.random(in: 0...maximum)
    }
}

To switch that over to @dynamicCallable we’d write something like this instead:

@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
        let numberOfZeroes = Double(args.first?.value ?? 0)
        let maximum = pow(10, numberOfZeroes)
        return Double.random(in: 0...maximum)
    }
}

That method can be called with any number of parameters, or perhaps zero, so we read the first value carefully and use nil coalescing to make sure there’s a sensible default.

We can now create an instance of RandomNumberGenerator and call it like a function:

let random = RandomNumberGenerator()
let result = random(numberOfZeroes: 0)

If you had used dynamicallyCall(withArguments:) instead – or at the same time, because you can have them both a single type – then you’d write this:

@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withArguments args: [Int]) -> Double {
        let numberOfZeroes = Double(args[0])
        let maximum = pow(10, numberOfZeroes)
        return Double.random(in: 0...maximum)
    }
}

let random = RandomNumberGenerator()
let result = random(0)

There are some important rules to be aware of when using @dynamicCallable:

  • You can apply it to structs, enums, classes, and protocols.
  • If you implement withKeywordArguments: and don’t implement withArguments:, your type can still be called without parameter labels – you’ll just get empty strings for the keys.
  • If your implementations of withKeywordArguments: or withArguments: are marked as throwing, calling the type will also be throwing.
  • You can’t add @dynamicCallable to an extension, only the primary definition of a type.
  • You can still add other methods and properties to your type, and use them as normal.

Perhaps more importantly, there is no support for method resolution, which means we must call the type directly (e.g. random(numberOfZeroes: 5)) rather than calling specific methods on the type (e.g. random.generate(numberOfZeroes: 5)). There is already some discussion on adding the latter using a method signature such as this:

func dynamicallyCallMethod(named: String, withKeywordArguments: KeyValuePairs<String, Int>)

If that became possible in future Swift versions it might open up some very interesting possibilities for test mocking.

In the meantime, @dynamicCallable is not likely to be widely popular, but it is hugely important for a small number of people who want interactivity with Python, JavaScript, and other languages.

Handling future enum cases

SE-0192 adds the ability to distinguish between enums that are fixed and enums that might change in the future.

One of Swift’s security features is that it requires all switch statements to be exhaustive – that they must cover all cases. While this works well from a safety perspective, it causes compatibility issues when new cases are added in the future: a system framework might send something different that you hadn’t catered for, or code you rely on might add a new case and cause your compile to break because your switch is no longer exhaustive.

With the @unknown attribute we can now distinguish between two subtly different scenarios: “this default case should be run for all other cases because I don’t want to handle them individually,” and “I want to handle all cases individually, but if anything comes up in the future use this rather than causing an error.”

Here’s an example enum:

enum PasswordError: Error {
    case short
    case obvious
    case simple
}

We could write code to handle each of those cases using a switch block:

func showOld(error: PasswordError) {
    switch error {
    case .short:
        print("Your password was too short.")
    case .obvious:
        print("Your password was too obvious.")
    default:
        print("Your password was too simple.")
    }
}

That uses two explicit cases for short and obvious passwords, but bundles the third case into a default block.

Now, if in the future we added a new case to the enum called old, for passwords that had been used previously, our default case would automatically be called even though its message doesn’t really make sense – the password might not be too simple.

Swift can’t warn us about this code because it’s technically correct (the best kind of correct), so this mistake would easily be missed. Fortunately, the new @unknown attribute fixes it perfectly – it can be used only on the default case, and is designed to be run when new cases come along in the future.

For example:

func showNew(error: PasswordError) {
    switch error {
    case .short:
        print("Your password was too short.")
    case .obvious:
        print("Your password was too obvious.")
    @unknown default:
        print("Your password wasn't suitable.")
    }
}

That code will now issue warnings because the switch block is no longer exhaustive – Swift wants us to handle each case explicitly. Helpfully this is only a warning, which is what makes this attribute so useful: if a framework adds a new case in the future you’ll be warned about it, but it won’t break your source code.

Flattening nested optionals resulting from try?

SE-0230 modifies the way try? works so that nested optionals are flattened to become regular optionals. This makes it work the same way as optional chaining and conditional typecasts, both of which flatten optionals in earlier Swift versions.

Here’s a practical example that demonstrates the change:

struct User {
    var id: Int

    init?(id: Int) {
        if id < 1 {
            return nil
        }

        self.id = id
    }

    func getMessages() throws -> String {
        // complicated code here
        return "No messages"
    }
}

let user = User(id: 1)
let messages = try? user?.getMessages()

The User struct has a failable initializer, because we want to make sure folks create users with a valid ID. The getMessages() method would in theory contain some sort of complicated code to get a list of all the messages for the user, so it’s marked as throws; I’ve made it return a fixed string so the code compiles.

The key line is the last one: because the user is optional it uses optional chaining, and because getMessages() can throw it uses try? to convert the throwing method into an optional, so we end up with a nested optional. In Swift 4.2 and earlier this would make messages a String?? – an optional optional string – but in Swift 5.0 and later try? won’t wrap values in an optional if they are already optional, so messages will just be a String?.

This new behavior matches the existing behavior of optional chaining and conditional typecasting. That is, you could use optional chaining a dozen times in a single line of code if you wanted, but you wouldn’t end up with 12 nested optionals. Similarly, if you used optional chaining with as?, you would still end up with only one level of optionality, because that’s usually what you want.

Checking for integer multiples

SE-0225 adds an isMultiple(of:) method to integers, allowing us to check whether one number is a multiple of another in a much clearer way than using the division remainder operation, %.

For example:

let rowNumber = 4

if rowNumber.isMultiple(of: 2) {
    print("Even")
} else {
    print("Odd")
}

Yes, we could write the same check using if rowNumber % 2 == 0 but you have to admit that’s less clear – having isMultiple(of:) as a method means it can be listed in code completion options in Xcode, which aids discoverability.

Transforming and unwrapping dictionary values with compactMapValues()

SE-0218 adds a new compactMapValues() method to dictionaries, bringing together the compactMap() functionality from arrays (“transform my values, unwrap the results, then discard anything that’s nil”) with the mapValues() method from dictionaries (“leave my keys intact but transform my values”).

As an example, here’s a dictionary of people in a race, along with the times they took to finish in seconds. One person did not finish, marked as “DNF”:

let times = [
    "Hudson": "38",
    "Clarke": "42",
    "Robinson": "35",
    "Hartis": "DNF"
]

We can use compactMapValues() to create a new dictionary with names and times as an integer, with the one DNF person removed:

let finishers1 = times.compactMapValues { Int($0) }

Alternatively, you could just pass the Int initializer directly to compactMapValues(), like this:

let finishers2 = times.compactMapValues(Int.init)

You can also use compactMapValues() to unwrap optionals and discard nil values without performing any sort of transformation, like this:

let people = [
    "Paul": 38,
    "Sophie": 8,
    "Charlotte": 5,
    "William": nil
]

let knownAges = people.compactMapValues { $0 }

New in Swift 5.1

Improvements to synthesized memberwise initializers

SE-0242 introduced major improvements to one of Swift’s most commonly used features: memberwise initializers for structs.

In earlier versions of Swift, a memberwise initializer was automatically created to accept parameters matching the properties of a struct, like this:

struct User {
    var name: String
    var loginCount: Int = 0
}

let piper = User(name: "Piper Chapman", loginCount: 0)

In Swift 5.1 this has been enhanced so that the memberwise initializer now uses default parameter values for any properties that have them. In the User struct we’ve given loginCount a default value of 0, which means we can either specify it or leave it to the memberwise initializer:

let gloria = User(name: "Gloria Mendoza", loginCount: 0)
let suzanne = User(name: "Suzanne Warren")

This lets us avoid repeating code, which is always welcome.

Implicit returns from single-expression functions

SE-0255 has removed a small but important inconsistency in the language: single-expression functions that return a value can now remove the return keyword and Swift will understand it implicitly.

In previous versions of Swift, single-line closures that returned a value you could omit the return keyword because the only line of code that was there must be the one that returned a value. So, these two pieces of code were identical:

let doubled1 = [1, 2, 3].map { $0 * 2 }
let doubled2 = [1, 2, 3].map { return $0 * 2 }

In Swift 5.1, this behavior has now been extended to functions as well: if they contain a single expression – effectively a single piece of code that evaluates to a value – then you can leave off the return keyword, like this:

func double(_ number: Int) -> Int {
    number * 2
}

That will probably cause some people to do a double take at first, but I’m sure it will become second nature over time.

Universal Self

SE-0068 expands Swift’s use of Self so that it refers to the containing type when used inside classes, structs, and enums. This is particularly useful for dynamic types, where the exact type of something needs to be determined at runtime.

As an example, consider this code:

class NetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(NetworkManager.maximumActiveRequests).")
    }
}

That declares a static maximumActiveRequests property for a network manager, and adds a printDebugData() method to print the static property. That works fine right now, but when NetworkManager is subclassed things become more complicated:

class ThrottledNetworkManager: NetworkManager {
    override class var maximumActiveRequests: Int {
        return 1
    }
}

That subclass changes maximumActiveRequests so that it allows only one request at a time, but if we call printDebugData() it will print out the value from its parent class:

let manager = ThrottledNetworkManager()
manager.printDebugData()

That should print out 1 rather than 4, and that’s where SE-0068 comes in: we can now write Self (with a capital S) to refer to the current type. So, we can rewrite printDebugData() to this:

class ImprovedNetworkManager {
    class var maximumActiveRequests: Int {
        return 4
    }

    func printDebugData() {
        print("Maximum network requests: \(Self.maximumActiveRequests).")
    }
}

This means Self works the same way as it did for protocols in earlier Swift versions.

Opaque return types

SE-0244 introduced the concept of opaque types into Swift. An opaque type is one where we’re told about the capabilities of an object without knowing specifically what kind of object it is.

At first glance that sounds a lot like a protocol, but opaque return types take the concept of protocols significantly further because are able to work with associated types, they require the same type to be used internally each time, and they allow us to hide implementation details.

As an example, if we wanted to launch different kinds of fighters from a Rebel base we might write code like this:

protocol Fighter { }
struct XWing: Fighter { }

func launchFighter() -> Fighter {
    return XWing()
}

let red5 = launchFighter()

Whoever calls that function knows it will return some sort of Fighter but doesn’t know precisely what. As a result, we could add struct YWing: Fighter { } or other types, and have any of them be returned.

But there’s a problem: what if we wanted to check whether a specific fighter was Red 5? You might think the solution is to make Fighter conform to the Equatable protocol so we can use ==. However, as soon as you do that Swift will throw up a particularly dreaded error for the launchFighter function: “Protocol 'Fighter' can only be used as a generic constraint because it has Self or associated type requirements.”

The “Self” part of that error is what is hitting us here. The Equatable protocol has to compare two instances of itself (“Self”) to see whether they are the same, but Swift has no guarantee that the two equatable things are remotely the same – we could be comparing a Fighter with an array of integers, for example.

Opaque types solve this problem because even though we just see a protocol being used, internally the Swift compiler knows exactly what that protocol actually resolves to – it knows it’s an XWing, an array of strings, or whatever.

To send back an opaque type, use the keyword some before your protocol name:

func launchOpaqueFighter() -> some Fighter {
    return XWing()
}

From the caller’s perspective that still gets back a Fighter, which might be an XWing, a YWing, or something else that conforms to the Fighter protocol. But from the compiler’s perspective it knows exactly what is being returned, so it can make sure we follow all the rules correctly.

For example, consider a function that returned some Equatable like this:

func makeInt() -> some Equatable {
    Int.random(in: 1...10)
}

When we call that, all we know is that it is some sort of Equatable value, however if call it twice then we can compare the results of those two calls because Swift knows for sure it will be the same underlying type:

let int1 = makeInt()
let int2 = makeInt()
print(int1 == int2)

The same is not true if we had a second function that returned some Equatable, like this:

func makeString() -> some Equatable {
    "Red"
}

Even though from our perspective both send us back an Equatable type, and we can compare the results of two calls to makeString() or two calls to makeInt(), Swift won’t let us compare the return value of makeString() to the return value of makeInt() because it knows comparing a string and an integer doesn’t make any sense.

An important proviso here is that functions with opaque return types must always return one specific type. If for example we tried to use Bool.random() to randomly launch an XWing or a YWing then Swift would refuse to build our code because the compiler can no longer tell what will be sent back.

You might well think “if we always need to return the same type, why not just write the function as func launchFighter() -> XWing? While that might work sometimes, it creates new problems such as:

  • We end up with types we don’t really want to expose to the world. For example, if we used someArray.lazy.drop { … } we get sent back a LazyDropWhileSequence – a dedicated and highly specific type from the Swift standard library. All we actually care about is that this thing is a sequence; we don’t need to know how Swift’s internals work.
  • We lose the ability to change our mind later. Making launchFighter() return only an XWing means we can’t switch to a different type in the future, and given how much Disney relies on Star Wars toy sales that would be a problem! By returning an opaque type we can return X-Wings today, then move to B-Wings in a year – we only ever return one in any given build of our code, but we can still have the flexibility to change our mind.

In some respects all this might sound similar to generics, which also solve the “Self or associated type requirements” problem. Generics allow us to write code like this:

protocol ImperialFighter {
    init()
}

struct TIEFighter: ImperialFighter { }
struct TIEAdvanced: ImperialFighter { }

func launchImperialFighter<T: ImperialFighter>() -> T {
    return T()
}

That defines a new protocol that requires conforming types to be initializable with no parameters, defines two structs that conform to that protocol, then creates a generic function to use it. However, the difference here is that now callers of launchImperialFighter() are the ones to choose what kind of fighter they get, like this:

let fighter1: TIEFighter = launchImperialFighter()
let fighter2: TIEAdvanced = launchImperialFighter()

If you want callers to be able to select their data type then generics work well, but if you want the function to decide the return type then they fall down;

So, opaque result types allow us to do several things:

  • Our functions decide what type of data gets returned, not the caller of those functions.
  • We don’t need to worry about Self or associated type requirements, because the compiler knows exactly what type is inside.
  • We get to change our minds in the future whenever we need to.
  • We don’t expose private internal types to the outside world.

If you ever forget the difference between protocols and opaque types, think of this: returning Fighter means "any sort of Fighter type but we don't know what", whereas returning some Fighter means "a specific sort of Fighter type but we still don't know what." In the latter case, the difference is that the underlying type is something specific that the compiler knows about, whereas in the former case it can literally be anything that conforms to the protocol – even being different every time we call the method.

Static and class subscripts

SE-0254 adds the ability to mark subscripts as being static, which means they apply to types rather than instances of a type.

Static properties and methods are used when one set of values is shared between all instances of that type. For example, if you had one centralized type to store your app settings, you might write code like this:

public enum OldSettings {
    private static var values = [String: String]()

    static func get(_ name: String) -> String? {
        return values[name]
    }

    static func set(_ name: String, to newValue: String?) {
        print("Adjusting \(name) to \(newValue ?? "nil")")
        values[name] = newValue
    }
}

OldSettings.set("Captain", to: "Gary")
OldSettings.set("Friend", to: "Mooncake")
print(OldSettings.get("Captain") ?? "Unknown")

Wrapping the dictionary inside a type means that we can control access more carefully, and using an enum with no cases means we can’t try to instantiate the type – we can’t make various instances of Settings.

With Swift 5.1 we can now use a static subscript instead, allowing us to rewrite our code to this:

public enum NewSettings {
    private static var values = [String: String]()

    public static subscript(_ name: String) -> String? {
        get {
            return values[name]
        }
        set {
            print("Adjusting \(name) to \(newValue ?? "nil")")
            values[name] = newValue
        }
    }
}

NewSettings["Captain"] = "Gary"
NewSettings["Friend"] = "Mooncake"
print(NewSettings["Captain"] ?? "Unknown")

Custom subscripts like this have always been possible for instances of types; this improvement makes static or class subscripts possible too.

Warnings for ambiguous none cases

Swift’s optionals are implemented as an enum of two cases: some and none. This gave rise to the possibility of confusion if we created our own enums that had a none case, then wrapped that inside an optional.

For example:

enum BorderStyle {
    case none
    case solid(thickness: Int)
}

Used as a non-optional this was always clear:

let border1: BorderStyle = .none
print(border1)

That will print “none”. But if we used an optional for that enum – if we didn’t know what border style to use – then we’d hit problems:

let border2: BorderStyle? = .none
print(border2)

That prints “nil”, because Swift assumes .none means the optional is empty, rather than an optional with the value BorderStyle.none.

In Swift 5.1 this confusion now prints a warning: “Assuming you mean 'Optional.none'; did you mean 'BorderStyle.none' instead?” This avoids the source compatibility breakage of an error, but at least informs developers that their code might not quite mean what they thought.

Matching optional enums against non-optionals

Swift has always been smart enough to handle switch/case pattern matching between optionals and non-optionals for strings and integers, but before Swift 5.1 that wasn’t extended to enums.

Well, in Swift 5.1 we can now use switch/case pattern matching to match optional enums with non-optionals, like this:

enum BuildStatus {
    case starting
    case inProgress
    case complete
}

let status: BuildStatus? = .inProgress

switch status {
case .inProgress:
    print("Build is starting…")
case .complete:
    print("Build is complete!")
default:
    print("Some other build status")
}

Swift is able to compare the optional enum directly with the non-optional cases, so that code will print “Build is starting…”

Ordered collection diffing

SE-0240 introduced the ability to calculate and apply the differences between ordered collections. This could prove particularly interesting for developers who have complex collections in table views, where they want to add and remove lots of items smoothly using animations.

The basic principle is straightforward: Swift 5.1 gives us a new difference(from:) method that calculates the differences between two ordered collections – what items to remove and what items to insert. This can be used with any ordered collection that contains Equatable elements.

To demonstrate this, we can create an array of scores, calculate the difference from one to the other, then loop over those differences and apply each one to make our two collections the same.

Note: Because Swift now ships inside Apple’s operating systems, new features like this one must be used with an #available check to make sure the code is being run on an OS that includes the new functionality. For features that will land in an unknown, unannounced operating system shipping at some point in the future, a special version number of “9999” is used to mean “we don’t know what the actual number is just yet.”

Here’s the code:

var scores1 = [100, 91, 95, 98, 100]
let scores2 = [100, 98, 95, 91, 100]

if #available(iOS 9999, *) {
    let diff = scores2.difference(from: scores1)

    for change in diff {
        switch change {
        case .remove(let offset, _, _):
            scores1.remove(at: offset)
        case .insert(let offset, let element, _):
            scores1.insert(element, at: offset)
        }
    }

    print(scores1)
}

For more advanced animations, you can use the third value of the changes: associatedWith. So, rather than using .insert(let offset, let element, _) above you might write .insert(let offset, let element, let associatedWith) instead. This lets you track pairs of changes at the same time: moving an item two places down in your collection is a removal then an insertion, but the associatedWith value ties those two changes together so you treat it as a move instead.

Rather than applying changes by hand, you can apply the whole collection using a new applying() method, like this:

if #available(iOS 9999, *) {
    let diff = scores2.difference(from: scores1)
    let result = scores1.applying(diff) ?? []
}

Creating uninitialized arrays

SE-0245 introduced a new initializer for arrays that doesn’t pre-fill values with a default. This was previously available as a private API, which meant Xcode wouldn’t list it in its code completion but you could still use it if you wanted – and if you were happy to take the risk that it wouldn’t be withdrawn in the future!

To use the initializer, tell it the capacity you want, then provide a closure to fill in the values however you need. Your closure will be given an unsafe mutable buffer pointer where you can write your values, as well as an inout second parameter that lets you report back how many values you actually used.

For example, we could make an array of 10 random integers like this:

let randomNumbers = Array<Int>(unsafeUninitializedCapacity: 10) { buffer, initializedCount in
    for x in 0..<10 {
        buffer[x] = Int.random(in: 0...10)
    }

    initializedCount = 10
}

There are some rules here:

  1. You don’t need to use all the capacity you ask for, but you can’t go over capacity. So, if you ask for a capacity of 10 you can set initializedCount to 0 through 10, but not 11.
  2. If you don’t initialize elements that end up being in your array – for example if you set initializedCount to 5 but don’t actually provide values for elements 0 through 4 – then they are likely to be filled with random data. This is A Bad Idea.
  3. If you don’t set initializedCount it will be 0, so any data you assigned will be lost.

Now, we could have rewritten the above code using map(), like this:

let randomNumbers2 = (0...9).map { _ in Int.random(in: 0...10) }

That’s certainly easier to read, but it’s less efficient: it creates a range, creates a new empty array, sizes it up to the correct amount, loops over the range, and calls the closure once for each range item.

New in Swift 5.2

Key path expressions as functions

SE-0249 introduced a marvelous shortcut that allows us to use keypaths in a handful of specific circumstances.

The Evolution proposal describes this as being able to use “\Root.value wherever functions of (Root) -> Value are allowed”, but what it means is that if previously you sent a Car into a method and got back its license plate, you can now use Car.licensePlate instead.

This is best understood as an example, so here’s a User type that defines four properties:

struct User {
    let name: String
    let age: Int
    let bestFriend: String?

    var canVote: Bool {
        age >= 18
    }
}

We could create some instance of that struct and put them into an array, like this:

let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]

Now for the important part: if you want to get an array of all the users names, you can do so by using a key path like this:

let userNames = users.map(\.name)
print(userNames)

Previously you would have had to write a closure to retrieve the name by hand, like this:

let oldUserNames = users.map { $0.name }

This same approach works elsewhere – anywhere where previously you would have received a value and passed back one of its properties, you can now use a key path instead. For example, this will return all users who can vote:

let voters = users.filter(\.canVote)

And this will return the best friends for all users who have one:

let bestFriends = users.compactMap(\.bestFriend)

Callable values of user-defined nominal types

SE-0253 introduced statically callable values to Swift, which is a fancy way of saying that you can now call a value directly if its type implements a method named callAsFunction(). You don’t need to conform to any special protocol to make this behavior work; you just need to add that method to your type.

For example, we could create a Dice struct that has properties for lowerBound and upperBound, then add callAsFunction so that every time you call a dice value you get a random roll:

struct Dice {
    var lowerBound: Int
    var upperBound: Int

    func callAsFunction() -> Int {
        (lowerBound...upperBound).randomElement()!
    }
}

let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)

That will print a random number from 1 through 6, and it’s identical to just using callAsFunction() directly. For example, we could call it like this:

let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)

Swift automatically adapts your call sites based on how callAsFunction() is defined. For example, you can add as many parameters as you want, you can control the return value, and you can even mark methods as mutating if needed.

For example, this creates a StepCounter struct that tracks how far someone has walked and reports back whether they reached their target of 10,000 steps:

struct StepCounter {
    var steps = 0

    mutating func callAsFunction(count: Int) -> Bool {
        steps += count
        print(steps)
        return steps > 10_000
    }
}

var steps = StepCounter()
let targetReached = steps(count: 10)

For more advanced usage, callAsFunction() supports both throws and rethrows, and you can even define multiple callAsFunction() methods on a single type – Swift will choose the correct one depending on the call site, just like regular overloading.

Subscripts can now declare default arguments

When adding custom subscripts to a type, you can now use default arguments for any of the parameters. For example, if we had a PoliceForce struct with a custom subscript to read officers from the force, we could add a default parameter to send back if someone tries to read an index outside of the array’s bounds:

struct PoliceForce {
    var officers: [String]

    subscript(index: Int, default default: String = "Unknown") -> String {
        if index >= 0 && index < officers.count {
            return officers[index]
        } else {
            return `default`
        }
    }
}

let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])

That will print “Amy” then “Unknown”, with the latter being caused because there is no officer at index 5. Note that you do need to write your parameter labels twice if you want them to be used, because subscripts don’t use parameter labels otherwise.

So, because I use default default in my subscript, I can use a custom value like this:

print(force[-1, default: "The Vulture"])

Lazy filtering order is now reversed

There’s a small change in Swift 5.2 that could potentially cause your functionality to break: if you use a lazy sequence such as an array, and apply multiple filters to it, those filters are now run in the reverse order.

For example, this code below has one filter that selects names that start with S, then a second filter that prints out the name then returns true:

let people = ["Arya", "Cersei", "Samwell", "Stannis"]
    .lazy
    .filter { $0.hasPrefix("S") }
    .filter { print($0); return true }
_ = people.count

In Swift 5.2 and later that will print “Samwell” and “Stannis”, because after the first filter runs those are the only names that remain to go into the second filter. But before Swift 5.2 it would have returned all four names, because the second filter would have been run before the first one. This was confusing, because if you removed the lazy then the code would always return just Samwell and Stannis, regardless of Swift version.

This is particularly problematic because the behavior of this depends on where the code is being run: if you run Swift 5.2 code on iOS 13.3 or earlier, or macOS 10.15.3 or earlier, then you’ll get the old backward behavior, but the same code running on newer operating systems will give the new, correct behavior.

New and improved diagnostics

Swift 5.2 introduced a new diagnostic architecture that aims to improves the quality and precision of error messages issued by Xcode when you make a coding error. This is particularly apparent when working with SwiftUI code, where Swift would often produce false positive error messages.

For an example, consider code like this:

struct ContentView: View {
    @State private var name = 0

    var body: some View {
        VStack {
            Text("What is your name?")
            TextField("Name", text: $name)
                .frame(maxWidth: 300)
        }
    }
}

That attempts to bind a TextField view to an integer @State property, which is invalid. In Swift 5.1 this caused an error for the frame() modifier saying 'Int' is not convertible to 'CGFloat?’, but in Swift 5.2 and later this correctly identifies the error is the $name binding: Cannot convert value of type Binding<Int> to expected argument type Binding<String>.

You can find out more about the new diagnostic architecture on the Swift.org blog.

New in Swift 5.3

Multi-pattern catch clauses

SE-0276 introduced the ability to catch multiple error cases inside a single catch block, which allows us to remove some duplication in our error handling.

For example, we might have some code that defines two enum cases for an error:

enum TemperatureError: Error {
    case tooCold, tooHot
}

When reading the temperature of something, we can either throw one of those 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"
    }
}

When it comes to catching errors thrown there, SE-0276 lets us handle both tooHot and tooCold in the same way by separating them with a comma:

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

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

Multiple trailing closures

SE-0279 introduced multiple trailing closures, making for a simpler way to call a function with several closures.

This will be particularly welcome in SwiftUI, where code like this:

struct OldContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button(action: {
            self.showOptions.toggle()
        }) {
            Image(systemName: "gear")
        }
    }
}

Can now be written as this:

struct NewContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        } label: {
            Image(systemName: "gear")
        }
    }
}

Technically there is no reason why label: needs to be on the same line as the preceding }, so you could even write this if you wanted:

struct BadContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        }

        label: {
            Image(systemName: "gear")
        }
    }
}

However, I would caution against that for readability – a floating piece of code like that is never pleasant, and in Swift it looks like a labeled block rather than a second parameter to the Button initializer.

Synthesized Comparable conformance for enums

SE-0266 lets us opt in to Comparable conformance for enums that either have no associated values or have associated values that are themselves Comparable. This allows us to compare two cases from the same enum using <, >, and similar.

For example, if we had an enum that describes clothing sizes we could ask Swift to synthesize Comparable conformance like this:

enum Size: Comparable {
    case small
    case medium
    case large
    case extraLarge
}

We can now create two instances of that enum and compare them using <, like this:

let shirtSize = Size.small
let personSize = Size.large

if shirtSize < personSize {
    print("That shirt is too small")
}

This synthesized conformance works great with associated values that are Comparable. For example, if we had an enum that described the football World Cup wins for a team, we might write this:

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

We could then create several instances of that enum with varying values, and have Swift sort them:

let americanMen = WorldCupResult.neverWon
let americanWomen = WorldCupResult.winner(stars: 4)
let japaneseMen = WorldCupResult.neverWon
let japaneseWomen = WorldCupResult.winner(stars: 1)

let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]
let sortedByWins = teams.sorted()
print(sortedByWins)

That will sort the array so that the two teams who haven’t won the World Cup come first, then the Japanese women’s team, then the American women’s team – it considers the two winner cases to be higher than the two neverWon cases, and considers winner(stars: 4) to be higher than winner(stars: 1).

self is no longer required in many places

SE-0269 allows us to stop using self in many places where it isn’t necessary. Prior to this change, we’d need to write self. in any closure that referenced self so we were making our capture semantics explicit, however often it was the case that our closure could not result in a reference cycle, meaning that the self was just clutter.

For example, before this change we would write code like this:

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)")
    }
}

That call to self.cell(for:) cannot cause a reference cycle, because it’s being used inside a struct. 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)")
    }
}

This is likely to be extremely popular in any framework that makes heavy use of closures, including SwiftUI and Combine.

Type-based program entry points

SE-0281 introduced a new @main attribute to allow us to declare where the entry point for a program is. This allows us to control exactly which part of our code should start running, which is particularly useful for command-line programs.

For example, when creating a terminal app previously we needed to create a file called main.swift that was able to bootstrap our code:

struct OldApp {
    func run() {
        print("Running!")
    }
}

let app = OldApp()
app.run()

Swift automatically considered code in main.swift to be top-level code, so it would create the App instance and run it. That is still the case even after SE-0281, but now if you want to you can remove main.swift and instead use the @main attribute to mark a struct or base class that contains a static main() method to be used as the program’s entry point:

@main
struct NewApp {
    static func main() {
        print("Running!")
    }
}

When that runs, Swift will automatically call NewApp.main() to start your code.

The new @main attribute will be familiar to UIKit and AppKit developers, where we use @UIApplicationMain and @NSApplicationMain to mark our app delegates.

However, there are some provisos you should be aware of when using @main:

  • You may not use this attribute in an app that already has a main.swift file.
  • You may not have more than one @main attribute
  • The @main attribute can be applied only to a base class – it will not be inherited by any subclasses.

where clauses on contextually generic declarations

SE-0267 introduced the ability to attach a where clause to functions inside generic types and extensions.

For example, we could start with a simple Stack struct that let us push and pop values from a private array:

struct Stack<Element> {
    private var array = [Element]()

    mutating func push(_ obj: Element) {
        array.append(obj)
    }

    mutating func pop() -> Element? {
        array.popLast()
    }
}

Using SE-0267, we could add a new sorted() method to that stack, but only for times when the elements inside the stack conform to Comparable:

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

Enum cases as protocol witnesses

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

For example, you could write code to handle various types of data, but what if that data were missing? Sure, you could use something like nil coalescing to provide a default value every time, but you could also make a protocol that requires a default value, then make various types conform to it with whatever default values you wanted:

protocol Defaultable {
    static var defaultValue: Self { get }
}

// make integers have a default value of 0
extension Int: Defaultable {
    static var defaultValue: Int { 0 }
}

// make arrays have a default of an empty array
extension Array: Defaultable {
    static var defaultValue: Array { [] }
}

// make dictionaries have a default of an empty dictionary
extension Dictionary: Defaultable {
    static var defaultValue: Dictionary { [:] }
}

What SE-0280 allows us to do is exactly the same thing just for enums. For example, you want to create a padding enum that can take some number of pixels, some number of centimeters, or a default value decided by the system:

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

That kind of code wouldn’t have been possible before SE-0280 – Swift would have said that Padding doesn’t satisfy the protocol. However, if you think it through the protocol really is satisfied: we said it needs a static defaultValue that returns Self, i.e. whatever concrete type is conforming to the protocol, and that’s exactly what Padding.defaultValue does.

Refined didSet semantics

SE-0268 adjusts the way the didSet property observers work so that they are more efficient. This doesn’t require a code change unless you were somehow relying on the previous buggy behavior; you’ll just get a small performance improvement for free.

Internally, this change makes Swift not retrieve the previous value when setting a new value in any instance where you weren’t using the old value, and if you don’t reference oldValue and don’t have a willSet Swift will change your data in-place.

If you do happen to be relying on the old behavior, you can work around it simply by referencing oldValue to trigger your custom getter, like this:

didSet {
    _ = oldValue
}

A new Float16 type

SE-0277 introduced a new half-precision floating point type called Float16, which is commonly used in graphics programming and machine learning.

This new floating-point type fits in alongside Swift’s other similar types:

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

Swift Package Manager gains binary dependencies, resources, and more

Swift 5.3 introduced many improvements for Swift Package Manager (SPM). Although it’s not possible to give hands-on examples of these 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, and more. This is more than just copying files into a finished app bundle – for example, we can apply a custom processing step to our assets, such as optimizing images for iOS. This also adds a new Bundle.module property for accessing these assets at runtime. SE-0278 (Package Manager Localized Resources) builds on this to allow for localized versions of resources, for example images that are in French.

Second, SE-0272 (Package Manager Binary Dependencies) allows SPM to use binary packages alongside its existing support for source packages. This means 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 targets to have dependencies only for specific platforms and configurations. For example, we might say that we need some specific extra frameworks when compiling for Linux, or that we should build in some debug code when compiling for local testing.

It’s worth adding that the “Future Directions” section of SE-0271 mentions the possibility of type-safe access to individual resource files – the ability for SPM to generate specific declarations for our resource files as Swift code, meaning that things like Image("avatar") become something like Image(module.avatar).

New in Swift 5.4

Improved implicit member syntax

SE-0287 improves Swift’s ability to use implicit member expressions, so rather than just having support for exactly one single static member you can make chains of them.

Swift has always had the ability to use implicit member syntax for simple expressions, for example if you wanted to color some text in SwiftUI you could use .red rather than Color.red:

import SwiftUI

struct ContentView1: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red)
    }
}

Prior to Swift 5.4 this did not work with more complex expressions. For example, if you wanted your red color to be slightly transparent you would need to write this:

struct ContentView2: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(Color.red.opacity(0.5))
    }
}

From Swift 5.4 onwards the compiler is able to understand multiple chained members, meaning that the Color type can be inferred:

struct ContentView3: View {
    var body: some View {
        Text("You're not my supervisor!")
            .foregroundColor(.red.opacity(0.5))
    }
}

Multiple variadic parameters in functions

SE-0284 introduced the ability to have functions, subscripts, and initializers use multiple variadic parameters as long as all parameters that follow a variadic parameter have labels. Before Swift 5.4, you could only have one variadic parameter in this situation.

So, with this improvement in place we could write a function that accepts a variadic parameter storing the times goals were scored during a football match, plus a second variadic parameter scoring the names of players who scored:

import Foundation

func summarizeGoals(times: Int..., players: String...) {
    let joinedNames = ListFormatter.localizedString(byJoining: players)
    let joinedTimes = ListFormatter.localizedString(byJoining: times.map(String.init))

    print("\(times.count) goals where scored by \(joinedNames) at the follow minutes: \(joinedTimes)")
}

To call that function, provide both sets of values as variadic parameters, making sure that all parameters after the first variadic are labeled:

summarizeGoals(times: 18, 33, 55, 90, players: "Dani", "Jamie", "Roy")

Local functions now support overloading

SR-10069 requested the ability to overload functions in local contexts, which in practice means nested functions can now be overloaded so that Swift chooses which one to run based on the types that are used.

For example, if we wanted to make some simple cookies we might write code like this:

struct Butter { }
struct Flour { }
struct Sugar { }

func makeCookies() {
    func add(item: Butter) {
        print("Adding butter…")
    }

    func add(item: Flour) {
        print("Adding flour…")
    }

    func add(item: Sugar) {
        print("Adding sugar…")
    }

    add(item: Butter())
    add(item: Flour())
    add(item: Sugar())
    print("Come and get some cookies!")
}

makeCookies()

Prior to Swift 5.4, the three add() methods could be overloaded only if they were not nested inside makeCookies(), but from Swift 5.4 onwards function overloading is supported in this case as well.

Swift 5.4 also lets us call local functions before they are declared, meaning that we can now write code like this if needed:

func makeCookies2() {   
    add(item: Butter())
    add(item: Flour())
    add(item: Sugar())

    func add(item: Butter) {
        print("Adding butter…")
    }

    func add(item: Flour) {
        print("Adding flour…")
    }

    func add(item: Sugar) {
        print("Adding sugar…")
    }
}

makeCookies2()

Creating variables that call a function of the same name

From Swift 5.4 onwards it’s possible to create a local variable by calling a function of the same name. That might sound obscure, but it’s actually a problem we hit all the time.

For example, this creates a struct with a color(forRow:) method, which gets called and assigned to a local variable called color:

struct Table {
    let count = 10

    func color(forRow row: Int) -> String {
        if row.isMultiple(of: 2) {
            return "red"
        } else {
            return "black"
        }
    }

    func printRows() {
        for i in 0..<count {
            let color = color(forRow: i)
            print("Row \(i): \(color)")
        }
    }
}

let table = Table()
table.printRows()

That kind of usage is only allowed from Swift 5.4 and later. In earlier versions of Swift, it would create a circular reference because Swift couldn’t distinguish between the local color constant and the color(forRow:) method it was calling – you would have seen the error “Variable used within its own initial value”.

This usually resulted in us either using self.color(forRow: 1989) to make it clear we mean the method call, or just naming the local value something else such as colorForRow.

Fortunately Swift 5.4 resolves this and allows us to use the more natural naming.

This change also allows us to make local copies of properties and global variables. For example, we can take a copy of a username property that is also called username, like this:

struct User {
    let username = "Taylor"

    func suggestAlternativeUsername() -> String {
        var username = username
        username += String(Int.random(in: 1000...9999))
        return username
    }
}

let user = User()
user.suggestAlternativeUsername()

Because this also applies to global variables, that same code works just fine even without the struct in place:

let username = "Taytay"

func suggestAlternativeUsername() -> String {
    var username = username
    username += String(Int.random(in: 1000...9999))
    return username
}

suggestAlternativeUsername()

Result builders

Function builders unofficially arrived in Swift 5.1, but in the run up to Swift 5.4 they formally went through the Swift Evolution proposal process as SE-0289 in order to be discussed and refined. As part of that process they were renamed to result builders to better reflect their actual purpose, and even acquired some new functionality.

First up, the most important part: result builders allow us to create a new value step by step by passing in a sequence of our choosing. They power large parts of SwiftUI’s view creation system, so that when we have a VStack with a variety of views inside, Swift silently groups them together into an internal TupleView type so that they can be stored as a single child of the VStack – it turns a sequence of views into a single view.

Result builders deserve their own detailed article, but I at least want to give you some small code examples so you can see them in action.

Here is a function that returns a single string:

func makeSentence1() -> String {
    "Why settle for a Duke when you can have a Prince?"
}

print(makeSentence1())

That works great, but what if had several strings we wanted to join together? Just like SwiftUI, we might want to provide them all individually and have Swift figure it out, however this kind of code won’t work:

// This is invalid Swift, and will not compile.
// func makeSentence2() -> String {
//     "Why settle for a Duke"
//     "when you can have"
//     "a Prince?"
// }

By itself, that code won’t work because Swift no longer understands what we mean. However, we could create a result builder that understands how to convert several strings into one string using whatever transformation we want, like this:

@resultBuilder
struct SimpleStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }
}

Even though that’s a small amount of code, there’s a lot to unpack:

  • The @resultBuilder attribute tells Swift the following type should be treated as a result builder. Previously this behavior was achieved using @_functionBuilder, which had an underscore to show that this wasn’t designed for general use.
  • Every result builder must provide at least one static method called buildBlock(), which should take in some sort of data and transform it. The example above takes in zero or more strings, joins them, and sends them back as a single string.
  • The end result is that our SimpleStringBuilder struct becomes a result builder, meaning that we can use @SimpleStringBuilder anywhere we need its string joining powers.

There’s nothing to stop us from using SimpleStringBuilder.buildBlock() directly, like this:

let joined = SimpleStringBuilder.buildBlock(
    "Why settle for a Duke",
    "when you can have",
    "a Prince?"
)

print(joined)

However, because we used the @resultBuilder annotation with our SimpleStringBuilder struct, we can also apply that to functions, like this:

@SimpleStringBuilder func makeSentence3() -> String {
    "Why settle for a Duke"
    "when you can have"
    "a Prince?"
}

print(makeSentence3())

Notice how we no longer need the commas at the end of each string – @resultBuilder automatically transforms each statement in makeSentence() into a single string by using SimpleStringBuilder.

In practice, result builders are capable of significantly more, accomplished by adding more methods to your builder type. For example, we could add if/else support to our SimpleStringBuilder by adding two extra methods that describe how we want to transform the data. In our code we don’t want to transform our strings at all, so we can send them right back:

@resultBuilder
struct ConditionalStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }

    static func buildEither(first component: String) -> String {
        return component
    }

    static func buildEither(second component: String) -> String {
        return component
    }
}

I know that looks like we’ve done almost no work, but now our functions are able to use conditions:

@ConditionalStringBuilder func makeSentence4() -> String {
    "Why settle for a Duke"
    "when you can have"

    if Bool.random() {
        "a Prince?"
    } else {
        "a King?"
    }
}

print(makeSentence4())

Similarly, we could add support for loops by adding a buildArray() method to our builder type:

@resultBuilder
struct ComplexStringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: "\n")
    }

    static func buildEither(first component: String) -> String {
        return component
    }

    static func buildEither(second component: String) -> String {
        return component
    }

    static func buildArray(_ components: [String]) -> String {
        components.joined(separator: "\n")
    }
}

And now we can use for loops:

@ComplexStringBuilder func countDown() -> String {
    for i in (0...10).reversed() {
        "\(i)…"
    }

    "Lift off!"
}

print(countDown())

It feels almost like magic because the result builder system is doing almost all the work for us, and even though our example has been fairly simple I hope you can get a taste for the remarkable power result builders bring to Swift.

It’s worth adding that Swift 5.4 extends the result builder system to support attributes being placed on stored properties, which automatically adjusts the implicit memberwise initializer for structs to apply the result builder.

This is particularly helpful for custom SwiftUI views that use result builders, such as this one:

import SwiftUI

struct CustomVStack<Content: View>: View {
    @ViewBuilder let content: Content

    var body: some View {
        VStack {
            // custom functionality here
            content
        }
    }
}

If you’d like to see more advanced, real-world examples of result builders in action, you should check out the Awesome Function Builders repository on GitHub.

Property wrappers are now supported for local variables

Property wrappers were first introduced in Swift 5.1 as a way of attaching extra functionality to properties in an easy, reusable way, but in Swift 5.4 their behavior got extended to support using them as local variables in functions.

For example, we could create a property wrapper that ensures its value never goes below zero:

@propertyWrapper struct NonNegative<T: Numeric & Comparable> {
    var value: T

    var wrappedValue: T {
        get { value }

        set {
            if newValue < 0 {
                value = 0
            } else {
                value = newValue
            }
        }
    }

    init(wrappedValue: T) {
        if wrappedValue < 0 {
            self.value = 0
        } else {
            self.value = wrappedValue
        }
    }
}

And from Swift 5.4 onwards we can use that property wrapper inside a regular function, rather than just attaching to a property. For example, we might write a game where our player can gain or lose points, but their score should never go below 0:

func playGame() {
    @NonNegative var score = 0

    // player was correct
    score += 4

    // player was correct again
    score += 8

    // player got one wrong
    score -= 15

    // player got another one wrong
    score -= 16

    print(score)
}

playGame()

Packages can now declare executable targets

SE-0294 adds a new target option for apps using Swift Package manager, allowing us to explicitly declare an executable target.

This is particularly important for folks who want to use SE-0281 (using @main to mark your program’s entry point), because it didn’t play nicely with Swift Package Manager – it would always look for a main.swift file.

With this change, we can now remove main.swift and use @main instead. Note: You must specify // swift-tools-version:5.4 in your Package.swift file in order to get this new functionality.

New in Swift 5.5

Async await

SE-0296 introduced asynchronous (async) functions into Swift, allowing us to run complex asynchronous code almost is if it were synchronous. This is done in two steps: marking async functions with the new async keyword, then calling them using the await keyword, similar to other languages such as C# and JavaScript.

To see how async/await helps the language, it’s helpful to look at how we solved the same problem previously. Completion handlers are commonly used in Swift code to allow us to send back values after a function returns, but they had tricky syntax as you’ll see.

For example, if we wanted to write code that fetched 100,000 weather records from a server, processes them to calculate the average temperature over time, then uploaded the resulting average back to a server, we might have written this:

import Foundation

func fetchWeatherHistory(completion: @escaping ([Double]) -> Void) {
    // Complex networking code here; we'll just send back 100,000 random temperatures
    DispatchQueue.global().async {
        let results = (1...100_000).map { _ in Double.random(in: -10...30) }
        completion(results)
    }
}

func calculateAverageTemperature(for records: [Double], completion: @escaping (Double) -> Void) {
    // Sum our array then divide by the array size
    DispatchQueue.global().async {
        let total = records.reduce(0, +)
        let average = total / Double(records.count)
        completion(average)
    }
}

func upload(result: Double, completion: @escaping (String) -> Void) {
    // More complex networking code; we'll just send back "OK"
    DispatchQueue.global().async {
        completion("OK")
    }
}

I’ve substituted actual networking code with fake values because the networking part isn’t relevant here. What matters is that each of those functions can take some time to run, so rather than blocking execution of the function and returning a value directly we instead use a completion closure to send something back only when we’re ready.

When it comes to using that code, we need to call them one by one in a chain, providing completion closures for each one to continue the chain, like this:

fetchWeatherHistory { records in
    calculateAverageTemperature(for: records) { average in
        upload(result: average) { response in
            print("Server response: \(response)")
        }
    }
}

Hopefully you can see the problems with this approach:

  • It’s possible for those functions to call their completion handler more than once, or forget to call it entirely.
  • The parameter syntax @escaping (String) -> Void can be hard to read.
  • At the call site we end up with a so-called pyramid of doom, with code increasingly indented for each completion handler.
  • Until Swift 5.0 added the Result type, it was harder to send back errors with completion handlers.

From Swift 5.5, we can now clean up our functions by marking them as asynchronously returning a value rather than relying on completion handlers, like this:

func fetchWeatherHistory() async -> [Double] {
    (1...100_000).map { _ in Double.random(in: -10...30) }
}

func calculateAverageTemperature(for records: [Double]) async -> Double {
    let total = records.reduce(0, +)
    let average = total / Double(records.count)
    return average
}

func upload(result: Double) async -> String {
    "OK"
}

That has already removed a lot of the syntax around returning values asynchronously, but at the call site it’s even cleaner:

func processWeather() async {
    let records = await fetchWeatherHistory()
    let average = await calculateAverageTemperature(for: records)
    let response = await upload(result: average)
    print("Server response: \(response)")
}

As you can see, all the closures and indenting have gone, making for what is sometimes called “straight-line code” – apart from the await keywords, it looks just like synchronous code.

There are some straightforward, specific rules about the way async functions work:

  • Synchronous functions cannot simply call async functions directly – it wouldn’t make sense, so Swift will throw an error.
  • Async functions can call other async functions, but they can also call regular synchronous functions if they need to.
  • If you have async and synchronous functions that can be called in the same way, Swift will prefer whichever one matches your current context – if the call site is currently async then Swift will call the async function, otherwise it will call the synchronous function.

That last point is important, because it allows library authors to provide both synchronous and asynchronous versions of their code without having to name the async functions specially.

The addition of async/await fits perfectly alongside try/catch, meaning that async functions and initializers can throw errors if needed. The only proviso here is that Swift enforces a particular order for the keywords, and that order is reversed between call site and function.

For example, we might have functions that attempt to fetch a number of users from a server, and save them to disk, both of which might fail by throwing errors:

enum UserError: Error {
    case invalidCount, dataTooLong
}

func fetchUsers(count: Int) async throws -> [String] {
    if count > 3 {
        // Don't attempt to fetch too many users
        throw UserError.invalidCount
    }

    // Complex networking code here; we'll just send back up to `count` users
    return Array(["Antoni", "Karamo", "Tan"].prefix(count))
}

func save(users: [String]) async throws -> String {
    let savedUsers = users.joined(separator: ",")

    if savedUsers.count > 32 {
        throw UserError.dataTooLong
    } else {
        // Actual saving code would go here
        return "Saved \(savedUsers)!"
    }
}

As you can see, both those functions are marked async throws – they are asynchronous functions, and they might throw errors.

When it comes to calling them the order of keywords is flipped to try await rather than await try, like this:

func updateUsers() async {
    do {
        let users = try await fetchUsers(count: 3)
        let result = try await save(users: users)
        print(result)
    } catch {
        print("Oops!")
    }
}

So, “asynchronous, throwing” in the function definition, but “throwing, asynchronous” at the call site – think of it as unwinding a stack. Not only does try await read a little more naturally than await try, but it’s also more reflective of what’s actually happening: we’re waiting for some work to complete, and when it does complete it might end up throwing.

With async/await now in Swift itself, the Result type introduced in Swift 5.0 becomes much less important as one of its primary benefits was improving completion handlers. That doesn’t mean Result is useless, because it’s still the best way to store the result of an operation for later evaluation.

Important: Making a function asynchronous doesn’t mean it magically runs concurrently with other code, which means unless you specify otherwise calling multiple async functions will still run them sequentially.

All the async functions you’ve seen so far have in turn been called by other async functions, which is intentional: taken by itself this Swift Evolution proposal does not actually provide any way to run asynchronous code from a synchronous context. Instead, this functionality is defined in a separate Structured Concurrency proposal, although hopefully we’ll see some major updates to Foundation too.

Async sequences

SE-0298 introduced the ability to loop over asynchronous sequences of values using a new AsyncSequence protocol. This is helpful for places when you want to process values in a sequence as they become available rather than precomputing them all at once – perhaps because they take time to calculate, or because they aren’t available yet.

Using AsyncSequence is almost identical to using Sequence, with the exception that your types should conform to AsyncSequence and AsyncIterator, and your next() method should be marked async. When it comes time for your sequence to end, make sure you send back nil from next(), just as with Sequence.

For example, we could make a DoubleGenerator sequence that starts from 1 and doubles its number every time it’s called:

struct DoubleGenerator: AsyncSequence {
    typealias Element = Int

    struct AsyncIterator: AsyncIteratorProtocol {
        var current = 1

        mutating func next() async -> Int? {
            defer { current &*= 2 }

            if current < 0 {
                return nil
            } else {
                return current
            }
        }
    }

    func makeAsyncIterator() -> AsyncIterator {
        AsyncIterator()
    }
}

Tip: If you just remove “async” from everywhere it appears in that code, you have a valid Sequence doing exactly the same thing – that’s how similar these two are.

Once you have your asynchronous sequence, you can loop over its values by using for await in an async context, like this:

func printAllDoubles() async {
    for await number in DoubleGenerator() {
        print(number)
    }
}

The AsyncSequence protocol also provides default implementations of a variety of common methods, such as map(), compactMap(), allSatisfy(), and more. For example, we could check whether our generator outputs a specific number like this:

func containsExactNumber() async {
    let doubles = DoubleGenerator()
    let match = await doubles.contains(16_777_216)
    print(match)
}

Again, you need to be in an async context to use this.

Effectful read-only properties

SE-0310 upgrades Swift’s read-only properties to support the async and throws keywords, either individually or together, making them significantly more flexible.

To demonstrate this, we could create a BundleFile struct that attempts to load the contents of a file in our app’s resource bundle. Because the file might not be there, might be there but can’t be read for some reason, or might be readable but so big it takes time to read, we could mark the contents property as async throws like this:

import Foundation

enum FileError: Error {
    case missing, unreadable
}

struct BundleFile {
    let filename: String

    var contents: String {
        get async throws {
            guard let url = Bundle.main.url(forResource: filename, withExtension: nil) else {
                throw FileError.missing
            }

            do {
                return try String(contentsOf: url)
            } catch {
                throw FileError.unreadable
            }
        }
    }
}

Because contents is both async and throwing, we must use try await when trying to read it:

func printHighScores() async throws {
    let file = BundleFile(filename: "highscores")
    try await print(file.contents)
}

Structured concurrency

SE-0304 introduced a whole range of approaches to execute, cancel, and monitor concurrent operations in Swift, and builds upon the work introduced by async/await and async sequences.

For easier demonstration purposes, here are a couple of example functions we can work with – an async function to simulate fetching a certain number of weather readings for a particular location, and a synchronous function to calculate which number lies at a particular position in the Fibonacci sequence:

enum LocationError: Error {
    case unknown
}

func getWeatherReadings(for location: String) async throws -> [Double] {
    switch location {
    case "London":
        return (1...100).map { _ in Double.random(in: 6...26) }
    case "Rome":
        return (1...100).map { _ in Double.random(in: 10...32) }
    case "San Francisco":
        return (1...100).map { _ in Double.random(in: 12...20) }
    default:
        throw LocationError.unknown
    }
}

func fibonacci(of number: Int) -> Int {
    var first = 0
    var second = 1

    for _ in 0..<number {
        let previous = first
        first = second
        second = previous + first
    }

    return first
}

The simplest async approach introduced by structured concurrency is the ability to use the @main attribute to go immediately into an async context, which is done simply by marking the main() method with async, like this:

@main
struct Main {
    static func main() async throws {
        let readings = try await getWeatherReadings(for: "London")
        print("Readings are: \(readings)")
    }
}

The main changes introduced by structured concurrency are backed by two new types, Task and TaskGroup, which allow us to run concurrent operations either individually or in a coordinated way.

In its simplest form, you can start concurrent work by creating a new Task object and passing it the operation you want to run. This will start running on a background thread immediately, and you can use await to wait for its finished value to come back.

So, we might call fibonacci(of:) many times on a background thread, in order to calculate the first 50 numbers in the sequence:

func printFibonacciSequence() async {
    let task1 = Task { () -> [Int] in
        var numbers = [Int]()

        for i in 0..<50 {
            let result = fibonacci(of: i)
            numbers.append(result)
        }

        return numbers
    }

    let result1 = await task1.value
    print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
}

As you can see, I’ve needed to explicitly write Task { () -> [Int] in so that Swift understands that the task is going to return, but if your task code is simpler that isn’t needed. For example, we could have written this and gotten exactly the same result:

let task1 = Task {
    (0..<50).map(fibonacci)
}

Again, the task starts running as soon as it’s created, and the printFibonacciSequence() function will continue running on whichever thread it was while the Fibonacci numbers are being calculated.

Tip: Our task's operation is a non-escaping closure because the task immediately runs it rather than storing it for later, which means if you use Task inside a class or a struct you don’t need to use self to access properties or methods.

When it comes to reading the finished numbers, await task1.value will make sure execution of printFibonacciSequence() pauses until the task’s output is ready, at which point it will be returned. If you don’t actually care what the task returns – if you just want the code to start running and finish whenever – you don’t need to store the task anywhere.

For task operations that throw uncaught errors, reading your task’s value property will automatically also throw errors. So, we could write a function that performs two pieces of work at the same time then waits for them both to complete:

func runMultipleCalculations() async throws {
    let task1 = Task {
        (0..<50).map(fibonacci)
    }

    let task2 = Task {
        try await getWeatherReadings(for: "Rome")
    }

    let result1 = await task1.value
    let result2 = try await task2.value
    print("The first 50 numbers in the Fibonacci sequence are: \(result1)")
    print("Rome weather readings are: \(result2)")
}

Swift provides us with the built-in task priorities of high, default, low, and background. The code above doesn’t specifically set one so it will get default, but we could have said something like Task(priority: .high) to customize that. If you’re writing just for Apple’s platforms, you can also use the more familiar priorities of userInitiated in place of high, and utility in place of low, but you can’t access userInteractive because that is reserved for the main thread.

As well as just running operations, Task also provides us with a handful of static methods to control the way our code runs:

  • Calling Task.sleep() will cause the current task to sleep for a specific number of nanoseconds. Until something better comes along, this means writing 1_000_000_000 to mean 1 second.
  • Calling Task.checkCancellation() will check whether someone has asked for this task to be cancelled by calling its cancel() method, and if so throw a CancellationError.
  • Calling Task.yield() will suspend the current task for a few moments in order to give some time to any tasks that might be waiting, which is particularly important if you’re doing intensive work in a loop.

You can see both sleeping and cancellation in the following code example, which puts a task to sleep for one second then cancels it before it completes:

func cancelSleepingTask() async {
    let task = Task { () -> String in
        print("Starting")
        try await Task.sleep(nanoseconds: 1_000_000_000)
        try Task.checkCancellation()
        return "Done"
    }

    // The task has started, but we'll cancel it while it sleeps
    task.cancel()

    do {
        let result = try await task.value
        print("Result: \(result)")
    } catch {
        print("Task was cancelled.")
    }
}

In that code, Task.checkCancellation() will realize the task has been cancelled and immediately throw CancellationError, but that won’t reach us until we attempt to read task.value.

Tip: Use task.result to get a Result value containing the task’s success and failure values. For example, in the code above we’d get back a Result<String, Error>. This does not require a try call because you still need to handle the success or failure case.

For more complex work, you should create task groups instead – collections of tasks that work together to produce a finished value.

To minimize the risk of programmers using task groups in dangerous ways, they don’t have a simple public initializer. Instead, task groups are created using functions such as withTaskGroup(): call this with the body of work you want done, and you’ll be passed in the task group instance to work with. Once inside the group you can add work using the addTask() method, and it will start executing immediately.

Important: You should not attempt to copy that task group outside the body of withTaskGroup() – the compiler can’t stop you, but you’re just going to make problems for yourself.

To see a simple example of how task groups work – along with demonstrating an important point of how they order their operations, try this:

func printMessage() async {
    let string = await withTaskGroup(of: String.self) { group -> String in
        group.addTask { "Hello" }
        group.addTask { "From" }
        group.addTask { "A" }
        group.addTask { "Task" }
        group.addTask { "Group" }

        var collected = [String]()

        for await value in group {
            collected.append(value)
        }

        return collected.joined(separator: " ")
    }

    print(string)
}

That creates a task group designed to produce one finished string, then queues up several closures using the addTask() method of the task group. Each of those closures returns a single string, which then gets collected into an array of strings, before being joined into one single string and returned for printing.

Tip: All tasks in a task group must return the same type of data, so for complex work you might find yourself needing to return an enum with associated values in order to get exactly what you want. A simpler alternative is introduced in a separate Async Let Bindings proposal.

Each call to addTask() can be any kind of function you like, as long as it results in a string. However, although task groups automatically wait for all the child tasks to complete before returning, when that code runs it’s a bit of a toss up what it will print because the child tasks can complete in any order – we’re as likely to get “Hello From Task Group A” as we are “Hello A Task Group From”, for example.

If your task group is executing code that might throw, you can either handle the error directly inside the group or let it bubble up outside the group to be handled there. That latter option is handled using a different function, withThrowingTaskGroup(), which must be called with try if you haven’t caught all the errors you throw.

For example, this next code sample calculates weather readings for several locations in a single group, then returns the overall average for all locations:

func printAllWeatherReadings() async {
    do {
        print("Calculating average weather…")

        let result = try await withThrowingTaskGroup(of: [Double].self) { group -> String in
            group.addTask {
                try await getWeatherReadings(for: "London")
            }

            group.addTask {
                try await getWeatherReadings(for: "Rome")
            }

            group.addTask {
                try await getWeatherReadings(for: "San Francisco")
            }

            // Convert our array of arrays into a single array of doubles
            let allValues = try await group.reduce([], +)

            // Calculate the mean average of all our doubles
            let average = allValues.reduce(0, +) / Double(allValues.count)
            return "Overall average temperature is \(average)"
        }

        print("Done! \(result)")
    } catch {
        print("Error calculating data.")
    }
}

In that instance, each of the calls to addTask() is identical apart from the location string being passed in, so you can use something like for location in ["London", "Rome", "San Francisco"] { to call addTask() in a loop.

Task groups have a cancelAll() method that cancels any tasks inside the group, but using addTask() afterwards will continue to add work to the group. As an alternative, you can use addTaskUnlessCancelled() to skip adding work if the group has been cancelled – check its returned Boolean to see whether the work was added successfully or not.

async let bindings

SE-0317 introduced the ability to create and await child tasks using the simple syntax async let. This is particularly useful as an alternative to task groups where you’re dealing with heterogeneous result types – i.e., if you want tasks in a group to return different kinds of data.

To demonstrate this, we could create a struct that has three different types of properties that will come from three different async functions:

struct UserData {
    let name: String
    let friends: [String]
    let highScores: [Int]
}

func getUser() async -> String {
    "Taylor Swift"
}

func getHighScores() async -> [Int] {
    [42, 23, 16, 15, 8, 4]
}

func getFriends() async -> [String] {
    ["Eric", "Maeve", "Otis"]
}

If we wanted to create a User instance from all three of those values, async let is the easiest way – it run each function concurrently, wait for all three to finish, then use them to create our object.

Here’s how it looks:

func printUserDetails() async {
    async let username = getUser()
    async let scores = getHighScores()
    async let friends = getFriends()

    let user = await UserData(name: username, friends: friends, highScores: scores)
    print("Hello, my name is \(user.name), and I have \(user.friends.count) friends!")
}

Important: You can only use async let if you are already in an async context, and if you don’t explicitly await the result of an async let Swift will implicitly wait for it when exiting its scope.

When working with throwing functions, you don’t need to use try with async let – that can automatically be pushed back to where you await the result. Similarly, the await keyword is also implied, so rather than typing try await someFunction() with an async let you can just write someFunction().

To demonstrate this, we could write an async function to recursively calculate numbers in the Fibonacci sequence. This approach is hopelessly naive because without memoization we’re just repeating vast amounts of work, so to avoid causing everything to grind to a halt we’re going to limit the input range from 0 to 22:

enum NumberError: Error {
    case outOfRange
}

func fibonacci(of number: Int) async throws -> Int {
    if number < 0 || number > 22 {
        throw NumberError.outOfRange
    }

    if number < 2 { return number }
    async let first = fibonacci(of: number - 2)
    async let second = fibonacci(of: number - 1)
    return try await first + second
}

In that code the recursive calls to fibonacci(of:) are implicitly try await fibonacci(of:), but we can leave them off and handle them directly on the following line.

Continuations for interfacing async tasks with synchronous code

SE-0300 introduced new functions to help us adapt older, completion handler-style APIs to modern async code.

For example, this function returns its values asynchronously using a completion handler:

import Foundation

func fetchLatestNews(completion: @escaping ([String]) -> Void) {
    DispatchQueue.main.async {
        completion(["Swift 5.5 release", "Apple acquires Apollo"])
    }
}

If you wanted to use that using async/await you might be able to rewrite the function, but there are various reasons why that might not be possible – it might come from an external library, for example.

Continuations allow us to create a shim between the completion handler and async functions so that we wrap up the older code in a more modern API. For example, the withCheckedContinuation() function creates a new continuation that can run whatever code you want, then call resume(returning:) to send a value back whenever you’re ready – even if that’s part of a completion handler closure.

So, we could make a second fetchLatestNews() function that is async, wrapping around the older completion handler function:

func fetchLatestNews() async -> [String] {
    await withCheckedContinuation { continuation in
        fetchLatestNews { items in
            continuation.resume(returning: items)
        }
    }
}

With that in place we can now get our original functionality in an async function, like this:

func printNews() async {
    let items = await fetchLatestNews()

    for item in items {
        print(item)
    }
}

The term “checked” continuation means that Swift is performing runtime checks on our behalf: are we calling resume() once and only once? This is important, because if you never resume the continuation then you will leak resources, but if you call it twice then you’re likely to hit problems.

Important: To be crystal clear, you must resume your continuation exactly once.

As there is a runtime performance cost of checking your continuations, Swift also provides a withUnsafeContinuation() function that works in exactly the same way except does not perform runtime checks on your behalf. This means Swift won’t warn you if you forget to resume the continuation, and if you call it twice then the behavior is undefined.

Because these two functions are called in the same way, you can switch between them easily. So, it seems likely people will use withCheckedContinuation() while writing their functions so Swift will emit warnings and even trigger crashes if the continuations are used incorrectly, but some may then switch over to withUnsafeContinuation() as they prepare to ship if they are affected by the runtime performance cost of checked continuations.

Actors

SE-0306 introduced actors, which are conceptually similar to classes that are safe to use in concurrent environments. This is possible because Swift ensures that mutable state inside your actor is only ever accessed by a single thread at any given time, which helps eliminate a variety of serious bugs right at the compiler level.

To demonstrate the problem actors solve, consider this Swift code that creates a RiskyCollector class able to trade cards from their deck with another collector:

class RiskyCollector {
    var deck: Set<String>

    init(deck: Set<String>) {
        self.deck = deck
    }

    func send(card selected: String, to person: RiskyCollector) -> Bool {
        guard deck.contains(selected) else { return false }

        deck.remove(selected)
        person.transfer(card: selected)
        return true
    }

    func transfer(card: String) {
        deck.insert(card)
    }
}

In a single-threaded environment that code is safe: we check whether our deck contains the card in question, remove it, then add it to the other collector’s deck. However, in a multi-threaded environment our code has a potential race condition, which is a problem whereby the results of the code will vary as two separate parts of our code run side by side.

If we call send(card:to:) more than once at the same time, the following chain of events can happen:

  1. The first thread checks whether the card is in the deck, and it is so it continues.
  2. The second thread also checks whether the card is in the deck, and it is so it continues.
  3. The first thread removes the card from the deck and transfer it to the other person.
  4. The second thread attempts to remove the card from the deck, but actually it’s already gone so nothing will happen. However, it still transfers the card to the other person.

In that situation one player loses a card while the other gains two cards, and if that card happened to be a Black Lotus from Magic the Gathering then you’ve got a big problem!

Actors solve this problem by introducing actor isolation: stored properties and methods cannot be read from outside the actor object unless they are performed asynchronously, and stored properties cannot be written from outside the actor object at all. The async behavior isn’t there for performance; instead it’s because Swift automatically places these requests into a queue that is processed sequentially to avoid race conditions.

So, we could rewrite out RiskyCollector class to be a SafeCollector actor, like this:

actor SafeCollector {
    var deck: Set<String>

    init(deck: Set<String>) {
        self.deck = deck
    }

    func send(card selected: String, to person: SafeCollector) async -> Bool {
        guard deck.contains(selected) else { return false }

        deck.remove(selected)
        await person.transfer(card: selected)
        return true
    }

    func transfer(card: String) {
        deck.insert(card)
    }
}

There are several things to notice in that example:

  1. Actors are created using the new actor keyword. This is a new concrete nominal type in Swift, joining structs, classes, and enums.
  2. The send() method is marked with async, because it will need to suspend its work while waiting for the transfer to complete.
  3. Although the transfer(card:) method is not marked with async, we still need to call it with await because it will wait until the other SafeCollector actor is able to handle the request.

To be clear, an actor can use its own properties and methods freely, asynchronously or otherwise, but when interacting with a different actor it must always be done asynchronously. With these changes Swift can ensure that all actor-isolated state is never accessed concurrently, and more importantly this is done at compile time so that safety is guaranteed.

Actors and classes have some similarities:

  • Both are reference types, so they can be used for shared state.
  • They can have methods, properties, initializers, and subscripts.
  • They can conform to protocols and be generic.
  • Any properties and methods that are static behave the same in both types, because they have no concept of self and therefore don’t get isolated.

Beyond actor isolation, there are two other important differences between actors and classes:

  • Actors do not currently support inheritance, which makes their initializers much simpler – there is no need for convenience initializers, overriding, the final keyword, and more. This might change in the future.
  • All actors implicitly conform to a new Actor protocol; no other concrete type can use this. This allows you to restrict other parts of your code so it can work only with actors.

The best way I’ve heard to explain how actors differ from classes is this: “actors pass messages, not memory.” So, rather than one actor poking directly around in another’s properties or calling their methods, we instead send a message asking for the data and let the Swift runtime handle it for us safely.

Global actors

SE-0316 allows global state to be isolated from data races by using actors.

Although in theory this could result in many global actors, the main benefit at least right now is the introduction of an @MainActor global actor you can use to mark properties and methods that should be accessed only on the main thread.

As an example, we might have a class to handle data storage in our app, and for safety reasons we refuse to write out change to persistent storage unless we’re on the main thread:

import Foundation 

class OldDataController {
    func save() -> Bool {
        guard Thread.isMainThread else {
            return false
        }

        print("Saving data…")
        return true
    }
}

That works, but with @MainActor we can guarantee that save() is always called on the main thread as if we specifically ran it using DispatchQueue.main:

class NewDataController {
    @MainActor func save() {
        print("Saving data…")
    }
}

That’s all it takes – Swift will make sure whenever you call save() on a data controller, that work will happen on the main thread.

Note: Because we’re pushing work through an actor, you must call save() using await, async let, or similar.

@MainActor is a global actor wrapper around the underlying MainActor struct, which is helpful because it has a static run() method that lets us schedule work to be run. This will execute your code on the main thread, optionally sending back a result.

Sendable and @Sendable closures

SE-0302 adds support for “sendable” data, which is data that can safely be transferred to another thread. This is accomplished through a new Sendable protocol, and an @Sendable attribute for functions.

Many things are inherently safe to send across threads:

  • All of Swift’s core value types, including Bool, Int, String, and similar.
  • Optionals, where the wrapped data is a value type.
  • Standard library collections that contain value types, such as Array<String> or Dictionary<Int, String>.
  • Tuples where the elements are all value types.
  • Metatypes, such as String.self.

These have been updated to conform to the Sendable protocol.

As for custom types, it depends what you’re making:

  • Actors automatically conform to Sendable because they handle their synchronization internally.
  • Custom structs and enums you define will also automatically conform to Sendable if they contain only values that also conform to Sendable, similar to how Codable works.
  • Custom classes can conform to Sendable as long as they either inherits from NSObject or from nothing at all, all properties are constant and themselves conform to Sendable, and they are marked as final to stop further inheritance.

Swift lets us use the @Sendable attribute on functions or closure to mark them as working concurrently, and will enforce various rules to stop us shooting ourself in the foot. For example, the operation we pass into the Task initializer is marked @Sendable, which means this kind of code is allowed because the value captured by Task is a constant:

func printScore() async { 
    let score = 1

    Task { print(score) }
    Task { print(score) }
}

However, that code would not be allowed if score were a variable, because it could be accessed by one of the tasks while the other was changing its value.

You can mark your own functions and closures using @Sendable, which will enforce similar rules around captured values:

import Foundation 

func runLater(_ function: @escaping @Sendable () -> Void) -> Void {
    DispatchQueue.global().asyncAfter(deadline: .now() + 3, execute: function)
}

#if for postfix member expressions

SE-0308 allows Swift to use #if conditions with postfix member expressions. This sounds a bit obscure, but it solves a problem commonly seen with SwiftUI: you can now optionally add modifiers to a view.

For example, this change allows us to create a text view with two different font sizes depending on whether we’re using iOS or another platform:

import SwiftUI

Text("Welcome")
#if os(iOS)
    .font(.largeTitle)
#else
    .font(.headline)
#endif

You can nest these if you want, although it’s a bit hard on your eyes:

Text("Welcome")
#if os(iOS)
    .font(.largeTitle)
    #if DEBUG
        .foregroundColor(.red)
    #endif
#else
    .font(.headline)
#endif

You could use wildly different postfix expressions if you wanted:

let result = [1, 2, 3]
#if os(iOS)
    .count
#else
    .reduce(0, +)
#endif

print(result)

Technically you could make result end up as two completely different types if you wanted, but that seems like a bad idea. What you definitely can’t do is use other kinds of expressions such as using + [4] instead of .count – if it doesn’t start with . then it’s not a postfix member expression.

Interchangeable use of CGFloat and Double types

SE-0307 introduced a small but important quality of life improvement: Swift is able to implicitly convert between CGFloat and Double in most places where it is needed.

In its simplest form, this means we can add a CGFloat and a Double together to produce a new Double, like this:

import Foundation
let first: CGFloat = 42
let second: Double = 19
let result = first + second
print(result)

Swift implements this by inserting an implicit initializer as needed, and it will always prefer Double if it’s possible. More importantly, none of this is achieved by rewriting existing APIs: technically things like scaleEffect() in SwiftUI still work with CGFloat, but Swift quietly bridges this to Double.

Codable synthesis for enums with associated values

SE-0295 upgrades Swift’s Codable system to support writing enums with associated values. Previously enums were only supported if they conformed to RawRepresentable, but this extends support to general enums as well as enum cases with any number of Codable associated values.

For example, we could define a Weather enum like this one:

enum Weather: Codable {
    case sun
    case wind(speed: Int)
    case rain(amount: Int, chance: Int)
}

That has one simple case, one case with a single associated values, and a third case with two associated values – all are integers, but you could use strings or other Codable types.

With that enum defined, we can create an array of weather to make a forecast, then use JSONEncoder or similar and convert the result to a printable string:

import Foundation

let forecast: [Weather] = [
    .sun,
    .wind(speed: 10),
    .sun,
    .rain(amount: 5, chance: 50)
]

do {
    let result = try JSONEncoder().encode(forecast)
    let jsonString = String(decoding: result, as: UTF8.self)
    print(jsonString)
} catch {
    print("Encoding error: \(error.localizedDescription)")
}

Behind the scenes, this is implemented using multiple CodingKey enums capable of handling the nested structure that results from having values attached to enum cases, which means writing your own custom coding methods to do the same is a little more work.

lazy now works in local contexts

The lazy keyword has always allowed us to write stored properties that are only calculated when first used, but from Swift 5.5 onwards we can use lazy locally inside a function to create values that work similarly.

This code demonstrates local lazy in action:

func printGreeting(to: String) -> String {
    print("In printGreeting()")
    return "Hello, \(to)"
}

func lazyTest() {
    print("Before lazy")
    lazy var greeting = printGreeting(to: "Paul")
    print("After lazy")
    print(greeting)
}

lazyTest()

When that runs you’ll see “Before lazy” and “After lazy” printed first, followed by “In printGreeting()” then “Hello, Paul” – Swift only runs the printGreeting(to:) code when its result is accessed on the print(greeting) line.

In practice, this feature is going to be really helpful as a way of selectively running code when you have conditions in place: you can prepare the result of some work lazily, and only actual perform the work if it’s still needed later on.

Extending property wrappers to function and closure parameters

SE-0293 extends property wrappers so they can be applied to parameters for functions and closures. Parameters passed this way are still immutable unless you take a copy of them, and you are still able to access the underlying property wrapper type using a leading underscore if you want.

As an example, we could write a function that accepts an integer and prints it out:

func setScore1(to score: Int) {
    print("Setting score to \(score)")
}

When that’s called we can pass it any range of values, like this:

setScore1(to: 50)
setScore1(to: -50)
setScore1(to: 500)

If we wanted our scores to lie only within the range 0...100 we could write a simple property wrapper that clamps numbers as they are created:

@propertyWrapper
struct Clamped<T: Comparable> {
    let wrappedValue: T

    init(wrappedValue: T, range: ClosedRange<T>) {
        self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

Now we can write and call a new function using that wrapper:

func setScore2(@Clamped(range: 0...100) to score: Int) {
    print("Setting score to \(score)")
}

setScore2(to: 50)
setScore2(to: -50)
setScore2(to: 500)

Calling setScore2() with the same input values as before will print different output, because the numbers will get clamped to 50, 0, 100.

Tip: Our property wrapper is trivial because parameters passed into a function are immutable – we don’t need to handle re-clamping the wrapped value when it changes because it won’t change. However, you can make your property wrappers as complex as you need; they work just as they would with properties or local variables.

Extending static member lookup in generic contexts

SE-0299 allows Swift to perform static member lookup for members of protocols in generic functions, which sounds obscure but actually fixes a small but important legibility problem that hit SwiftUI particularly hard.

At this time SwiftUI hasn’t been updated to support this change, but if everything goes to plan we can stop writing this:

import SwiftUI

Toggle("Example", isOn: .constant(true))
    .toggleStyle(SwitchToggleStyle())

And instead write something like this:

Toggle("Example", isOn: .constant(true))
    .toggleStyle(.switch)

This was possible in early SwiftUI betas because Apple had put extensive workarounds in place, but these were withdrawn before release.

To see what’s actually changing here, imagine a Theme protocol with several structs conforming to it:

protocol Theme { }
struct LightTheme: Theme { }
struct DarkTheme: Theme { }
struct RainbowTheme: Theme { }

We could also define a Screen protocol that is able to have a theme() method called on it with some sort of theme:

protocol Screen { }

extension Screen {
    func theme<T: Theme>(_ style: T) -> Screen {
        print("Activating new theme!")
        return self
    }
}

And now we could create an instance of a screen:

struct HomeScreen: Screen { }

Following older SwiftUI code, we could enable a light theme on that screen by specifying LightTheme():

let lightScreen = HomeScreen().theme(LightTheme())

If we wanted to make that easier to access, we could try adding a static light property to our Theme protocol like this:

extension Theme where Self == LightTheme {
    static var light: LightTheme { .init() }
}

However, using that with the theme() method of our generic protocol was what caused the problem: before Swift 5.5 it was not possible and you had to use LightTheme() every time. However, in Swift 5.5 or later this is now possible:

let lightTheme = HomeScreen().theme(.light)

New in Swift 5.6

Introduce existential any

SE-0335 introduces a new any keyword to mark existential types, and although that might sound esoteric please don’t skip ahead: this one is a big change, and is likely to break a lot of Swift code in future versions.

Protocols allow us to specify a set of requirements that conforming types must adhere to, such as methods they must implement. So, we often write code like this:

protocol Vehicle {
    func travel(to destination: String)
}

struct Car: Vehicle {
    func travel(to destination: String) {
        print("I'm driving to \(destination)")
    }
}

let vehicle = Car()
vehicle.travel(to: "London")

It’s also possible to use protocols as generic type constraints in functions, meaning that we write code that can work with any kind of data that conforms to a particular protocol. For example, this will work with any kind of type that conforms to Vehicle:

func travel<T: Vehicle>(to destinations: [String], using vehicle: T) {
    for destination in destinations {
        vehicle.travel(to: destination)
    }
}

travel(to: ["London", "Amarillo"], using: vehicle)

When that code compiles, Swift can see we’re calling travel() with a Car instance and so it is able to create optimized code to call the travel() function directly – a process known as static dispatch.

All this matters because there is a second way to use protocols, and it looks very similar to the other code we’ve used so far:

let vehicle2: Vehicle = Car()
vehicle2.travel(to: "Glasgow")

Here we are still creating a Car struct, but we’re storing it as a Vehicle. This isn’t just a simple matter of hiding the underlying information, but instead this Vehicle type is a whole other thing called an existential type: a new data type that is able to hold any value of any type that conforms to the Vehicle protocol.

Important: Existential types are different from opaque types that use the some keyword, e.g. some View, which must always represent one specific type that conforms to whatever constraints you specify.

We can use existential types with functions too, like this:

func travel2(to destinations: [String], using vehicle: Vehicle) {
    for destination in destinations {
        vehicle.travel(to: destination)
    }
}

That might look similar to the other travel() function, but as this one accepts any kind of Vehicle object Swift can no longer perform the same set of optimizations – it has to use a process called dynamic dispatch, which is less efficient than the static dispatch available in the generic equivalent. So, Swift was in a position where both uses of protocols looked very similar, and actually the slower, existential version of our function was easier to write.

To resolve this problem, Swift 5.6 introduces a new any keyword for use with existential types, so that we’re explicitly acknowledging the impact of existentials in our code. In Swift 5.6 this new behavior is enabled and works, but in future Swift versions failing to use it will generate warnings, and from Swift 6 onwards the plan is to issue errors – you will be required to mark existential types using any.

So, you would write this:

let vehicle3: any Vehicle = Car()
vehicle3.travel(to: "Glasgow")

func travel3(to destinations: [String], using vehicle: any Vehicle) {
    for destination in destinations {
        vehicle.travel(to: destination)
    }
}

I know it took a lot of explanation to reach this conclusion, but hopefully it makes sense: when we use Vehicle as a conformance or a generic constraint we will carry on writing Vehicle, but when we use it as its own type we should start moving across to any Vehicle.

This is a big breaking change in Swift. Fortunately, like I said the Swift team are taking it slow – here’s what they said in the acceptance decision:

“The goal is that that one can write code that compiles without warnings for the current Swift release and at least one major release prior, after which warnings can be introduced to guide users to the new syntax in existing language modes. Finally, the old syntax can be removed or repurposed only in a new major language version.”

Type placeholders

SE-0315 introduces the concept of type placeholders, which allow us to explicitly specify only some parts of a value’s type so that the remainder can be filled in using type inference.

In practice, this means writing _ as your type in any place you want Swift to use type inference, meaning that these three lines of code are the same:

let score1 = 5
let score2: Int = 5
let score3: _ = 5

In those trivial examples type placeholders don’t add anything, but they are useful when the compiler is able to correctly infer part of a type but not all. For example, if you were creating a dictionary of student names and all the exam results they had this year, you might write this:

var results1 = [
    "Cynthia": [],
    "Jenny": [],
    "Trixie": [],
]

Swift will infer that to be a dictionary with strings as keys, and an array of Any as values – almost certainly not what you want. You could specify the entire type explicitly, like this:

var results2: [String: [Int]] = [
    "Cynthia": [],
    "Jenny": [],
    "Trixie": [],
]

However, type placeholders allow you to write _ in place of the parts you want the compiler to infer – it’s a way for us to explicitly say “this part should use type inference”, alongside places where we want an exact type of our choosing.

So, we could also write this:

var results3: [_: [Int]] = [
    "Cynthia": [],
    "Jenny": [],
    "Trixie": [],
]

As you can see, the _ there is an explicit request for type inference, but we still have the opportunity to specify the exact array type.

Tip: Type placeholders can be optional too – use _? to have Swift infer your type as optional.

Types placeholders do not affect the way we write function signatures: you must still provide their parameter and return types in full. However, I have found that type placeholders do still serve a purpose for when you’re busy experimenting with a prototype: telling the compiler you want it to infer some type often prompts Xcode to offer a Fix-it to complete the code for you.

For example, you might write code to create a player like this:

struct Player<T: Numeric> {
    var name: String
    var score: T
}

func createPlayer() -> _ {
    Player(name: "Anonymous", score: 0)
}

That fails to specify a return type for createPlayer(), which will cause a compiler error. However, as we’ve asked Swift to infer the type, the error in Xcode will offer a Fix-it to replace _ with Player<Int> – you can imagine that saving a fair amount of hassle when dealing with more complex types.

Think of type placeholders as a way of simplifying long type annotations: you can replace all the less relevant or boilerplate parts with underscores, leaving the important parts spelled out to help make your code more readable.

Allow coding of non String/Int keyed Dictionary into a KeyedContainer

SE-0320 introduces a new CodingKeyRepresentable protocol that allows dictionaries with keys that aren’t a plain String or Int to be encoded as keyed containers rather than unkeyed containers.

To understand why this is important, you first need to see the behavior without CodingKeyRepresentable in place. As an example, this old code uses enum cases for keys in a dictionary, then encodes it to JSON and prints out the resulting string:

import Foundation

enum OldSettings: String, Codable {
    case name
    case twitter
}

let oldDict: [OldSettings: String] = [.name: "Paul", .twitter: "@twostraws"]
let oldData = try JSONEncoder().encode(oldDict)
print(String(decoding: oldData, as: UTF8.self))

Although the enum has a String raw value, because the dictionary keys aren’t String or Int the resulting string will be ["twitter","@twostraws","name","Paul"] – four separate string values, rather than something that is obviously key/value pairs. Swift is smart enough to recognize this in decoding, and will match alternating strings inside each pair to the original enum keys and string values, but this isn’t helpful if you want to send the JSON to a server.

The new CodingKeyRepresentable resolves this, allowing the new dictionary keys to be written correctly. However, as this changes the way your Codable JSON is written, you must explicitly add CodingKeyRepresentable conformance to get the new behavior, like this:

enum NewSettings: String, Codable, CodingKeyRepresentable {
    case name
    case twitter
}

let newDict: [NewSettings: String] = [.name: "Paul", .twitter: "@twostraws"]
let newData = try! JSONEncoder().encode(newDict)
print(String(decoding: newData, as: UTF8.self))

That will print {"twitter":"@twostraws","name":"Paul”}, which is much more useful outside of Swift.

If you’re using custom structs as your keys, you can also conform to CodingKeyRepresentable and provide your own methods for converting your data into a string.

Unavailability condition

SE-0290 introduces an inverted form of #available called #unavailable, which will run some code if an availability check fails.

This is going to be particularly useful in places where you were previously using #available with an empty true block because you only wanted to run code if a specific operating system was unavailable. So, rather than writing code like this:

if #available(iOS 15, *) { } else {
    // Code to make iOS 14 and earlier work correctly
}

We can now write this:

if #unavailable(iOS 15) {
    // Code to make iOS 14 and earlier work correctly
}

This problem wasn’t just theoretical – using an empty #available block was a fairly common workaround, particularly in the transition to the scene-based UIKit APIs in iOS 13.

Apart from their flipped behavior, one key difference between #available and #unavailable is the platform wildcard, *. This is required with #available to allow for potential future platforms, which meant that writing if #available(iOS 15, *) would mark some code as being available on iOS versions 15 or later, or all other platforms – macOS, tvOS, watchOS, and any future unknown platforms.

With #unavailable, this behavior no longer makes sense: would writing if #unavailable(iOS 15, *) mean “the following code should be run on iOS 14 and earlier,” or should it also include macOS, tvOS, watchOS, Linux, and more, where iOS 15 is also unavailable? To avoid this ambiguity, the platform wildcard is not allowed with #unavailable: only platforms you specifically list are considered for the test.

More concurrency changes

Swift 5.5 added a lot of features around concurrency, and 5.6 continues the process of refining those features to make them safer and more consistent, while also working towards bigger, breaking changes coming in Swift 6.

The biggest change is SE-0337, which aims to provide a roadmap towards full, strict concurrency checking for our code. This is designed to be incremental: you can import whole modules using @preconcurrency to tell Swift the module was created without modern concurrency in mind; or you can mark individual classes, structs, properties, methods and more as @preconcurrency to be more selective.

In the short term this makes it significantly easier to migrate larger projects to modern concurrency.

Another area that’s changing is the use of actors, because as a result of SE-0327 Swift 5.6 now issues a warning if you attempt to instantiate a @MainActor property using @StateObject like this:

import SwiftUI

@MainActor class Settings: ObservableObject { }

struct OldContentView: View {
    @StateObject private var settings = Settings()

    var body: some View {
        Text("Hello, world!")
    }
}

This warning will be upgraded to an error in Swift 6, so you should be prepared to move away from this code and use this instead:

struct NewContentView: View {
    @StateObject private var settings: Settings

    init() {
        _settings = StateObject(wrappedValue: Settings())
    }

    var body: some View {
        Text("Hello, world!")
    }
}

Plugins for Swift Package Manager

Swift 5.6 includes a raft ([1], [2], [3], [4]) of improvements to Swift Package Manager, which combine to add the beginnings of plugin support using external build tools.

At this point earlier adopters are reporting that the functionality isn’t quite far along enough to support more complex use cases, but it does at least seem to support SwiftGen and there are further examples demonstrating possibilities for DocC and swift-format.

New in Swift 5.7

if let shorthand for unwrapping optionals

SE-0345 introduces new shorthand syntax for unwrapping optionals into shadowed variables of the same name using if let and guard let. This means we can now write code like this:

var name: String? = "Linda"

if let name {
    print("Hello, \(name)!")
}

Whereas previously we would have written code more like this:

if let name = name {
    print("Hello, \(name)!")
}

if let unwrappedName = name {
    print("Hello, \(unwrappedName)!")
}        

This change doesn’t extend to properties inside objects, which means code like this will not work:

struct User {
    var name: String
}

let user: User? = User(name: "Linda")

if let user.name {
    print("Welcome, \(user.name)!")
}

Multi-statement closure type inference

SE-0326 dramatically improves Swift’s ability to use parameter and type inference for closures, meaning that many places where we had to specify explicit input and output types can now be removed.

Previously Swift really struggled for any closures that weren’t trivial, but from Swift 5.7 onwards we can now write code like this:

let scores = [100, 80, 85]

let results = scores.map { score in
    if score >= 85 {
        return "\(score)%: Pass"
    } else {
        return "\(score)%: Fail"
    }
}

Prior to Swift 5.7, we needed to specify the return type explicitly, like this:

let oldResults = scores.map { score -> String in
    if score >= 85 {
        return "\(score)%: Pass"
    } else {
        return "\(score)%: Fail"
    }
}

Clock, Instant, and Duration

SE-0329 introduces a new, standardized way of referring to times and durations in Swift. As the name suggests, it’s broken down into three main components:

  • Clocks represent a way of measuring time passing. There are two built in: the continuous clock keeps incrementing time even when the system is asleep, and the suspending clock does not.
  • Instants represent an exact moment in time.
  • Durations represent how much time elapsed between two instants.

The most immediate application of this for many people will be the newly upgraded Task API, which can now specify sleep amounts in much more sensible terms than nanoseconds:

try await Task.sleep(until: .now +  .seconds(1), clock: .continuous)

This newer API also comes with the benefit of being able to specify tolerance, which allows the system to wait a little beyond the sleep deadline in order to maximize power efficiency. So, if we wanted to sleep for at least 1 seconds but would be happy for it to last up to 1.5 seconds in total, we would write this:

try await Task.sleep(until: .now + .seconds(1), tolerance: .seconds(0.5), clock: .continuous)

Tip: This tolerance is only in addition to the default sleep amount – the system won’t end the sleep before at least 1 second has passed.

Although it hasn’t happened yet, it looks like the older nanoseconds-based API will be deprecated in the near future.

Clocks are also useful for measuring some specific work, which is helpful if you want to show your users something like how long a file export took:

let clock = ContinuousClock()

let time = clock.measure {
    // complex work here
}

print("Took \(time.components.seconds) seconds")

Regular expressions

Swift 5.7 introduces a whole raft of improvements relating to regular expressions (regexes), and in doing so dramatically improves the way we process strings. This is actually a whole chain of interlinked proposals, including

  • SE-0350 introduces a new Regex type
  • SE-0351 introduces a result builder-powered DSL for creating regular expressions.
  • SE-0354 adds the ability co create a regular expression using /.../ rather than going through Regex and a string.
  • SE-0357 adds many new string processing algorithms based on regular expressions.

Put together this is pretty revolutionary for strings in Swift, which have often been quite a sore point when compared to other languages and platforms.

To see what’s changing, let’s start simple and work our way up.

First, we can now draw on a whole bunch of new string methods, like so:

let message = "the cat sat on the mat"
print(message.ranges(of: "at"))
print(message.replacing("cat", with: "dog"))
print(message.trimmingPrefix("the "))

But the real power of these is that they all accept regular expressions too:

print(message.ranges(of: /[a-z]at/))
print(message.replacing(/[a-m]at/, with: "dog"))
print(message.trimmingPrefix(/The/.ignoresCase()))

In case you’re not familiar with regular expressions:

  • In that first regular expression we’re asking for the range of all substrings that match any lowercase alphabetic letter followed by “at”, so that would find the locations of “cat”, “sat”, and “mat”.
  • In the second one we’re matching the range “a” through “m” only, so it will print “the dog sat on the dog”.
  • In the third one we’re looking for “The”, but I’ve modified the regex to be case insensitive so that it matches “the”, “THE”, and so on.

Notice how each of those regexes are made using regex literals – the ability to create a regular expression by starting and ending your regex with a /.

Along with regex literals, Swift provides a dedicated Regex type that works similarly:

do {
    let atSearch = try Regex("[a-z]at")
    print(message.ranges(of: atSearch))
} catch {
    print("Failed to create regex")
}

However, there’s a key difference that has significant side effects for our code: when we create a regular expression from a string using Regex, Swift must parse the string at runtime to figure out the actual expression it should use. In comparison, using regex literals allows Swift to check your regex at compile time: it can validate the regex contains no errors, and also understand exactly what matches it will contain.

This bears repeating, because it’s quite remarkable: Swift parses your regular expressions at compile time, making sure they are valid – this is, for me at least, the coding equivalent of the head explode emoji.

To see how powerful this difference is, consider this code:

let search1 = /My name is (.+?) and I'm (\d+) years old./
let greeting1 = "My name is Taylor and I'm 26 years old."

if let result = try? search1.wholeMatch(in: greeting1) {
    print("Name: \(result.1)")
    print("Age: \(result.2)")
}

That creates a regex looking for two particular values in some text, and if it finds them both prints them. But notice how the result tuple can reference its matches as .1 and .2, because Swift knows exactly which matches will occur. (In case you were wondering, .0 will return the whole matched string.)

In fact, we can go even further because regular expressions allow us to name our matches, and these flow through to the resulting tuple of matches:

let search2 = /My name is (?<name>.+?) and I'm (?<age>\d+) years old./
let greeting2 = "My name is Taylor and I'm 26 years old."

if let result = try? search2.wholeMatch(in: greeting2) {
    print("Name: \(result.name)")
    print("Age: \(result.age)")
}

This kind of safety just wouldn’t be possible with regexes created from strings.

But Swift goes one step further: you can create regular expressions from strings, you can create them from regex literals, but you can also create them from a domain-specific language similar to SwiftUI code.

For example, if we wanted to match the same “My name is Taylor and I’m 26 years old” text, we could write a regex like this:

import RegexBuilder

let search3 = Regex {
    "My name is "

    Capture {
        OneOrMore(.word)
    }

    " and I'm "

    Capture {
        OneOrMore(.digit)
    }

    " years old."
}

Even better, this DSL approach is able to apply transformations to the matches it finds, and if we use TryCapture rather than Capture then Swift will automatically consider the whole regex not to match if the capture fails or throws an error. So, in the case of our age matching we could write this to convert the age string into an integer:

let search4 = Regex {
    "My name is "

    Capture {
        OneOrMore(.word)
    }

    " and I'm "

    TryCapture {
        OneOrMore(.digit)
    } transform: { match in
        Int(match)
    }

    " years old."
}

And you can even bring together named matches using variables with specific types like this:

let nameRef = Reference(Substring.self)
let ageRef = Reference(Int.self)

let search5 = Regex {
    "My name is "

    Capture(as: nameRef) {
        OneOrMore(.word)
    }

    " and I'm "

    TryCapture(as: ageRef) {
        OneOrMore(.digit)
    } transform: { match in
        Int(match)
    }

    " years old."
}

if let result = greeting1.firstMatch(of: search5) {
    print("Name: \(result[nameRef])")
    print("Age: \(result[ageRef])")
}

Of the three options, I suspect the regex literals will get the most use because it’s the most natural, but helpfully Xcode has the ability to convert regex literals into the RegexBuilder syntax.

Type inference from default expressions

SE-0347 expands Swift ability to use default values with generic parameter types. What it allows seems quite niche, but it does matter: if you have a generic type or function you can now provide a concrete type for a default expression, in places where previously Swift would have thrown up a compiler error.

As an example, we might have a function that returns count number of random items from any kind of sequence:

func drawLotto1<T: Sequence>(from options: T, count: Int = 7) -> [T.Element] {
    Array(options.shuffled().prefix(count))
}

That allows us to run a lottery using any kind of sequence, such as an array of names or an integer range:

print(drawLotto1(from: 1...49))
print(drawLotto1(from: ["Jenny", "Trixie", "Cynthia"], count: 2))

SE-0347 extends this to allow us to provide a concrete type as default value for the T parameter in our function, allowing us to keep the flexibility to use string arrays or any other kind of collection, while also defaulting to the range option that we want most of the time:

func drawLotto2<T: Sequence>(from options: T = 1...49, count: Int = 7) -> [T.Element] {
    Array(options.shuffled().prefix(count))
}

And now we can call our function either with a custom sequence, or let the default take over:

print(drawLotto2(from: ["Jenny", "Trixie", "Cynthia"], count: 2))
print(drawLotto2())

Concurrency in top-level code

SE-0343 upgrades Swift’s support for top-level code – think main.swift in a macOS Command Line Tool project – so that it supports concurrency out of the box. This is one of those changes that might seem trivial on the surface, but took a lot of work to make happen.

In practice, it means you can write code like this directly into your main.swift files:

import Foundation
let url = URL(string: "https://hws.dev/readings.json")!
let (data, _) = try await URLSession.shared.data(from: url)
let readings = try JSONDecoder().decode([Double].self, from: data)
print("Found \(readings.count) temperature readings")

Previously, we had to create a new @main struct that had an asynchronous main() method, so this new, simpler approach is a big improvement.

Opaque parameter declarations

SE-0341 unlocks the ability to use some with parameter declarations in places where simpler generics were being used.

As an example, if we wanted to write a function that checks whether an array is sorted, Swift 5.7 and later allow us to write this:

func isSorted(array: [some Comparable]) -> Bool {
    array == array.sorted()
}

The [some Comparable] parameter type means this function works with an array containing elements of one type that conforms to the Comparable protocol, which is syntactic sugar for the equivalent generic code:

func isSortedOld<T: Comparable>(array: [T]) -> Bool {
    array == array.sorted()
}

Of course, we could also write the even longer constrained extension:

extension Array where Element: Comparable {
    func isSorted() -> Bool {
        self == self.sorted()
    }
}

This simplified generic syntax does mean we no longer have the ability to add more complex constraints our types, because there is no specific name for the synthesized generic parameter.

Important: You can switch between explicit generic parameters and this new simpler syntax without breaking your API.

Structural opaque result types

SE-0328 widens the range of places that opaque result types can be used.

For example, we can now return more than one opaque type at a time:

import SwiftUI

func showUserDetails() -> (some Equatable, some Equatable) {
    (Text("Username"), Text("@twostraws"))
}

We can also return opaque types:

func createUser() -> [some View] {
    let usernames = ["@frankefoster", "@mikaela__caron", "@museumshuffle"]
    return usernames.map(Text.init)
}

Or even send back a function that itself returns an opaque type when called:

func createDiceRoll() -> () -> some View {
    return {
        let diceRoll = Int.random(in: 1...6)
        return Text(String(diceRoll))
    }
}

So, this is another great example of Swift harmonizing the language to make things consistently possible.

Unlock existentials for all protocols

SE-0309 significantly loosens Swift’s ban on using protocols as types when they have Self or associated type requirements, moving to a model where only specific properties or methods are off limits based on what they do.

In simple terms, this means the following code becomes legal:

let firstName: any Equatable = "Paul"
let lastName: any Equatable = "Hudson"

Equatable is a protocol with Self requirements, which means it provides functionality that refers to the specific type that adopts it. For example, Int conforms to Equatable, so when we say 4 == 4 we’re actually running a function that accepts two integers and returns true if they match.

Swift could implement this functionality using a function similar to func ==(first: Int, second: Int) -> Bool, but that wouldn’t scale well – they would need to write dozens of such functions to handle Booleans, strings, arrays, and so on. So, instead the Equatable protocol has a requirement like this: func ==(lhs: Self, rhs: Self) -> Bool. In English, that means “you need to be able to accept two instances of the same type and tell me if they are the same.” That might be two integers, two strings, two Booleans, or two of any other type that conforms to Equatable.

To avoid this problem and similar ones, any time Self appeared in a protocol before Swift 5.7 the compiler would simply not allow us to use it in code such as this:

let tvShow: [any Equatable] = ["Brooklyn", 99]

From Swift 5.7 onwards, this code is allowed, and now the restrictions are pushed back to situations where you attempt to use the type in a place where Swift must actually enforce its restrictions. This means we can’t write firstName == lastName because as I said == must be sure it has two instances of the same type in order to work, and by using any Equatable we’re hiding the exact types of our data.

However, what we have gained is the ability to do runtime checks on our data to identify specifically what we’re working with. In the case of our mixed array, we could write this:

for item in tvShow {
    if let item = item as? String {
        print("Found string: \(item)")
    } else if let item = item as? Int {
        print("Found integer: \(item)")
    }
}

Or in the case of our two strings, we could use this:

if let firstName = firstName as? String, let lastName = lastName as? String {
    print(firstName == lastName)
}

The key to understanding what this change does is remembering that it allow us to use these protocol more freely, as long as we don’t do anything that specifically needs to knows about the internals of the type. So, we could write code to check whether all items in any sequence conform to the Identifiable protocol:

func canBeIdentified(_ input: any Sequence) -> Bool {
    input.allSatisfy { $0 is any Identifiable }
}

Lightweight same-type requirements for primary associated types

SE-0346 adds newer, simpler syntax for referring to protocols that have specific associated types.

As an example, if we were writing code to cache different kinds of data in different kinds of ways, we might start like this:

protocol Cache<Content> {
    associatedtype Content

    var items: [Content] { get set }

    init(items: [Content])
    mutating func add(item: Content)
}

Notice that the protocol now looks like both a protocol and a generic type – it has an associated type declaring some kind of hole that conforming types must fill, but also lists that type in angle brackets: Cache<Content>.

The part in angle brackets is what Swift calls its primary associated type, and it’s important to understand that not all associated types should be declared up there. Instead, you should list only the ones that calling code normally cares about specifically, e.g. the types of dictionary keys and values or the identifier type in the Identifiable protocol. In our case we’ve said that our cache’s content – strings, images, users, etc – is its primary associated type.

At this point, we can go ahead and use our protocol as before – we might create some kind of data we want to cache, and then create a concrete cache type conforming to the protocol, like this:

struct File {
    let name: String
}

struct LocalFileCache: Cache {
    var items = [File]()

    mutating func add(item: File) {
        items.append(item)
    }
}

Now for the clever part: when it comes to creating a cache, we can obviously create a specific one directly, like this:

func loadDefaultCache() -> LocalFileCache {
    LocalFileCache(items: [])
}

But very often we want to hide the specifics of what we’re doing, like this:

func loadDefaultCacheOld() -> some Cache {
    LocalFileCache(items: [])
}

Using some Cache gives us the flexibility of changing our mind about what specific cache is sent back, but what SE-0346 lets us do is provide a middle ground between being absolutely specific with a concrete type, and being rather vague with an opaque return type. So, we can specialize the protocol, like this:

func loadDefaultCacheNew() -> some Cache<File> {
    LocalFileCache(items: [])
}

So, we’re still retaining the ability to move to a different Cache-conforming type in the future, but we’ve made it clear that whatever is chosen here will store files internally.

This smarter syntax extends to other places too, including things like extensions:

extension Cache<File> {
    func clean() {
        print("Deleting all cached files…")
    }
}

And generic constraints:

func merge<C: Cache<File>>(_ lhs: C, _ rhs: C) -> C {
    print("Copying all files into a new location…")
    // now send back a new cache with items from both other caches
    return C(items: lhs.items + rhs.items)
}

But what will prove most helpful of all is that SE-0358 brings these primary associated types to Swift’s standard library too, so Sequence, Collection, and more will benefit – we can write Sequence<String> to write code that is agnostic of whatever exact sequence type is being used.

Constrained existential types

SE-0353 provides the ability to compose SE-0309 (“Unlock existentials for all protocols”) and SE-0346 (“Lightweight same-type requirements for primary associated types”) to write code such as any Sequence<String>.

It’s a huge feature in its own right, but once you understand the component parts hopefully you can see how it all fits together!

Distributed actor isolation

SE-0336 and SE-0344 introduce the ability for actors to work in a distributed form – to read and write properties or call methods over a network using remote procedure calls (RPC).

This is every part as complicated a problem as you might imagine, but there are three things to make it easier:

  1. Swift’s approach of location transparency effectively forces us to assume the actors are remote, and in fact provides no way of determining at compile time whether an actor is local or remote – we just use the same await calls we would no matter what, and if the actor happens to be local then the call is handled as a regular local actor function.
  2. Rather than forcing us to build our own actor transport systems, Apple is providing a ready-made implementation for us to use. Apple has said they “only expect a handful of mature implementations to take the stage eventually,” but helpfully all the distributed actor features in Swift are agnostic of whatever actor transport you use.
  3. To move from an actor to a distributed actor we mostly just need to write distributed actor then distributed func as needed.

So, we can write code like this to simulate someone tracking a trading card system:

// use Apple's ClusterSystem transport 
typealias DefaultDistributedActorSystem = ClusterSystem

distributed actor CardCollector {
    var deck: Set<String>

    init(deck: Set<String>) {
        self.deck = deck
    }

    distributed func send(card selected: String, to person: CardCollector) async -> Bool {
        guard deck.contains(selected) else { return false }

        do {
            try await person.transfer(card: selected)
            deck.remove(selected)
            return true
        } catch {
            return false
        }
    }

    distributed func transfer(card: String) {
        deck.insert(card)
    }
}

Because of the throwing nature of distributed actor calls, we can be sure it’s safe to remove the card from one collector if the call to person.transfer(card:) didn’t throw.

Swift’s goal is that you can transfer your knowledge of actors over to distributed actors very easily, but there are some important differences that might catch you out.

First, all distributed functions must be called using try as well as await even if the function isn’t marked as throwing, because it’s possible for a failure to happen as a result of the network call going awry.

Second, all parameters and return values for distributed methods must conform to a serialization process of your choosing, such as Codable. This gets checked at compile time, so Swift can guarantee it’s able to send and receive data from remote actors.

And third, you should consider adjusting your actor API to minimize data requests. For example, if you want to read the username, firstName, and lastName properties of a distributed actor, you should prefer to request all three with a single method call rather than requesting them as individual properties to avoid potentially having to go back and forward over the network several times.

buildPartialBlock for result builders

SE-0348 dramatically simplifies the overloads required to implement complex result builders, which is part of the reason Swift’s advanced regular expression support was possible. However, it also theoretically removes the 10-view limit for SwiftUI without needing to add variadic generics, so if it’s adopted by the SwiftUI team it will make a lot of folks happy.

To give you a practical example, here’s a simplified version of what SwiftUI’s ViewBuilder looks like:

import SwiftUI

@resultBuilder
struct SimpleViewBuilderOld {
    static func buildBlock<C0, C1>(_ c0: C0, _ c1: C1) -> TupleView<(C0, C1)> where C0 : View, C1 : View {
        TupleView((c0, c1))
    }

    static func buildBlock<C0, C1, C2>(_ c0: C0, _ c1: C1, _ c2: C2) -> TupleView<(C0, C1, C2)> where C0: View, C1: View, C2: View {
        TupleView((c0, c1, c2))
    }
}

I’ve made that to include two versions of buildBlock(): one that accepts two views and one that accepts three. In practice, SwiftUI accepts a wide variety of alternatives, but critically only up to 10 – there’s a buildBlock() variant that returns TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)>, but there isn’t anything beyond that for practical reasons.

We could then use that result builder with functions or computed properties, like this:

@SimpleViewBuilderOld func createTextOld() -> some View {
    Text("1")
    Text("2")
    Text("3")
}

That will accept all three Text views using the buildBlock<C0, C1, C2>() variant, and return a single TupleView containing them all. However, in this simplified example there’s no way to add a fourth Text view, because I didn’t provide any more overloads in just the same way that SwiftUI doesn’t support 11 or more.

This is where the new buildPartialBlock() comes in, because it works like the reduce() method of sequences: it has an initial value, then updates that by adding whatever it has already to whatever comes next.

So, we could create a new result builder that knows how to accept a single view, and how to combine that view with another one:

@resultBuilder
struct SimpleViewBuilderNew {
    static func buildPartialBlock<Content>(first content: Content) -> Content where Content: View {
        content
    }

    static func buildPartialBlock<C0, C1>(accumulated: C0, next: C1) -> TupleView<(C0, C1)> where C0: View, C1: View {
        TupleView((accumulated, next))
    }
}

Even though we only have variants accepting one or two views, because they accumulate we can actually use as many as we want:

@SimpleViewBuilderNew func createTextNew() -> some View {
    Text("1")
    Text("2")
    Text("3")
}

The result isn’t identical, however: in the first example we would get back a TupleView<Text, Text, Text>, whereas now we would get back a TupleView<(TupleView<(Text, Text)>, Text)> – one TupleView nested inside another. Fortunately, if the SwiftUI team do intend to adopt this they ought to be able to create the same 10 buildPartialBlock() overloads they had before, which should mean the compile automatically creates groups of 10 just like we’re doing explicitly right now.

Tip: buildPartialBlock() is part of Swift as opposed to any platform-specific runtime, so if you adopt it you’ll find it back deploys to earlier OS releases.

Implicitly opened existentials

SE-0352 allows Swift to call generic functions using a protocol in many situations, which removes a somewhat odd barrier that existed previously.

As an example, here’s a simple generic function that is able to work with any kind of Numeric value:

func double<T: Numeric>(_ number: T) -> T {
    number * 2
}

If we call that directly, e.g. double(5), then the Swift compiler can choose to specialize the function – to effectively create a version that accepts an Int directly, for performance reasons.

However, what SE-0352 does is allow that function to be callable when all we know is that our data conforms to a protocol, like this:

let first = 1
let second = 2.0
let third: Float = 3

let numbers: [any Numeric] = [first, second, third]

for number in numbers {
    print(double(number))
}

Swift calls these existential types: the actual data type you’re using sits inside a box, and when we call methods on that box Swift understands it should implicitly call the method on the data inside the box. SE-0352 extends this same power to function calls too: the number value in our loop is an existential type (a box containing either an Int, Double, or Float), but Swift is able to pass it in to the generic double() function by sending in the value inside the box.

There are limits to what this capable of, and I think they are fairly self explanatory. For example, this kind of code won’t work:

func areEqual<T: Numeric>(_ a: T, _ b: T) -> Bool {
    a == b
}

print(areEqual(numbers[0], numbers[1]))

Swift isn’t able to statically verify (i.e., at compile time) that both values are things that can be compared using ==, so the code simply won’t build.

Unavailable from async attribute

SE-0340 partially closes a potentially risky situation in Swift’s concurrency model, by allowing us to mark types and functions as being unavailable in asynchronous contexts because using them in such a way could cause problems. Unless you’re using thread-local storage, locks, mutexes, or semaphores, it’s unlikely you’ll use this attribute yourself, but you might call code that uses it so it’s worth at least being aware it exists.

To mark something as being unavailable in async context, use @available with your normal selection of platforms, then add noasync to the end. For example, we might have a function that works on any platform, but might cause problems when called asynchronously, so we’d mark it like this:

@available(*, noasync)
func doRiskyWork() {

}

We can then call that from a regular synchronous function as normal:

func synchronousCaller() {
    doRiskyWork()
}

However, Swift will issue an error if we attempted the same from an asynchronous function, so this code will not work:

func asynchronousCaller() async {
    doRiskyWork()
}

This protection is an improvement over the current situation, but should not be leaned on too heavily because it doesn’t stop us from nesting the call to our noasync function, like this:

func sneakyCaller() async {
    synchronousCaller()
}

That runs in an async context, but calls a synchronous function, which can in turn call the noasync function doRiskyWork().

So, noasync is an improvement, but you still need to be careful when using it. Fortunately, as the Swift Evolution proposal says, “the attribute is expected to be used for a fairly limited set of specialized use-cases” – there’s a good chance you might never come across code that uses it.

New in Swift 5.8

Lift all limitations on variables in result builders

SE-0373 relaxes some of the restrictions on variables when used inside result builders, allowing us to write code that would previously have been disallowed by the compiler.

For example, in Swift 5.8 we can use lazy variables directly inside result builders, like so:

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            lazy var user = fetchUsername()
            Text("Hello, \(user).")
        }
        .padding()
    }

    func fetchUsername() -> String {
        "@twostraws"
    }
}

That shows the concept, but doesn’t provide any benefit because the lazy variable is always used – there’s no difference between using lazy var and let in that code. To see where it’s actually useful takes a longer code example, like this one:

// The user is an active subscriber, not an active subscriber, or we don't know their status yet.
enum UserState {
    case subscriber, nonsubscriber, unknown
}

// Two small pieces of information about the user
struct User {
    var id: UUID
    var username: String
}

struct SubscriberView: View {
    @State private var state = UserState.unknown

    var body: some View {
        VStack {
            lazy var user = fetchUsername()

            switch state {
            case .subscriber:
                Text("Hello, \(user.username). Here's what's new for subscribers…")
            case .nonsubscriber:
                Text("Hello, \(user.username). Here's why you should subscribe…")
                Button("Subscribe now") {
                    startSubscription(for: user)
                }
            case .unknown:
                Text("Sign up today!")
            }
        }
        .padding()
    }

    // Example function that would do complex work
    func fetchUsername() -> User {
        User(id: UUID(), username: "Anonymous")
    }

    func startSubscription(for user: User) {
        print("Starting subscription…")
    }
}

This approach solves problems that would appear in the alternatives:

  • If we didn’t use lazy, then fetchUsername() would be called in all three cases of state, even when it isn’t used in one.
  • If we removed lazy and placed the call to fetchUsername() inside the two cases then we would be duplicating code – not a massive problem with a simple one liner, but you can imagine how this would cause problems in more complex code.
  • If we moved user out to a computed property, it would be called a second time when the user clicked the "Subscribe now" button.

This change also allows us to use property wrappers and local computed properties inside result builders, although I suspect they will be less useful. For example, this kind of code is now allowed:

struct CounterView: View {
    var body: some View {
        @AppStorage("counter") var tapCount = 0

        Button("Count: \(tapCount)") {
            tapCount += 1
        }
    }
}

However, although that will cause the underlying UserDefaults value to change with each tap, using @AppStorage in this way won’t cause the body property to be reinvoked every time tapCount changes – our UI won’t automatically be updated to reflect the change.

Function back deployment

SE-0376 adds a new @backDeployed attribute that makes it possible to allow new APIs to be used on older versions of frameworks. It works by writing the code for a function into your app’s binary then performing a runtime check: if your user is on a suitably new version of the operating system then the system’s own version of the function will be used, otherwise the version copied into your app’s binary will be used instead.

On the surface this sounds like a fantastic way for Apple to make some new features retroactively available in earlier operating systems, but I don’t think it’s some kind of silver bullet – @backDeployed applies only to functions, methods, subscripts, and computed properties, so while it might work great for smaller API changes such as the fontDesign() modifier introduced in iOS 16.1, it wouldn’t work for any code that requires new types to be used, such as the new scrollBounceBehavior() modifier that relies on a new ScrollBounceBehavior struct.

As an example, iOS 16.4 introduced a monospaced(_ isActive:) variant for Text. If this were using @backDeployed, the SwiftUI team might ensure the modifier is available to whatever earliest version of SwiftUI supports the implementation code they actually need, like this:

import SwiftUI

extension Text {
    @backDeployed(before: iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4)
    @available(iOS 14.0, macOS 11, tvOS 14.0, watchOS 7.0, *)
    public func monospaced(_ isActive: Bool) -> Text {
        fatalError("Implementation here")
    }
}

At runtime, Swift will use the system’s copy of SwiftUI if it has that modifier already, otherwise using the back-deployed version back to iOS 14.0 and similar.

Allow implicit self for weak self captures, after self is unwrapped

SE-0365 takes another step towards letting us remove self from closures by allowing an implicit self in places where a weak self capture has been unwrapped.

For example, in the code below we have a closure that captures self weakly, but then unwraps self immediately:

import Foundation

class TimerController {
    var timer: Timer?
    var fireCount = 0

    init() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] timer in
            guard let self else { return }
            print("Timer has fired \(fireCount) times")
            fireCount += 1
        }
    }
}

That code would not have compiled before Swift 5.8, because both instances of fireCount in the closure would need to be written self.fireCount.

Concise magic file names

SE-0274 adjusts the #file magic identifier to use the format Module/Filename, e.g. MyApp/ContentView.swift. Previously, #file contained the whole path to the Swift file, e.g. /Users/twostraws/Desktop/WhatsNewInSwift/WhatsNewInSwift/ContentView.swift, which is unnecessarily long and also likely to contain things you’d rather not reveal.

Important: Right now this behavior is not enabled by default. SE-0362 adds a new -enable-upcoming-feature compiler flag designed to let developers opt into new features before they are fully enabled in the language, so to enable the new #file behavior you should add -enable-upcoming-feature ConciseMagicFile to Other Swift Flags in Xcode.

If you’d like to have the old behavior after this flag is enabled, you should use #filePath instead:

// New behavior, when enabled
print(#file)

// Old behavior, when needed
print(#filePath)

The Swift Evolution proposal for this change is worth reading because it mentions surprisingly large improvements in binary size and execution performance, and also for this quite brilliant paragraph explaining why having the full path is often a bad idea:

“The full path to a source file may contain a developer's username, hints about the configuration of a build farm, proprietary versions or identifiers, or the Sailor Scout you named an external disk after.”

Opening existential arguments to optional parameters

SE-0375 extends a Swift 5.7 feature that allowed us to call generic functions using a protocol, fixing a small but annoying inconsistency: Swift 5.7 would not allow this behavior with optionals, whereas Swift 5.8 does.

For example, this code worked great in Swift 5.7, because it uses a non-optional T parameter:

func double<T: Numeric>(_ number: T) -> T {
    number * 2
}

let first = 1
let second = 2.0
let third: Float = 3

let numbers: [any Numeric] = [first, second, third]

for number in numbers {
    print(double(number))
}

In Swift 5.8, that same parameter can now be optional, like this:

func optionalDouble<T: Numeric>(_ number: T?) -> T {
    let numberToDouble = number ?? 0
    return  numberToDouble * 2
}

for number in numbers {
    print(optionalDouble(number))
}

In Swift 5.7 that would have issued the rather baffling error message “Type 'any Numeric' cannot conform to 'Numeric’”, so it’s good to see this inconsistency resolved.

Collection downcasts in cast patterns are now supported

This resolves another small but potentially annoying inconsistency in Swift where downcasting a collection – e.g. casting an array of ClassA to an array of another type that inherits from ClassA – would not be allowed in some circumstances.

For example, this code is now valid in Swift 5.8, whereas it would not have worked previously:

class Pet { }
class Dog: Pet {
    func bark() { print("Woof!") }
}

func bark(using pets: [Pet]) {
    switch pets {
    case let pets as [Dog]:
        for pet in pets {
            pet.bark()
        }
    default:
        print("No barking today.")
    }
}

Before Swift 5.8 that would have led to the error message, “Collection downcast in cast pattern is not implemented; use an explicit downcast to '[Dog]' instead.” In practice, syntax such as if let dogs = pets as? [Dog] { worked just fine, so I would imagine that error was rarely seen. However, this change does mean another language inconsistency is resolved, which is always welcome.

No results – perhaps you should broaden your filter.

Credits

What's New In Swift was written by Paul Hudson. If you have questions or comments you can tweet me @twostraws or email paul@hackingwithswift.com.

This site is specifically about the Swift language. If you want to learn SwiftUI you should check my list of free SwiftUI tutorials or my free online book SwiftUI By Example.

A lot of the content for this site was drawn from previous articles I've written:

If you're just learning Swift, Hacking with Swift has many free Swift tutorials – check them out!