NEW! Pre-order my latest book, Testing Swift! >>

What’s new in Swift 5.0

Paul Hudson       @twostraws

Swift 5.0 is the next major release of Swift, and is slated to bring ABI stability at long last. That's not all, though: several key new features are already implemented, including raw strings, future enum cases, checking for integer multiples and more.

If you want to try out Swift 5.0 ahead of its release early next year, download the latest Swift trunk development snapshot, activate it inside your current Xcode version, then follow along with my examples below!

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.

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.

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.

Counting matching items in a sequence

SE-0220 introduces a new count(where:) method that performs the equivalent of a filter() and count in a single pass. This saves the creation of a new array that gets immediately discarded, and provides a clear and concise solution to a common problem.

This example creates an array of test results, and counts how many are greater or equal to 85:

let scores = [100, 80, 85]
let passCount = scores.count { $0 >= 85 }

And this counts how many names in an array start with “Terry”:

let pythons = ["Eric Idle", "Graham Chapman", "John Cleese", "Michael Palin", "Terry Gilliam", "Terry Jones"]
let terryCount = pythons.count { $0.hasPrefix("Terry") }

This method is available to all types that conform to Sequence, so you can use it on sets and dictionaries too.

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 }

More still to come!

Those are the features that have already been implemented in the Swift 5.0 branch, but more will undoubtedly come in the months leading up to Swift 5.0's release.

And let's not forget ABI stability remains the single largest feature for 5.0: many large companies (including almost certainly Apple itself) are waiting for this to happen so the great Swift migration can begin.

Exciting times!

 

MASTER SWIFT NOW
Buy Practical iOS 12 Buy Pro Swift Buy Swift Design Patterns Buy Practical iOS 11 Buy Swift Coding Challenges Buy Server-Side Swift (Vapor Edition) Buy Server-Side Swift (Kitura Edition) Buy Hacking with macOS Buy Advanced iOS Volume One Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with Swift Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

About the author

Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and Mario Kart world champion. OK, so that last part isn't true. If you're curious you can learn more here.

Was this page useful? Let me know!

Average rating: 4.7/5

Click here to visit the Hacking with Swift store >>