Online Preview

Advanced Swift

A deep dive into Swift’s features, from low-level programming to high-level abstractions.

by Chris Eidhof, Ole Begemann, and Airspeed Velocity

See Buying Options

Table of Contents

Introduction

Advanced Swift is quite a bold title for a book, so perhaps we should start with what we mean by it.

When we began writing the first edition of this book, Swift was barely a year old. We did so before the beta of 2.0 was released — albeit tentatively, because we suspected the language would continue to evolve as it entered its second year. Few languages — perhaps no other language — have been adopted so rapidly by so many developers.

But that left people with unanswered questions. How do you write “idiomatic” Swift? Is there a correct way to do certain things? The standard library provided some clues, but even that has changed over time, dropping some conventions and adopting others. Over the past three years, Swift has evolved at a high pace, and it has become clearer what idiomatic Swift is.

To someone coming from another language, Swift can resemble everything you like about your language of choice. Low-level bit twiddling can look very similar to (and can be as performant as) C, but without many of the undefined behavior gotchas. The lightweight trailing closure syntax of map or filter will be familiar to Rubyists. Swift generics are similar to C++ templates, but with type constraints to ensure generic functions are correct at the time of definition rather than at the time of use. The flexibility of higher-order functions and operator overloading means you can write code that’s similar in style to Haskell or F#. And the @objc and dynamic keywords allow you to use selectors and runtime dynamism in ways you would in Objective-C.

Given these resemblances, it’s tempting to adopt the idioms of other languages. Case in point: Objective-C example projects can almost be mechanically ported to Swift. The same is true for Java or C# design patterns. And monad tutorials appeared to be everyone’s favorite blog post topic in the first few months after Swift’s introduction.

But then comes the frustration. Why can’t we use protocol extensions with associated types like interfaces in Java? Why are arrays not covariant in the way we expect? Why can’t we write “functor?” Sometimes the answer is because the part of Swift in question isn’t yet implemented. But more often, it’s either because there’s a different Swift-like way to do what you want to do, or because the Swift feature you thought was like the equivalent in some other language is not quite what you think.

Swift is a complex language — most programming languages are. But it hides that complexity well. You can get up and running developing apps in Swift without needing to know about generics or overloading or the difference between static and dynamic dispatch. You may never need to call into a C library or write your own collection type, but after a while, we think you’ll find it necessary to know about these things — either to improve your code’s performance, to make it more elegant or expressive, or just to get certain things done.

Learning more about these features is what this book is about. We intend to answer many of the “How do I do this?” or “Why does Swift behave like that?” questions we’ve seen come up on various forums. Hopefully, once you’ve read our book, you’ll have gone from being aware of the basics of the language to knowing about many advanced features and having a much better understanding of how Swift works. Being familiar with the material presented is probably necessary, if not sufficient, for calling yourself an advanced Swift programmer.

Who Is This Book For?

This book targets experienced (though not necessarily expert) programmers — such as existing Apple-platform developers, or those coming from other languages such as Java or C++ — who want to bring their knowledge of Swift to the same level as that of Objective-C or some other language. It’s also suitable for new programmers who started on Swift, have grown familiar with the basics, and are looking to take things to the next level.

The book isn’t meant to be an introduction to Swift; it assumes you’re familiar with the syntax and structure of the language. If you want some good, compact coverage of the basics of Swift, the best source is the official Apple Swift book (available on iBooks or on Apple’s website). If you’re already a confident programmer, you could try reading our book and the Apple Swift book in parallel.

This is also not a book about programming for macOS or iOS devices. Of course, since Swift is currently mainly used on Apple platforms, we’ve tried to include examples of practical use, but we hope this book will be useful for non-Apple-platform programmers as well. The vast majority of the examples in the book should run unchanged on other operating systems. The ones that don’t are either fundamentally tied to Apple’s platforms (because they use iOS frameworks or rely on the Objective-C runtime) or only require minimal changes, such as replacing the BSD-specific function we use to generate random numbers with a Linux equivalent.

Themes

We’ve organized the book under the heading of basic concepts. There are in-depth chapters on some fundamental basic concepts like optionals or strings, and some deeper dives into topics like C interoperability. But throughout the book, hopefully a few themes regarding Swift emerge:

Swift is both a high- and low-level language. Swift allows you to write code similarly to Ruby and Python, with map and reduce, and to write your own higher-order functions easily. Swift also allows you to write fast code that compiles directly to native binaries with performance similar to code written in C.

What’s exciting to us, and what’s possibly the aspect of Swift we most admire, is that you’re able to do both these things at the same time. Mapping a closure expression over an array compiles to the same assembly code as looping over a contiguous block of memory does.

However, there are some things you need to know about to make the most of this feature. For example, it will benefit you to have a strong grasp on how structs and classes differ, or an understanding of the difference between dynamic and static method dispatch. We’ll cover topics such as these in more depth later on.

Swift is a multi-paradigm language. You can use it to write object-oriented code or pure functional code using immutable values, or you can write imperative C-like code using pointer arithmetic.

This is both a blessing and a curse. It’s great, in that you have a lot of tools available to you, and you aren’t forced into writing code one way. But it also exposes you to the risk of writing Java or C or Objective-C in Swift.

Swift still has access to most of the capabilities of Objective-C, including message sending, runtime type identification, and key-value observation. But Swift introduces many capabilities not available in Objective-C.

Erik Meijer, a well-known programming language expert, tweeted the following in October 2015:

At this point, @SwiftLang is probably a better, and more valuable, vehicle for learning functional programming than Haskell.

Swift is a good introduction to a more functional style through its use of generics, protocols, value types, and closures. It’s even possible to write operators that compose functions together. The early months of Swift brought many functional programming blog posts into the world. But since the release of Swift 2.0 and the introduction of protocol extensions, this trend has shifted.

Swift is very flexible. In the introduction to the book On Lisp, Paul Graham writes that:

Experienced Lisp programmers divide up their programs differently. As well as top-down design, they follow a principle which could be called bottom-up design– changing the language to suit the problem. In Lisp, you don’t just write your program down toward the language, you also build the language up toward your program. As you’re writing a program you may think “I wish Lisp had such-and-such an operator.” So you go and write it. Afterward you realize that using the new operator would simplify the design of another part of the program, and so on. Language and program evolve together.

Swift is a long way from Lisp. But still, we feel like Swift shares this characteristic of encouraging “bottom-up” programming — of making it easy to write very general reusable building blocks that you then combine into larger features, which you then use to solve your actual problem. Swift is particularly good at making these building blocks feel like primitives — like part of the language. A good demonstration of this is that the many features you might think of as fundamental building blocks, like optionals or basic operators, are actually defined in a library — the Swift standard library — rather than directly in the language. Trailing closures enable you to extend the language with features that feel like they’re built in.

Swift code can be compact and concise while still being clear. Swift lends itself to relatively terse code. There’s an underlying goal here, and it isn’t to save on typing. The idea is to get to the point quicker and to make code readable by dropping a lot of the “ceremonial” boilerplate you often see in other languages that obscure rather than clarify the meaning of the code.

For example, type inference removes the clutter of type declarations that are obvious from the context. Semicolons and parentheses that add little or no value are gone. Generics and protocol extensions encourage you to avoid repeating yourself by packaging common operations into reusable functions. The goal is to write code that’s readable at a glance.

At first, this can be off-putting. If you’ve never used functions like map, filter, and reduce before, they might look harder to read than a simple for loop. But our hope is that this is a short learning curve and that the reward is code that is more “obviously correct” at first glance.

Swift tries to be as safe as is practical, until you tell it not to be. This is unlike languages such as C and C++ (where you can be unsafe easily just by forgetting to do something), or like Haskell or Java (which are sometimes safe whether or not you like it).

Eric Lippert, one of the principal designers of C#, wrote about his 10 regrets of C#, including the lesson that:

sometimes you need to implement features that are only for experts who are building infrastructure; those features should be clearly marked as dangerous—not invitingly similar to features from other languages.

Eric was specifically referring to C#’s finalizers, which are similar to C++ destructors. But unlike destructors, they run at a nondeterministic time (perhaps never) at the behest of the garbage collector (and on the garbage collector’s thread). However, Swift, being reference counted, does execute a class’s deinit deterministically.

Swift embodies this sentiment in other ways. Undefined and unsafe behavior is avoided by default. For example, a variable can’t be used until it’s been initialized, and using out-of-bounds subscripts on an array will trap, as opposed to continuing with possibly garbage values.

There are a number of “unsafe” options available (such as the unsafeBitcast function, or the UnsafeMutablePointer type) for when you really need them. But with great power comes great undefined behavior. You can write the following:

var someArray = [1,2,3]
let uhOh = someArray.withUnsafeBufferPointer { ptr in
    // ptr is only valid within this block, but
    // there is nothing stopping you letting it
    // escape into the wild:
    return ptr
}
// Later...
print(uhOh[10])

It’ll compile, but who knows what it’ll do. However, you can’t say nobody warned you.

Swift is an opinionated language. We as authors have strong opinions about the “right” way to write Swift. You’ll see many of them in this book, sometimes expressed as if they’re facts. But they’re just, like, our opinions, man. Feel free to disagree! Swift is still a young language, and many things aren’t settled. What’s more is that many blog posts are flat-out wrong or outdated (including several ones we wrote, especially in the early days). Whatever you’re reading, the most important thing is to try things out for yourself, check how they behave, and decide how you feel about them. Think critically, and beware of out-of-date information.

Swift continues to evolve. The period of major yearly syntax changes may be behind us, but important areas of the language are still new (strings), in flux (the generics system), or haven’t been tackled yet (concurrency).

Terminology

‘When I use a word,’ Humpty Dumpty said, in rather a scornful tone, ‘it means just what I choose it to mean — neither more nor less.’

Through the Looking Glass, by Lewis Carroll

Programmers throw around terms of art a lot. To avoid confusion, what follows are some definitions of terms we use throughout this book. Where possible, we’re trying to adhere to the same usage as the official documentation, or sometimes a definition that’s been widely adopted by the Swift community. Many of these definitions are covered in more detail in later chapters, so don’t worry if not everything makes sense on first reading. If you’re already familiar with all of these terms, it’s still best to skim through to make sure your accepted meanings don’t differ from ours.

In Swift, we make the distinctions between values, variables, references, and constants.

A value is immutable and forever — it never changes. For example, 1, true, and [1,2,3] are all values. These are examples of literals, but values can also be generated at runtime. The number you get when you square the number five is a value.

When we assign a value to a name using var x = [1,2], we’re creating a variable named x that holds the value [1,2]. By changing x, e.g. by performing x.append(3), we didn’t change the original value. Rather, we replaced the value that x holds with the new value, [1,2,3] — at least logically, if not in the actual implementation (which might actually just tack a new entry on the back of some existing memory). We refer to this as mutating the variable.

We can declare constant variables (constants, for short) with let instead of var. Once a constant has been assigned a value, it can never be assigned a new value.

We also don’t need to give a variable a value immediately. We can declare the variable first (let x: Int) and then later assign a value to it (x = 1). Swift, with its emphasis on safety, will check that all possible code paths lead to a variable being assigned a value before its value can be read. There’s no concept of a variable having an as-yet-undefined value. Of course, if the variable was declared with let, it can only be assigned to once.

Structs and enums are value types. When you assign one struct variable to another, the two variables will then contain the same value. You can think of the contents as being copied, but it’s more accurate to say that one variable was changed to contain the same value as the other.

A reference is a special kind of value: a value that “points to” another value. Because two references can refer to the same value, this introduces the possibility of that value getting mutated by two different parts of the program at once.

Classes are reference types. You can’t hold an instance of a class (which we might occasionally call an object — a term fraught with troublesome overloading!) directly in a variable. Instead, you must hold a reference to it in a variable and access it via that reference.

Reference types have identity — you can check if two variables are referring to the exact same object by using ===. You can also check if they’re equal, assuming == is implemented for the relevant type. Two objects with different identity can still be equal.

Value types don’t have identity. You can’t check if a particular variable holds the “same” number 2 as another. You can only check if they both contain the value 2. === is really asking: “Do both these variables hold the same reference as their value?” In programming language literature, == is sometimes called structural equality, and === is called pointer equality or reference equality.

Class references aren’t the only kind of reference in Swift. For example, there are also pointers, accessed through withUnsafeMutablePointer functions and the like. But classes are the simplest reference type to use, in part because their reference-like nature is partially hidden from you by syntactic sugar. You don’t need to do any explicit “dereferencing” like you do with pointers in some other languages. (We’ll cover the other kind of references in more detail in the chapter on interoperability.)

A variable that holds a reference can be declared with let — that is, the reference is constant. This means that the variable can never be changed to refer to something else. But — and this is important — it doesn’t mean that the object it refers to can’t be changed. So when referring to a variable as a constant, be careful — it’s only constant in what it points to. It doesn’t mean what it points to is constant. (Note: if those last few sentences sound like doublespeak, don’t worry, as we cover this again in the chapter on structs and classes). Unfortunately, this means that when looking at a declaration of a variable with let, you can’t tell at a glance whether or not what’s being declared is completely immutable. Instead, you have to know whether it’s holding a value type or a reference type.

We refer to types as having value semantics to distinguish a value type that performs a deep copy. This copy can occur eagerly (whenever a new variable is introduced) or lazily (whenever a variable gets mutated).

Here we hit another complication. If our struct contains reference types, the reference types won’t automatically get copied upon assigning the struct to a new variable. Instead, the references themselves get copied. This is called a shallow copy.

For example, the Data struct in Foundation is a wrapper around the NSData reference type. However, the authors of the Data struct took extra steps to also perform a deep copy of the NSData object whenever the Data struct is mutated. They do this efficiently using a technique called copy-on-write, which we’ll explain in the chapter on structs and classes. For now, it’s important to know that this behavior doesn’t come for free.

The collections in Swift are also wrapping reference types and use copy-on-write to efficiently provide value semantics. However, if the elements in a collection are references (for example, an array containing objects), the objects won’t get copied. Instead, only the references get copied. This means that a Swift array only has value semantics if its elements have value semantics too.

Some classes are completely immutable — that is, they provide no methods for changing their internal state after they’re created. This means that even though they’re classes, they also have value semantics (because even if they’re shared, they can never change). Be careful though — only final classes can be guaranteed not to be subclassed with added mutable state.

In Swift, functions are also values. You can assign a function to a variable, have an array of functions, and call the function held in a variable. Functions that take other functions as arguments (such as map, which takes a function to transform every element of a sequence) or return functions are referred to as higher-order functions.

Functions don’t have to be declared at the top level — you can declare a function within another function or in a do or other scope. Functions defined within an outer scope, but passed out from it (say, as the returned value of a function), can “capture” local variables, in which case those local variables aren’t destroyed when the local scope ends, and the function can hold state through them. This behavior is called “closing over” variables, and functions that do this are called closures.

Functions can be declared either with the func keyword or by using a shorthand { } syntax called a closure expression. Sometimes this gets shortened to “closures,” but don’t let it give you the impression that only closure expressions can be closures. Functions declared with the func keyword are also closures when they close over external variables.

Functions are held by reference. This means assigning a function that has captured state to another variable doesn’t copy that state; it shares it, similar to object references. What’s more is that when two closures close over the same local variable, they both share that variable, so they share state. This can be quite surprising, and we’ll discuss this more in the chapter on functions.

Functions defined inside a class or protocol are methods, and they have an implicit self parameter. Sometimes we call functions that aren’t methods free functions. This is to distinguish them from methods.

