Swift 5 — it’s alive!

Another major version of Swift dropped this week, surprisingly in March — every other major version of Swift has dropped in September. Most likely this was due to issues in implementing ABI stability, since Ted Kremenek, manager of the languages and run time team at Apple, deemed it a requirement for Swift 5. Instead, we saw a raft of new changes in Swift 4.2 in September, the traditional month for new versions, and I imagine the Swift team were given a little extra time to focus on bringing Swift 5 to life!

So what has Swift 5 brought us? Well obviously, to begin with…

ABI stability

ABI what now? First, a little refresher — when you compile your app, it is converted into lower level machine code called a binary. To determine how your Swift code is represented in machine code, the compiler uses something called the Application Binary Interface(ABI). Prior to Swift 5, when you generated a binary, some files were also included with the binary, called the Swift standard library and runtime. These represent the Swift ABI.

From Swift 5 going forward, this ABI will be standardized and all future versions of Swift will be compatible with this ABI. This means that operating systems that run Swift binaries(such as iOS) will be able to embed the Swift standard library and runtime (aka the Swift ABI), meaning that apps will no longer need to embed them. Hooray! This will mean smaller app bundles, and compatibility between applications and libraries that may have been compiled with different Swift versions.

Source compatibility

Apple’s Swift ABI Stability Manifesto also describes another important compatibility goal — source compatibility.

This has historically been a bugbear of Swift — the possibility of future versions rendering your Swift code out-of-date. While migration tools have automated much of these issues, migration can still be buggy, and incompatibilities with external dependencies can cause major headaches.

From Swift 4 onwards, this migation will not be necessary. The compiler will be able to compile code from Swift 4 onwards. So — I guess you could stop reading now, if you like, and keep writing Swift 4 code! Or, if you’re interested in knowing what’s new and exciting in Swift 5, read on!

isMultiple

Here’s an easy one to start with, a little syntactic sugar. Ever needed to check if a number is odd or even? You probably used the remainder operator. (which looks deceptively like a percentage symbol!)

var number = 4
let numberIsEven = number % 2

Swift 5 makes this coded read more intuitively, with the isMultiple method:

var number = 4
let numberIsEven = number.isMultiple(of: 2)

Dictionary compactMapValues

It seems like every version of Swift, something new has happened to map!

Back in Swift 4.1, the flatMap method was divided into two methods. One method continued to be called flatMap, and we were introduced to a new method calledcompactMap. The compactMap method basically did the same as map method but also removed any nil values from the result.

Say you want to convert an Array of String to an Array of Int. Converting a String of course can fail, so if we used map we would actually end up with an array of Optional Int, and possible nil values:

let arrOfString = [“0”, “1”, “Y”]
let arrOfOptionalInt = arrOfString.map{Int($0)} //[0,1,nil]

Using compactMap instead ensures any nil values are removed from the result, and we end up with the Array of Int we were after:

let arrOfString = [“0”, “1”, “Y”]
let arrOfInt = arrOfString.compactMap{Int($0)} //[0,1]

The Dictionary had access to the map method as well, but was only able to map to an Array:

var dict = ["a":"0","b":"1","c":"y"]
let arrayOfOptionalInt = dict.map { Int($0.value) } //[0,1,nil]

That was, until Swift 4.0, when mapValues was introduced, which allowed us to call map on a dictionary, and return a dictionary:

let dict = ["a":"0","b":"1","c":"y"]
let dictOfOptionalInt = dictOfString.mapValues { Int($0) }
//["a": 0, "b": 1, "c": nil]

We didn’t, however, have a compactMap version of this mapValues method, however, until finally, in Swift 5.0, we have what has been called compactMapValues!

let dict = [“a”:”0",”b”:”1",”c”:”y”]
let dictOfInt = dictOfString.compactMapValues { Int($0) }
//[“a”: 0, “b”: 1]

Result

Let’s say we set up some sort of long process, which calls a completion handler passing it either data or an error. First let’s set up an error type for it:

enum LongProcessError:Error {
case randomError
}

Next let’s set up the long process itself:

func doLongProcess(completionHandler: @escaping (String?,  
LongProcessError?)->Void) {
//long process
if Int.random(in: 0..<1) == 1 {
//error
completionHandler(nil,.randomError)
} else {
//no error, return result
completionHandler("Here's the info",nil)
}
}

Now, when we call this method, our completion handler will receive either a String, or a LongProcessError, let’s give them labels of response and error:

doLongProcess { (response, error) in
//Deal with the response/error here
}

The two propertiesresponse and error are optional. If the response property is nil, the error will not be nil, and vice versa.

Prior to Swift 5, dealing with these properties could be a little cumbersome. You might have ended up with something like:

doLongProcess { (response, error) in
if let error = error {
print("You have an error!")
return
} else if let response = response {
print("\(response)")
} else {
print("Huh? What am I doing here?")
}
}

The new Result type implemented in Swift 5 provides a much tidier and more elegant way to respond to this common scenario. First we would update our long process to use the Result type: (I’ve highlighted the changes)

func doLongProcess(completionHandler: @escaping 
(Result<String,LongProcessError>)->Void) {
//long process
if Int.random(in: 0..<1) == 1 {
//error
completionHandler(.failure(.randomError))
} else {
//no error, return result
completionHandler(.success("Here's the info"))
}
}

Now we can update our doLongProcess method:

doLongProcess { (result) in
switch result {
case .failure(.randomError):
print("You have a random error!")
case .success(let result):
print("\(result)")
}
}

Much more elegant! A much more readable solution and clearer that we have dealt with all cases.

Raw strings

You might remember that in Swift 4, we had a little improvement to String, allowing multi-line String literals, with triple quotes:

let multiLineString = """
Wow, this is
a multiline string
"""

Well, Swift 5 gives us some more fancy String modifiers, by allowing us to create what are called raw strings. Raw strings ignore escape sequences. One escape sequence you are probably familiar with, for example is \n. While \n is usually interpreted in a String as a new line, in a raw string, this escape sequence is ignored, and \n is left as is.

Raw strings are created with at least one # symbol before and after the quotes surrounding the String, for example:

let text = #"This looks like a newline -> \n but it’s not!"#

If you prefer you can use multiple # symbols, just be sure that they match:

let text = ##"This looks like string interpolation-> \(text2) but it’s not!"##

You can combine a raw string with a multi-line String literal too:

let text = #"""
This looks like a newline \n but it’s not!
"""#

And more!

Phew! That’s a lot to start with, and that’s not all! You can find a full list of changes in Swift 5 in the official Swift blog. You’ll find some interesting stuff there on future enum cases and customizing string interpolation.


Interested in learning to build iOS apps using Swift? Click on the link to check out my video course, iOS Development with Swift in Motion.

Good luck!