A fully qualified function name in Swift includes not just the function’s base name (the part before the parentheses), but also the argument labels. For example, the full name of the method for moving a collection index by a number of steps is index(_:offsetBy:), indicating that this function takes two arguments (represented by the two colons), the first one of which has no label (represented by the underscore). We often omit the labels in the book if it’s clear from the context what function we’re referring to (the compiler allows you to do the same).

Free functions, and methods called on structs, are statically dispatched. This means the function that’ll be called is known at compile time. It also means the compiler might be able to inline the function, i.e. not call the function at all, but instead replace it with the code the function would execute. The optimizer can also discard or simplify code that it can prove at compile time won’t actually run.

Methods on classes or protocols might be dynamically dispatched. This means the compiler doesn’t necessarily know at compile time which function will run. This dynamic behavior is done either by using vtables (similar to how Java or C++ dynamic dispatch work), or in the case of some @objc classes and protocols, by using selectors and objc_msgSend.

Subtyping and method overriding is one way of getting polymorphic behavior, i.e. behavior that varies depending on the types involved. A second way is function overloading, where a function is written multiple times for different types. (It’s important not to mix up overriding and overloading, as they behave very differently.) A third way is via generics, where a function or method is written once to take any type that provides certain functions or methods, but the implementations of those functions can vary. Unlike method overriding, the results of function overloading and generics are known statically at compile time. We’ll cover this more in the generics chapter.

Swift Style Guide

When writing this book, and when writing Swift code for our own projects, we try to stick to the following rules:

  • For naming, clarity at the point of use is the most important consideration. Since APIs are used many more times than they’re declared, their names should be optimized for how well they work at the call site. Familiarize yourself with the Swift API Design Guidelines and try to adhere to them in your own code.

  • Clarity is often helped by conciseness, but brevity should never be a goal in and of itself.

  • Always add documentation comments to functions — especially generic ones.

  • Types start with UpperCaseLetters. Functions, variables, and enum cases start with lowerCaseLetters.

  • Use type inference. Explicit but obvious types get in the way of readability.

  • Don’t use type inference in cases of ambiguity or when defining contracts (which is why, for example, funcs have an explicit return type).

  • Default to structs unless you actually need a class-only feature or reference semantics.

  • Mark classes as final unless you’ve explicitly designed them to be inheritable. If you want to use inheritance internally but not allow subclassing for external clients, mark a class public but not open.

  • Use the trailing closure syntax, except when that closure is immediately followed by another opening brace.

  • Use guard to exit functions early.

  • Eschew force-unwraps and implicitly unwrapped optionals. They’re occasionally useful, but needing them constantly is usually a sign something is wrong.

  • Don’t repeat yourself. If you find you’ve written a very similar piece of code more than a couple of times, extract it into a function. Consider making that function a protocol extension.

  • Favor map and filter. But don’t force it: use a for loop when it makes sense. The purpose of higher-order functions is to make code more readable. An obfuscated use of reduce when a simple for loop would be clearer defeats this purpose.

  • Favor immutable variables: default to let unless you know you need mutation. But use mutation when it makes the code clearer or more efficient. Again, don’t force it: a mutating method on a struct is often more idiomatic and efficient than returning a brand new struct.

  • Swift generics tend to lead to very long function signatures. Unfortunately, we have yet to settle on a good way of breaking up long function declarations into multiple lines. We’ll try to be consistent in how we do this in sample code.

  • Leave off self. when you don’t need it. In closure expressions, it’s a clear signal that self is being captured by the closure.

  • Write extensions on existing types and protocols, instead of free functions, whenever you can. This helps readability and discoverability.

One final note about our code samples throughout the book: to save space and focus on the essentials, we usually omit import statements that would be required to make the code compile. If you try out the code yourself and the compiler tells you it doesn’t recognize a particular symbol, try adding an import Foundation or import UIKit statement.

Built-In Collections

Collections of elements are among the most important data types in any programming language. Good language support for different kinds of containers has a big impact on programmer productivity and happiness. Swift places special emphasis on sequences and collections — so much of the standard library is dedicated to this topic that we sometimes have the feeling it deals with little else. The resulting model is way more extensible than what you may be used to from other languages, but it’s also quite complex.

In this chapter, we’re going to take a look at the major collection types Swift ships with, with a focus on how to work with them effectively and idiomatically. In the next chapter, we’ll climb up the abstraction ladder and see how the collection protocols in the standard library work.

Arrays

Arrays and Mutability

Arrays are the most common collections in Swift. An array is an ordered container of elements that all have the same type, with random access to each element. As an example, to create an array of numbers, we can write the following:

// The Fibonacci numbers
let fibs = [0, 1, 1, 2, 3, 5]

If we try to modify the array defined above (by using append(_:), for example), we get a compile error. This is because the array is defined as a constant, using let. In many cases, this is exactly the right thing to do; it prevents us from accidentally changing the array. If we want the array to be a variable, we have to define it using var:

var mutableFibs = [0, 1, 1, 2, 3, 5]

Now we can easily append a single element or a sequence of elements:

mutableFibs.append(8)
mutableFibs.append(contentsOf: [13, 21])
mutableFibs // [0, 1, 1, 2, 3, 5, 8, 13, 21]

There are a couple of benefits that come with making the distinction between var and let. Constants defined with let are easier to reason about because they’re immutable. When you read a declaration like let fibs = ..., you know that the value of fibs will never change — it’s enforced by the compiler. This helps greatly when reading through code. However, note that this is only true for types that have value semantics. A let variable to a class instance (i.e. a reference type) guarantees that the reference will never change, i.e. you can’t assign another object to that variable. However, the object the reference points to can change. We’ll go into more detail on these differences in the chapter on structs and classes.

Arrays, like all collection types in the standard library, have value semantics. When you assign an existing array to another variable, the array contents are copied over. For example, in the following code snippet, x is never modified:

var x = [1,2,3]
var y = x
y.append(4)
y // [1, 2, 3, 4]
x // [1, 2, 3]

The statement var y = x makes a copy of x, so appending 4 to y won’t change x — the value of x will still be [1, 2, 3]. The same thing happens when you pass an array into a function; the function gets a local copy, and any changes it makes don’t affect the caller.

Contrast this with the approach to mutability taken by NSArray in Foundation. NSArray has no mutating methods — to mutate an array, you need an NSMutableArray. But just because you have a non-mutating NSArray reference does not mean the array can’t be mutated underneath you:

let a = NSMutableArray(array: [1,2,3])
// I don't want to be able to mutate b
let b: NSArray = a
// But it can still be mutated — via a
a.insert(4, at: 3)
b // ( 1, 2, 3, 4 )

The correct way to write this is to manually create a copy upon assignment:

let c = NSMutableArray(array: [1,2,3])
// I don't want to be able to mutate d
let d = c.copy() as! NSArray
c.insert(4, at: 3)
d // ( 1, 2, 3 )

In the example above, it’s very clear that we need to make a copy — a is mutable, after all. However, when passing around arrays between methods and functions, this isn’t always so easy to see.

In Swift, there’s just one array type, and mutability is controlled by declaring with var instead of let. But there’s no reference sharing — when you declare a second array with let, you’re guaranteed it’ll never change.

Making so many copies could be a performance problem, but in practice, all collection types in the Swift standard library are implemented using a technique called copy-on-write, which makes sure the data is only copied when necessary. So in our example, x and y shared internal storage up the point where y.append was called. In the chapter on structs and classes, we’ll take a deeper look at value semantics, including how to implement copy-on-write for your own types.

Arrays and Optionals

Swift arrays provide all the usual operations you’d expect, like isEmpty and count. Arrays also allow for direct access of elements at a specific index through subscripting, like fibs[3]. Keep in mind that you need to make sure the index is within bounds before getting an element via subscript. Fetch the element at index 3, and you’d better be sure the array has at least four elements in it. Otherwise, your program will trap, i.e. abort with a fatal error.

The reason for this is mainly driven by how array indices are used. It’s pretty rare in Swift to actually need to calculate an index:

  • Want to iterate over the array?
    for x in array

  • Want to iterate over all but the first element of an array?
    for x in array.dropFirst()

  • Want to iterate over all but the last 5 elements?
    for x in array.dropLast(5)

  • Want to number all the elements in an array?
    for (num, element) in collection.enumerated()

  • Want to find the location of a specific element?
    if let idx = array.index { someMatchingLogic($0) }

  • Want to transform all the elements in an array?
    array.map { someTransformation($0) }

  • Want to fetch only the elements matching a specific criterion?
    array.filter { someCriteria($0) }

Another sign that Swift wants to discourage you from doing index math is the removal of traditional C-style for loops from the language in Swift 3. Manually fiddling with indices is a rich seam of bugs to mine, so it’s often best avoided. And if it can’t be, well, we’ll see in the generics chapter that it’s easy enough to write a new reusable general function that does what you need and in which you can wrap your carefully tested index calculations.

But sometimes you do have to use an index. And with array indices, the expectation is that when you do, you’ll have thought very carefully about the logic behind the index calculation. So to have to unwrap the value of a subscript operation is probably overkill — it means you don’t trust your code. But chances are you do trust your code, so you’ll probably resort to force-unwrapping the result, because you know that the index must be valid. This is (a) annoying, and (b) a bad habit to get into. When force-unwrapping becomes routine, eventually you’re going to slip up and force-unwrap something you don’t mean to. So to avoid this habit becoming routine, arrays don’t give you the option.

While a subscripting operation that responds to a invalid index with a controlled crash could arguably be called unsafe, that’s only one aspect of safety. Subscripting is totally safe in regard to memory safety — the standard library collections always perform bounds checks to prevent unauthorized memory access with an out-of-bounds index.

Other operations behave differently. The first and last properties have an optional type; they return nil if the array is empty. first is equivalent to isEmpty ? nil : self[0]. Similarly, the removeLast method will trap if you call it on an empty array, whereas popLast will only delete and return the last element if the array isn’t empty, and will otherwise do nothing and return nil. Which one you’d want to use depends on your use case. When you’re using the array as a stack, you’ll probably always want to combine checking for empty and removing the last entry. On the other hand, if you already know through invariants whether or not the array is empty, dealing with the optional is fiddly.

We’ll encounter these tradeoffs again later in this chapter when we talk about dictionaries. Additionally, there’s an entire chapter dedicated to optionals.

Transforming Arrays

Map

It’s common to need to perform a transformation on every value in an array. Every programmer has written similar code hundreds of times: create a new array, loop over all elements in an existing array, perform an operation on an element, and append the result of that operation to the new array. For example, the following code squares an array of integers:

var squared: [Int] = []
for fib in fibs {
    squared.append(fib * fib)
}
squared // [0, 1, 1, 4, 9, 25]

Swift arrays have a map method, adopted from the world of functional programming. Here’s the exact same operation, using map:

let squares = fibs.map { fib in fib * fib }
squares // [0, 1, 1, 4, 9, 25]

This version has three main advantages. It’s shorter, of course. There’s also less room for error. But more importantly, it’s clearer. All the clutter has been removed. Once you’re used to seeing and using map everywhere, it acts as a signal — you see map, and you know immediately what’s happening: a function is going to be applied to every element, returning a new array of the transformed elements.

The declaration of squared no longer needs to be made with var, because we aren’t mutating it any longer — it’ll be delivered out of the map fully formed, so we can declare squares with let, if appropriate. And because the type of the contents can be inferred from the function passed to map, squares no longer needs to be explicitly typed.

map isn’t hard to write — it’s just a question of wrapping up the boilerplate parts of the for loop into a generic function. Here’s one possible implementation (though in Swift, it’s actually an extension of Sequence, something we’ll cover in the chapter on writing generic algorithms):

extension Array {
    func map<T>(_ transform: (Element) -> T) -> [T] {
        var result: [T] = []
        result.reserveCapacity(count)
        for x in self {
            result.append(transform(x))
        }
        return result
    }
}

Element is the generic placeholder for whatever type the array contains. And T is a new placeholder that can represent the result of the element transformation. The map function itself doesn’t care what Element and T are; they can be anything at all. The concrete type T of the transformed elements is defined by the return type of the transform function the caller passes to map.

Really, the signature of this method should be func map<T>(_ transform: (Element) throws -> T) rethrows -> [T], indicating that map will forward any error the transformation function might throw to the caller. We’ll cover this in detail in the errors chapter. We’ve left the error handling annotations out here for simplicity. If you’d like, you can check out the source code for Sequence.map in the Swift repository on GitHub.

Parameterizing Behavior with Functions

Even if you’re already familiar with map, take a moment and consider the map code. What makes it so general yet so useful?

map manages to separate out the boilerplate — which doesn’t vary from call to call — from the functionality that always varies: the logic of how exactly to transform each element. It does this through a parameter the caller supplies: the transformation function.

This pattern of parameterizing behavior is found throughout the standard library. There are more than a dozen separate functions that take a function that allows the caller to customize the key step:

  • map and flatMap — how to transform an element

  • filter — should an element be included?

  • reduce — how to fold an element into an aggregate value

  • sequence — what should the next element of the sequence be?

  • forEach — what side effect to perform with an element

  • sort, lexicographicallyPrecedes, and partition — in what order should two elements come?

  • index, first, and contains — does this element match?

  • min and max — which is the min/max of two elements?

  • elementsEqual and starts — are two elements equivalent?

  • split — is this element a separator?

  • prefix — filter elements while a predicate returns true, then drop the rest (similar to filter, but with an early exit, and useful for infinite or lazily computed sequences)

  • drop — drop elements until the predicate ceases to be true, and then return the rest (similar to prefix, but this returns the inverse)

The goal of all these functions is to get rid of the clutter of the uninteresting parts of the code, such as the creation of a new array, the for loop over the source data, and the like. Instead, the clutter is replaced with a single word that describes what’s being done. This brings the important code – the logic the programmer wants to express – to the forefront.

Several of these functions have a default behavior. sort sorts elements in ascending order when they’re comparable, unless you specify otherwise. contains can take a value to check for, so long as the elements are equatable. These defaults help make the code even more readable. Ascending order sort is natural, so the meaning of array.sort() is intuitive. array.index(of: "foo") is clearer than array.index { $0 == "foo" }.

But in every instance, these are just shorthand for the common cases. Elements don’t have to be comparable or equatable, and you don’t have to compare the whole element — you can sort an array of people by their ages (people.sort { $0.age < $1.age }) or check if the array contains anyone underage (people.contains { $0.age < 18 }). You can also compare some transformation of the element. For example, an admittedly inefficient case-insensitive sort could be performed via people.sort { $0.name.uppercased() < $1.name.uppercased() }.

There are other functions of similar usefulness that would also take a function to specify their behaviors but aren’t in the standard library. You could easily define them yourself (and might like to try):

  • accumulate — combine elements into an array of running values (like reduce, but returning an array of each interim combination)

  • all(matching:) and none(matching:) — test if all or no elements in a sequence match a criterion (can be built with contains, with some carefully placed negation)

  • count(where:) — count the number of elements that match (similar to filter, but without constructing an array)

  • indices(where:) — return a list of indices matching a criteria (similar to index(where:), but doesn’t stop on the first one)

Some of these we define elsewhere in the book.

You might find yourself writing something that fits a pattern more than a couple of times — something like this, where you search an array in reverse order for the first element that matches a certain condition:

let names = ["Paula", "Elena", "Zoe"]

var lastNameEndingInA: String?
for name in names.reversed() where name.hasSuffix("a") {
    lastNameEndingInA = name
    break
}
lastNameEndingInA // Optional("Elena")

If that’s the case, consider writing a short extension to Sequence. The method last(where:) wraps this logic — we use a function argument to abstract over the part of our for loop that varies:

extension Sequence {
    func last(where predicate: (Element) -> Bool) -> Element? {
        for element in reversed() where predicate(element) {
            return element
        }
        return nil
    }
}

This then allows you to replace your for loop with the following:

let match = names.last { $0.hasSuffix("a") }
match // Optional("Elena")

This has all the same benefits we described for map. The example with last(where:) is more readable than the example with the for loop; even though the for loop is simple, you still have to run the loop through in your head, which is a small mental tax. Using last(where:) introduces less chance of error (such as accidentally forgetting to reverse the array), and it allows you to declare the result variable with let instead of var.

It also works nicely with guard — in all likelihood, you’re going to terminate a flow early if the element isn’t found:

guard let match = someSequence.last(where: { $0.passesTest() })
    else { return }
// Do something with match

We’ll say more about extending collections and using functions later in the book.

Mutation and Stateful Closures

When iterating over an array, you could use map to perform side effects (e.g. inserting the elements into some lookup table). We don’t recommend doing this. Take a look at the following:

array.map { item in
    table.insert(item)
}

This hides the side effect (the mutation of the lookup table) in a construct that looks like a transformation of the array. If you ever see something like the above, then it’s a clear case for using a plain for loop instead of a function like map. The forEach method would also be more appropriate than map in this case, but it has its own issues. We’ll look at forEach in a bit.

Performing side effects is different than deliberately giving the closure local state, which is a particularly useful technique, and it’s what makes closures — functions that can capture and mutate variables outside their scope — so powerful a tool when combined with higher-order functions. For example, the accumulate function described above could be implemented with map and a stateful closure, like this:

extension Array {
    func accumulate<Result>(_ initialResult: Result, 
        _ nextPartialResult: (Result, Element) -> Result) -> [Result] 
    {
        var running = initialResult
        return map { next in
            running = nextPartialResult(running, next)
            return running
        }
    }
}

This creates a temporary variable to store the running value and then uses map to create an array of the running values as the computation progresses:

[1,2,3,4].accumulate(0, +) // [1, 3, 6, 10]

Note that this code assumes that map performs its transformation in order over the sequence. In the case of our map above, it does. But there are possible implementations that could transform the sequence out of order — for example, one that performs the transformation of the elements concurrently. The official standard library version of map doesn’t specify whether or not it transforms the sequence in order, though it seems like a safe bet.

Filter

Another very common operation is to take an array and create a new array that only includes those elements that match a certain condition. The pattern of looping over an array and selecting the elements that match the given predicate is captured in the filter method:

let nums = [1,2,3,4,5,6,7,8,9,10]
nums.filter { num in num % 2 == 0 } // [2, 4, 6, 8, 10]

We can use Swift’s shorthand notation for arguments of a closure expression to make this even shorter. Instead of naming the num argument, we can write the above code like this:

nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]

For very short closures, this can be more readable. If the closure is more complicated, it’s almost always a better idea to name the arguments explicitly, as we’ve done before. It’s really a matter of personal taste — choose whichever is more readable at a glance. A good rule of thumb is this: if the closure fits neatly on one line, shorthand argument names are a good fit.

By combining map and filter, we can easily write a lot of operations on arrays without having to introduce a single intermediate variable, and the resulting code will become shorter and easier to read. For example, to find all squares under 100 that are even, we could map the range 0..<10, in order to square it, and then filter out all odd numbers:

(1..<10).map { $0 * $0 }.filter { $0 % 2 == 0 } // [4, 16, 36, 64]

The implementation of filter looks much the same as map:

extension Array {
    func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for x in self where isIncluded(x) {
            result.append(x)
        }
        return result
    }
}

For more on the where clause we use in the for loop, see the optionals chapter.

One quick performance tip: if you ever find yourself writing something like the following, stop!

bigArray.filter { someCondition }.count > 0

filter creates a brand new array and processes every element in the array. But this is unnecessary. This code only needs to check if one element matches — in which case, contains(where:) will do the job:

bigArray.contains { someCondition }

This is much faster for two reasons: it doesn’t create a whole new array of the filtered elements just to count them, and it exits early, as soon as it finds the first match. Generally, only ever use filter if you want all the results.

Often you want to do something that can be done with contains, but it looks pretty ugly. For example, you can check if every element of a sequence matches a predicate using !sequence.contains { !condition }, but it’s much more readable to wrap this in a new function that has a more descriptive name:

extension Sequence {
    public func all(matching predicate: (Element) -> Bool) -> Bool {
        // Every element matches a predicate if no element doesn't match it:
        return !contains { !predicate($0) }
    }
}

let evenNums = nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]
evenNums.all { $0 % 2 == 0 } // true

Reduce

Both map and filter take an array and produce a new, modified array. Sometimes, however, you want to combine all elements into a new value. For example, to sum up all the elements, we could write the following code:

let fibs = [0, 1, 1, 2, 3, 5]
var total = 0
for num in fibs {
    total = total + num
}
total // 12

The reduce method takes this pattern and abstracts two parts: the initial value (in this case, zero), and the function for combining the intermediate value (total) and the element (num). Using reduce, we can write the same example like this:

let sum = fibs.reduce(0) { total, num in total + num } // 12

Operators are functions too, so we could’ve also written the same example like this:

fibs.reduce(0, +) // 12

The output type of reduce doesn’t have to be the same as the element type. For example, if we want to convert a list of integers into a string, with each number followed by a space, we can do the following:

fibs.reduce("") { str, num in str + "\(num), " } // 0, 1, 1, 2, 3, 5, 

Here’s the implementation for reduce:

extension Array {
    func reduce<Result>(_ initialResult: Result, 
        _ nextPartialResult: (Result, Element) -> Result) -> Result 
    {
        var result = initialResult
        for x in self {
            result = nextPartialResult(result, x)
        }
        return result
    }
}

Another performance tip: reduce is very flexible, and it’s common to see it used to build arrays and perform other operations. For example, you can implement map and filter using only reduce:

extension Array {
    func map2<T>(_ transform: (Element) -> T) -> [T] {
        return reduce([]) {
            $0 + [transform($1)]
        }
    }

    func filter2(_ isIncluded: (Element) -> Bool) -> [Element] {
        return reduce([]) {
            isIncluded($1) ? $0 + [$1] : $0
        }
    }
}

This is kind of beautiful and has the benefit of not needing those icky imperative for loops. But Swift isn’t Haskell, and Swift arrays aren’t lists. What’s happening here is that every time, through the combine function, a brand new array is being created by appending the transformed or included element to the previous one. This means that both these implementations are O(n2), not O(n) — as the length of the array increases, the time these functions take increases quadratically.

There’s a second version of reduce, which has a different type. Specifically, the function for combining an intermediate result and an element now takes Result as an inout parameter:

public func reduce<Result>(into initialResult: Result,
    _ updateAccumulatingResult:
        (_ partialResult: inout Result, Element) throws -> ()
  ) rethrows -> Result

We discuss inout parameters in detail in the chapter on structs and classes, but for now, think of the inout Result parameter as a mutable parameter: we can modify it within the function. This allows us to write filter in a much more efficient way:

extension Array {
    func filter3(_ isIncluded: (Element) -> Bool) -> [Element] {
        return reduce(into: []) { result, element in
            if isIncluded(element) {
                result.append(element)
            }
        }
    }
}

When using inout, the compiler doesn’t have to create a new array each time, so this version of filter is again O(n). When the call to reduce(into:_:) is inlined by the compiler, the generated code is often the same as when using a for loop.

A Flattening Map

Sometimes, you want to map an array where the transformation function returns another array and not a single element.

For example, let’s say we have a function, links, which reads a Markdown file and returns an array containing the URLs of all the links in the file. The function type looks like this:

func extractLinks(markdownFile: String) -> [URL]

If we have a bunch of Markdown files and want to extract the links from all files into a single array, we could try to write something like markdownFiles.map(extractLinks). But this returns an array of arrays containing the URLs: one array per file. Now you could just perform the map, get back an array of arrays, and then call joined to flatten the results into a single array:

let markdownFiles: [String] = // ...
let nestedLinks = markdownFiles.map(extractLinks)
let links = nestedLinks.joined()

The flatMap method combines these two operations into a single step. So markdownFiles.flatMap(extractLinks) returns all the URLs in an array of Markdown files as a single array.

The signature for flatMap is almost identical to map, except its transformation function returns an array. The implementation uses append(contentsOf:) instead of append(_:) to flatten the result array:

extension Array {
    func flatMap<T>(_ transform: (Element) -> [T]) -> [T] {
        var result: [T] = []
        for x in self {
            result.append(contentsOf: transform(x))
        }
        return result
    }
}

Another great use case for flatMap is combining elements from different arrays. To get all possible pairs of two arrays, flatMap over one array and then map over the other:

let suits = ["♠︎", "♥︎", "♣︎", "♦︎"]
let ranks = ["J","Q","K","A"]
let result = suits.flatMap { suit in
    ranks.map { rank in
        (suit, rank)
    }
}
/*
[("♠︎", "J"), ("♠︎", "Q"), ("♠︎", "K"), ("♠︎", "A"), ("♥︎", "J"), ("♥︎",
 "Q"), ("♥︎", "K"), ("♥︎", "A"), ("♣︎", "J"), ("♣︎", "Q"), ("♣︎", "K"),
 ("♣︎", "A"), ("♦︎", "J"), ("♦︎", "Q"), ("♦︎", "K"), ("♦︎", "A")]
*/

Iteration using forEach

The final operation we’d like to discuss is forEach. It works almost like a for loop: the passed-in function is executed once for each element in the sequence. And unlike map, forEach doesn’t return anything. Let’s start by mechanically replacing a loop with forEach:

for element in [1,2,3] {
    print(element)
}

[1,2,3].forEach { element in
    print(element)
}

This isn’t a big win, but it can be handy if the action you want to perform is a single function call on each element in a collection. Passing a function name to forEach instead of a closure expression can lead to clear and concise code. For example, if you’re inside a view controller and want to add an array of subviews to the main view, you can just do theViews.forEach(view.addSubview).

However, there are some subtle differences between for loops and forEach. For instance, if a for loop has a return statement in it, rewriting it with forEach can significantly change the code’s behavior. Consider the following example, which is written using a for loop with a where condition:

extension Array where Element: Equatable {
    func index(of element: Element) -> Int? {
        for idx in self.indices where self[idx] == element {
            return idx
        }
        return nil
    }
}

We can’t directly replicate the where clause in the forEach construct, so we might (incorrectly) rewrite this using filter:

extension Array where Element: Equatable {
    func index_foreach(of element: Element) -> Int? {
        self.indices.filter { idx in
            self[idx] == element
        }.forEach { idx in
            return idx
        }
        return nil
    }
}

The return inside the forEach closure doesn’t return out of the outer function; it only returns from the closure itself. In this particular case, we’d probably have found the bug because the compiler generates a warning that the argument to the return statement is unused, but you shouldn’t rely on it finding every such issue.

Also, consider the following simple example:

(1..<10).forEach { number in
    print(number)
    if number > 2 { return }
}

It’s not immediately obvious that this prints out all the numbers in the input range. The return statement isn’t breaking the loop, rather it’s returning from the closure.

In some situations, such as the addSubview example above, forEach can be nicer than a for loop. However, because of the non-obvious behavior with return, we recommend against most other uses of forEach. Just use a regular for loop instead.

Array Types

Slices

In addition to accessing a single element of an array by subscript (e.g. fibs[0]), we can also access a range of elements by subscript. For example, to get all but the first element of an array, we can do the following:

let slice = fibs[1...]
slice // [1, 1, 2, 3, 5]
type(of: slice) // ArraySlice<Int>

This gets us a slice of the array starting at the second element. The type of the result is ArraySlice, not Array. ArraySlice is a view on arrays. It’s backed by the original array, yet it provides a view on just the slice. This makes certain the array doesn’t need to get copied. The ArraySlice type has the same methods defined as Array does, so you can use a slice as if it were an array. If you do need to convert a slice into an array, you can just construct a new array out of the slice:

let newArray = Array(slice)
type(of: newArray) // Array<Int>
Array Slices

Array Slices

Bridging

Swift arrays can bridge to Objective-C. They can also be used with C, but we’ll cover that in a later chapter. Because NSArray can only hold objects, the compiler and runtime will automatically wrap incompatible values (for example, Swift enums) in an opaque box object. A number of value types (such as Int, Bool, and String, but also Dictionary and Set) are bridged automatically to their Objective-C counterparts.

A universal bridging mechanism for all Swift types to Objective-C doesn’t just make working with arrays more pleasant. It also applies to other collections, like dictionaries and sets, and it opens up a lot of potential for future enhancements to the interoperability between Swift and Objective-C. For example, a future Swift version might allow Swift value types to conform to @objc protocols.

Dictionaries

Another key data structure is Dictionary. A dictionary contains keys with corresponding values; duplicate keys aren’t supported. Retrieving a value by its key takes constant time on average, whereas searching an array for a particular element grows linearly with the array’s size. Unlike arrays, dictionaries aren’t ordered. The order in which pairs are enumerated in a for loop is undefined.

In the following example, we use a dictionary as the model data for a fictional settings screen in a smartphone app. The screen consists of a list of settings, and each individual setting has a name (the keys in our dictionary) and a value. A value can be one of several data types, such as text, numbers, or booleans. We use an enum with associated values to model this:

enum Setting {
    case text(String)
    case int(Int)
    case bool(Bool)
}

let defaultSettings: [String:Setting] = [
    "Airplane Mode": .bool(false),
    "Name": .text("My iPhone"),
]
defaultSettings["Name"] // Optional(Setting.text("My iPhone"))

We use subscripting to get the value of a setting. Dictionary lookup always returns an optional value — when the specified key doesn’t exist, it returns nil. Contrast this with arrays, which respond to an out-of-bounds access by crashing the program.

The rationale for this difference is that array indices and dictionary keys are used very differently. We’ve already seen that it’s quite rare that you actually need to work with array indices directly. And if you do, an array index is usually directly derived from the array in some way (e.g. from a range like 0..<array.count); thus, using an invalid index is a programmer error. On the other hand, it’s very common for dictionary keys to come from some source other than the dictionary itself.

Unlike arrays, dictionaries are also sparse. The existence of the value under the key "name" doesn’t tell you anything about whether or not the key “address” also exists.

Mutation

Just like with arrays, dictionaries defined using let are immutable: no entries can be added, removed, or changed. And just like with arrays, we can define a mutable variant using var. To remove a value from a dictionary, we can either set it to nil using subscripting or call removeValue(forKey:). The latter additionally returns the deleted value, or nil if the key didn’t exist. If we want to take an immutable dictionary and make changes to it, we have to make a copy:

var userSettings = defaultSettings
userSettings["Name"] = .text("Jared's iPhone")
userSettings["Do Not Disturb"] = .bool(true)

Note that, again, the value of defaultSettings didn’t change. As with key removal, an alternative to updating via subscript is the updateValue(_:forKey:) method, which returns the previous value (if any):

let oldName = userSettings
    .updateValue(.text("Jane's iPhone"), forKey: "Name")
userSettings["Name"] // Optional(Setting.text("Jane\'s iPhone"))
oldName // Optional(Setting.text("Jared\'s iPhone"))

Some Useful Dictionary Methods

What if we wanted to combine the default settings dictionary with any custom settings the user has changed? Custom settings should override defaults, but the resulting dictionary should still include default values for any keys that haven’t been customized. Essentially, we want to merge two dictionaries, where the dictionary that’s being merged in overwrites duplicate keys.

Dictionary has a merge(_:uniquingKeysWith:) method, which takes the key-value pairs to be merged in and a function that specifies how to combine two values with the same key. We can use this to merge one dictionary into another, as shown in the following example:

var settings = defaultSettings
let overriddenSettings: [String:Setting] = ["Name": .text("Jane's iPhone")]
settings.merge(overriddenSettings, uniquingKeysWith: { $1 })
settings
// ["Name": Setting.text("Jane\'s iPhone"), "Airplane Mode": Setting.bool(false)]

In the example above, we used { $1 } as the policy for combining two values. In other words, in case a key exists in both settings and overriddenSettings, we use the value from overriddenSettings.

We can also construct a new dictionary out of a sequence of (Key,Value) pairs. If we guarantee that the keys are unique, we can use Dictionary(uniqueKeysWithValues:). However, in case we have a sequence where a key can exist multiple times, we need to provide a function to combine two values for the same keys, just like above. For example, to compute how often elements appear in a sequence, we can map over each element, combine it with a 1, and then create a dictionary out of the resulting element-frequency pairs. If we encounter two values for the same key (in other words, if we saw the same element more than once), we simply add the frequencies up using +:

extension Sequence where Element: Hashable {
    var frequencies: [Element:Int] {
        let frequencyPairs = self.map { ($0, 1) }
        return Dictionary(frequencyPairs, uniquingKeysWith: +)
    }
}
let frequencies = "hello".frequencies // ["e": 1, "o": 1, "l": 2, "h": 1]
frequencies.filter { $0.value > 1 } // ["l": 2]

Another useful method is a map over the dictionary’s values. Because Dictionary is a Sequence, it already has a map method that produces an array. However, sometimes we want to keep the dictionary structure intact and only transform its values. The mapValues method does just this:

let settingsAsStrings = settings.mapValues { setting -> String in
    switch setting {
    case .text(let text): return text
    case .int(let number): return String(number)
    case .bool(let value): return String(value)
    }
}
settingsAsStrings // ["Name": "Jane\'s iPhone", "Airplane Mode": "false"]

Hashable Requirement

Dictionaries are hash tables. The dictionary assigns each key a position in its underlying storage array based on the key’s hashValue. This is why Dictionary requires its Key type to conform to the Hashable protocol. All the basic data types in the standard library already do, including strings, integers, floating-point, and Boolean values. Enumerations without associated values also get automatic Hashable conformance for free.

If you want to use your own custom types as dictionary keys, you must add Hashable conformance manually. This requires an implementation of the hashValue property and, because Hashable extends Equatable, an overload of the == operator function for your type. Your implementation must hold an important invariant: two instances that are equal (as defined by your == implementation) must have the same hash value. The reverse isn’t true: two instances with the same hash value don’t necessarily compare equally. This makes sense, considering that there’s only a finite number of distinct hash values, while many hashable types (like strings) have essentially infinite cardinality.

The potential for duplicate hash values means that Dictionary must be able to handle collisions. Nevertheless, a good hash function should strive for a minimal number of collisions in order to preserve the collection’s performance characteristics, i.e. the hash function should produce a uniform distribution over the full integer range. In the extreme case where your implementation returns the same hash value (e.g. zero) for every instance, a dictionary’s lookup performance degrades to O(n).

The second characteristic of a good hash function is that it’s fast. Keep in mind that the hash value is computed every time a key is inserted, removed, or looked up. If your hashValue implementation takes too much time, it might eat up any gains you got from the O(1) complexity.

Writing a good hash function that meets these requirements isn’t easy. Fortunately, soon we won’t have to do this ourselves in most situations, as there’s an accepted proposal for synthesizing Equatable and Hashable conformance. Once this feature is fully implemented and merged, we can instruct the compiler to automatically generate both Equatable and Hashable conformance for our custom types.

Finally, be extra careful when you use types that don’t have value semantics (e.g. mutable objects) as dictionary keys. If you mutate an object after using it as a dictionary key in a way that changes its hash value and/or equality, you’ll not be able to find it again in the dictionary. The dictionary now stores the object in the wrong slot, effectively corrupting its internal storage. This isn’t a problem with value types because the key in the dictionary doesn’t share your copy’s storage and therefore can’t be mutated from the outside.

Sets

The third major collection type in the standard library is Set. A set is an unordered collection of elements, with each element appearing only once. You can essentially think of a set as a dictionary that only stores keys and no values. Like Dictionary, Set is implemented with a hash table and has similar performance characteristics and requirements. Testing a value for membership in the set is a constant-time operation, and set elements must be Hashable, just like dictionary keys.

Use a set instead of an array when you need to test efficiently for membership (an O(n) operation for arrays) and the order of the elements is not important, or when you need to ensure that a collection contains no duplicates.

Set conforms to the ExpressibleByArrayLiteral protocol, which means that we can initialize it with an array literal like this:

let naturals: Set = [1, 2, 3, 2]
naturals // [2, 3, 1]
naturals.contains(3) // true
naturals.contains(0) // false

Note that the number 2 appears only once in the set; the duplicate never even gets inserted.

Like all collections, sets support the common operations we’ve already seen: you can iterate over the elements in a for loop, map or filter them, and do all other sorts of things.

Set Algebra

As the name implies, Set is closely related to the mathematical concept of a set; it supports all common set operations you learned in math class. For example, we can subtract one set from another:

let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", 
    "iPod shuffle", "iPod Classic"]
let discontinuedIPods: Set = ["iPod mini", "iPod Classic", 
    "iPod nano", "iPod shuffle"]
let currentIPods = iPods.subtracting(discontinuedIPods) // ["iPod touch"]

We can also form the intersection of two sets, i.e. find all elements that are in both:

let touchscreen: Set = ["iPhone", "iPad", "iPod touch", "iPod nano"]
let iPodsWithTouch = iPods.intersection(touchscreen)
// ["iPod touch", "iPod nano"]

Or, we can form the union of two sets, i.e. combine them into one (removing duplicates, of course):

var discontinued: Set = ["iBook", "Powerbook", "Power Mac"]
discontinued.formUnion(discontinuedIPods)
discontinued
/*
["iBook", "Powerbook", "Power Mac", "iPod Classic", "iPod mini",
 "iPod shuffle", "iPod nano"]
*/

Here, we used the mutating variant formUnion to mutate the original set (which, as a result, must be declared with var). Almost all set operations have both non-mutating and mutating forms, the latter beginning with form.... For even more set operations, check out the SetAlgebra protocol.

Index Sets and Character Sets

Set and OptionSet are the only types in the standard library that conform to SetAlgebra, but the protocol is also adopted by two interesting types in Foundation: IndexSet and CharacterSet. Both of these date back to a time long before Swift was a thing. The way these and other Objective-C classes are now bridged into Swift as fully featured value types — adopting common standard library protocols in the process — is great because they’ll instantly feel familiar to Swift developers.

IndexSet represents a set of positive integer values. You can, of course, do this with a Set<Int>, but IndexSet is more storage efficient because it uses a list of ranges internally. Say you have a table view with 1,000 elements and you want to use a set to manage the indices of the rows the user has selected. A Set<Int> needs to store up to 1,000 elements, depending on how many rows are selected. An IndexSet, on the other hand, stores continuous ranges, so a selection of the first 500 rows in the table only takes two integers to store (the selection’s lower and upper bounds).

However, as a user of an IndexSet, you don’t have to worry about the internal structure, as it’s completely hidden behind the familiar SetAlgebra and Collection interfaces. (Unless you want to work on the ranges directly, that is. IndexSet exposes a view to them via its rangeView property, which itself is a collection.) For example, you can add a few ranges to an index set and then map over the indices as if they were individual members:

var indices = IndexSet()
indices.insert(integersIn: 1..<5)
indices.insert(integersIn: 11..<15)
let evenIndices = indices.filter { $0 % 2 == 0 } // [2, 4, 12, 14]

CharacterSet is an equally efficient way to store a set of Unicode code points. It’s often used to check if a particular string only contains characters from a specific character subset, such as alphanumerics or decimalDigits. However, unlike IndexSet, CharacterSet isn’t a collection. The name CharacterSet, imported from Objective-C, is also unfortunate in Swift because CharacterSet isn’t compatible with Swift’s Character type. A better name would be UnicodeScalarSet. We’ll talk a bit more about CharacterSet in the chapter on strings.

Using Sets Inside Closures

Dictionaries and sets can be very handy data structures to use inside your functions, even when you’re not exposing them to the caller. For example, if we want to write an extension on Sequence to retrieve all unique elements in the sequence, we could easily put the elements in a set and return its contents. However, that won’t be stable: because a set has no defined order, the input elements might get reordered in the result. To fix this, we can write an extension that maintains the order by using an internal Set for bookkeeping:

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        var seen: Set<Element> = []
        return filter { element in
            if seen.contains(element) {
                return false
            } else {
                seen.insert(element)
                return true
            }
        }
    }
}

[1,2,3,12,1,3,4,5,6,4,6].unique() // [1, 2, 3, 12, 4, 5, 6]

The method above allows us to find all unique elements in a sequence while still maintaining the original order (with the constraint that the elements must be Hashable). Inside the closure we pass to filter, we refer to the variable seen that we defined outside the closure, thus maintaining state over multiple iterations of the closure. In the chapter on functions, we’ll look at this technique in more detail.

Ranges

A range is an interval of values, defined by its lower and upper bounds. You create ranges with the two range operators: ..< for half-open ranges that don’t include their upper bound, and ... for closed ranges that include both bounds:

// 0 to 9, 10 is not included
let singleDigitNumbers = 0..<10
Array(singleDigitNumbers) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// "z" is included
let lowercaseLetters = Character("a")...Character("z")

There are also prefix and postfix variants of these operators, used to express one-sided ranges:

let fromZero = 0...
let upToZ = ..<Character("z")

There are eight distinct concrete types that represent ranges, and each type captures different constraints on the value. The two most essential types are Range (a half-open range, created using ..<) and ClosedRange (created using ...). Both have a generic Bound parameter: the only requirement is that Bound must be Comparable. For example, the lowercaseLetters expression above is of type ClosedRange<Character>. Somewhat surprisingly, we can’t iterate over Range and ClosedRange, but we can check whether elements are contained within the range:

singleDigitNumbers.contains(9) // true
lowercaseLetters.overlaps("c"..<"f") // true

(The answer why iterating over characters isn’t as straightforward as it would seem has to do with Unicode, and we’ll cover it at length in the chapter on strings.)

Half-open and closed ranges both have a place:

  • Only a half-open range can represent an empty interval (when the lower and upper bounds are equal, as in 5..<5).

  • Only a closed range can contain the maximum value its element type can represent (e.g. 0...Int.max). A half-open range always requires at least one representable value that’s greater than the highest value in the range.

Countable Ranges

Ranges seem like a natural fit to be sequences or collections, so it may surprise you that Range and ClosedRange are neither. Some ranges are sequences, however, otherwise iterating over a range of integers in a for loop wouldn’t work:

for i in 0..<10 {
    print("\(i)", terminator: " ")
} // 0 1 2 3 4 5 6 7 8 9 

What’s going on here? If you inspect the type of the expression 0..<10, you’ll discover it’s CountableRange<Int>. A CountableRange is just like a Range, with the additional constraint that its element type conforms to Strideable (with integer steps). Swift calls these more capable ranges countable because only they can be iterated over. Valid bounds for countable ranges include integer and pointer types — but not floating-point types, because of the integer constraint on the type’s Stride. If you need to iterate over consecutive floating-point values, you can use the stride(from:to:by) and stride(from:through:by) functions to create such a sequence. The Strideable constraint enables CountableRange and CountableClosedRange to conform to RandomAccessCollection, so we can iterate over them.

The four basic range types we’ve discussed thus far can be classified in a two-by-two matrix, as follows:

Half-open range Closed range
Elements are Comparable Range ClosedRange
Elements are Strideable CountableRange CountableClosedRange
(with integer steps)

The columns of the matrix correspond to the two range operators we saw above, which create a [Countable]Range (half-open) or a [Countable]ClosedRange (closed), respectively.

The rows in the table distinguish between “normal” ranges with an element type that only conforms to the Comparable protocol (which is the minimum requirement), and ranges over types that are Strideable and use integer steps between elements. Only the latter ranges are collections, inheriting all the powerful functionality we’ll see in the next chapter.

Partial Ranges

Partial ranges are constructed by using ... or ..< as a prefix or a postfix operator. For example, 0... means the range starting at zero. These ranges are called partial because they’re still missing one of their bounds. There are four different kinds:

let fromA: PartialRangeFrom<Character> = Character("a")...
let throughZ: PartialRangeThrough<Character> = ...Character("z")
let upto10: PartialRangeUpTo<Int> = ..<10
let fromFive: CountablePartialRangeFrom<Int> = 5...

There’s only one countable variant: CountablePartialRangeFrom. It’s the only partial range we can iterate over. Iteration works by starting with the lowerBound and repeatedly calling advanced(by: 1). If you use such a range in a for loop, you must take care to add a break condition lest you end up in an infinite loop (or crash when the counter overflows). PartialRangeFrom can’t be iterated over because its Bound is not Strideable, and both PartialRangeThrough and PartialRangeUpTo are missing a lower bound.

Range Expressions

All eight range types conform to the RangeExpression protocol. The protocol itself is small enough to print in this book. First of all, it allows you to ask whether an element is contained within the range. Second, given a collection, it can compute a fully specified Range for you:

public protocol RangeExpression {
    associatedtype Bound: Comparable
    func contains(_ element: Bound) -> Bool
    func relative<C: _Indexable>(to collection: C) -> Range<Bound> 
        where C.Index == Bound
}

For partial ranges with a missing lower bound, the relative(to:) method adds the collection’s startIndex as the lower bound. For partial ranges with a missing upper bound, it’ll use the collection’s endIndex. Partial ranges enable a very compact syntax for slicing collections:

let arr = [1,2,3,4]
arr[2...] // [3, 4]
arr[..<1] // [1]
arr[1...2] // [2, 3]

This works because the corresponding subscript declaration in the Collection protocol takes a RangeExpression rather than one of the eight concrete range types. You can even omit both bounds to get a slice of the entire collection:

arr[...] // [1, 2, 3, 4]
type(of: arr) // Array<Int>

(This is implemented as a special case in the standard library in Swift 4.0. Such an unbounded range isn’t a valid RangeExpression yet, but it should become one eventually.)

Ranges and Conditional Conformance

The standard library currently has to have separate types for countable ranges: CountableRange, CountableClosedRange, and CountablePartialRangeFrom. Ideally, these wouldn’t be distinct types, but rather extensions on Range, ClosedRange, and PartialRangeFrom that declare Collection conformance on the condition that the generic parameters meet the required constraints. We’ll talk a lot more about this in the next chapter, but the code might look like this:

// Invalid in Swift 4
extension Range: RandomAccessCollection
    where Bound: Strideable, Bound.Stride: SignedInteger
{
    // Implement RandomAccessCollection
}

Alas, Swift 4’s type system can’t express this idea, so separate types are needed. Support for conditional conformance is expected for Swift 5, and CountableRange, CountableClosedRange, and CountablePartialRangeFrom will then be deprecated or removed from the standard library.

The distinction between the half-open Range and the closed ClosedRange will likely remain, and it can sometimes make working with ranges harder than it should intuitively be. Say you have a function that takes a Range<Character> and you want to pass it the closed character range we created above. You may be surprised to find out it’s not possible! Inexplicably, there appears to be no way to convert a ClosedRange into a Range. But why? Well, to turn a closed range into an equivalent half-open range, you’d have to find the element that comes after the original range’s upper bound. And that’s simply not possible unless the element is Strideable, which is only guaranteed for countable ranges.

This means the caller of such a function will have to provide the correct type. If the function expects a Range, you can’t use the ... operator to create it. We’re not certain how big of a limitation this is in practice, since most ranges are likely integer based, but it’s definitely unintuitive.

If possible, try copying the standard library’s approach and make your own functions take a RangeExpression rather than a concrete type. It’s not always possible because the protocol doesn’t give you access to the range’s bounds unless you’re in the context of a collection, but if it is, you’ll give consumers of your APIs much more freedom to pass in any kind of range expression they like.

Recap

In this chapter, we saw a number of different collections: arrays, dictionaries, sets, index sets, and ranges. We also looked at a number of methods that each of these collections have, and in the next chapter, we’ll see that most of those methods are defined on the Sequence protocol. We saw how Swift’s built-in collections allow you to control mutability through let and var, and we also saw how to make sense of all the different Range types.

Collection Protocols

We saw in the previous chapter that Array, Dictionary, and Set don’t exist in a vacuum. They’re all implemented on top of a rich set of abstractions for processing series of elements that’s provided by the Swift standard library. This chapter is all about the Sequence and Collection protocols, which form the cornerstones of this model. We’ll cover how these protocols work, why they work the way they do, and how you can write your own sequences and collections.

Sequences

The Sequence protocol stands at the base of the hierarchy. A sequence is a series of values of the same type that lets you iterate over the values. The most common way to traverse a sequence is a for loop:

for element in someSequence {
    doSomething(with: element)
}

This seemingly simple capability of stepping over elements forms the foundation for a large number of useful operations Sequence provides to adopters of the protocol. We already mentioned many of them in the previous chapter. Whenever you come up with a common operation that depends on sequential access to a series of values, you should consider implementing it on top of Sequence, too. We’ll see many examples of how to do this throughout this chapter and the rest of the book.

The requirements for a type to conform to Sequence are fairly small. All it must do is provide a makeIterator() method that returns an iterator:

protocol Sequence {
    associatedtype Iterator: IteratorProtocol
    func makeIterator() -> Iterator
    // ...
}

The only thing we learn from this (simplified) definition of Sequence is that it’s a type that knows how to make an iterator. So let’s first take a closer look at iterators.

Collections

Conforming to Collection

Indices

Slices

Specialized Collections

Recap

Optionals

Sentinel Values

An extremely common pattern in programming is to have an operation that may or may not return a value.

Perhaps not returning a value is an expected outcome when you’ve reached the end of a file you were reading, as in the following C snippet:

int ch;
while ((ch = getchar()) != EOF) {
    printf("Read character %c\n", ch);
}
printf("Reached end-of-file\n");

EOF is just a #define for -1. As long as there are more characters in the file, getchar returns them. But if the end of the file is reached, getchar returns -1.

Or perhaps returning no value means “not found,” as in this bit of C++:

auto vec = {1, 2, 3};
auto iterator = std::find(vec.begin(), vec.end(), someValue);
if (iterator != vec.end()) {
    std::cout << "vec contains " << *iterator << std::endl;
}

Here, vec.end() is the iterator “one past the end” of the container; it’s a special iterator you can check against the container’s end but that you mustn’t ever actually use to access a value — similar to a collection’s endIndex in Swift. The find function uses it to indicate that no such value is present in the container.

Or maybe the value can’t be returned because something went wrong during the function’s processing. Probably the most notorious example is that of the null pointer. This innocuous-looking piece of Java code will likely throw a NullPointerException:

int i = Integer.getInteger("123")

It happens that Integer.getInteger doesn’t parse strings into integers, but rather gets the integer value of a system property named “123.” This property probably doesn’t exist, in which case getInteger returns null. When the null then gets unboxed into an int, Java throws an exception.

Or take this example in Objective-C:

[[NSString alloc] initWithContentsOfURL:url 
    encoding:NSUTF8StringEncoding error:&e];

Here, the NSString might be nil, in which case — and only then — the error pointer should be checked. There’s no guarantee the error pointer is valid if the result is non-nil.

In all of the above examples, the function returns a special “magic” value to indicate that it hasn’t returned a real value. Magic values like these are called “sentinel values.”

But this approach is problematic. The result returned looks and feels like a real value. An int of -1 is still a valid integer, but you don’t ever want to print it out. v.end() is an iterator, but the results are undefined if you try to use it. And everyone loves seeing a stack dump when your Java program throws a NullPointerException.

Unlike Java, Objective-C allows sending messages to nil. This is “safe” in so far as the runtime guarantees that the return value from a message to nil will always be the equivalent of zero, i.e. nil for object return types, 0 for numeric types, and so on. If the message returns a struct, it’ll have all its members initialized to zero. With this in mind, consider the following snippet:

NSString *someString = ...;
if ([someString rangeOfString:@"Swift"].location != NSNotFound) {
    NSLog(@"Someone mentioned Swift!");
}

If someString is nil, whether accidentally or on purpose, the rangeOfString: message will return a zeroed NSRange. Hence, its .location will be zero, and the inequality comparison against NSNotFound (which is defined as NSIntegerMax) will succeed. Therefore, the body of the if statement will be executed when it shouldn’t be.

Null references cause so many problems that Tony Hoare, credited with their creation in 1965, calls them his “billion-dollar mistake”:

At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

We’ve seen that sentinel values are error-prone — you can forget to check the sentinel value and accidentally use it. They also require prior knowledge. Sometimes there’s an idiom that’s widely followed in a community, as with the C++ end iterator or the error handling conventions in Objective-C. If such rules don’t exist or you’re not aware of them, you have to refer to the documentation. Moreover, there’s no way for the function to indicate it can’t fail. If a call returns a pointer, that pointer might never be nil. But there’s no way to tell except by reading the documentation, and even then, the documentation might be wrong.

Solving the Magic Value Problem with Enumerations

A Tour of Optional Techniques

When to Force-Unwrap

Living Dangerously: Implicitly Unwrapped Optionals

Recap

Structs and Classes

In Swift, we can choose from multiple options to store structured data: structs, enums, classes, and capturing variables with closures. Most of the public types in Swift’s standard library are defined as structs, with enums and classes making up a much smaller percentage. Part of this may be the nature of the types in the standard library, but it does give an indication as to the importance of structs in Swift. Likewise, many of the classes in Foundation now have struct counterparts specifically built for Swift. That said, we’ll focus on the differences between structs and classes in this chapter. Meanwhile, enums behave in a way that’s similar to structs.

Here are some of the major things that help distinguish between structs and classes:

  • Structs (and enums) are value types, whereas classes are reference types. When designing with structs, we can ask the compiler to enforce immutability. With classes, we have to enforce it ourselves.

  • How memory is managed differs. Structs can be held and accessed directly, whereas class instances are always accessed indirectly through their references. Structs aren’t referenced but instead copied. Structs have a single owner, whereas class instances can have many owners.

  • With classes, we can use inheritance to share code. With structs (and enums), inheritance isn’t possible. Instead, to share code using structs and enums, we need to use different techniques, such as composition, generics, and protocol extensions.

In this chapter, we’ll explore these differences in more detail. We’ll start by looking at the differences between entities and values. Next, we’ll continue by discussing issues with mutability. Then we’ll take a look at structs and mutability. After that, we’ll demonstrate how to wrap a reference type in a struct in order to use it as an efficient value type. Finally, we’ll compare the differences in how memory is managed — particularly how memory is managed in combination with closures, and how to avoid reference cycles.

Value Types

Mutability

Structs

Copy-On-Write

Closures and Mutability

Memory

Closures and Memory

Recap

Encoding and Decoding

Serializing a program’s internal data structures into some kind of data interchange format and vice versa is one of the most common programming tasks. Swift calls these operations encoding and decoding. One of the headline features of Swift 4 is a standardized design for encoding and decoding data that all custom types can opt into.

Overview

The Codable system (named after its base “protocol,” which is really a type alias) is designed around three central goals:

  • Universality — It should work with structs, enums, and classes.

  • Type safety — Interchange formats such as JSON are often weakly typed, whereas your code should work with strongly typed data structures.

  • Reducing boilerplate — Developers should have to write as little repetitive “adapter code” as possible to let custom types participate in the system. The compiler should generate this code for you automatically.

Types declare their ability to be (de-)serialized by conforming to the Encodable and/or Decodable protocols. Each of these protocols has just one requirement — Encodable defines an encode(to:) method in which a value encodes itself, and Decodable specifies an initializer for creating an instance from serialized data:

/// A type that can encode itself to an external representation.
public protocol Encodable {
    /// Encodes this value into the given encoder.
    public func encode(to encoder: Encoder) throws
}

/// A type that can decode itself from an external representation.
public protocol Decodable {
    /// Creates a new instance by decoding from the given decoder.
    public init(from decoder: Decoder) throws
}

Because most types that adopt one will also adopt the other, the standard library provides the Codable typealias as a shorthand for both:

public typealias Codable = Decodable & Encodable

All basic standard library types — including Bool, the number types, and String — are codable out of the box, as are optionals, arrays, dictionaries, and sets containing codable elements. Lastly, many common data types used by Apple’s frameworks have already adopted Codable, including Data, Date, URL, CGPoint, and CGRect.

Once you have a value of a codable type, you can create an encoder and tell it to serialize the value into the target format, such as JSON. In the reverse direction, a decoder takes serialized data and turns it back into an instance of the originating type. On the surface, the corresponding Encoder and Decoder protocols aren’t much more complex than Encodable and Decodable. The central task of an encoder or decoder is to manage a hierarchy of containers that store the serialized data. While you rarely have to interact with the Encoder and Decoder protocols directly unless you’re writing your own coders, understanding this structure and the three kinds of containers is necessary when you want to customize how your own types encode themselves. We’ll see many examples of this below.

A Minimal Example

The Encoding Process

The Synthesized Code

Manual Conformance

Common Coding Tasks

Recap

Functions

To open this chapter, let’s recap some main points regarding functions. If you’re already very familiar with first-class functions, feel free to skip ahead to the next section. But if you’re even slightly hazy, skim through what’s below.

To understand functions and closures in Swift, you really need to understand three things, in roughly this order of importance:

  1. Functions can be assigned to variables and passed in and out of other functions as arguments, just as an Int or a String can be.

  2. Functions can capture variables that exist outside of their local scope.

  3. There are two ways of creating functions — either with the func keyword, or with { }. Swift calls the latter closure expressions.

Sometimes people new to the topic of closures come at it in reverse order and maybe miss one of these points, or conflate the terms closure and closure expression — and this can cause a lot of confusion. It’s a three-legged stool, and if you miss one of the three points above, you’ll fall over when you try to sit down.

1. Functions can be assigned to variables and passed in and out of other functions as arguments.

In Swift, as in many modern languages, functions are referred to as “first-class objects.” You can assign functions to variables, and you can pass them in and out of other functions to be called later.

This is the most important thing to understand. “Getting” this for functional programming is akin to “getting” pointers in C. If you don’t quite grasp this part, everything else will just be noise.

Let’s start with a function that simply prints an integer:

func printInt(i: Int) {
    print("you passed \(i)")
}

To assign the function to a variable, funVar, we just use the function name as the value. Note the absence of parentheses after the function name:

let funVar = printInt

Now we can call the printInt function using the funVar variable. Note the use of parentheses after the variable name:

funVar(2) // you passed 2

It’s also noteworthy that we must not include an argument label in the funVar call, whereas printInt calls require the argument label, as in printInt(i: 2). Swift only allows argument labels in function declarations; the labels aren’t included in a function’s type. This means that you currently can’t assign argument labels to a variable of a function type, though this will likely change in a future Swift version.

We can also write a function that takes a function as an argument:

func useFunction(function: (Int) -> () ) {
    function(3)
}

useFunction(function: printInt) // you passed 3
useFunction(function: funVar) // you passed 3

Why is being able to treat functions like this such a big deal? Because it allows you to easily write “higher-order” functions, which take functions as arguments and apply them in useful ways, as we saw in the chapter on built-in collections.

Functions can also return other functions:

func returnFunc() -> (Int) -> String {
    func innerFunc(i: Int) -> String {
        return "you passed \(i)"
    }
    return innerFunc
}
let myFunc = returnFunc()
myFunc(3) // you passed 3

2. Functions can capture variables that exist outside of their local scope.

When a function references variables outside the function’s scope, those variables are captured and stick around after they would otherwise fall out of scope and be destroyed.

To see this, let’s revisit our returnFunc function but add a counter that increases each time we call it:

func counterFunc() -> (Int) -> String {
    var counter = 0
    func innerFunc(i: Int) -> String {
        counter += i   // counter is captured
        return "running total: \(counter)"
    }
    return innerFunc
}

Normally counter, being a local variable of counterFunc, would go out of scope just after the return statement, and it’d be destroyed. Instead, because it’s captured by innerFunc, the Swift runtime will keep it alive until the function that captured it gets destroyed. As we discussed in structs and classes, counter will exist on the heap rather than on the stack. We can call the inner function multiple times, and we see that the running total increases:

let f = counterFunc()
f(3) // running total: 3
f(4) // running total: 7

If we call counterFunc() again, a fresh counter variable will be created and captured:

let g = counterFunc()
g(2) // running total: 2
g(2) // running total: 4

This doesn’t affect our first function, which still has its own captured version of counter:

f(2) // running total: 9

Think of these functions combined with their captured variables as similar to instances of classes with a single method (the function) and some member variables (the captured variables).

In programming terminology, a combination of a function and an environment of captured variables is called a closure. So f and g above are examples of closures, because they capture and use a non-local variable (counter) that was declared outside of them.

3. Functions can be declared using the { } syntax for closure expressions.

In Swift, you can define functions in two ways. One is with the func keyword. The other way is to use a closure expression. Consider this simple function to double a number:

func doubler(i: Int) -> Int { 
  return i * 2 
}
[1, 2, 3, 4].map(doubler) // [2, 4, 6, 8]

And here’s the same function written using the closure expression syntax. Just like before, we can pass it to map:

let doublerAlt = { (i: Int) -> Int in return i*2 }
[1, 2, 3, 4].map(doublerAlt) // [2, 4, 6, 8]

Functions declared as closure expressions can be thought of as function literals in the same way that 1 and "hello" are integer and string literals. They’re also anonymous — they aren’t named, unlike with the func keyword. The only way they can be used is to assign them to a variable when they’re created (as we do here with doubler), or to pass them to another function or method.

There’s a third way anonymous functions can be used: you can call a function directly in line as part of the same expression that defines it. This can be useful for defining properties whose initialization requires more than one line. We’ll see an example of this below in the section on lazy properties.

The doubler declared using the closure expression, and the one declared earlier using the func keyword, are completely equivalent, apart from the differences in their handling of argument labels we mentioned above. They even exist in the same “namespace,” unlike in some languages.

Why is the { } syntax useful then? Why not just use func every time? Well, it can be a lot more compact, especially when writing quick functions to pass into other functions, such as map. Here’s our doubler map example written in a much shorter form:

[1, 2, 3].map { $0 * 2 } // [2, 4, 6]

This looks very different because we’ve leveraged several features of Swift to make code more concise. Here they are one by one:

  1. If you’re passing the closure in as an argument and that’s all you need it for, there’s no need to store it in a local variable first. Think of this like passing in a numeric expression, such as 5*i, to a function that takes an Int as a parameter.

  2. If the compiler can infer a type from the context, you don’t need to specify it. In our example, the function passed to map takes an Int (inferred from the type of the array elements) and returns an Int (inferred from the type of the multiplication expression).

  3. If the closure expression’s body contains just a single expression, it’ll automatically return the value of the expression, and you can leave off the return.

  4. Swift automatically provides shorthand names for the arguments to the function — $0 for the first, $1 for the second, etc.

  5. If the last argument to a function is a closure expression, you can move the expression outside the parentheses of the function call. This trailing closure syntax is nice if you have a multi-line closure expression, as it more closely resembles a regular function definition or other block statement such as if expr { }.

  6. Finally, if a function has no arguments other than a closure expression, you can leave off the parentheses after the function name altogether.

Using each of these rules, we can boil down the expression below to the form shown above:

[1, 2, 3].map( { (i: Int) -> Int in return i * 2 } )
[1, 2, 3].map( { i in return i * 2 } )
[1, 2, 3].map( { i in i * 2 } )
[1, 2, 3].map( { $0 * 2 } )
[1, 2, 3].map() { $0 * 2 }
[1, 2, 3].map { $0 * 2 }

If you’re new to Swift’s syntax, and to functional programming in general, these compact function declarations might seem daunting at first. But as you get more comfortable with the syntax and the functional programming style, they’ll start to feel more natural, and you’ll be grateful for the ability to remove the clutter so you can see more clearly what the code is doing. Once you get used to reading code written like this, it’ll be much clearer to you at a glance than the equivalent code written with a conventional for loop.

Sometimes, Swift needs a helping hand with inferring the types. And sometimes, you may get something wrong and the types aren’t what you think they should be. If ever you get a mysterious error when trying to supply a closure expression, it’s a good idea to write out the full form (first version above), complete with types. In many cases, that will help clear up where things are going wrong. Once you have the long form compiling, take the types out again one by one until the compiler complains. And if the error was yours, you’ll have fixed your code in the process.

Swift will also insist you be more explicit sometimes. For example, you can’t completely ignore input parameters. Suppose you wanted an array of random numbers. A quick way to do this is to map a range with a function that just generates random numbers. But you must supply an argument nonetheless. You can use _ in such a case to indicate to the compiler that you acknowledge there’s an argument but that you don’t care what it is:

(0..<3).map { _ in arc4random() } // [1156455564, 1418571693, 569924125]

When you need to explicitly type the variables, you don’t have to do it inside the closure expression. For example, try defining isEven without any types:

let isEven = { $0 % 2 == 0 }

Above, the type of isEven is inferred to be (Int) -> Bool in the same way that let i = 1 is inferred to be Int — because Int is the default type for integer literals.

This is because of a typealias, IntegerLiteralType, in the standard library:

protocol ExpressibleByIntegerLiteral {
    associatedtype IntegerLiteralType
    /// Create an instance initialized to `value`.
    init(integerLiteral value: Self.IntegerLiteralType)
}

/// The default type for an otherwise unconstrained integer literal.
typealias IntegerLiteralType = Int

If you were to define your own typealias, it would override the default one and change this behavior:

typealias IntegerLiteralType = UInt32
let i = 1  // i will be of type UInt32.

This is almost certainly a bad idea.

If, however, you needed a version of isEven for a different type, you could type the argument and return value inside the closure expression:

let isEvenAlt = { (i: Int8) -> Bool in i % 2 == 0 }

But you could also supply the context from outside the closure:

let isEvenAlt2: (Int8) -> Bool = { $0 % 2 == 0 }
let isEvenAlt3 = { $0 % 2 == 0 } as (Int8) -> Bool

Since closure expressions are most commonly used in some context of existing input or output types, adding an explicit type isn’t often necessary, but it’s useful to know.

Flexibility through Functions

Local Functions and Variable Capture

Functions as Delegates

inout Parameters and Mutating Methods

Properties

Subscripts

Key Paths

Automatic Closures

The @escaping Annotation

Recap

Strings

All modern programming languages have support for Unicode strings, but that often only means that the native string type can store Unicode data — it’s not a promise that simple operations like getting the length of a string will return “sensible” results. In fact, most languages, and in turn most string manipulation code written in those languages, exhibit a certain level of denial about Unicode’s inherent complexity. This can lead to some unpleasant bugs.

Swift’s string implementation goes to heroic efforts to be as Unicode-correct as possible. A String in Swift is a collection of Character values, where a Character is what a human reader of a text would perceive as a single character, regardless of how many Unicode code points it’s composed of. As a result, all standard Collection operations like count or prefix(5) work on the level of user-perceived characters.

This is great for correctness, but it comes at a price, mostly in terms of unfamiliarity; if you’re used to manipulating strings with integer indices in other languages, Swift’s design will seem unwieldy at first, leaving you wondering. Why can’t I write str[999] to access a string’s one-thousandth character? Why doesn’t str[idx+1] get the next character? Why can’t I loop over a range of Character values such as "a"..."z"? It also has performance implications: String does not support random access, i.e. jumping to an arbitrary character is not an O(1) operation. It can’t be — when characters have variable width, the string doesn’t know where the nth character is stored without looking at all characters that come before it.

In this chapter, we’ll discuss the string architecture in detail, as well as some techniques for getting the most out of Swift strings in terms of functionality and performance. But we’ll start with an overview of the required Unicode terminology.

Unicode, Or: No More Fixed Width

Things used to be so simple. ASCII strings were a sequence of integers between 0 and 127. If you stored them in an 8-bit byte, you even had a bit to spare! Since every character was of a fixed size, ASCII strings could be random access.

But ASCII wasn’t enough if you were writing in anything other than English or for a non-U.S. audience; other countries and languages needed other characters (even English-speaking Britain needed a £ sign). Most of them needed more characters than would fit into seven bits. ISO 8859 takes the extra bit and defines 16 different encodings above the ASCII range, such as Part 1 (ISO 8859-1, aka Latin-1), covering several Western European languages; and Part 5, covering languages that use the Cyrillic alphabet.

This is still limiting, though. If you want to use ISO 8859 to write in Turkish about Ancient Greek, you’re out of luck, since you’d need to pick either Part 7 (Latin/Greek) or Part 9 (Turkish). And eight bits is still not enough to encode many languages. For example, Part 6 (Latin/Arabic) doesn’t include the characters needed to write Arabic-script languages such as Urdu or Persian. Meanwhile, Vietnamese — which is based on the Latin alphabet but with a large number of diacritic combinations — only fits into eight bits by replacing a handful of ASCII characters from the lower half. And this isn’t even an option for other East Asian languages.

When you run out of room with a fixed-width encoding, you have a choice: either increase the size, or switch to variable-width encoding. Initially, Unicode was defined as a 2-byte fixed-width format, now called UCS-2. This was before reality set in, and it was accepted that even two bytes would not be sufficient, while four would be horribly inefficient for most purposes.

So today, Unicode is a variable-width format, and it’s variable in two different senses: in the combining of code units into Unicode scalars, and in the combining of scalars into characters.

Unicode data can be encoded with many different widths of code unit, most commonly 8 (UTF-8) or 16 (UTF-16) bits. UTF-8 has the added benefit of being backwardly compatible with 8-bit ASCII — something that’s helped it overtake ASCII as the most popular encoding on the web. Swift represents UTF-16 and UTF-8 code units as UInt16 and UInt8 values, respectively (aliased as Unicode.UTF16.CodeUnit and Unicode.UTF8.CodeUnit).

A code point in Unicode is a single value in the Unicode code space with a possible value from 0 to 0x10FFFF (in decimal: 1,114,111). Only about 137,000 of the 1.1 million available code points are currently in use, so there’s a lot of room for more emoji. A given code point might take a single code unit if you’re using UTF-32, or it might take between one and four if you’re using UTF-8. The first 256 Unicode code points match the characters found in Latin-1.

Unicode scalars are almost, but not quite, the same as code points. They’re all the code points except the 2,048 surrogate code points in the range 0xD800–0xDFFF, i.e. the code points used for the leading and trailing codes that indicate pairs in UTF-16 encoding. Scalars are represented in Swift string literals as "\u{xxxx}", where xxxx represents hex digits. So the euro sign can be written in Swift as either "€" or "\u{20AC}". The corresponding Swift type is Unicode.Scalar, which is a wrapper around a UInt32 value.

To represent each Unicode scalar by a single code unit, you’d need a 21-bit encoding scheme (which usually gets rounded up to 32-bit, i.e. UTF-32), but even that wouldn’t get you a fixed-width encoding: Unicode is still a variable-width format when it comes to “characters.” What a user might consider “a single character” — as displayed on the screen — might require multiple scalars composed together. The Unicode term for such a user-perceived character is (extended) grapheme cluster.

The rules for how scalars form grapheme clusters determine how text is segmented. For example, if you hit the backspace key on your keyboard, you expect your text editor to delete exactly one grapheme cluster, even if that “character” is composed of multiple Unicode scalars, each of which may use a varying number of code units in the text’s representation in memory. Grapheme clusters are represented in Swift by the Character type, which can encode an arbitrary number of scalars, as long as they form a single user-perceived character. We’ll see some examples of this in the next section.

Grapheme Clusters and Canonical Equivalence

Combining Marks

A quick way to see how String handles Unicode data is to look at the two different ways to write é. Unicode defines U+00E9, Latin small letter e with acute, as a single value. But you can also write it as the plain letter e, followed by U+0301, combining acute accent. In both cases, what’s displayed is é, and a user probably has a reasonable expectation that two strings displayed as “résumé” would not only be equal to each other but also have a “length” of six characters, no matter which technique was used to produce the é in either one. They would be what the Unicode specification describes as canonically equivalent.

And in Swift, this is exactly the behavior you get:

let single = "Pok\u{00E9}mon" // Pokémon
let double = "Poke\u{0301}mon" // Pokémon

They both display identically:

(single, double) // ("Pokémon", "Pokémon")

And both have the same character count:

single.count // 7
double.count // 7

Consequently, they also compare equal:

single == double // true

Only if you drop down to a view of the underlying representation can you see that they’re different:

single.utf16.count // 7
double.utf16.count // 8

Contrast this with NSString in Foundation: the two strings aren’t equal, and the length property — which many programmers probably use to count the number of characters to be displayed on the screen — gives different results:

let nssingle = single as NSString
nssingle.length // 7
let nsdouble = double as NSString
nsdouble.length // 8
nssingle == nsdouble // false

Here, == is defined as the version for comparing two NSObjects:

extension NSObject: Equatable {
    static func ==(lhs: NSObject, rhs: NSObject) -> Bool {
        return lhs.isEqual(rhs)
    }
}

In the case of NSString, this will do a literal comparison on the level of UTF-16 code units, rather than one accounting for equivalent but differently composed characters. Most string APIs in other languages work this way too. If you really want to perform a canonical comparison, you must use NSString.compare(_:). Didn’t know that? Enjoy your future undiagnosable bugs and grumpy international user base.

Of course, there’s one big benefit to just comparing code units: it’s faster! This is an effect that can still be achieved with Swift strings, via the utf16 view:

single.utf16.elementsEqual(double.utf16) // false

Why does Unicode support multiple representations of the same character at all? The existence of precomposed characters is what enables the opening range of Unicode code points to be compatible with Latin-1, which already had characters like é and ñ. While they might be a pain to deal with, it makes conversion between the two encodings quick and simple.

And ditching precomposed forms wouldn’t have helped anyway, because composition doesn’t just stop at pairs; you can compose more than one diacritic together. For example, Yoruba has the character ọ́, which could be written three different ways: by composing ó with a dot, or by composing ọ with an acute, or by composing o with both an acute and a dot. And for that last one, the two diacritics can be in either order! So these are all equal:

let chars: [Character] = [
    "\u{1ECD}\u{300}",      // ọ́
    "\u{F2}\u{323}",        // ọ́
    "\u{6F}\u{323}\u{300}", // ọ́
    "\u{6F}\u{300}\u{323}"  // ọ́
]
let allEqual = chars.dropFirst().all { $0 == chars.first } // true

(The all(matching:) method checks if the condition is true for all elements in a sequence and is defined in the chapter on built-in collections.)

In fact, some diacritics can be added ad infinitum. A famous internet meme illustrates this nicely:

let zalgo = "s̼̐͗͜o̠̦̤ͯͥ̒ͫ́ͅo̺̪͖̗̽ͩ̃͟ͅn̢͔͖͇͇͉̫̰ͪ͑"

zalgo.count // 4
zalgo.utf16.count // 36

In the above, zalgo.count (correctly) returns 4, while zalgo.utf16.count returns 36. And if your code doesn’t work correctly with internet memes, then what good is it, really?

Unicode’s grapheme breaking rules even affect you when all strings you deal with are pure ASCII: CR+LF, the character pair of carriage return and line feed that’s commonly used as a line break on Windows, is a single grapheme:

// CR+LF is a single Character
let crlf = "\r\n"
crlf.count // 1

Emoji

Strings containing emoji can also be surprising in many other languages. Many emoji are assigned Unicode scalars that don’t fit in a single UTF-16 code unit. Languages that represent strings as collections of UTF-16 code units, such as Java or C#, would say that the string "😂" is two “characters” long. Swift handles this case correctly:

let oneEmoji = "😂" // U+1F602
oneEmoji.count // 1

Notice that the important thing is how the string is exposed to the program, not how it’s stored in memory. Swift also uses UTF-16 as its internal encoding for non-ASCII strings, but that’s an implementation detail. The public API is based on grapheme clusters.

Other emoji are composed of multiple scalars. An emoji flag is a combination of two regional indicator letters that correspond to an ISO country code. Swift treats it correctly as one Character:

let flags = "🇧🇷🇳🇿"
flags.count // 2

To inspect the Unicode scalars a string is composed of, use the unicodeScalars view. Here, we format the scalar values as hex numbers in the common format for code points:

flags.unicodeScalars.map {
    "U+\(String($0.value, radix: 16, uppercase: true))"
}
// ["U+1F1E7", "U+1F1F7", "U+1F1F3", "U+1F1FF"]

Skin tones combine a base character such as 👧 with one of five skin tone modifiers (e.g. 🏽, or the type-4 skin tone modifier) to yield the final emoji (e.g. 👧🏽). Again, Swift handles this correctly:

let skinTone = "👧🏽" // 👧 + 🏽
skinTone.count // 1

This time, let’s use a Foundation API to apply an ICU string transform that converts Unicode scalars to their official Unicode names:

extension StringTransform {
    static let toUnicodeName = StringTransform(rawValue: "Any-Name")
}

extension Unicode.Scalar {
    /// The scalar's Unicode name, e.g. "LATIN CAPITAL LETTER A".
    var unicodeName: String {
        // Force-unwrapping is safe because this transform always succeeds
        let name = String(self).applyingTransform(.toUnicodeName, reverse: false)!

        // The string transform returns the name wrapped in "\\N{...}". Remove those.
        let prefixPattern = "\\N{"
        let suffixPattern = "}"
        let prefixLength = name.hasPrefix(prefixPattern) ? prefixPattern.count : 0
        let suffixLength = name.hasSuffix(suffixPattern) ? suffixPattern.count : 0
        return String(name.dropFirst(prefixLength).dropLast(suffixLength))
    }
}

skinTone.unicodeScalars.map { $0.unicodeName }
// ["GIRL", "EMOJI MODIFIER FITZPATRICK TYPE-4"]

The essential part of this code snippet is the applyingTransform(.toUnicodeName, …) call. The remaining lines clean up the name returned from the transform method by removing the wrapping braces. We code this defensively: we first check whether the string matches the expected pattern and compute the number of characters to strip from the start and end. If the format returned by the transform method changes in the future, it’s better to return the string unchanged than to remove characters we didn’t anticipate. Notice how we use the standard Collection methods dropFirst and dropLast to perform the stripping operation. This is a good example of how you can manipulate a string without doing manual index calculations. It’s also efficient because dropFirst and dropLast return a Substring, which is a slice of the original string. No new memory allocations are needed until the final step when we create a new String from the substring. We’ll have more to say about substrings later in this chapter.

Emoji depicting families and couples, such as 👨‍👩‍👧‍👦 and 👩‍❤️‍👩, present another challenge to the Unicode standards body. Due to the countless possible combinations of genders and the number of people in a group, providing a separate code point for each variation is problematic. Combine this with a distinct skin tone for each person and it becomes impossible. Unicode solves this by specifying that these emoji are actually sequences of multiple emoji, combined with the invisible zero-width joiner (ZWJ) character (U+200D). So the family 👨‍👩‍👧‍👦 is really man 👨 + ZWJ + woman 👩 + ZWJ + girl 👧 + ZWJ + boy 👦. The ZWJ serves as an indicator to the operating system that it should use a single glyph if available.

You can verify that this is really what’s going on:

let family1 = "👨‍👩‍👧‍👦"
let family2 = "👨\u{200D}👩\u{200D}👧\u{200D}👦"
family1 == family2 // true

And once again, Swift is smart enough to treat such a sequence as a single Character:

family1.count // 1
family2.count // 1

New emoji for professions introduced in 2016 are ZWJ sequences too. For example, the female firefighter 👩‍🚒 is composed of woman 👩 + ZWJ + fire engine 🚒, and the male health worker 👨‍⚕️ is a sequence of man 👨 + ZWJ + staff of aesculapius ⚕.

Rendering these sequences into a single glyph is the task of the operating system. On Apple platforms in 2017, the OS includes glyphs for the subset of sequences the Unicode standard lists as “recommended for general interchange” (RGI), i.e. the ones “most likely to be widely supported across multiple platforms.” When no glyph is available for a syntactically valid sequence, the text rendering system falls back to rendering each component as a separate glyph. Notice that this can cause a mismatch “in the other direction” between user-perceived characters and what Swift sees as a grapheme cluster; all examples up until now were concerned with programming languages overcounting characters, but here we see the reverse. As an example, family sequences containing skin tones are currently not part of the RGI subset. But even if the operating system renders such a sequence as multiple glyphs, Swift still counts it as a single Character because the Unicode text segmentation rules are not concerned with rendering:

// Family with skin tones is rendered as multiple glyphs
// on most platforms in 2017
let family3 = "👱🏾\u{200D}👩🏽\u{200D}👧🏿\u{200D}👦🏻"
// But Swift still counts it as a single Character
family3.count // 1

Microsoft can already render this and other variations as a single glyph, by the way, and the other OS vendors will almost certainly follow soon. But the point still stands: no matter how carefully a string API is designed, text is so complicated that it may never catch all edge cases.

In the past, Swift had trouble keeping up with Unicode changes. Swift 3 handled skin tones and ZWJ sequences incorrectly because its grapheme breaking algorithm was based on an old version of the Unicode standard. As of Swift 4, Swift uses the operating system’s ICU library. As a result, your programs will automatically adopt new Unicode rules as users update their OSes. The other side of the coin is of course that you can’t rely on users seeing the same behavior you see during development.

In the examples we discussed in this section, we treated the length of a string as a proxy for all sorts of things that can go wrong when a language doesn’t take the full complexity of Unicode into account. Just think of the gibberish a simple task such as reversing a string can produce in a programming language that doesn’t process strings by grapheme clusters when the string contains composed character sequences. This isn’t a new problem, but the emoji explosion has made it much more likely that bugs caused by sloppy text handling will come to the surface, even if your user base is predominantly English-speaking. And the magnitude of errors has increased as well: whereas a decade ago a botched accented character would cause an off-by-one error, messing up a modern emoji can easily cause results to be off by 10 or more “characters.” For example, a four-person family emoji is 11 (UTF-16) or 25 (UTF-8) code units long:

family1.count // 1
family1.utf16.count // 11
family1.utf8.count // 25

It’s not that other languages don’t have Unicode-correct APIs at all — most do. For instance, NSString has the enumerateSubstrings method that can be used to walk through a string by grapheme clusters. But defaults matter; Swift’s priority is to do the correct thing by default. And if you ever need to drop down to a lower level of abstraction, String provides views that let you operate directly on Unicode scalars or code units. We’ll say more about those below.

Strings and Collections

As we’ve seen, String is a collection of Character values. In Swift’s first three years of existence, String went back and forth between conforming and not conforming to the Collection protocol. The argument for not adding the conformance was that programmers would expect all generic collection-processing algorithms to be completely safe and Unicode-correct, which wouldn’t necessarily be true for all edge cases.

As a simple example, you might assume that if you concatenate two collections, the resulting collection’s length would be the sum of the lengths of the two source collections. But this doesn’t hold for strings if a suffix of the first string forms a grapheme cluster with a prefix of the second string:

let flagLetterJ = "🇯"
let flagLetterP = "🇵"
let flag = flagLetterJ + flagLetterP // 🇯🇵
flag.count // 1
flag.count == flagLetterJ.count + flagLetterP.count // false

To this end, String itself was not made a Collection in Swift 2 and 3; a collection-of-characters view was moved to a property, characters, which put it on a footing similar to the other collection views: unicodeScalars, utf8, and utf16. Picking a specific view prompted you to acknowledge you were moving into a “collection-processing” mode and that you should consider the consequences of the algorithm you were about to run.

In practice, the loss in usability and learnability caused by this change turned out to vastly outweigh the gain in correctness for a few edge cases that are rarely relevant in real code (unless you’re writing a text editor). So String was made a Collection again in Swift 4. The characters view still exists, but only for backward compatibility.

Bidirectional, Not Random Access

However, for reasons that should be clear from the examples in the previous section, String is not a random-access collection. How could it be, when knowing where the nth character of a particular string is involves evaluating just how many Unicode scalars precede that character? For this reason, String conforms only to BidirectionalCollection. You can start at either end of the string, moving forward or backward, and the code will look at the composition of the adjacent characters and skip over the correct number of bytes. However, you need to iterate up and down one character at a time.

Keep the performance implications of this in mind when writing string-processing code. Algorithms that depend on random access to maintain their performance guarantees aren’t a good match for Unicode strings. Consider this String extension for generating a list of a string’s prefixes, which works by generating an integer range from zero to the string’s length and then mapping over the range to create the prefix for each length:

extension String {
    var allPrefixes1: [Substring] {
        return (0...self.count).map(self.prefix)
    }
}

let hello = "Hello"
hello.allPrefixes1 // ["", "H", "He", "Hel", "Hell", "Hello"]

As simple as this code looks, it’s very inefficient. It first walks over the string once to calculate the length, which is fine. But then each of the n + 1 calls to prefix is another O(n) operation because prefix always starts at the beginning and has to work its way through the string to count the desired number of characters. Running a linear process inside another linear loop means this algorithm is accidentally O(n2) — as the length of the string increases, the time this algorithm takes increases quadratically.

If possible, an efficient string algorithm should walk over a string only once and then operate on string indices to denote the substrings it’s interested in. Here’s another version of the same algorithm:

extension String {
    var allPrefixes2: [Substring] {
        return [""] + self.indices.map { index in self[...index] }
    }
}

hello.allPrefixes2 // ["", "H", "He", "Hel", "Hell", "Hello"]

This code also has to iterate over the string once to generate the indices collection. But once that’s done, the subscripting operation inside map is O(1). This makes the whole algorithm O(n).

The PrefixIterator we implemented in the collection protocols chapter solves the same problem in a generic way for all sequences.

Range-Replaceable, Not Mutable

String also conforms to RangeReplaceableCollection. Here’s an example of how you’d replace part of a string by first identifying the appropriate range in terms of string indices and then calling replaceSubrange. The replacement string can have a different length or could even be empty (which would be equivalent to calling removeSubrange):

var greeting = "Hello, world!"
if let comma = greeting.index(of: ",") {
    greeting[..<comma] // Hello
    greeting.replaceSubrange(comma..., with: " again.")
}
greeting // Hello again.

As always, keep in mind that results may be surprising if parts of the replacement string form new grapheme clusters with adjacent characters in the original string.

One collection-like feature strings do not provide is that of MutableCollection. This protocol adds one feature to a collection — that of the single-element subscript set — in addition to get. This isn’t to say strings aren’t mutable — as we’ve just seen, they have several mutation methods. But what you can’t do is replace a single character using the subscript operator. The reason comes back to variable-length characters. Most people can probably intuit that a single-element subscript update would happen in constant time, as it does for Array. But since a character in a string may be of variable width, updating a single character could take linear time in proportion to the length of the string: changing the width of a single element would require shuffling all the later elements up or down in memory. Moreover, indices that come after the replaced index would become invalid through the shuffling, which is equally unintuitive. For these reasons, you have to use replaceSubrange, even if the range you pass in is only a single element.

String Indices

Most programming languages use integers for subscripting strings, e.g. str[5] would return the sixth “character” of str (for whatever that language’s idea of a “character” is). Swift doesn’t allow this. Why? The answer should sound familiar to you by now: subscripting is supposed to take constant time (intuitively as well as per the requirements of the Collection protocol), and looking up the nth Character is impossible without looking at all bytes that come before it.

String.Index, the index type used by String and its views, is an opaque value that essentially stores a byte offset from the beginning of the string. It’s still an O(n) operation if you want to compute the index for the nth character and have to start at the beginning of the string, but once you have a valid index, subscripting the string with it now only takes O(1) time. And crucially, finding the next index after an existing index is also fast because you can start at the existing index’s byte offset — you don’t need to go back to the beginning again. This is why iterating over the characters in a string in order (forward or backward) is efficient.

String index manipulation is based on the same Collection APIs you’d use with any other collection. It’s easy to miss this equivalence since the collections we use by far the most — arrays — use integer indices, and we usually use simple arithmetic to manipulate those. The index(after:) method returns the index of the next character:

let s = "abcdef"
let second = s.index(after: s.startIndex)
s[second] // b

You can automate iterating over multiple characters in one go via the index(_:offsetBy:) method:

// Advance 4 more characters
let sixth = s.index(second, offsetBy: 4)
s[sixth] // f

If there’s a risk of advancing past the end of the string, you can add a limitedBy: parameter. The method returns nil if it hits the limit before reaching the target index:

let safeIdx = s.index(s.startIndex, offsetBy: 400, limitedBy: s.endIndex)
safeIdx // nil

This is undoubtedly more code than simple integer indices would require, but again, that’s the point. If Swift allowed integer subscripting of strings, the temptation to accidentally write horribly inefficient code (e.g. by using integer subscripting inside a loop) would be too big.

Nevertheless, to someone used to dealing with fixed-width characters, working with strings in Swift seems challenging at first — how will you navigate without integer indices? And indeed, some seemingly simple tasks like extracting the first four characters of a string can turn into monstrosities like this one:

s[..<s.index(s.startIndex, offsetBy: 4)] // abcd

But thankfully, being able to access the string via the Collection interface also means you have several helpful techniques at your disposal. Many of the methods that operate on Array also work on String. Using the prefix method, the same thing looks much clearer:

s.prefix(4) // abcd

(Note that either expression returns a Substring; you can convert it back into a String by wrapping it in a String.init. We’ll talk more about substrings in the next section.)

Iterating over characters in a string is easy without integer indices; just use a for loop. If you want to number each character in turn, use enumerated():

for (i, c) in s.enumerated() {
    print("\(i): \(c)")
}

Or say you want to find a specific character. In that case, you can use index(of:):

var hello = "Hello!"
if let idx = hello.index(of: "!") {
    hello.insert(contentsOf: ", world", at: idx)
}
hello // Hello, world!

The insert(contentsOf:) method inserts another collection of the same element type (e.g. Character for strings) before a given index. This doesn’t have to be another String; you could insert an array of characters into a string just as easily.

Substrings

Like all collections, String has a specific slice or SubSequence type named Substring. A substring is much like an ArraySlice: it’s a view of a base string with different start and end indices. Substrings share the text storage of their base strings. This has the huge benefit that slicing a string is a very cheap operation. Creating the firstWord variable in the following example requires no expensive copies or memory allocations:

let sentence = "The quick brown fox jumped over the lazy dog."
let firstSpace = sentence.index(of: " ") ?? sentence.endIndex
let firstWord = sentence[..<firstSpace] // The
type(of: firstWord) // Substring

Slicing being cheap is especially important in loops where you iterate over the entire (potentially long) string to extract its components. Tasks like finding all occurrences of a word in a text or parsing a CSV file come to mind. A very useful string processing operation in this context is splitting. The split method is defined on Collection and returns an array of subsequences (i.e. [Substring]). Its most common variant is defined like so:

extension Collection where Element: Equatable {
    public func split(separator: Element, maxSplits: Int = Int.max,
        omittingEmptySubsequences: Bool = true) -> [SubSequence]
}

You can use it like this:

let poem = """
    Over the wintry
    forest, winds howl in rage
    with no leaves to blow.
    """
let lines = poem.split(separator: "\n")
// ["Over the wintry", "forest, winds howl in rage", "with no leaves to blow."]
type(of: lines) // Array<Substring>

This can serve a function similar to the components(separatedBy:) method String inherits from NSString, with added configurations for whether or not to drop empty components. Again, no copies of the input string are made. And since there’s another variant of split that takes a closure, it can do more than just compare characters. Here’s an example of a primitive word wrap algorithm, where the closure captures a count of the length of the line thus far:

extension String {
    func wrapped(after: Int = 70) -> String {
        var i = 0
        let lines = self.split(omittingEmptySubsequences: false) {
            character in
            switch character {
            case "\n", " " where i >= after:
                i = 0
                return true
            default:
                i += 1
                return false
            }
        }
        return lines.joined(separator: "\n")
    }
}

sentence.wrapped(after: 15)
/*
 The quick brown
 fox jumped over
 the lazy dog.
*/

Or, consider writing a version that takes a sequence of multiple separators:

extension Collection where Element: Equatable {
    func split<S: Sequence>(separators: S) -> [SubSequence]
        where Element == S.Element
    {
        return split { separators.contains($0) }
    }
}

This way, you can write the following:

"Hello, world!".split(separators: ",! ") // ["Hello", "world"]

StringProtocol

Substring has almost the same interface as String. This is achieved through a common protocol named StringProtocol, which both types conform to. Since almost the entire string API is defined on StringProtocol, you can mostly work with a Substring as you would with a String. At some point, though, you’ll have to turn your substrings back into String instances; like all slices, substrings are only intended for short-term storage, in order to avoid expensive copies during an operation. When the operation is complete and you want to store the results or pass them on to another subsystem, you should create a new String. You can do this by initializing a String with a Substring, as we do in this example:

func lastWord(in input: String) -> String? {
    // Process the input, working on substrings
    let words = input.split(separators: [",", " "])
    guard let lastWord = words.last else { return nil }
    // Convert to String for return
    return String(lastWord)
}

lastWord(in: "one, two, three, four, five") // Optional("five")

The rationale for discouraging long-term storage of substrings is that a substring always holds on to the entire original string. A substring representing a single character of a huge string will hold the entire string in memory, even after the original string’s lifetime would normally have ended. Long-term storage of substrings would therefore effectively cause memory leaks because the original strings have to be kept in memory even when they’re no longer accessible.

By working with substrings during an operation and only creating new strings at the end, we defer copies until the last moment and make sure to only incur the cost of those copies that are actually necessary. In the example above, we split the entire (potentially long) string into substrings, but only pay the cost for a single copy of one short substring at the end. (Ignore for a moment that this algorithm isn’t efficient anyway; iterating backward from the end until we find the first separator would be the better approach.)

Encountering a function that only accepts a Substring when you want to pass a String is less common — most functions should either take a String or any StringProtocol-conforming type. But if you do need to pass a String, the quickest way is to subscript the string with the range operator ... without specifying any bounds:

// Substring with identical start and end index as the base string
let substring = sentence[...]

We already saw an example of this in the chapter on collection-protocols when we defined the Words collection.

The Substring type is new in Swift 4. In Swift 3, String.CharacterView used to be its own slice type. This had the advantage that users only had to understand a single type, but it meant that a stored substring would keep the entire original string buffer alive even after it normally would’ve been released. Swift 4 trades a small loss in convenience for cheap slicing operations and predictable memory usage.

The Swift team does recognize that requiring explicit conversions from Substring to String is a little annoying. If this turns out to be a big problem in practical use, the team is considering wiring an implicit subtype relationship between Substring and String directly into the compiler, in the same way that Int is a subtype of Optional<Int>. This would allow you to pass a Substring anywhere a String is expected, and the compiler would perform the conversion for you.

You may be tempted to take full advantage of the existence of StringProtocol and convert all your APIs to take StringProtocol instances rather than plain Strings. But the advice of the Swift team is not to do that:

Our general advice is to stick with String. Most APIs would be simpler and clearer just using String rather than being made generic (which itself can come at a cost), and user conversion on the way in on the few occasions that’s needed isn’t much of a burden.

APIs that are extremely likely to be used with substrings, and at the same time aren’t further generalizable to the Sequence or Collection level, are an exception to this rule. An example of this in the standard library is the joined method. Swift 4 added an overload for sequences with StringProtocol-conforming elements:

extension Sequence where Element: StringProtocol {
    /// Returns a new string by concatenating the elements of the sequence,
    /// adding the given separator between each element.
    public func joined(separator: String = "") -> String
}

This lets you call joined directly on an array of substrings (which you got from a call to split, for example) without having to map over the array and copy every substring into a new string. This is more convenient and much faster.

The number type initializers that take a string and convert it into a number also take StringProtocol values in Swift 4. Again, this is especially handy if you want to process an array of substrings:

let commaSeparatedNumbers = "1,2,3,4,5"
let numbers = commaSeparatedNumbers
    .split(separator: ",").flatMap { Int($0) }
// [1, 2, 3, 4, 5]

Since substrings are intended to be short-lived, it’s generally not advisable to return one from a function unless we’re talking about Sequence or Collection APIs that return slices. If you write a similar function that only makes sense for strings, having it return a substring tells readers that it doesn’t make a copy. Functions that create new strings requiring memory allocations, such as uppercased(), should always return String instances.

If you want to extend String with new functionality, placing the extension on StringProtocol is a good idea to keep the API surface between String and Substring consistent. StringProtocol is explicitly designed to be used whenever you would’ve previously extended String. If you want to move existing extensions from String to StringProtocol, the only change you should have to make is to replace any passing of self into an API that takes a concrete String with String(self).

Keep in mind, though, that as of Swift 4, StringProtocol is not yet intended as a conformance target for your own custom string types. The documentation explicitly warns against it:

Do not declare new conformances to StringProtocol. Only the String and Substring types of the standard library are valid conforming types.

Allowing developers to write their own string types (with special storage or performance optimizations, for instance) is the eventual goal, but the protocol design hasn’t yet been finalized, so adopting it now may break your code in Swift 5.

Code Unit Views

Strings and Foundation

Internal Structure of String and Character

A Simple Regular Expression Matcher

ExpressibleByStringLiteral

CustomStringConvertible and CustomDebugStringConvertible

Text Output Streams

String Performance

Recap

Error Handling

Swift provides multiple ways for dealing with errors and even allows us to build our own mechanisms. In the chapter on optionals, we looked at two of them: optionals and assertions. An optional indicates that a value may or may not be there; we have to inspect the optional and unwrap the value before we can use it. An assertion validates that a condition is true; if the condition doesn’t hold, the program crashes.

Looking at the interfaces of types in the standard library can give us a good feel for when to use an optional and when not to. Optionals are used for operations that have a clear and commonly used “not found” or “invalid input” case. For instance, consider the failable initializer for Int that takes a string: it returns nil if the input isn’t a valid integer. Another example: when you look up a key in a dictionary, it’s often expected that the key might not be present. Therefore, a dictionary lookup returns an optional result.

Contrast this with arrays: when looking up an element at a specific index, the element is returned directly and isn’t wrapped in an optional. This is because the programmer is expected to know if an array index is valid. Accessing an array with an index that’s out of bounds is considered a programmer error, and consequently, this will crash your application. If you’re unsure whether or not an index is within bounds, you need to check beforehand.

Assertions are a great tool for identifying bugs in your code. Used correctly, they show you at the earliest possible moment when your program is in a state you didn’t expect. They should never be used to signal expected errors such as a network error.

Note that arrays also have accessors that return optionals. For example, the first and last properties on Collection return nil when called on an empty collection. The developers of Swift’s standard library deliberately designed the API this way because it’s common to access these values in situations when the collection might be empty.

An alternative to returning an optional from a function that can fail is to mark the function as throws. Besides a different syntax for how the caller must handle the success and failure cases, the key difference to returning an optional is that throwing functions can return a rich error value that carries information about the error that occurred.

This difference is a good guideline for when to use one approach over the other. Consider the first and last properties on Collection again. They have exactly one error condition (the collection is empty) — returning a rich error wouldn’t give the caller more information than what’s already present in the optional value. Compare this to a function that executes a network request: many things can fail, from the network being down, to being unable to parse the server’s response. Rich error information is necessary to allow the caller to react differently to different errors (or just to show the user what exactly went wrong).

The Result Type

Throwing and Catching

Typed Errors

Bridging Errors to Objective-C

Errors and Function Parameters

Cleaning Up Using defer

Errors and Optionals

Chaining Errors

Higher-Order Functions and Errors

Recap

Generics

Like most modern languages, Swift has a number of features that can all be grouped under generic programming. Generic code allows you to write reusable functions and data types that can work with any type that matches the constraints you define. For example, types such as Array and Set are generic over their elements. Generic functions are generic over their input and/or output types. The declaration func identity<A>(input: A) -> A defines a function that works on any type, identified by the placeholder A. In a way, we can also think of protocols with an associated type as “generic protocols.” The associated type allows us to abstract over specific implementations. IteratorProtocol is an example of such a protocol: it’s generic over the Element type it produces.

The goal of generic programming is to express the essential interface an algorithm or data structure requires. For example, consider the last(where:) method we wrote in the built-in collections chapter. Writing this method as an extension on Array would’ve been the obvious choice, but an Array has lots of capabilities last(where:) doesn’t need. By asking ourselves what the essential interface is — that is, the minimal set of features required to implement the desired functionality — we can make the function available to a much broader set of types. In this example, last(where:) has only one requirement: it needs to traverse a sequence of elements in reverse order. This makes an extension on Sequence the right place for this algorithm (and we can add a more efficient variant to BidirectionalCollection).

In this chapter, we’ll look at how to write generic code. We’ll start by talking about overloading because this concept is closely related to generics. We’ll use generic programming to provide multiple implementations of an algorithm, each relying on a different set of assumptions. We’ll also discuss some common difficulties you may encounter when writing generic algorithms for collections. We’ll then look at how you can use generic data types to refactor code to make it testable and more flexible. Finally, we’ll cover how the compiler handles generic code and what we can do to get the best performance for our own generic code.

Overloading

Overloaded functions, i.e. multiple functions that have the same name but different argument and/or return types, are not generic per se. But like generics, they allow us to make one interface available to multiple types.

Overload Resolution for Free Functions

For example, we could define a function named raise(_:to:) to perform exponentiation and provide separate overloads for Double and Float arguments:

Operating Generically on Collections

Designing with Generics

How Generics Work

Recap

Protocols

In previous chapters, we saw how functions and generics can help us write very dynamic programs. Protocols work together with functions and generics to enable even more dynamic behavior.

Swift protocols aren’t unlike Objective-C protocols. They can be used for delegation, and they allow you to specify abstract interfaces (such as IteratorProtocol or Sequence). Yet, at the same time, they’re very different from Objective-C protocols. For example, we can make structs and enums conform to protocols. Additionally, protocols can have associated types. We can even add method implementations to protocols using protocol extensions. We’ll look at all these things in the section on protocol-oriented programming.

Protocols allow for dynamic dispatch: the correct method implementation is selected at runtime based on the type of the receiver. However, when a method is or isn’t dynamically dispatched is sometimes unintuitive, and this can lead to surprising behavior. We’ll look at this issue in the next section.

Regular protocols can be used either as type constraints or as standalone types. Protocols with associated types and protocols with Self requirements, however, are special: we can’t use them as standalone types, so something like let x: Equatable isn’t allowed; we can only use them as type constraints, such as func f<T: Equatable>(x: T). This may sound like a small limitation, but it makes protocols with associated types almost entirely different things in practice. We’ll take a closer look at this later. We’ll also discuss type erasers (such as AnyIterator) as a way of making it easier to work with protocols with associated types.

In object-oriented programming, subclassing is a powerful way to share code among multiple classes. A subclass inherits all the methods from its superclass and can choose to override some of the methods. For example, we could have an AbstractSequence class and subclasses such as Array and Dictionary. Doing so allows us to add methods to AbstractSequence, and all the subclasses would automatically inherit those methods.

Yet in Swift, the code sharing in Sequence is implemented using protocols and protocol extensions. In this way, the Sequence protocol and its extensions also work with value types, such as structs and enums, which don’t support subclassing.

Not relying on subclassing also makes types more flexible. In Swift (as in most other object-oriented languages), a class can only have a single superclass. When we create a class, we have to choose the superclass well, because we can only pick one; we can’t subclass both AbstractSequence, and, say, Stream. This can sometimes be a problem. There are examples in Cocoa — e.g. with NSMutableAttributedString, where the designers had to choose between NSAttributedString and NSMutableString as a superclass.

Some languages have multiple inheritance, the most common being C++. But this leads to something called the “diamond problem.” For example, if multiple inheritance were possible, we could envision an NSMutableAttributedString that inherits from both NSMutableString and NSAttributedString. But what happens if both of those classes override a method of NSString? You could deal with it by just picking one of the methods. But what if that method is isEqual:? Providing good behavior for multiple inheritance is really hard.

Because multiple inheritance is so complicated, most languages don’t support it. Yet many languages do support conforming to multiple protocols, which doesn’t have the same problems. In Swift, we can conform to multiple protocols, and the compiler warns us when the use of a method is ambiguous.

Protocol extensions are a way of sharing code without sharing base classes. Protocols define a minimal viable set of methods for a type to implement. Extensions can then build on these minimal methods to implement more complex features.

For example, to implement a generic algorithm that sorts any sequence, you need two things. First, you need a way to iterate over the elements. Second, you need to be able to compare the elements. That’s it. There are no demands as to how the elements are held. They could be in a linked list, an array, or any iterable container. What they are doesn’t matter, either — they could be strings, integers, dates, or people. As long as you write down the two aforementioned constraints in the type system, you can implement sort:

extension Sequence where Element: Comparable {
    func sorted() -> [Element]
}

To implement an in-place sort, you need more building blocks. More specifically, you need index-based access to the elements rather than just linear iteration. Collection captures this and MutableCollection adds mutation to it. Finally, you need to compare and offset indices in constant time. RandomAccessCollection allows for that. This may sound complex, but it captures the prerequisites needed to perform an in-place sort:

extension MutableCollection where
    Self: RandomAccessCollection, Element: Comparable {
        mutating func sort()
}

Minimal capabilities described by protocols compose well. You can add different capabilities of different protocols to a type, bit by bit. We saw this in the collection protocols chapter when we first built a List type by giving it a single method, cons. Without changing the original List struct, we made it conform to Sequence. In fact, we could’ve done this even if we weren’t the original authors of this type, using a technique called retroactive modeling. By adding conformance to Sequence, we get all the extension methods of Sequence for free.

Adding new shared features via a common superclass isn’t this flexible. You can’t just decide later to add a new common base class to many different classes; that would be a major refactoring. And if you aren’t the owner of these subclasses, you can’t do it at all!

Subclasses have to know which methods they can override without breaking the superclass. For example, when a method is overridden, a subclass might need to call the superclass method at the right moment: either at the beginning, somewhere in the middle, or at the end of the method. This moment is often unspecified. Also, by overriding the wrong method, a subclass might break the superclass without warning.

Protocol-Oriented Programming

Two Types of Protocols

Protocols with Self Requirements

Protocol Internals

Recap

Interoperability

One of Swift’s greatest strengths is the low friction when interoperating with C and Objective-C. Swift can automatically bridge Objective-C types to native Swift types, and it can even bridge with many C types. This allows us to use existing libraries and provide a nice interface on top.

In this chapter, we’ll create a wrapper around the CommonMark library. CommonMark is a formal specification for Markdown, a popular syntax for formatting plain text. If you’ve ever written a post on GitHub or Stack Overflow, you’ve probably used Markdown. After this practical example, we’ll take a look at the tools the standard library provides for working with memory, and we’ll see how they can be used to interact with C code.

Hands-On: Wrapping CommonMark

Swift’s ability to call into C code allows us to take advantage of the abundance of existing C libraries. Writing a wrapper around an existing library’s interface in Swift is often much easier and involves less work than building something from scratch; meanwhile, users of our wrapper will see no difference in terms of type safety or ease of use compared to a fully native solution. All we need to start is the dynamic library and its C header files.

Our example, the CommonMark C library, is a reference implementation of the CommonMark spec that’s both fast and well tested. We’ll take a layered approach in order to make CommonMark accessible from Swift. First, we’ll create a very thin Swift class around the opaque types the library exposes. Then, we’ll wrap this class with Swift enums in order to provide a more idiomatic API.

An Overview of Low-Level Types

Function Pointers

Recap

Final Words

We hope you enjoyed this journey through Swift with us.

Despite its young age, Swift is already a complex language. It’d be a daunting task to cover every aspect of it in one book, let alone expect readers to remember it all.

We chose the topics for this book largely based on our own interests. What did we want to know that was missing from the official documentation? How do things work under the hood? And perhaps even more importantly, why does Swift behave the way it does?

Even if you don’t immediately put everything you learned to practical use, we’re confident that having a better understanding of your language makes you a more accomplished programmer.

Swift is still changing rapidly. While the era of large-scale source-breaking changes is probably behind us, there are several areas where we expect major enhancements in the coming years:

  • The big goal for Swift 5 is achieving ABI stability (application binary interface). This mostly affects internal details like memory layouts and calling conventions, but one prerequisite for ABI stability that affects everyone is finalizing the standard library API. This is why enhancements to the generics system that the standard library can profit from are a major focus for Swift 5.

  • An explicit memory ownership model will allow developers to annotate function arguments with ownership requirements. The goal is to give the compiler all the information it needs to avoid unnecessary copies when passing values to functions. We’re already seeing the first pieces of this in Swift 4 with the introduction of compiler-enforced exclusive memory access.

  • Discussions about adding first-class concurrency support to Swift are still in their infancy, and this is a project that will take several years to complete. Still, there’s a good chance we’ll get something like the coroutine-based async/await model that’s popular in other languages in the not-too-distant future.

If you’re interested in shaping how these and other features turn out, remember that Swift is being developed in the open. Consider joining the swift-evolution mailing list and adding your perspective to the discussions.

Finally, we’d like to encourage you to take advantage of the fact that Swift is open source. When you have a question the documentation doesn’t answer, the source code can often give you the answer. If you made it this far, you’ll have no problem finding your way through the standard library source files. Being able to check how things are implemented in there was a big help for us when writing this book.