You are on page 1of 232

HACKING WITH SWIFT

SWIFT
FOR COMPLETE
BEGINNERS
YOUR HANDS-ON GUIDE

Get started with the


fundamentals of Swift
programming
Paul Hudson
Swift for Complete
Beginners
Paul Hudson

Version: 2022-05-29
Contents
Introduction 6
Why Swift?
About this course
How to follow along

Simple data 10
How to create variables and constants
How to create strings
How to store whole numbers
How to store decimal numbers
How to store truth with Booleans
How to join strings together
Summary: Simple data
Checkpoint 1

Complex data 31
How to store ordered data in arrays
How to store and find data in dictionaries
How to use sets for fast data lookup
How to create and use enums
How to use type annotations
Summary: Complex data
Checkpoint 2

Conditions and loops 55


How to check a condition is true or false
How to check multiple conditions
How to use switch statements to check multiple conditions
How to use the ternary conditional operator for quick tests
How to use a for loop to repeat work
How to use a while loop to repeat work
How to skip loop items with break and continue
Summary: Conditions and loops
Checkpoint 3

www.hackingwithswift.com 3
Functions 89
How to reuse code with functions
How to return values from functions
How to return multiple values from functions
How to customize parameter labels
How to provide default values for parameters
How to handle errors in functions
Summary: Functions
Checkpoint 4

Closures 122
How to create and use closures
How to use trailing closures and shorthand syntax
How to accept functions as parameters
Summary: Closures
Checkpoint 5

Structs 142
How to create your own structs
How to compute property values dynamically
How to take action when a property changes
How to create custom initializers
How to limit access to internal data using access control
Static properties and methods
Summary: Structs
Checkpoint 6

Classes 165
How to create your own classes
How to make one class inherit from another
How to add initializers for classes
How to copy classes
How to create a deinitializer for a class
How to work with variables inside classes
Summary: Classes
Checkpoint 7

Protocols and extensions 184


How to create and use protocols
How to use opaque return types
How to create and use extensions

4 www.hackingwithswift.com
How to create and use protocol extensions
How to get the most from protocol extensions
Summary: Protocols and extensions
Checkpoint 8

Optionals 213
How to handle missing data with optionals
How to unwrap optionals with guard
How to unwrap optionals with nil coalescing
How to handle multiple optionals using optional chaining
How to handle function failure with optionals
Summary: Optionals
Checkpoint 9

Wrap up 231
Where now?

www.hackingwithswift.com 5
Chapter 1
Introduction

6 www.hackingwithswift.com
Why Swift?
There are lots of programming languages out there, but I think you’re going to really enjoy
learning Swift. This is partly for practical reasons – you can make a lot of money on the App
Store! – but there are lots of technical reasons too.

You see, Swift is a relatively young language, having launched only in 2014. This means it
doesn’t have a lot of the language cruft that old languages can suffer from, and usually means
there is only one way to solve a particular problem.

At the same time, being such a new programming language means that Swift leverages all sorts
of new ideas built upon the successes – and sometimes mistakes – of older languages. For
example, it makes it hard to accidentally write unsafe code, it makes it very easy to write code
that is clear and understandable, and it supports all the world languages so you’ll never see
those strange character errors that plague other languages.

Swift itself is just the language, and isn’t designed to draw anything on the screen. When it
comes to building software with Swift, you’ll be using SwiftUI: Apple’s powerful framework
that creates text, buttons, images, user interaction, and much more. As the name suggests,
SwiftUI was built for Swift – it’s literally designed to leverage the power and safety offered by
the language, which makes it remarkably quick to build really powerful apps.

So, you should learn Swift because you can make a lot of money with it, but also because it
does so many things really well. No cruft, no confusion, just lots of power at your fingertips.
What’s not to like?

www.hackingwithswift.com 7
About this course
I’ve been teaching folks to write Swift since 2014, the same year Swift launched, and at this
point Hacking with Swift is the world’s largest site dedicated to teaching Swift.

Along the way I learned a huge amount about what topics matter the most, how to structure
topics into a smooth and consistent flow, and most importantly how to help learners remember
topics they’ve learned. This course is the product of all that learning.

Unlike my previous work this does not strive to teach you every aspect of Swift, but instead it
spends more time on the subset of features that matter the most – the ones you’ll use in every
app you build, time and time again. Yes, there are some advanced language features covered,
but I’ve cherrypicked them based on usefulness. When you’ve finished the book you might
want to carry on learning some of the more advanced features, but I suspect you’d much rather
get busy learning how to use SwiftUI.

Each chapter of this book is available as both text and video, but they cover exactly the same
material so you’re welcome to learn whichever way suits you best. If you’re using the videos
you’ll notice that I sometimes introduce topics using slides and sometimes demonstrate them
in Xcode. It might feel repetitive, but it’s intentional – there’s a lot of things to learn, and if
you saw each one only once it just wouldn’t stay in your memory!

There’s one last thing: you might notice how many chapters start with “How to…”, and that’s
intentional – this book is here to show you how to do things in a hands-on way, as opposed to
delving into theory. Theory is important, and you’ll come across a lot of it as you can continue
to learn, but here the focus is relentlessly practical because I believe the best way to learn
something new is to try it yourself.

Programming is an art: don't spend all your time sharpening your pencil when you should be
drawing.

8 www.hackingwithswift.com
How to follow along
There’s a lot of code shown off in this book, and I really want to encourage you to try it all
yourself – type the code into your computer, run it and see the output, then experiment a little
to make sure you understand it.

To run the code in this book you should have installed Xcode 13.0 or later from the Mac App
Store. It’s free, and includes everything you need to follow along.

We’ll be using a Swift Playground for all the code in this book. You can create one by
launching Xcode, then going to the File menu and choosing New > Playground. When you’re
asked what kind of playground to create, choose Blank from the macOS tab, then save it
somewhere you can get to easily.

Playgrounds are like little sandboxes where you can try out Swift code easily, seeing the result
of your work side by side with the code. You can use one playground for all the work you’ll be
doing, or create new a playground for each chapter – do whatever works best for you.

Tip: The first time you run code in a playground it might take a few seconds to start, but
subsequent runs will be fast.

www.hackingwithswift.com 9
Chapter 2
Simple data

10 www.hackingwithswift.com
How to create variables and
constants
Whenever you build programs, you’re going to want to store some data. Maybe it’s the user’s
name they just typed in, maybe it’s some news stories you downloaded from the internet, or
maybe it’s the result of a complex calculation you just performed.

Swift gives us two ways of storing data, depending on whether you want the data to change
over time. The first option is automatically used when you create a new playground, because it
will contain this line:

var greeting = "Hello, playground"

That creates a new variable called greeting, and because it’s a variable its value can vary – it
can change as our program runs.

Tip: The other line in a macOS playground is import Cocoa, which brings in a huge
collection of code provided by Apple to make app building easier. This includes lots of
important functionality, so please don’t delete it.

There are really four pieces of syntax in there:

• The var keyword means “create a new variable”; it saves a little typing.
• We’re calling our variable greeting. You can call your variable anything you want, but
most of the time you’ll want to make it descriptive.
• The equals sign assigns a value to our variable. You don’t need to have those spaces on
either side of the equals sign if you don’t want to, but it’s the most common style.
• The value we’re assigning is the text “Hello, playground”. Notice that text is written inside
double quotes, so that Swift can see where the text starts and where it ends.

If you’ve used other languages, you might have noticed that our code doesn’t need a semicolon
at the end of the line. Swift does allow semicolons, but they are very rare – you’ll only ever

www.hackingwithswift.com 11
Simple data

need them if you want to write two pieces of code on the same line for some reason.

When you make a variable, you can change it over time:

var name = "Ted"


name = "Rebecca"
name = "Keeley"

That creates a new variable called name, and gives it the value “Ted”. It then gets changed
twice, first to “Rebecca” and then to “Keeley” – we don’t use var again because we are
modifying an existing variable rather than creating a new one. You can change variables as
much as you need to, and the old value is discarded each time.

(You’re welcome to put different text in your variables, but I’m a big fan of the TV show Ted
Lasso so I went with Ted. And yes, you can expect other Ted Lasso references and more in the
following chapters.)

If you don’t ever want to change a value, you need to use a constant instead. Creating a
constant works almost identically to creating a variable, except we use let rather than var, like
this:

let character = "Daphne"

Now, when we use let we make a constant, which is a value that can’t change. Swift literally
won’t let us, and will show a big error if we try.

Don’t believe me? Try putting this into Xcode:

let character = "Daphne"


character = "Eloise"
character = "Francesca"

Again, there are no let keywords in those second and third lines because we aren’t creating
new constants, we’re just trying to change the one we already have. However, like I said that

12 www.hackingwithswift.com
How to create variables and constants

won’t work – you can’t change a constant, otherwise it wouldn’t be constant!

If you were curious, “let” comes from the mathematics world, where they say things like “let x
be equal to 5.”

Important: Please delete the two lines of code that are showing errors – you really can’t
change constants!

When you’re learning Swift, you can ask Xcode to print out the value of any variable. You
won’t use this much in real apps because users can’t see what’s printed, but it’s really helpful
as a simple way of seeing what’s inside your data.

For example, we could print out the value of a variable each time it’s set – try entering this into
your playground:

var playerName = "Roy"


print(playerName)

playerName = "Dani"
print(playerName)

playerName = "Sam"
print(playerName)

Tip: You can run code in your Xcode playground by clicking the blue play icon to the left of
it. If you move up or down along that blue strip, you’ll see the play icon moves too – this lets
you run the code up to a certain point if you want, but most of the time here you’ll want to run
up to the last line.

You might have noticed that I named my variable playerName, and not playername,
player_name, or some other alternative. This is a choice: Swift doesn’t really care what you
name your constants and variables, as long as you refer to them the same way everywhere. So,
I can’t use playerName first then playername later – Swift sees those two as being different
names.

www.hackingwithswift.com 13
Simple data
names.

Although Swift doesn’t care how we name our data, the naming style I’m using is the standard
among Swift developers – what we call a convention. If you’re curious, the style is called
“camel case”, because the second and subsequent words in a name start with a little bump for
the capital letter:

let managerName = "Michael Scott"


let dogBreed = "Samoyed"
let meaningOfLife = "How many roads must a man walk down?"

If you can, prefer to use constants rather than variables – not only does it give Swift the chance
to optimize your code a little better, but it also allows Swift to make sure you never change a
constant’s value by accident.

14 www.hackingwithswift.com
⭐ ⭐

How to create strings


When you assign text to a constant or variable, we call that a string – think of a bunch of
Scrabble tiles threaded onto a string to make a word.

Swift’s strings start and end with double quotes, but what you put inside those quotes is down
to you. You can use short pieces of alphabetic text, like this:

let actor = "Denzel Washington"

You can use punctuation, emoji and other characters, like this:

let filename = "paris.jpg"


let result = " You win! "

And you can even use other double quotes inside your string, as long as you’re careful to put a
backslash before them so that Swift understands they are inside the string rather than ending
the string:

let quote = "Then he tapped a sign saying \"Believe\" and


walked away."

Don’t worry – if you miss off the backslash, Swift will be sure to shout loudly that your code
isn’t quite right.

There is no realistic limit to the length of your strings, meaning that you could use a string to
store something very long such as the complete works of Shakespeare. However, what you’ll
find is that Swift doesn’t like line breaks in its strings. So, this kind of code isn’t allowed:

let movie = "A day in


the life of an
Apple engineer"

That doesn’t mean you can’t create strings across multiple lines, just that Swift needs you to

www.hackingwithswift.com 15
Simple data

treat them specially: rather than one set of quotes on either side of your string, you use three,
like this:

let movie = """


A day in
the life of an
Apple engineer
"""

These multi-line strings aren’t used terribly often, but at least you can see how it’s done: the
triple quotes at the start and end are on their own line, with your string in between.

Once you’ve created your string, you’ll find that Swift gives us some useful functionality to
work with its contents. You’ll learn more about this functionality over time, but I want to
introduce you to three things here.

First, you can read the length of a string by writing .count after the name of the variable or
constant:

print(actor.count)

Because actor has the text “Denzel Washington”, that will print 17 – one for each letter in the
name, plus the space in the middle.

You don’t need to print the length of a string directly if you don’t want to – you can assign it to
another constant, like this:

let nameLength = actor.count


print(nameLength)

The second useful piece of functionality is uppercased(), which sends back the same string
except every one of its letter is uppercased:

print(result.uppercased())

16 www.hackingwithswift.com
How to create strings

Yes, the open and close parentheses are needed here but aren’t needed with count. The reason
for this will become clearer as you learn, but at this early stage in your Swift learning the
distinction is best explained like this: if you’re asking Swift to read some data you don’t need
the parentheses, but if you’re asking Swift to do some work you do. That’s not wholly true as
you’ll learn later, but it’s enough to get you moving forward for now.

The last piece of helpful string functionality is called hasPrefix(), and lets us know whether a
string starts with some letters of our choosing:

print(movie.hasPrefix("A day"))

There’s also a hasSuffix() counterpart, which checks whether a string ends with some text:

print(filename.hasSuffix(".jpg"))

Important: Strings are case-sensitive in Swift, which means using


filename.hasSuffix(".JPG") will return false because the letters in the string are lowercase.

Strings are really powerful in Swift, and we’ve only really scratched the surface of what they
can do!

www.hackingwithswift.com 17
How to store whole numbers
When you’re working with whole numbers such as 3, 5, 50, or 5 million, you’re working with
what Swift calls integers, or Int for short – “integer” is originally a Latin word meaning
“whole”, if you were curious.

Making a new integer works just like making a string: use let or var depending on whether
you want a constant or variable, provide a name, then give it a value. For example, we could
create a score constant like this:

let score = 10

Integers can be really big – past billions, past trillions, past quadrillions, and well into
quintillions, but they they can be really small too – they can hold negative numbers up to
quintillions.

When you’re writing out numbers by hand, it can be hard to see quite what’s going on. For
example, what number is this?

let reallyBig = 100000000

If we were writing that out by hand we’d probably write “100,000,000” at which point it’s
clear that the number is 100 million. Swift has something similar: you can use underscores, _,
to break up numbers however you want.

So, we could change our previous code to this:

let reallyBig = 100_000_000

Swift doesn’t actually care about the underscores, so if you wanted you could write this
instead:

let reallyBig = 1_00__00___00____00

18 www.hackingwithswift.com
How to store whole numbers

The end result is the same: reallyBig gets set to an integer with the value of 100,000,000.

Of course, you can also create integers from other integers, using the kinds of arithmetic
operators that you learned at school: + for addition, - for subtraction, * for multiplication, and /
for division.

For example:

let lowerScore = score - 2


let higherScore = score + 10
let doubledScore = score * 2
let squaredScore = score * score
let halvedScore = score / 2
print(score)

Rather than making new constants each time, Swift has some special operations that adjust an
integer somehow and assigns the result back to the original number.

For example, this creates a counter variable equal to 10, then adds 5 more to it:

var counter = 10
counter = counter + 5

Rather than writing counter = counter + 5, you can use the shorthand operator +=, which adds
a number directly to the integer in question:

counter += 5
print(counter)

That does exactly the same thing, just with less typing. We call these compound assignment
operators, and they come in other forms:

counter *= 2
print(counter)

www.hackingwithswift.com 19
Simple data

counter -= 10
print(counter)
counter /= 2
print(counter)

Before we’re done with integers, I want to mention one last thing: like strings, integers have
some useful functionality attached. For example, you can call isMultiple(of:) on an integer to
find out whether it’s a multiple of another integer.

So, we could ask whether 120 is a multiple of three like this:

let number = 120


print(number.isMultiple(of: 3))

I’m calling isMultiple(of:) on a constant there, but you can just use the number directly if you
want:

print(120.isMultiple(of: 3))

20 www.hackingwithswift.com
How to store decimal numbers
When you’re working with decimal numbers such as 3.1, 5.56, or 3.141592654, you’re
working with what Swift calls floating-point numbers. The name comes from the surprisingly
complex way the numbers are stored by your computer: it tries to store very large numbers
such as 123,456,789 in the same amount of space as very small numbers such as
0.0000000001, and the only way it can do that is by moving the decimal point around based on
the size of the number.

This storage method causes decimal numbers to be notoriously problematic for programmers,
and you can get a taste of this with just two lines of Swift code:

let number = 0.1 + 0.2


print(number)

When that runs it won’t print 0.3. Instead, it will print 0.30000000000000004 – that 0.3, then
15 zeroes, then a 4 because… well, like I said, it’s complex.

I’ll explain more why it’s complex in a moment, but first let’s focus on what matters.

First, when you create a floating-point number, Swift considers it to be a Double. That’s short
for “double-precision floating-point number”, which I realize is quite a strange name – the way
we’ve handled floating-point numbers has changed a lot over the years, and although Swift
does a good job of simplifying this you might sometimes meet some older code that is more
complex. In this case, it means Swift allocates twice the amount of storage as some older
languages would do, meaning a Double can store absolutely massive numbers.

Second, Swift considers decimals to be a wholly different type of data to integers, which
means you can’t mix them together. After all, integers are always 100% accurate, whereas
decimals are not, so Swift won’t let you put the two of them together unless you specifically
ask for it to happen.

In practice, this means you can’t do things like adding an integer to a decimal, so this kind of
code will produce an error:

www.hackingwithswift.com 21
Simple data
code will produce an error:

let a = 1
let b = 2.0
let c = a + b

Yes, we can see that b is really just the integer 2 masquerading as a decimal, but Swift still
won’t allow that code to run. This is called type safety: Swift won’t let us mix different types
of data by accident.

If you want that to happen you need to tell Swift explicitly that it should either treat the
Double inside b as an Int:

let c = a + Int(b)

Or treat the Int inside a as a Double:

let c = Double(a) + b

Third, Swift decides whether you wanted to create a Double or an Int based on the number
you provide – if there’s a dot in there, you have a Double, otherwise it’s an Int. Yes, even if
the numbers after the dot are 0.

So:

let double1 = 3.1


let double2 = 3131.3131
let double3 = 3.0
let int1 = 3

Combined with type safety, this means that once Swift has decided what data type a constant
or variable holds, it must always hold that same data type. That means this code is fine:

var name = "Nicolas Cage"


name = "John Travolta"

22 www.hackingwithswift.com
How to store decimal numbers

But this kind of code is not:

var name = "Nicolas Cage"


name = 57

That tells Swift name will store a string, but then it tries to put an integer in there instead.

Finally, decimal numbers have the same range of operators and compound assignment
operators as integers:

var rating = 5.0


rating *= 2

Many older APIs use a slightly different way of storing decimal numbers, called CGFloat.
Fortunately, Swift lets us use regular Double numbers everywhere a CGFloat is expected, so
although you will see CGFloat appear from time to time you can just ignore it.

In case you were curious, the reason floating-point numbers are complex is because computers
are trying to use binary to store complicated numbers. For example, if you divide 1 by 3 we
know you get 1/3, but that can’t be stored in binary so the system is designed to create very
close approximations. It’s extremely efficient, and the error is so small it’s usually irrelevant,
but at least you know why Swift doesn’t let us mix Int and Double by accident!

www.hackingwithswift.com 23
How to store truth with Booleans
So far we’ve looked at strings, integers, and decimals, but there’s a fourth type of data that
snuck in at the same time: a very simple type called a Boolean, which stores either true or
false. If you were curious, Booleans were named after George Boole, an English
mathematician who spent a great deal of time researching and writing about logic.

I say that Booleans snuck in because you’ve seen them a couple of times already:

let filename = "paris.jpg"


print(filename.hasSuffix(".jpg"))

let number = 120


print(number.isMultiple(of: 3))

Both hasSuffix() and isMultiple(of:) return a new value based on their check: either the string
has the suffix or it doesn’t, and either 120 is a multiple of 3 or it isn’t. In both places there’s
always a simple true or false answer, which is where Booleans come in – they store just that,
and nothing else.

Making a Boolean is just like making the other data types, except you should assign an initial
value of either true or false, like this:

let goodDogs = true


let gameOver = false

You can also assign a Boolean’s initial value from some other code, as long as ultimately it’s
either true or false:

let isMultiple = 120.isMultiple(of: 3)

Unlike the other types of data, Booleans don’t have arithmetic operators such as + and - – after
all, what would true + true equal? However, Booleans do have one special operator, !, which

24 www.hackingwithswift.com
How to store truth with Booleans

means “not”. This flips a Boolean’s value from true to false, or false to true.

For example, we could flip a Boolean’s value like this:

var isAuthenticated = false


isAuthenticated = !isAuthenticated
print(isAuthenticated)
isAuthenticated = !isAuthenticated
print(isAuthenticated)

That will print “true” then “false” when it runs, because isAuthenticated started as false, and
we set it to not false, which is true, then flip it again so it’s back to false.

Booleans do have a little extra functionality that can be useful. In particular, if you call
toggle() on a Boolean it will flip a true value to false, and a false value to true. To try this out,
try making gameOver a variable and modifying it like this:

var gameOver = false


print(gameOver)

gameOver.toggle()
print(gameOver)

That will print false first, then after calling toggle() will print true. Yes, that’s the same as
using ! just in slightly less code, but it’s surprisingly useful when you’re dealing with complex
code!

www.hackingwithswift.com 25
How to join strings together
Swift gives us two ways to combine strings together: joining them using +, and a special
technique called string interpolation that can place variables of any type directly inside strings.

Let’s start with the easier option first, which is using + to join strings together: when you have
two strings, you can join them together into a new string just by using +, like this:

let firstPart = "Hello, "


let secondPart = "world!"
let greeting = firstPart + secondPart

You can do this many times if you need to:

let people = "Haters"


let action = "hate"
let lyric = people + " gonna " + action
print(lyric)

When that runs it will print “Haters gonna hate” – yes, I’m a big fan of Taylor Swift, and I
think her lyrics make a natural fit for a tutorial about Swift programming!

Notice how we’re using + to join two strings, but when we used Int and Double it added
numbers together? This is called operator overloading – the ability for one operator such as +
to mean different things depending on how it’s used. For strings, it also applies to +=, which
adds one string directly to another.

This technique works great for small things, but you wouldn’t want to do it too much. You see,
each time Swift sees two strings being joined together using + it has to make a new string out
of them before continuing, and if you have lots of things being joined it’s quite wasteful.

Think about this for example:

let luggageCode = "1" + "2" + "3" + "4" + "5"

26 www.hackingwithswift.com
How to join strings together

Swift can’t join all those strings in one go. Instead, it will join the first two to make “12”, then
join “12” and “3” to make “123”, then join “123” and “4” to make “1234”, and finally join
“1234” and “5” to make “12345” – it makes temporary strings to hold “12”, “123”, and “1234”
even though they aren’t ultimately used when the code finishes.

Swift has a better solution called string interpolation, and it lets us efficiently create strings
from other strings, but also from integers, decimal numbers, and more.

If you remember, earlier I said that you can include double quotes inside strings as long as they
have a backslash before them so Swift knows to treat them specially:

let quote = "Then he tapped a sign saying \"Believe\" and


walked away."

Something very similar is used with string interpolation: you write a backslash inside your
string, then place the name of a variable or constant inside parentheses.

For example, we could create one string constant and one integer constant, then combine them
into a new string:

let name = "Taylor"


let age = 26
let message = "Hello, my name is \(name) and I'm \(age) years
old."
print(message)

When that code runs, it will print “Hello, my name is Taylor and I’m 26 years old.”

String interpolation is much more efficient than using + to join strings one by one, but there’s
another important benefit too: you can pull in integers, decimals, and more with no extra work.

You see, using + lets us add strings to strings, integers to integers, and decimals to decimals,
but doesn’t let us add integers to strings. So, this kind of code is not allowed:

www.hackingwithswift.com 27
Simple
but data
doesn’t let us add integers to strings. So, this kind of code is not allowed:

let number = 11
let missionMessage = "Apollo " + number + " landed on the
moon."

You could ask Swift to treat the number like a string if you wanted, like this:

let missionMessage = "Apollo " + String(number) + " landed on


the moon."

It is still both faster and easier to read to use string interpolation:

let missionMessage = "Apollo \(number) landed on the moon."

Tip: You can put calculations inside string interpolation if you want to. For example, this will
print “5 x 5 is 25”:

print("5 x 5 is \(5 * 5)")

28 www.hackingwithswift.com
Summary: Simple data
We’ve covered a lot about the basics of data in the previous chapters, so let’s recap:

• Swift lets us create constants using let, and variables using var.
• If you don’t intend to change a value, make sure you use let so that Swift can help you
avoid mistakes.
• Swift’s strings contain text, from short strings up to whole novels. They work great with
emoji and any world language, and have helpful functionality such as count and
uppercased().
• You create strings by using double quotes at the start and end, but if you want your string
to go over several lines you need to use three double quotes at the start and end.
• Swift calls its whole numbers integers, and they can be positive or negative. They also
have helpful functionality, such as isMultiple(of:).
• In Swift decimal numbers are called Double, short for double-length floating-point
number. That means they can hold very large numbers if needed, but they also aren’t 100%
accurate – you shouldn’t use them when 100% precision is required, such as when dealing
with money.
• There are lots of built-in arithmetic operators, such as +, -, *, and /, along with the special
compound assignment operators such as += that modify variables directly.
• You can represent a simple true or false state using a Boolean, which can be flipped using
the ! operator or by calling toggle().
• String interpolation lets us place constants and variables into our strings in a streamlined,
efficient way.

It’s a lot, right? And that’s okay – you’ll be using everything from that list time and time again
as you build apps, until eventually you’ll understand it all without needing to refer back here.

www.hackingwithswift.com 29
Checkpoint 1
You already know enough to start writing your first useful code, albeit fairly simple: we’re
going to convert temperatures from Celsius to Fahrenheit.

Your goal is to write a Swift playground that:

1. Creates a constant holding any temperature in Celsius.


2. Converts it to Fahrenheit by multiplying by 9, dividing by 5, then adding 32.
3. Prints the result for the user, showing both the Celsius and Fahrenheit values.

You already know everything you need to solve that problem, but if you’d like some hints then
I’ll add some below.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 1. If you don’t already subscribe, you can start a free trial today.

Note: I really do encourage you to try building this playground before reading any hints or
trying my solution. I know it might seem simple, but the course does start to get harder soon
and it’s important to be sure you’ve learned all the fundamentals.

Please go ahead and try building the playground now.

Still here? Okay, here are some hints:

1. Use let to make your constant. You can call it whatever you want, but I think celsius would
be an appropriate name.
2. Celsius is commonly stored as a decimal, so make sure and create it as one. This might
mean adding “.0” to the end – using 25.0 rather than 25, for example.
3. We use * for multiplication and / for division.
4. Use \(someVariable) to activate string interpolation.
5. If you want to get fancy with print(), you can use Option+Shift+8 to get the degrees
symbol: °. This means you can write something like 25°F.

30 www.hackingwithswift.com
Chapter 3
Complex data

www.hackingwithswift.com 31
How to store ordered data in
arrays
It’s extremely common to want to have lots of data in a single place, whether that’s the days of
the week, a list of students in a class, a city’s population for the last 100 years, or any of
countless other examples.

In Swift, we do this grouping using an array. Arrays are their own data type just like String,
Int, and Double, but rather than hold just one string they can hold zero strings, one string, two
strings, three, fifty, fifty million, or even more strings – they can automatically adapt to hold as
many as you need, and always hold data in the order you add it.

Let’s start with some simple examples of creating arrays:

var beatles = ["John", "Paul", "George", "Ringo"]


let numbers = [4, 8, 15, 16, 23, 42]
var temperatures = [25.3, 28.2, 26.4]

That creates three different arrays: one holding strings of people’s names, one holding integers
of important numbers, and one holding decimals of temperatures in Celsius. Notice how we
start and end arrays using square brackets, with commas between every item.

When it comes to reading values out from an array, we ask for values by the position they
appear in the array. The position of an item in an array is commonly called its index.

This confuses beginners a bit, but Swift actually counts an item’s index from zero rather than
one – beatles[0] is the first element, and beatles[1] is the second, for example.

So, we could read some values out from our arrays like this:

print(beatles[0])
print(numbers[1])
print(temperatures[2])

32 www.hackingwithswift.com
How to store ordered data in arrays

Tip: Make sure an item exists at the index you’re asking for, otherwise your code will crash –
your app will just stop working.

If your array is variable, you can modify it after creating it. For example, you can use
append() to add new items:

beatles.append("Adrian")

And there’s nothing stopping you from adding items more than once:

beatles.append("Allen")
beatles.append("Adrian")
beatles.append("Novall")
beatles.append("Vivian")

However, Swift does watch the kind of data you’re trying to add, and will make sure your
array only ever contains one type of data at a time. So, this kind of code isn’t allowed:

temperatures.append("Chris")

This also applies to reading data out of the array – Swift knows that the beatles array contains
strings, so when you read one value out you’ll always get a string. If you try to do the same
with numbers, you’ll always get an integer. Swift won’t let you mix these two different types
together, so this kind of code isn’t allowed:

let firstBeatle = beatles[0]


let firstNumber = numbers[0]
let notAllowed = firstBeatle + firstNumber

This is type safety, just like how Swift won’t let us mix integers and decimals, except it’s taken
to a deeper level. Yes, all beatles and numbers are both arrays, but they are specialized types
of arrays: one is an array of strings, and one is an array of integers.

www.hackingwithswift.com 33
Complex data

You can see this more clearly when you want to start with an empty array and add items to it
one by one. This is done with very precise syntax:

var scores = Array<Int>()


scores.append(100)
scores.append(80)
scores.append(85)
print(scores[1])

We’ve covered the last four lines already, but that first line shows how we have a specialized
array type – this isn’t just any array, it’s an array that holds integers. This is what allows Swift
to know for sure that beatles[0] must always be a string, and also what stops us from adding
integers to a string array.

The open and closing parentheses after Array<Int> are there because it’s possible to
customize the way the array is created if you need to. For example, you might want to fill the
array with lots of temporary data before adding the real stuff later on.

You can make other kinds of array by specializing it in different ways, like this:

var albums = Array<String>()


albums.append("Folklore")
albums.append("Fearless")
albums.append("Red")

Again, we’ve said that must always contain strings, so we can’t try to put an integer in there.

Arrays are so common in Swift that there’s a special way to create them: rather than writing
Array<String>, you can instead write [String]. So, this kind of code is exactly the same as
before:

var albums = [String]()


albums.append("Folklore")
albums.append("Fearless")

34 www.hackingwithswift.com
How to store ordered data in arrays

albums.append("Red")

Swift’s type safety means that it must always know what type of data an array is storing. That
might mean being explicit by saying albums is an Array<String>, but if you provide some
initial values Swift can figure it out for itself:

var albums = ["Folklore"]


albums.append("Fearless")
albums.append("Red")

Before we’re done, I want to mention some useful functionality that comes with arrays.

First, you can use .count to read how many items are in an array, just like you did with strings:

print(albums.count)

Second, you can remove items from an array by using either remove(at:) to remove one item
at a specific index, or removeAll() to remove everything:

var characters = ["Lana", "Pam", "Ray", "Sterling"]


print(characters.count)

characters.remove(at: 2)
print(characters.count)

characters.removeAll()
print(characters.count)

That will print 4, then 3, then 0 as characters are removed.

Third, you can check whether an array contains a particular item by using contains(), like this:

let bondMovies = ["Casino Royale", "Spectre", "No Time To Die"]


print(bondMovies.contains("Frozen"))

www.hackingwithswift.com 35
Complex data

Fourth, you can sort an array using sorted(), like this:

let cities = ["London", "Tokyo", "Rome", "Budapest"]


print(cities.sorted())

That returns a new array with its items sorted in ascending order, which means alphabetically
for strings but numerically for numbers – the original array remains unchanged.

Finally, you can reverse an array by calling reversed() on it:

let presidents = ["Bush", "Obama", "Trump", "Biden"]


let reversedPresidents = presidents.reversed()
print(reversedPresidents)

Tip: When you reverse an array, Swift is very clever – it doesn’t actually do the work of
rearranging all the items, but instead just remembers to itself that you want the items to be
reversed. So, when you print out reversedPresidents, don’t be surprised to see it’s not just a
simple array any more!

Arrays are extremely common in Swift, and you’ll have lots of opportunity to learn more about
them as you progress. Even better sorted(), reversed(), and lots of other array functionality
also exists for strings – using sorted() there puts the string’s letters in alphabetical order,
making something like “swift” into “fistw”.

36 www.hackingwithswift.com
How to store and find data in
dictionaries
You’ve seen how arrays are a great way to store data that has a particular order, such as days
of the week or temperatures for a city. Arrays are a great choice when items should be stored
in the order you add them, or when you might have duplicate items in there, but very often
accessing data by its position in the array can be annoying or even dangerous.

For example, here’s an array containing an employee’s details:

var employee = ["Taylor Swift", "Singer", "Nashville"]

I’ve told you that the data is about an employee, so you might be able to guess what various
parts do:

print("Name: \(employee[0])")
print("Job title: \(employee[1])")
print("Location: \(employee[2])")

But that has a couple of problems. First, you can’t really be sure that employee[2] is their
location – maybe that’s their password. Second, there’s no guarantee that item 2 is even there,
particularly because we made the array a variable. This kind of code would cause serious
problems:

print("Name: \(employee[0])")
employee.remove(at: 1)
print("Job title: \(employee[1])")
print("Location: \(employee[2])")

That now prints Nashville as the job title, which is wrong, and will cause our code to crash
when it reads employee[2], which is just bad.

Swift has a solution for both these problems, called dictionaries. Dictionaries don’t store items

www.hackingwithswift.com 37
Complex data

according to their position like arrays do, but instead let us decide where items should be
stored.

For example, we could rewrite our previous example to be more explicit about what each item
is:

let employee2 = ["name": "Taylor Swift", "job": "Singer",


"location": "Nashville"]

If we split that up into individual lines you’ll get a better idea of what the code does:

let employee2 = [
"name": "Taylor Swift",
"job": "Singer",
"location": "Nashville"
]

As you can see, we’re now being really clear: the name is Taylor Swift, the job is Singer, and
the location is Nashville. Swift calls the strings on the left – name, job, and location – the keys
to the dictionary, and the strings on the right are the values.

When it comes to reading data out from the dictionary, you use the same keys you used when
creating it:

print(employee2["name"])
print(employee2["job"])
print(employee2["location"])

If you try that in a playground, you’ll see Xcode throws up various warnings along the lines of
“Expression implicitly coerced from 'String?' to 'Any’”. Worse, if you look at the output from
your playground you’ll see it prints Optional("Taylor Swift") rather than just Taylor Swift –
what gives?

Well, think about this:

38 www.hackingwithswift.com
How to store and find data in dictionaries
Well, think about this:

print(employee2["password"])
print(employee2["status"])
print(employee2["manager"])

All of that is valid Swift code, but we’re trying to read dictionary keys that don’t have a value
attached to them. Sure, Swift could just crash here just like it will crash if you read an array
index that doesn’t exist, but that would make it very hard to work with – at least if you have an
array with 10 items you know it’s safe to read indices 0 through 9. (“Indices” is just the plural
form of “index”, in case you weren’t sure.)

So, Swift provides an alternative: when you access data inside a dictionary, it will tell us “you
might get a value back, but you might get back nothing at all.” Swift calls these optionals
because the existence of data is optional - it might be there or it might not.

Swift will even warn you when you write the code, albeit in a rather obscure way – it will say
“Expression implicitly coerced from 'String?' to 'Any’”, but it will really mean “this data might
not actually be there – are you sure you want to print it?”

Optionals are a pretty complex issue that we’ll be covering in detail later on, but for now I’ll
show you a simpler approach: when reading from a dictionary, you can provide a default value
to use if the key doesn’t exist.

Here’s how that looks:

print(employee2["name", default: "Unknown"])


print(employee2["job", default: "Unknown"])
print(employee2["location", default: "Unknown"])

All the examples have used strings for both the keys and values, but you can use other data
types for either of them. For example, we could track which students have graduated from
school using strings for names and Booleans for their graduation status:

let hasGraduated = [

www.hackingwithswift.com 39
Complex data

"Eric": false,
"Maeve": true,
"Otis": false,
]

Or we could track years when Olympics took place along with their locations:

let olympics = [
2012: "London",
2016: "Rio de Janeiro",
2021: "Tokyo"
]

print(olympics[2012, default: "Unknown"])

You can also create an empty dictionary using whatever explicit types you want to store, then
set keys one by one:

var heights = [String: Int]()


heights["Yao Ming"] = 229
heights["Shaquille O'Neal"] = 216
heights["LeBron James"] = 206

Notice how we need to write [String: Int] now, to mean a dictionary with strings for its keys
and integers for its values.

Because each dictionary item must exist at one specific key, dictionaries don’t allow duplicate
keys to exist. Instead, if you set a value for a key that already exists, Swift will overwrite
whatever was the previous value.

For example, if you were chatting with a friend about superheroes and supervillains, you might
store them in a dictionary like this:

var archEnemies = [String: String]()

40 www.hackingwithswift.com
How to store and find data in dictionaries

archEnemies["Batman"] = "The Joker"


archEnemies["Superman"] = "Lex Luthor"

If your friend disagrees that The Joker is Batman’s arch-enemy, you can just rewrite that value
by using the same key:

archEnemies["Batman"] = "Penguin"

Finally, just like arrays and the other data types we’ve seen so far, dictionaries come with some
useful functionality that you’ll want to use in the future – count and removeAll() both exists
for dictionaries, and work just like they do for arrays.

www.hackingwithswift.com 41
How to use sets for fast data
lookup
So far you’ve learned about two ways of collecting data in Swift: arrays and dictionaries.
There is a third very common way to group data, called a set – they are similar to arrays,
except you can’t add duplicate items, and they don’t store their items in a particular order.

Creating a set works much like creating an array: tell Swift what kind of data it will store, then
go ahead and add things. There are two important differences, though, and they are best
demonstrated using some code.

First, here’s how you would make a set of actor names:

let people = Set(["Denzel Washington", "Tom Cruise", "Nicolas


Cage", "Samuel L Jackson"])

Notice how that actually creates an array first, then puts that array into the set? That’s
intentional, and it’s the standard way of creating a set from fixed data. Remember, the set will
automatically remove any duplicate values, and it won’t remember the exact order that was
used in the array.

If you’re curious how the set has ordered the data, just try printing it out:

print(people)

You might see the names in the original order, but you might also get a completely different
order – the set just doesn’t care what order its items come in.

The second important difference when adding items to a set is visible when you add items
individually. Here’s the code:

var people = Set<String>()


people.insert("Denzel Washington")

42 www.hackingwithswift.com
How to use sets for fast data lookup

people.insert("Tom Cruise")
people.insert("Nicolas Cage")
people.insert("Samuel L Jackson")

Notice how we’re using insert()? When we had an array of strings, we added items by calling
append(), but that name doesn’t make sense here – we aren’t adding an item to the end of the
set, because the set will store the items in whatever order it wants.

Now, you might think sets just sound like simplified arrays – after all, if you can’t have
duplicates and you lose the order of your items, why not just use arrays? Well, both of those
restrictions actually get turned into an advantage.

First, not storing duplicates is sometimes exactly what you want. There’s a reason I chose
actors in the previous example: the Screen Actors Guild requires that all its members have a
unique stage name to avoid confusion, which means that duplicates must never be allowed. For
example, the actor Michael Keaton (Spider-Man Homecoming, Toy Story 3, Batman, and
more) is actually named Michael Douglas, but because there was already a Michael Douglas in
the guild (Avengers, Falling Down, Romancing the Stone, and more), he had to have a unique
name.

Second, instead of storing your items in the exact order you specify, sets instead store them in
a highly optimized order that makes it very fast to locate items. And the difference isn’t small:
if you have an array of 1000 movie names and use something like contains() to check whether
it contains “The Dark Knight” Swift needs to go through every item until it finds one that
matches – that might mean checking all 1000 movie names before returning false, because The
Dark Knight wasn’t in the array.

In comparison, calling contains() on a set runs so fast you’d struggle to measure it


meaningfully. Heck, even if you had a million items in the set, or even 10 million items, it
would still run instantly, whereas an array might take minutes or longer to do the same work.

Most of the time you’ll find yourself using arrays rather than sets, but sometimes – just
sometimes – you’ll find that a set is exactly the right choice to solve a particular problem, and

www.hackingwithswift.com 43
Complex data

it will make otherwise slow code run in no time at all.

Tip: Alongside contains(), you’ll also find count to read the number of items in a set, and
sorted() to return a sorted array containing the the set’s items.

44 www.hackingwithswift.com
How to create and use enums
An enum – short for enumeration – is a set of named values we can create and use in our code.
They don’t have any special meaning to Swift, but they are more efficient and safer, so you’ll
use them a lot in your code.

To demonstrate the problem, let’s say you wanted to write some code to let the user select a
day of the week. You might start out like this:

var selected = "Monday"

Later on in your code you change it, like so:

selected = "Tuesday"

That might work well in very simple programs, but take a look at this code:

selected = "January"

Oops! You accidentally typed in a month rather than a day – what will your code do? Well,
you might be lucky enough to have a colleague spot the error as they review your code, but
how about this:

selected = "Friday "

That has a space at the end of Friday, and “Friday ” with a space is different from “Friday”
without a space in Swift’s eyes. Again, what would your code do?

Using strings for this kind of thing takes some very careful programming, but it’s also pretty
inefficient – do we really need to store all the letters of “Friday” to track one single day?

This is where enums come in: they let us define a new data type with a handful of specific
values that it can have. Think of a Boolean, that can only have true or false – you can’t set it to
“maybe” or “probably”, because that isn’t in the range of values it understands. Enums are the

www.hackingwithswift.com 45
Complex data

same: we get to list up front the range of values it can have, and Swift will make sure you
never make a mistake using them.

So, we could rewrite our weekdays into a new enum like this:

enum Weekday {
case monday
case tuesday
case wednesday
case thursday
case friday
}

That calls the new enum Weekday, and provides five cases to handle the five weekdays.

Now rather than using strings, we would use the enum. Try this in your playground:

var day = Weekday.monday


day = Weekday.tuesday
day = Weekday.friday

With that change you can’t accidentally use “Friday ” with an extra space in there, or put a
month name instead – you must always choose one of the possible days listed in the enum.
You’ll even see Swift offer up all possible options when you’ve typed Weekday., because it
knows you’re going to select one of the cases.

Swift does two things that make enums a little easier to use. First, when you have many cases
in an enum you can just write case once, then separate each case with a comma:

enum Weekday {
case monday, tuesday, wednesday, thursday, friday
}

Second, remember that once you assign a value to a variable or constant, its data type becomes

46 www.hackingwithswift.com
How to create and use enums

fixed – you can’t set a variable to a string at first, then an integer later on. Well, for enums this
means you can skip the enum name after the first assignment, like this:

var day = Weekday.monday


day = .tuesday
day = .friday

Swift knows that .tuesday must refer to Weekday.tuesday because day must always be some
kind of Weekday.

Although it isn’t visible here, one major benefit of enums is that Swift stores them in an
optimized form – when we say Weekday.monday Swift is likely to store that using a single
integer such as 0, which is much more efficient to store and check than the letters M, o, n, d, a,
y.

www.hackingwithswift.com 47
How to use type annotations
Swift is able to figure out what type of data a constant or variable holds based on what we
assign to it. However, sometimes we don’t want to assign a value immediately, or sometimes
we want to override Swift’s choice of type, and that’s where type annotations come in.

So far we’ve been making constants and variables like this:

let surname = "Lasso"


var score = 0

This uses type inference: Swift infers that surname is a string because we’re assigning text to
it, and then infers that score is an integer because we’re assigning a whole number to it.

Type annotations let us be explicit about what data types we want, and look like this:

let surname: String = "Lasso"


var score: Int = 0

Now we’re being explicit: surname must be a string, and score must be an integer. That’s
exactly what Swift’s type inference would have done anyway, but sometimes it isn’t –
sometimes you will want to choose a different type.

For example, maybe score is a decimal because the user can get half points, so you’d write
this:

var score: Double = 0

Without the : Double part Swift would infer that to be an integer, but we’re overriding that and
saying it’s definitely a decimal number.

We’ve looked at a few types of data so far, and it’s important you know their names so you can
use the right type annotation when needed.

48 www.hackingwithswift.com
How to use type annotations

String holds text:

let playerName: String = "Roy"

Int holds whole numbers:

var luckyNumber: Int = 13

Double holds decimal numbers:

let pi: Double = 3.141

Bool holds either true or false:

var isAuthenticated: Bool = true

Array holds lots of different values, all in the order you add them. This must be specialized,
such as [String]:

var albums: [String] = ["Red", "Fearless"]

Dictionary holds lots of different values, where you get to decide how data should be
accessed. This must be specialized, such as [String: Int]:

var user: [String: String] = ["id": "@twostraws"]

Set holds lots of different values, but stores them in an order that’s optimized for checking
what it contains. This must be specialized, such as Set<String>:

var books: Set<String> = Set(["The Bluest Eye", "Foundation",


"Girl, Woman, Other"])

Knowing all these types is important for times when you don’t want to provide initial values.
For example, this creates an array of strings:

www.hackingwithswift.com 49
Complex
For data
example, this creates an array of strings:

var soda: [String] = ["Coke", "Pepsi", "Irn-Bru"]

Type annotation isn’t needed there, because Swift can see you’re assigning an array of strings.
However, if you wanted to create an empty array of strings, you’d need to know the type:

var teams: [String] = [String]()

Again, the type annotation isn’t required, but you still need to know that an array of strings is
written as [String] so that you can make the thing. Remember, you need to add the open and
close parentheses when making empty arrays, dictionaries, and sets, because it’s where Swift
allows us to customize the way they are created.

Some people prefer to use type annotation, then assign an empty array to it like this:

var cities: [String] = []

I prefer to use type inference as much as possible, so I’d write this:

var clues = [String]()

As well as all those, there are enums. Enums are a little different from the others because they
let us create new types of our own, such as an enum containing days of the week, an enum
containing which UI theme the user wants, or even an enum containing which screen is
currently showing in our app.

Values of an enum have the same type as the enum itself, so we could write something like
this:

enum UIStyle {
case light, dark, system
}

var style = UIStyle.light

50 www.hackingwithswift.com
How to use type annotations

This is what allows Swift to remove the enum name for future assignments, so we can write
style = .dark – it knows any new value for style must be some kind UIStyle

Now, there’s a very good chance you’ll be asking when you should use type annotations, so it
might be helpful for you to know that I prefer to use type inference as much as possible,
meaning that I assign a value to a constant or variable and Swift chooses the correct type
automatically. Sometimes this means using something like var score = 0.0 so that I get a
Double.

The most common exception to this is with constants I don’t have a value for yet. You see,
Swift is really clever: you can create a constant that doesn’t have a value just yet, later on
provide that value, and Swift will ensure we don’t accidentally use it until a value is present. It
will also ensure that you only ever set the value once, so that it remains constant.

For example:

let username: String


// lots of complex logic
username = "@twostraws"
// lots more complex logic
print(username)

That code is legal: we’re saying username will contain a string at some point, and we provide
a value before using it. If the assignment line – username = "@twostraws" – was missing,
then Swift would refuse to build our code because username wouldn’t have a value, and
similarly if we tried to set a value to username a second time Swift would also complain.

This kind of code requires a type annotation, because without an initial value being assigned
Swift doesn’t know what kind of data username will contain.

Regardless of whether you use type inference or type annotation, there is one golden rule:
Swift must at all times know what data types your constants and variables contain. This is at
the core of being a type-safe language, and stops us doing nonsense things like 5 + true or
similar.

www.hackingwithswift.com 51
Complex data
similar.

Important: Although type annotation can let us override Swift’s type inference to a degree,
our finished code must still be possible. For example, this is not allowed:

let score: Int = "Zero"

Swift just can’t convert “Zero” to an integer for us, even with a type annotation requesting it,
so the code just won’t build.

52 www.hackingwithswift.com
Summary: Complex data
We’ve gone beyond simple data types now, and started looking at ways to group them together
and even create our own using enums. So, let’s recap:

• Arrays let us store lots of values in one place, then read them out using integer indices.
Arrays must always be specialized so they contain one specific type, and they have helpful
functionality such as count, append(), and contains().
• Dictionaries also let us store lots of values in one place, but let us read them out using keys
we specify. They must be specialized to have one specific type for key and another for the
value, and have similar functionality to arrays, such as contains() and count.
• Sets are a third way of storing lots of values in one place, but we don’t get to choose the
order in which they store those items. Sets are really efficient at finding whether they
contain a specific item.
• Enums let us create our own simple types in Swift so that we can specify a range of
acceptable values such as a list of actions the user can perform, the types of files we are
able to write, or the types of notification to send to the user.
• Swift must always know the type of data inside a constant or variable, and mostly uses type
inference to figure that out based on the data we assign. However, it’s also possible to use
type annotation to force a particular type.

Out of arrays, dictionaries, and sets, it’s safe to say that you’ll use arrays by far the most. After
that comes dictionaries, and sets come a distant third. That doesn’t mean sets aren’t useful, but
you’ll know when you need them!

www.hackingwithswift.com 53
Checkpoint 2
Now that you’ve met arrays, dictionaries, and sets, I want to pause for a moment to give you
chance to solve a small coding challenge. It’s not designed to trip you up, but instead to
encourage you to stop for a while and think about what you’ve learned.

This time the challenge is to create an array of strings, then write some code that prints the
number of items in the array and also the number of unique items in the array.

I’ll provide some hints below, but please take the time to think about a solution before you
read them. Trust me: forgetting what you’ve learned then re-learning it actually makes it sink
in deeper!

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 2. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

1. You should start by creating an array of strings, using something like let albums = ["Red",
"Fearless"]
2. You can read the number of items in your array using albums.count.
3. count also exists for sets.
4. Sets can be made from arrays using Set(someArray)
5. Sets never include duplicates.

54 www.hackingwithswift.com
Chapter 4
Conditions and loops

www.hackingwithswift.com 55
How to check a condition is true
or false
Programs very often make choices:

• If the student’s exam score was over 80 then print a success message.
• If the user entered a name that comes after their friend’s name alphabetically, put the
friend’s name first.
• If adding a number to an array makes it contain more than 3 items, remove the oldest one.
• If the user was asked to enter their name and typed nothing at all, give them a default name
of “Anonymous”.

Swift handles these with if statements, which let us check a condition and run some code if the
condition is true. They look like this:

if someCondition {
print("Do something")
}

Let’s break that down:

1. The condition starts with if, which signals to Swift we want to check some kind of
condition in our code.
2. The someCondition part is where you write your condition – was the score over 80? Does
the array contain more than 3 items?
3. If the condition is true – if the score really is over 80 – then we print the “Do something”
message.

Of course, that isn’t everything in the code: I didn’t mention the little { and } symbols. These
are called braces – opening and closing braces, more specifically – although sometimes you’ll
hear them referred to as “curly braces” or “curly brackets”.

56 www.hackingwithswift.com
How to check a condition is true or false

These braces are used extensively in Swift to mark blocks of code: the opening brace starts the
block, and the closing brace ends it. Inside the code block is all the code we want to run if our
condition happens to be true when it’s checked, which in our case is printing a message.

You can include as much code in there as you want:

if someCondition {
print("Do something")
print("Do something else")
print("Do a third thing")
}

Of course, what really matters is the someCondition part, because that’s where your checking
code comes in: what condition do you actually want to check?

Well, let’s try the score example: if a score constant is over 80, let’s print a message. Here’s
how that would look in code:

let score = 85

if score > 80 {
print("Great job!")
}

In that code, score > 80 is our condition. You’ll remember > from school meaning “is greater
than”, so our complete condition is “if score is greater than 80.” And if it is greater than 80,
“Great job!” will be printed – nice!

That > symbol is a comparison operator, because it compares two things and returns a Boolean
result: is the thing on the left greater than the thing on the right? You can also use < for less
than, >= for “greater than or equal”, and <= for “less than or equal.”

Let’s try it out – what do you think this code will print?

www.hackingwithswift.com 57
Conditions and loops

let speed = 88
let percentage = 85
let age = 18

if speed >= 88 {
print("Where we're going we don't need roads.")
}

if percentage < 85 {
print("Sorry, you failed the test.")
}

if age >= 18 {
print("You're eligible to vote")
}

Try and run the code mentally in your head – which print() lines will actually be run?

Well, our first one will run if speed is greater than or equal to 88, and because it is exactly 88
the first print() code will be run.

The second one will run if percentage is less than 85, and because it is exactly 85 the second
print() will not run – we used less than, not less than or equal.

The third will run if age is greater than or equal to 18, and because it’s exactly 18 the third
print() will run.

Now let’s try our second example condition: if the user entered a name that comes after their
friend’s name alphabetically, put the friend’s name first. You’ve seen how <, >= and others
work great with numbers, but they also work equally well with strings right out of the box:

let ourName = "Dave Lister"


let friendName = "Arnold Rimmer"

58 www.hackingwithswift.com
How to check a condition is true or false

if ourName < friendName {


print("It's \(ourName) vs \(friendName)")
}

if ourName > friendName {


print("It's \(friendName) vs \(ourName)")
}

So, if the string inside ourName comes before the string inside friendName when sorted
alphabetically, it prints ourName first then friendName, exactly as we wanted.

Let’s take a look at our third example condition: if adding a number to an array makes it
contain more than 3 items, remove the oldest one. You’ve already met append(), count, and
remove(at:), so we can now put all three together with a condition like this:

// Make an array of 3 numbers


var numbers = [1, 2, 3]

// Add a 4th
numbers.append(4)

// If we have over 3 items


if numbers.count > 3 {
// Remove the oldest number
numbers.remove(at: 0)
}

// Display the result


print(numbers)

Now let’s look at our fourth example condition: if the user was asked to enter their name and
typed nothing at all, give them a default name of “Anonymous”.

www.hackingwithswift.com 59
Conditions and loops

To solve this you’ll first need to meet two other comparison operators you’ll use a lot, both of
which handle equality. The first is == and means “is equal to,” which is used like this:

let country = "Canada"

if country == "Australia" {
print("G'day!")
}

The second is !=, which means “is not equal to”, and is used like this:

let name = "Taylor Swift"

if name != "Anonymous" {
print("Welcome, \(name)")
}

In our case, we want to check whether the username entered by the user is empty, which we
could do like this:

// Create the username variable


var username = "taylorswift13"

// If `username` contains an empty string


if username == "" {
// Make it equal to "Anonymous"
username = "Anonymous"
}

// Now print a welcome message


print("Welcome, \(username)!")

That "" is an empty string: we start the string and end the string, with nothing in between. By

60 www.hackingwithswift.com
How to check a condition is true or false

comparing username to that, we’re checking if the user also entered an empty string for their
username, which is exactly what we want.

Now, there are other ways of doing this check, and it’s important you understand what they do.

First, we could compare the count of the string – how many letters it has – against 0, like this:

if username.count == 0 {
username = "Anonymous"
}

Comparing one string against another isn’t very fast in any language, so we’ve replaced the
string comparison with an integer comparison: does the number of letters in the string equal 0?

In many languages that’s very fast, but not in Swift. You see, Swift supports all sorts of
complex strings – literally every human language works out of the box, including emoji, and
that just isn’t true in so many other programming languages. However, this really great support
has a cost, and one part of that cost is that asking a string for its count makes Swift go through
and count up all the letters one by one – it doesn’t just store its length separately from the
string.

So, think about the situation where you have a massive string that stores the complete works of
Shakespeare. Our little check for count == 0 has to go through and count all the letters in the
string, even though as soon as we have counted at least one character we know the answer to
our question.

As a result, Swift adds a second piece of functionality to all its strings, arrays, dictionaries, and
sets: isEmpty. This will send back true if the thing you’re checking has nothing inside, and we
can use it to fix our condition like this:

if username.isEmpty == true {
username = "Anonymous"
}

www.hackingwithswift.com 61
Conditions and loops

That’s better, but we can go one step further. You see, ultimately what matters is that your
condition must boil down to either true or false; Swift won’t allow anything else. In our case,
username.isEmpty is already a Boolean, meaning it will be true or false, so we can make our
code even simpler:

if username.isEmpty {
username = "Anonymous"
}

If isEmpty is true the condition passes and username gets set to Anonymous, otherwise the
condition fails.

62 www.hackingwithswift.com
How to check multiple conditions
When we use if we must provide Swift some kind of condition that will either be true or false
once it has been evaluated. If you want to check for several different values, you can place
them one after the other like this:

let age = 16

if age >= 18 {
print("You can vote in the next election.")
}

if age < 18 {
print("Sorry, you're too young to vote.")
}

However, that’s not very efficient if you think about it: our two conditions are mutually
exclusive, because if someone is greater than or equal to 18 (the first condition) then they can’t
be less than 18 (the second condition), and the opposite is also true. We’re making Swift do
work that just isn’t needed.

In this situation, Swift provides us with a more advanced condition that lets us add an else
block to our code – some code to run if the condition is not true.

Using else we could rewrite our previous code to this:

let age = 16

if age >= 18 {
print("You can vote in the next election.")
} else {
print("Sorry, you're too young to vote.")
}

www.hackingwithswift.com 63
Conditions and loops

Now Swift only needs to check age once: if it’s greater than or equal to 18 the first print()
code is run, but if it’s any value less than 18 the second print() code is run.

So, now our condition looks like this:

if someCondition {
print("This will run if the condition is true")
} else {
print("This will run if the condition is false")
}

There’s an even more advanced condition called else if, which lets you run a new check if the
first one fails. You can have just one of these if you want, or have multiple else if, and even
combine else if with an else if needed. However, you can only ever have one else, because that
means “if all the other conditions have been false.”

Here’s how that looks:

let a = false
let b = true

if a {
print("Code to run if a is true")
} else if b {
print("Code to run if a is false but b is true")
} else {
print("Code to run if both a and b are false")
}

You can keep on adding more and more else if conditions if you want, but watch out that your
code doesn’t get too complicated!

As well as using else and else if to make more advanced conditions, you can also check more

64 www.hackingwithswift.com
How to check multiple conditions

than one thing. For example, we might want to say “if today’s temperature is over 20 degrees
Celsius but under 30, print a message.”

This has two conditions, so we could write this:

let temp = 25

if temp > 20 {
if temp < 30 {
print("It's a nice day.")
}
}

Although that works well enough, Swift provides a shorter alternative: we can use && to
combine two conditions together, and the whole condition will only be true if the two parts
inside the condition are true.

So, we could change our code to this:

if temp > 20 && temp < 30 {


print("It's a nice day.")
}

You should read && as “and”, so our whole conditions reads “if temp is greater than 20 and
temp is less than 30, print a message.” It’s called a logical operator because it combines
Booleans to make a new Boolean.

&& has a counterpart that is two pipe symbols, ||, which means “or”. Whereas && will only
make a condition be true if both subconditions are true, || will make a condition be true if
either subcondition is true.

For example, we could say that a user can buy a game if they are at least 18, or if they are
under 18 they must have permission from a parent. We could write that using || like so:

www.hackingwithswift.com 65
Conditions and loops

let userAge = 14
let hasParentalConsent = true

if userAge >= 18 || hasParentalConsent == true {


print("You can buy the game")
}

That will print “You can buy the game”, because although the first half of our condition fails –
the user is not at least 18 – the second half passes, because they do have parental consent.

Remember, using == true in a condition can be removed, because we’re obviously already
checking a Boolean. So, we could write this instead:

if userAge >= 18 || hasParentalConsent {


print("You can buy the game")
}

To finish up with checking multiple conditions, let’s try a more complex example that
combines if, else if, else, and || all at the same time, and even shows off how enums fit into
conditions.

In this example we’re going to create an enum called TransportOption, which contains five
cases: airplane, helicopter, bicycle, car, and scooter. We’ll then assign an example value to a
constant, and run some checks:

• If we are going somewhere by airplane or by helicopter, we’ll print “Let’s fly!”


• If we’re going by bicycle, we’ll print “I hope there’s a bike path…”
• If we’re going by car, we’ll print “Time to get stuck in traffic.”
• Otherwise we’ll print “I’m going to hire a scooter now!”

Here’s the code for that:

enum TransportOption {

66 www.hackingwithswift.com
How to check multiple conditions

case airplane, helicopter, bicycle, car, scooter


}

let transport = TransportOption.airplane

if transport == .airplane || transport == .helicopter {


print("Let's fly!")
} else if transport == .bicycle {
print("I hope there's a bike path…")
} else if transport == .car {
print("Time to get stuck in traffic.")
} else {
print("I'm going to hire a scooter now!")
}

I’d like to pick out a few parts of that code:

1. When we set the value for transport we need to be explicit that we’re referring to
TransportOption.airplane. We can’t just write .airplane because Swift doesn’t
understand we mean the TransportOption enum.
2. Once that has happened, we don’t need to write TransportOption any more because Swift
knows transport must be some kind of TransportOption. So, we can check whether it’s
equal to .airplane rather than TransportOption.airplane.
3. The code using || to check whether transport is equal to .airplane or equal to .helicopter,
and if either of them are true then the condition is true, and “Let’s fly!” is printed.
4. If the first condition fails – if the transport mode isn’t .airplane or .helicopter – then the
second condition is run: is the transport mode .bicycle? If so, “I hope there’s a bike path…”
is printed.
5. If we aren’t going by bicycle either, then we check whether we’re going by car. If we are,
“Time to get stuck in traffic.” is printed.
6. Finally, if all the previous conditions fail then the else block is run, and it means we’re
going by scooter.

www.hackingwithswift.com 67
Conditions and loops

68 www.hackingwithswift.com
How to use switch statements to
check multiple conditions
You can use if and else if repeatedly to check conditions as many times as you want, but it gets
a bit hard to read. For example, if we had a weather forecast from an enum we could choose
which message to print based on a series of conditions, like this:

enum Weather {
case sun, rain, wind, snow, unknown
}

let forecast = Weather.sun

if forecast == .sun {
print("It should be a nice day.")
} else if forecast == .rain {
print("Pack an umbrella.")
} else if forecast == .wind {
print("Wear something warm")
} else if forecast == .rain {
print("School is cancelled.")
} else {
print("Our forecast generator is broken!")
}

That works, but it has problems:

1. We keep having to write forecast, even though we’re checking the same thing each time.
2. I accidentally checked .rain twice, even though the second check can never be true because
the second check is only performed if the first check failed.
3. I didn’t check .snow at all, so we’re missing functionality.

www.hackingwithswift.com 69
Conditions and loops

We can solve all three of those problems using a different way of checking conditions called
switch. This also lets us check individual cases one by one, but now Swift is able to help out.
In the case of an enum, it knows all possible cases the enum can have, so if we miss one or
check one twice it will complain.

So, we can replace all those if and else if checks with this:

switch forecast {
case .sun:
print("It should be a nice day.")
case .rain:
print("Pack an umbrella.")
case .wind:
print("Wear something warm")
case .snow:
print("School is cancelled.")
case .unknown:
print("Our forecast generator is broken!")
}

Let’s break that down:

1. We start with switch forecast, which tells Swift that’s the value we want to check.
2. We then have a string of case statements, each of which are values we want to compare
against forecast.
3. Each of our cases lists one weather type, and because we’re switching on forecast we don’t
need to write Weather.sun, Weather.rain and so on – Swift knows it must be some kind
of Weather.
4. After each case, we write a colon to mark the start of the code to run if that case is matched.
5. We use a closing brace to end the switch statement.

If you try changing .snow for .rain, you’ll see Swift complains loudly: once that we’ve
checked .rain twice, and again that our switch statement is not exhaustive – that it doesn’t

70 www.hackingwithswift.com
How to use switch statements to check multiple conditions

handle all possible cases.

If you’ve ever used other programming languages, you might have noticed that Swift’s switch
statement is different in two places:

1. All switch statements must be exhaustive, meaning that all possible values must be handled
in there so you can’t leave one off by accident.
2. Swift will execute the first case that matches the condition you’re checking, but no more.
Other languages often carry on executing other code from all subsequent cases, which is
usually entirely the wrong default thing to do.

Although both those statements are true, Swift gives us a little more control if we need it.

First, yes all switch statements must be exhaustive: you must ensure all possible values are
covered. If you’re switching on a string then clearly it’s not possible to make an exhaustive
check of all possible strings because there is an infinite number, so instead we need to provide
a default case – code to run if none of the other cases match.

For example, we could switch over a string containing a place name:

let place = "Metropolis"

switch place {
case "Gotham":
print("You're Batman!")
case "Mega-City One":
print("You're Judge Dredd!")
case "Wakanda":
print("You're Black Panther!")
default:
print("Who are you?")
}

www.hackingwithswift.com 71
Conditions and loops

That default: at the end is the default case, which will be run if all cases have failed to match.

Remember: Swift checks its cases in order and runs the first one that matches. If you
place default before any other case, that case is useless because it will never be matched and
Swift will refuse to build your code.

Second, if you explicitly want Swift to carry on executing subsequent cases, use fallthrough.
This is not commonly used, but sometimes – just sometimes – it can help you avoid repeating
work.

For example, there’s a famous Christmas song called The Twelve Days of Christmas, and as
the song goes on more and more gifts are heaped on an unfortunate person who by about day
six has a rather full house.

We could make a simple approximation of this song using fallthrough. First, here’s how the
code would look without fallthrough:

let day = 5
print("My true love gave to me…")

switch day {
case 5:
print("5 golden rings")
case 4:
print("4 calling birds")
case 3:
print("3 French hens")
case 2:
print("2 turtle doves")
default:
print("A partridge in a pear tree")
}

That will print “5 golden rings”, which isn’t quite right. On day 1 only “A partridge in a pear

72 www.hackingwithswift.com
How to use switch statements to check multiple conditions

tree” should be printed, on day 2 it should be “2 turtle doves” then “A partridge in a pear tree”,
on day 3 it should be “3 French hens”, “2 turtle doves”, and… well, you get the idea.

We can use fallthrough to get exactly that behavior:

let day = 5
print("My true love gave to me…")

switch day {
case 5:
print("5 golden rings")
fallthrough
case 4:
print("4 calling birds")
fallthrough
case 3:
print("3 French hens")
fallthrough
case 2:
print("2 turtle doves")
fallthrough
default:
print("A partridge in a pear tree")
}

That will match the first case and print “5 golden rings”, but the fallthrough line means case 4
will execute and print “4 calling birds”, which in turn uses fallthrough again so that “3 French
hens” is printed, and so on. It’s not a perfect match to the song, but at least you can see the
functionality in action!

www.hackingwithswift.com 73
How to use the ternary conditional
operator for quick tests
There’s one last way to check conditions in Swift, and when you’ll see it chances are you’ll
wonder when it’s useful. To be fair, for a long time I very rarely used this approach, but as
you’ll see later it’s really important with SwiftUI.

This option is called the ternary conditional operator. To understand why it has that name, you
first need to know that +, -, ==, and so on are all called binary operators because they work
with two pieces of input: 2 + 5, for example, works with 2 and 5.

Ternary operators work with three pieces of input, and in fact because the ternary conditional
operator is the only ternary operator in Swift, you’ll often hear it called just “the ternary
operator.”

Anyway, enough about names: what does this actually do? Well, the ternary operator lets us
check a condition and return one of two values: something if the condition is true, and
something if it’s false.

For example, we could create a constant called age that stores someone’s age, then create a
second constant called canVote that will store whether that person is able to vote or not:

let age = 18
let canVote = age >= 18 ? "Yes" : "No"

When that code runs, canVote will be set to “Yes” because age is set to 18.

As you can see, the ternary operator is split into three parts: a check (age >= 18), something
for when the condition is true (“Yes”), and something for when the condition is false (“No”).
That makes it exactly like a regular if and else block, in the same order.

If it helps, Scott Michaud suggested a helpful mnemonic: WTF. It stands for “what, true,
false”, and matches the order of our code:

74 www.hackingwithswift.com
How to use the ternary conditional operator for quick tests

• What is our condition? Well, it’s age >= 18.


• What to do when the condition is true? Send back “Yes”, so it can be stored in canVote.
• And if the condition is false? Send back “No”.

Let’s look at some other examples, start with an easy one that reads an hour in 24-hour format
and prints one of two messages:

let hour = 23
print(hour < 12 ? "It's before noon" : "It's after noon")

Notice how that doesn’t assign the result anywhere – either the true or false case just gets
printed depending on the value of hour.

Or here’s one that reads the count of an array as part of its condition, then sends back one of
two strings:

let names = ["Jayne", "Kaylee", "Mal"]


let crewCount = names.isEmpty ? "No one" : "\(names.count)
people"
print(crewCount)

It gets a little hard to read when your condition use == to check for equality, as you can see
here:

enum Theme {
case light, dark
}

let theme = Theme.dark

let background = theme == .dark ? "black" : "white"


print(background)

www.hackingwithswift.com 75
Conditions and loops

The = theme == part is usually the bit folks find hard to read, but remember to break it down:

• What? theme == .dark


• True: “black”
• False: “white”

So if theme is equal to .dark return “Black”, otherwise return “White”, then assign that to
background.

Now, you might be wondering why the ternary operator is useful, particularly when we have
regular if/else conditions available to us. I realize it’s not a great answer, but you’ll have to
trust me on this: there are some times, particularly with SwiftUI, when we have no choice and
must use a ternary.

You can see roughly what the problem is with our code to check hours:

let hour = 23
print(hour < 12 ? "It's before noon" : "It's after noon")

If we wanted to write that out using if and else we’d either need to write this invalid code:

print(
if hour < 12 {
"It's before noon"
} else {
"It's after noon"
}
)

Or run print() twice, like this:

if hour < 12 {
print("It's before noon")
} else {

76 www.hackingwithswift.com
How to use the ternary conditional operator for quick tests

print("It's after noon")


}

That second one works fine here, but it becomes almost impossible in SwiftUI as you’ll see
much later. So, even though you might look at the ternary operator and wonder why you’d ever
use it, please trust me: it matters!

www.hackingwithswift.com 77
How to use a for loop to repeat
work
Computers are really great at doing repetitive work, and Swift makes it easy to repeat some
code a fixed number of times, or once for every item in an array, dictionary, or set.

Let’s start with something simple: if we have an array of strings, we can print each string out
like this:

let platforms = ["iOS", "macOS", "tvOS", "watchOS"]

for os in platforms {
print("Swift works great on \(os).")
}

That loops over all the items in platforms, putting them one by one into os. We haven’t
created os elsewhere; it’s created for us as part of the loop and made available only inside the
opening and closing braces.

Inside the braces is the code we want to run for each item in the array, so the code above will
print four lines – one for each loop item. First it puts “iOS” in there then calls print(), then it
puts “macOS” in there and calls print(), then “tvOS”, then “watchOS”.

To make things easier to understand, we give these things common names:

• We call the code inside the braces the loop body


• We call one cycle through the loop body a loop iteration.
• We call os the loop variable. This exists only inside the loop body, and will change to a
new value in the next loop iteration.

I should say that the name os isn’t special – we could have written this instead:

for name in platforms {

78 www.hackingwithswift.com
How to use a for loop to repeat work

print("Swift works great on \(name).")


}

Or even this:

for rubberChicken in platforms {


print("Swift works great on \(rubberChicken).")
}

The code will still work exactly the same.

In fact, Xcode is really smart here: if you write for plat it will recognize that there’s an array
called platforms, and offer to autocomplete all of for platform in platforms – it recognizes
that platforms is plural and suggests the singular name for the loop variable. When you see
Xcode’s suggestion appear, press Return to select it.

Rather than looping over an array (or set, or dictionary – the syntax is the same!), you can also
loop over a fixed range of numbers. For example, we could print out the 5 times table from 1
through 12 like this:

for i in 1...12 {
print("5 x \(i) is \(5 * i)")
}

A couple of things are new there, so let’s pause and examine them:

• I used the loop variable i, which is a common coding convention for “number you’re
counting with”. If you’re counting a second number you would use j, and if you’re
counting a third you would use k, but if you’re counting a fourth maybe you should pick
better variable names.
• The 1...12 part is a range, and means “all integer numbers between 1 and 12, as well as 1
and 12 themselves.” Ranges are their own unique data type in Swift.

www.hackingwithswift.com 79
Conditions and loops

So, when that loop first runs i will be 1, then it will be 2, then 3, etc, all the way up to 12, after
which the loop finishes.

You can also put loops inside loops, called nested loops, like this:

for i in 1...12 {
print("The \(i) times table:")

for j in 1...12 {
print(" \(j) x \(i) is \(j * i)")
}

print()
}

That shows off a couple of other new things, so again let’s pause and look closer:

• There’s now a nested loop: we count from 1 through 12, and for each number inside there
we count 1 through 12 again.
• Using print() by itself, with no text or value being passed in, will just start a new line. This
helps break up our output so it looks nicer on the screen.

So, when you see x...y you know it creates a range that starts at whatever number x is, and
counts up to and including whatever number y is.

Swift has a similar-but-different type of range that counts up to but excluding the final
number: ..<. This is best seen in code:

for i in 1...5 {
print("Counting from 1 through 5: \(i)")
}

print()

80 www.hackingwithswift.com
How to use a for loop to repeat work

for i in 1..<5 {
print("Counting 1 up to 5: \(i)")
}

When that runs, it will print for numbers 1, 2, 3, 4, 5 in the first loop, but only numbers 1, 2, 3,
and 4 in the second. I pronounce 1...5 as “one through five”, and 1..<5 as “one up to five,” and
you’ll see similar wording elsewhere in Swift.

Tip: ..< is really helpful for working with arrays, where we count from 0 and often want to
count up to but excluding the number of items in the array.

Before we’re done with for loops, there’s one more thing I want to mention: sometimes you
want to run some code a certain number of times using a range, but you don’t actually want the
loop variable – you don’t want the i or j, because you don’t use it.

In this situation, you can replace the loop variable with an underscore, like this:

var lyric = "Haters gonna"

for _ in 1...5 {
lyric += " hate"
}

print(lyric)

(Yes, that’s a Taylor Swift lyric from Shake It Off, written in Swift.)

www.hackingwithswift.com 81
How to use a while loop to repeat
work
Swift has a second kind of loop called while: provide it with a condition, and a while loop will
continually execute the loop body until the condition is false.

Although you’ll still see while loops from time to time, they aren’t as common as for loops.
As a result, I want to cover them so you know they exist, but let’s not dwell on them too long,
okay?

Here’s a basic while loop to get us started:

var countdown = 10

while countdown > 0 {


print("\(countdown)…")
countdown -= 1
}

print("Blast off!")

That creates an integer counter starting at 10, then starts a while loop with the condition
countdown > 0. So, the loop body – printing the number and subtracting 1 – will run
continually until countdown is equal to or below 0, at which point the loop will finish and the
final message will be printed.

while loops are really useful when you just don’t know how many times the loop will go
around. To demonstrate this, I want to introduce you to a really useful piece of functionality
that Int and Double both have: random(in:). Give that a range of numbers to work with, and
it will send back a random Int or Double somewhere inside that range.

For example, this creates a new integer between 1 and 1000

82 www.hackingwithswift.com
How to use a while loop to repeat work

let id = Int.random(in: 1...1000)

And this creates a random decimal between 0 and 1:

let amount = Double.random(in: 0...1)

We can use this functionality with a while loop to roll some virtual 20-sided dice again and
again, ending the loop only when a 20 is rolled – a critical hit for all you Dungeons & Dragons
players out there.

Here’s the code to make that happen:

// create an integer to store our roll


var roll = 0

// carry on looping until we reach 20


while roll != 20 {
// roll a new dice and print what it was
roll = Int.random(in: 1...20)
print("I rolled a \(roll)")
}

// if we're here it means the loop ended – we got a 20!


print("Critical hit!")

You’ll find yourself using both for and while loops in your own code: for loops are more
common when you have a finite amount of data to go through, such as a range or an array, but
while loops are really helpful when you need a custom condition.

www.hackingwithswift.com 83
How to skip loop items with break
and continue
Swift gives us two ways to skip one or more items in a loop: continue skips the current loop
iteration, and break skips all remaining iterations. Like while loops these are sometimes used,
but in practice much less than you might think.

Let’s look at them individually, starting with continue. When you’re looping over an array of
data, Swift will take out one item from the array and execute the loop body using it. If you call
continue inside that loop body, Swift will immediately stop executing the current loop
iteration and jump to the next item in the loop, where it will carry on as normal. This is
commonly used near the start of loops, where you eliminate loop variables that don’t pass a
test of your choosing.

Here’s an example:

let filenames = ["me.jpg", "work.txt", "sophie.jpg",


"logo.psd"]

for filename in filenames {


if filename.hasSuffix(".jpg") == false {
continue
}

print("Found picture: \(filename)")


}

That creates an array of filename strings, then loops over each one and checks to make sure it
has the suffix “.jpg” – that it’s a picture. continue is used with all the filenames failing that
test, so that the rest of the loop body is skipped.

As for break, that exits a loop immediately and skips all remaining iterations. To demonstrate

84 www.hackingwithswift.com
How to skip loop items with break and continue

this, we could write some code to calculate 10 common multiples for two numbers:

let number1 = 4
let number2 = 14
var multiples = [Int]()

for i in 1...100_000 {
if i.isMultiple(of: number1) && i.isMultiple(of: number2) {
multiples.append(i)

if multiples.count == 10 {
break
}
}
}

print(multiples)

That does quite a lot:

1. Create two constants to hold two numbers.


2. Create an integer array variable that will store common multiples of our two numbers.
3. Count from 1 through 100,000, assigning each loop variable to i.
4. If i is a multiple of both the first and second numbers, append it to the integer array.
5. Once we hit 10 multiples, call break to exit the loop.
6. Print out the resulting array.

So, use continue when you want to skip the rest of the current loop iteration, and use break
when you want to skip all remaining loop iterations.

www.hackingwithswift.com 85
Summary: Conditions and loops
We’ve covered a lot about conditions and loops in the previous chapters, so let’s recap:

• We use if statements to check a condition is true. You can pass in any condition you want,
but ultimately it must boil down to a Boolean.
• If you want, you can add an else block, and/or multiple else if blocks to check other
conditions. Swift executes these in order.
• You can combine conditions using ||, which means that the whole condition is true if either
subcondition is true, or &&, which means the whole condition is true if both subconditions
are true.
• If you’re repeating the same kinds of check a lot, you can use a switch statement instead.
These must always be exhaustive, which might mean adding a default case.
• If one of your switch cases uses fallthrough, it means Swift will execute the following
case afterwards. This is not used commonly.
• The ternary conditional operator lets us check WTF: What, True, False. Although it’s a
little hard to read at first, you’ll see this used a lot in SwiftUI.
• for loops let us loop over arrays, sets, dictionaries, and ranges. You can assign items to a
loop variable and use it inside the loop, or you can use underscore, _, to ignore the loop
variable.
• while loops let us craft custom loops that will continue running until a condition becomes
false.
• We can skip some or all loop items using continue or break respectively.

That’s another huge chunk of new material, but with conditions and loops you now know
enough to build some really useful software – give it a try!

86 www.hackingwithswift.com
Checkpoint 3
Now that you’re able to use conditions and loops, I’d like you to try a classic computer science
problem. It’s not hard to understand, but it might take you a little time to solve depending on
your prior experience!

The problem is called fizz buzz, and has been used in job interviews, university entrance tests,
and more for as long as I can remember. Your goal is to loop from 1 through 100, and for each
number:

1. If it’s a multiple of 3, print “Fizz”


2. If it’s a multiple of 5, print “Buzz”
3. If it’s a multiple of 3 and 5, print “FizzBuzz”
4. Otherwise, just print the number.

So, here are some example values you should have when your code runs:

• 1 should print “1”


• 2 should print “2”
• 3 should print “Fizz”
• 4 should print “4”
• 5 should print “Buzz”
• 6 should print “Fizz”
• 7 should print “7”
•…
• 15 should print “FizzBuzz”
•…
• 100 should print “Buzz”

Before you start: This problem seems extremely simple, but many, many developers struggle
to solve it. I’ve seen it happen personally, so don’t stress about it – just trying to solve the
problem already teaches you about it.

www.hackingwithswift.com 87
Conditions and loops

You already know everything you need to solve that problem, but if you’d like some hints then
I’ll add some below.

Please go ahead and try building the playground now.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 3. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

1. You can check whether one number is a multiple of another by using .isMultiple(of:). For
example, i.isMultiple(of: 3).
2. In this instance you need to check for 3 and 5 first because it’s the most specific, then 3,
then 5, and finally have an else block to handle all other numbers.
3. You can either use && to check for numbers that are multiples of 3 and 5, or have a nested
if statement.
4. You need to count from 1 through 100, so use ... rather than ..<.

88 www.hackingwithswift.com
Chapter 5
Functions

www.hackingwithswift.com 89
How to reuse code with functions
When you’ve written some code you really like, and want to use again and again, you should
consider putting it into a function. Functions are just chunks of code you’ve split off from the
rest of your program, and given a name so you can refer to them easily.

For example, let’s say we had this nice and simple code:

print("Welcome to my app!")
print("By default This prints out a conversion")
print("chart from centimeters to inches, but you")
print("can also set a custom range if you want.")

That’s a welcome message for an app, and you might want it to be printed when the app
launches, or perhaps when the user asks for help.

But what if you wanted it to be in both places? Yes, you could just copy those four print()
lines and put them in both places, but what if you wanted that text in ten places? Or what if
you wanted to change the wording later on – would you really remember to change it
everywhere it appeared in your code?

This is where functions come in: we can pull out that code, give it a name, and run it whenever
and wherever we need. This means all the print() lines stay in one place, and get reused
elsewhere.

Here’s how that looks:

func showWelcome() {
print("Welcome to my app!")
print("By default This prints out a conversion")
print("chart from centimeters to inches, but you")
print("can also set a custom range if you want.")
}

90 www.hackingwithswift.com
How to reuse code with functions

Let’s break that down…

1. It starts with the func keyword, which marks the start of a function.
2. We’re naming the function showWelcome. This can be any name you want, but try to make
it memorable – printInstructions(), displayHelp(), etc are all good choices.
3. The body of the function is contained within the open and close braces, just like the body of
loops and the body of conditions.

There’s one extra thing in there, and you might recognize it from our work so far: the ()
directly after showWelcome. We first met these way back when we looked at strings, when I
said that count doesn’t have () after it, but uppercased() does.

Well, now you’re learning why: those () are used with functions. They are used when you
create the function, as you can see above, but also when you call the function – when you ask
Swift to run its code. In our case, we can call our function like this:

showWelcome()

That’s known as the function’s call site, which is a fancy name meaning “a place where a
function is called.”

So what do the parentheses actually do? Well, that’s where we add configuration options for
our functions – we get to pass in data that customizes the way the function works, so the
function becomes more flexible.

As an example, we already used code like this:

let number = 139

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

www.hackingwithswift.com 91
Functions

isMultiple(of:) is a function that belongs to integers. If it didn’t allow any kind of


customization, it just wouldn’t make sense – is it a multiple of what? Sure, Apple could have
made this be something like isOdd() or isEven() so it never needed to have configuration
options, but by being able to write (of: 2) suddenly the function becomes more powerful,
because now we can check for multiples of 2, 3, 4, 5, 50, or any other number.

Similarly, when we were rolling virtual dice earlier we used code like this:

let roll = Int.random(in: 1...20)

Again, random() is a function, and the (in: 1...20) part marks configuration options – without
that we’d have no control over the range of our random numbers, which would be significantly
less useful.

We can make our own functions that are open to configuration, all by putting extra code in
between the parentheses when we create our function. This should be given a single integer,
such as 8, and calculate the multiplication tables for that from 1 through 12.

Here’s that in code:

func printTimesTables(number: Int) {


for i in 1...12 {
print("\(i) x \(number) is \(i * number)")
}
}

printTimesTables(number: 5)

Notice how I’ve placed number: Int inside the parentheses? That’s called a parameter, and
it’s our customization point. We’re saying whoever calls this function must pass in an integer
here, and Swift will enforce it. Inside the function, number is available to use like any other
constant, so it appears inside the print() call.

92 www.hackingwithswift.com
How to reuse code with functions

As you can see, when printTimesTables() is called, we need to explicitly write number: 5 –
we need to write the parameter name as part of the function call. This isn’t common in other
languages, but I think it’s really helpful in Swift as a reminder of what each parameter does.

This naming of parameters becomes even more important when you have multiple parameters.
For example, if we wanted to customize how high our multiplication tables went we might
make the end of our range be set using a second parameter, like this:

func printTimesTables(number: Int, end: Int) {


for i in 1...end {
print("\(i) x \(number) is \(i * number)")
}
}

printTimesTables(number: 5, end: 20)

Now that takes two parameters: an integer called number, and an end point called end. Both
of those need to be named specifically when printTablesTable() is run, and I hope you can
see now why they are important – imagine if our code was this instead:

printTimesTables(5, 20)

Can you remember which number was which? Maybe. But would you remember six months
from now? Probably not.

Now, technically we give slightly different names to sending data and receiving data, and
although many people (myself included) ignore this distinction, I’m going to at least make you
aware of it so you aren’t caught off guard later.

Take a look at this code:

func printTimesTables(number: Int, end: Int) {

There both number and end are parameters: they are placeholder names that will get filled in

www.hackingwithswift.com 93
Functions

with values when the function is called, so that we have a name for those values inside the
function.

Now look at this code:

printTimesTables(number: 5, end: 20)

There the 5 and 20 are arguments: they are the actual values that get sent into the function to
work with, used to fill number and end.

So, we have both parameters and arguments: one is a placeholder the other is an actual value,
so if you ever forget which is which just remember Parameter/Placeholder, Argument/Actual
Value.

Does this name distinction matter? Not really: I use “parameter” for both, and I’ve known
other people to use “argument” for both, and honestly never once in my career has it caused
even the slightest issue. In fact, as you’ll see shortly in Swift the distinction is extra confusing,
so it’s just not worth thinking about.

Important: If you prefer to use “argument” for data being passed in and “parameter” for data
being received, that’s down to you, but I really do use “parameter” for both, and will be doing
so throughout this book and beyond.

Regardless of whether you’re calling them “arguments” or “parameters”, when you ask Swift
to call the function you must always pass the parameters in the order they were listed when
you created the function.

So, for this code:

func printTimesTables(number: Int, end: Int) {

This is not valid code, because it puts end before number:

printTimesTables(end: 20, number: 5)

94 www.hackingwithswift.com
How to reuse code with functions

Tip: Any data you create inside a function is automatically destroyed when the function is
finished.

www.hackingwithswift.com 95
How to return values from
functions
You’ve seen how to create functions and how to add parameters to them, but functions often
also send data back – they perform some computation, then return the result of that work back
to the call site.

Swift has lots of these functions built in, and there are tens of thousands more in Apple’s
frameworks. For example, our playground has always had import Cocoa at the very top, and
that includes a variety of mathematical functions such as sqrt() for calculating the square root
of a number.

The sqrt() function accepts one parameter, which is the number we want to calculate the
square root of, and it will go ahead and do the work then send back the square root.

For example, we could write this:

let root = sqrt(169)


print(root)

If you want to return your own value from a function, you need to do two things:

1. Write an arrow then a data type before your function’s opening brace, which tells Swift
what kind of data will get sent back.
2. Use the return keyword to send back your data.

For example, perhaps you want to roll a dice in various parts of your program, but rather than
always forcing the dice roll to use a 6-sided dice you could instead make it a function:

func rollDice() -> Int {


return Int.random(in: 1...6)
}

96 www.hackingwithswift.com
How to return values from functions

let result = rollDice()


print(result)

So, that says the function must return an integer, and the actual value is sent back with the
return keyword.

Using this approach you can call rollDice() in lots of places across your program, and they will
all use a 6-sided dice. But if in the future you decide you want to use a 20-sided dice, you just
need to change that one function to have the rest of your program updated.

Important: When you say your function will return an Int, Swift will make sure it always
returns an Int – you can’t forget to send back a value, because your code won’t build.

Let’s try a more complex example: do two strings contain the same letters, regardless of their
order? This function should accept two string parameters, and return true if their letters are the
same – so, “abc” and “cab” should return true because they both contain one “a”, one “b”, and
one “c”.

You actually know enough to solve this problem yourself already, but you’ve learned so much
already you’ve probably forgotten the one thing that makes this task so easy: if you call
sorted() on any string, you get a new string back with all the letters in alphabetical order. So, if
you do that for both strings, you can use == to compare them to see if their letters are the same.

Go ahead and try writing the function yourself. Again, don’t worry if you struggle – it’s all
very new to you, and fighting to remember new knowledge is part of the learning process. I’ll
show you the solution in a moment, but please do try it yourself first.

Still here? Okay, here’s one example solution:

func areLettersIdentical(string1: String, string2: String) ->


Bool {
let first = string1.sorted()
let second = string2.sorted()
return first == second

www.hackingwithswift.com 97
Functions

Let’s break that down:

1. It creates a new function called areLettersIdentical().


2. The function accepts two string parameters, string1 and string2.
3. The function says it returns a Bool, so at some point we must always return either true or
false.
4. Inside the function body, we sort both strings then use == to compare the strings – if they
are the same it will return true, otherwise it will return false.

That code sorts both string1 and string2, assigning their sorted values to new constants, first
and second. However, that isn’t needed – we can skip those temporary constants and just
compare the results of sorted() directly, like this:

func areLettersIdentical(string1: String, string2: String) ->


Bool {
return string1.sorted() == string2.sorted()
}

That’s less code, but we can do even better. You see, we’ve told Swift that this function must
return a Boolean, and because there’s only one line of code in the function Swift knows that’s
the one that must return data. Because of this, when a function has only one line of code, we
can remove the return keyword entirely, like this:

func areLettersIdentical(string1: String, string2: String) ->


Bool {
string1.sorted() == string2.sorted()
}

We can go back and do the same for the rollDice() function too:

func rollDice() -> Int {

98 www.hackingwithswift.com
How to return values from functions

Int.random(in: 1...6)
}

Remember, this only works when your function contains a single line of code, and in particular
that line of code must actually return the data you promised to return.

Let’s try a third example. Do you remember the Pythagorean theorem from school? It states
that if you have a triangle with one right angle inside, you can calculate the length of the
hypotenuse by squaring both its other sides, adding them together, then calculating the square
root of the result

You already learned how to use sqrt(), so we can build a pythagoras() function that accepts
two decimal numbers and returns another one:

func pythagoras(a: Double, b: Double) -> Double {


let input = a * a + b * b
let root = sqrt(input)
return root
}

let c = pythagoras(a: 3, b: 4)
print(c)

So, that’s a function called pythagoras(), that accepts two Double parameters and returns
another Double. Inside it squares a and b, adds them together, then passes that into sqrt() and
sends back the result.

That function can also be boiled down to a single line, and have its return keyword removed –
give it a try. As usual I’ll show you my solution afterwards, but it’s important you try.

Still here? Okay, here’s my solution:

func pythagoras(a: Double, b: Double) -> Double {


sqrt(a * a + b * b)

www.hackingwithswift.com 99
Functions

There’s one last thing I want to mention before we move on: if your function doesn’t return a
value, you can still use return by itself to force the function to exit early. For example,
perhaps you have a check that the input matches what you expected, and if it doesn’t you want
to exit the function immediately before continuing.

100 www.hackingwithswift.com
How to return multiple values from
functions
When you want to return a single value from a function, you write an arrow and a data type
before your function’s opening brace, like this:

func isUppercase(string: String) -> Bool {


string == string.uppercased()
}

That compares a string against the uppercased version of itself. If the string was already fully
uppercased then nothing will have changed and the two strings will be identical, otherwise
they will be different and == will send back false.

If you want to return two or more values from a function, you could use an array. For example,
here’s one that sends back a user’s details:

func getUser() -> [String] {


["Taylor", "Swift"]
}

let user = getUser()


print("Name: \(user[0]) \(user[1])")

That’s problematic, because it’s hard to remember what user[0] and user[1] are, and if we
ever adjust the data in that array then user[0] and user[1] could end up being something else
or perhaps not existing at all.

We could use a dictionary instead, but that has its own problems:

func getUser() -> [String: String] {


[
"firstName": "Taylor",

www.hackingwithswift.com 101
Functions

"lastName": "Swift"
]
}

let user = getUser()


print("Name: \(user["firstName", default: "Anonymous"]) \
(user["lastName", default: "Anonymous"])")

Yes, we’ve now given meaningful names to the various parts of our user data, but look at that
call to print() – even though we know both firstName and lastName will exist, we still need
to provide default values just in case things aren’t what we expect.

Both of these solutions are pretty bad, but Swift has a solution in the form of tuples. Like
arrays, dictionaries, and sets, tuples let us put multiple pieces of data into a single variable, but
unlike those other options tuples have a fixed size and can have a variety of data types.

Here’s how our function looks when it returns a tuple:

func getUser() -> (firstName: String, lastName: String) {


(firstName: "Taylor", lastName: "Swift")
}

let user = getUser()


print("Name: \(user.firstName) \(user.lastName)")

Let’s break that down…

1. Our return type is now (firstName: String, lastName: String), which is a tuple containing
two strings.
2. Each string in our tuple has a name. These aren’t in quotes: they are specific names for each
item in our tuple, as opposed to the kinds of arbitrary keys we had in dictionaries.
3. Inside the function we send back a tuple containing all the elements we promised, attached
to the names: firstName is being set to “Taylor”, etc.

102 www.hackingwithswift.com
How to return multiple values from functions

4. When we call getUser(), we can read the tuple’s values using the key names: firstName,
lastName, etc.

I know tuples seem awfully similar to dictionaries, but they are different:

1. When you access values in a dictionary, Swift can’t know ahead of time whether they are
present or not. Yes, we knew that user["firstName"] was going to be there, but Swift
can’t be sure and so we need to provide a default value.
2. When you access values in a tuple, Swift does know ahead of time that it is available
because the tuple says it will be available.
3. We access values using user.firstName: it’s not a string, so there’s also no chance of typos.
4. Our dictionary might contain hundreds of other values alongside "firstName", but our
tuple can’t – we must list all the values it will contain, and as a result it’s guaranteed to
contain them all and nothing else.

So, tuples have a key advantage over dictionaries: we specify exactly which values will exist
and what types they have, whereas dictionaries may or may not contain the values we’re
asking for.

There are three other things it’s important to know when using tuples.

First, if you’re returning a tuple from a function, Swift already knows the names you’re giving
each item in the tuple, so you don’t need to repeat them when using return. So, this code does
the same thing as our previous tuple:

func getUser() -> (firstName: String, lastName: String) {


("Taylor", "Swift")
}

Second, sometimes you’ll find you’re given tuples where the elements don’t have names.
When this happens you can access the tuple’s elements using numerical indices starting from
0, like this:

www.hackingwithswift.com 103
Functions

func getUser() -> (String, String) {


("Taylor", "Swift")
}

let user = getUser()


print("Name: \(user.0) \(user.1)")

These numerical indices are also available with tuples that have named elements, but I’ve
always found using names preferable.

Finally, if a function returns a tuple you can actually pull the tuple apart into individual values
if you want to.

To understand what I mean by that, first take a look at this code:

func getUser() -> (firstName: String, lastName: String) {


(firstName: "Taylor", lastName: "Swift")
}

let user = getUser()


let firstName = user.firstName
let lastName = user.lastName

print("Name: \(firstName) \(lastName)")

That goes back to the named version of getUser(), and when the tuple comes out we’re
copying the elements from there into individual contents before using them. There’s nothing
new here; we’re just moving data around a bit.

However, rather than assigning the tuple to user, then copying individual values out of there,
we can skip the first step – we can pull apart the return value from getUser() into two separate
constants, like this:

let (firstName, lastName) = getUser()

104 www.hackingwithswift.com
How to return multiple values from functions

print("Name: \(firstName) \(lastName)")

That syntax might hurt your head at first, but it’s really just shorthand for what we had before:
convert the tuple of two elements that we get back from getUser() into two separate constants.

In fact, if you don’t need all the values from the tuple you can go a step further by using _ to
tell Swift to ignore that part of the tuple:

let (firstName, _) = getUser()


print("Name: \(firstName)")

www.hackingwithswift.com 105
How to customize parameter
labels
You’ve seen how Swift developers like to name their function parameters, because it makes it
easier to remember what they do when the function is called. For example, we could write a
function to roll a dice a certain number of times, using parameters to control the number of
sides on the dice and the number of rolls:

func rollDice(sides: Int, count: Int) -> [Int] {


// Start with an empty array
var rolls = [Int]()

// Roll as many dice as needed


for _ in 1...count {
// Add each result to our array
let roll = Int.random(in: 1...sides)
rolls.append(roll)
}

// Send back all the rolls


return rolls
}

let rolls = rollDice(sides: 6, count: 4)

Even if you came back to this code six months later, I feel confident that rollDice(sides: 6,
count: 4) is pretty self-explanatory.

This method of naming parameters for external use is so important to Swift that it actually uses
the names when it comes to figuring out which method to call. This is quite unlike many other
languages, but this is perfect valid in Swift:

106 www.hackingwithswift.com
How to customize parameter labels

func hireEmployee(name: String) { }


func hireEmployee(title: String) { }
func hireEmployee(location: String) { }

Yes, those are all functions called hireEmployee(), but when you call them Swift knows
which one you mean based on the parameter names you provide. To distinguish between the
various options, it’s very common to see documentation refer to each function including its
parameters, like this: hireEmployee(name:) or hireEmployee(title:).

Sometimes, though, these parameter names are less helpful, and there are two ways I want to
look at.

First, think about the hasPrefix() function you learned earlier:

let lyric = "I see a red door and I want it painted black"
print(lyric.hasPrefix("I see"))

When we call hasPrefix() we pass in the prefix to check for directly – we don’t say
hasPrefix(string:) or, worse, hasPrefix(prefix:). How come?

Well, when we’re defining the parameters for a function we can actually add two names: one
for use where the function is called, and one for use inside the function itself. hasPrefix() uses
this to specify _ as the external name for its parameter, which is Swift’s way of saying “ignore
this” and causes there to be no external label for that parameter.

We can use the same technique in our own functions, if you think it reads better. For example,
previously we had this function:

func isUppercase(string: String) -> Bool {


string == string.uppercased()
}

let string = "HELLO, WORLD"


let result = isUppercase(string: string)

www.hackingwithswift.com 107
Functions

You might look at that and think it’s exactly right, but you might also look at string: string
and see annoying duplication. After all, what else are we going to pass in there other than a
string?

If we add an underscore before the parameter name, we can remove the external parameter
label like so:

func isUppercase(_ string: String) -> Bool {


string == string.uppercased()
}

let string = "HELLO, WORLD"


let result = isUppercase(string)

This is used a lot in Swift, such as with append() to add items to an array, or contains() to
check whether an item is inside an array – in both places it’s pretty evident what the parameter
is without having a label too.

The second problem with external parameter names is when they aren’t quite right – you want
to have them, so _ isn’t a good idea, but they just don’t read naturally at the function’s call site.

As an example, here’s another function we looked at earlier:

func printTimesTables(number: Int) {


for i in 1...12 {
print("\(i) x \(number) is \(i * number)")
}
}

printTimesTables(number: 5)

That code is valid Swift, and we could leave it alone as it is. But the call site doesn’t read well:

108 www.hackingwithswift.com
How to customize parameter labels

printTimesTables(number: 5). It would be much better like this:

func printTimesTables(for: Int) {


for i in 1...12 {
print("\(i) x \(for) is \(i * for)")
}
}

printTimesTables(for: 5)

That reads much better at the call site – you can literally say “print times table for 5” aloud,
and it makes sense. But now we have invalid Swift: although for is allowed and reads great at
the call site, it’s not allowed inside the function.

You already saw how we can put _ before the parameter name so that we don’t need to write
an external parameter name. Well, the other option is to write a second name there: one to use
externally, and one to use internally.

Here’s how that looks:

func printTimesTables(for number: Int) {


for i in 1...12 {
print("\(i) x \(number) is \(i * number)")
}
}

printTimesTables(for: 5)

There are three things in there you need to look at closely:

1. We write for number: Int: the external name is for, the internal name is number, and it’s
of type Int.
2. When we call the function we use the external name for the parameter:
printTimesTables(for: 5).

www.hackingwithswift.com 109
Functions

3. Inside the function we use the internal name for the parameter: print("\(i) x \(number) is \
(i * number)").

So, Swift gives us two important ways to control parameter names: we can use _ for the
external parameter name so that it doesn’t get used, or add a second name there so that we
have both external and internal parameter names.

Tip: Earlier I mentioned that technically values you pass in to a function are called
“arguments”, and values you receive inside the function are called parameters. This is where
things get a bit muddled, because now we have argument labels and parameter names side by
side, both in the function definition. Like I said, I’ll be using the term “parameter” for both,
and when the distinction matters you’ll see I distinguish between them using “external
parameter name” and “internal parameter name”.

110 www.hackingwithswift.com
How to provide default values for
parameters
Adding parameters to functions lets us add customization points, so that functions can operate
on different data depending on our needs. Sometimes we want to make these customization
points available to keep our code flexible, but other times you don’t want to think about it –
you want the same thing nine times out of ten.

For example, previously we looked at this function:

func printTimesTables(for number: Int, end: Int) {


for i in 1...end {
print("\(i) x \(number) is \(i * number)")
}
}

printTimesTables(for: 5, end: 20)

That prints any times table, starting at 1 times the number up to any end point. That number is
always going to change based on what multiplication table we want, but the end point seems
like a great place to provide a sensible default – we might want to count up to 10 or 12 most of
the time, while still leaving open the possibility of going to a different value some of the time.

To solve this problem, Swift lets us specify default values for any or all of our parameters. In
this case, we could set end to have the default value of 12, meaning that if we don’t specify it
12 will be used automatically.

Here’s how that looks in code:

func printTimesTables(for number: Int, end: Int = 12) {


for i in 1...end {
print("\(i) x \(number) is \(i * number)")
}

www.hackingwithswift.com 111
Functions

printTimesTables(for: 5, end: 20)


printTimesTables(for: 8)

Notice how we can now call printTimesTables() in two different ways: we can provide both
parameters for times when we want it, but if we don’t – if we just write
printTimesTables(for: 8) – then the default value of 12 will be used for end.

We’ve actually seen a default parameter in action, back in some code we used before:

var characters = ["Lana", "Pam", "Ray", "Sterling"]


print(characters.count)
characters.removeAll()
print(characters.count)

That adds some strings to an array, prints its count, then removes them all and prints the count
again.

As a performance optimization, Swift gives arrays just enough memory to hold their items plus
a little extra so they can grow a little over time. If more items are added to the array, Swift
allocates more and more memory automatically, so that as little as possible is wasted.

When we call removeAll(), Swift will automatically remove all the items in the array, then
free up all the memory that was assigned to the array. That’s usually what you’ll want, because
after all you’re removing the objects for a reason. But sometimes – just sometimes – you might
be about to add lots of new items back into the array, and so there’s a second form of this
function that removes the items while also keeping the previous capacity:

characters.removeAll(keepingCapacity: true)

This is accomplished using a default parameter value: keepingCapacity is a Boolean with the
default value of false so that it does the sensible thing by default, while also leaving open the

112 www.hackingwithswift.com
How to provide default values for parameters

option of us passing in true for times we want to keep the array’s existing capacity.

As you can see, default parameter values let us keep flexibility in our functions without
making them annoying to call most of the time – you only need to send in some parameters
when you need something unusual.

www.hackingwithswift.com 113
How to handle errors in functions
Things go wrong all the time, such as when that file you wanted to read doesn’t exist, or when
that data you tried to download failed because the network was down. If we didn’t handle
errors gracefully then our code would crash, so Swift makes us handle errors – or at least
acknowledge when they might happen.

This takes three steps:

1. Telling Swift about the possible errors that can happen.


2. Writing a function that can flag up errors if they happen.
3. Calling that function, and handling any errors that might happen.

Let’s work through a complete example: if the user asks us to check how strong their password
is, we’ll flag up a serious error if the password is too short or is obvious.

So, we need to start by defining the possible errors that might happen. This means making a
new enum that builds on Swift’s existing Error type, like this:

enum PasswordError: Error {


case short, obvious
}

That says there are two possible errors with password: short and obvious. It doesn’t define
what those mean, only that they exist.

Step two is to write a function that will trigger one of those errors. When an error is triggered –
or thrown in Swift – we’re saying something fatal went wrong with the function, and instead
of continuing as normal it immediately terminates without sending back any value.

In our case, we’re going to write a function that checks the strength of a password: if it’s really
bad – fewer than 5 characters or is extremely well known – then we’ll throw an error
immediately, but for all other strings we’ll return either “OK”, “Good”, or “Excellent” ratings.

114 www.hackingwithswift.com
How to handle errors in functions

Here’s how that looks in Swift:

func checkPassword(_ password: String) throws -> String {


if password.count < 5 {
throw PasswordError.short
}

if password == "12345" {
throw PasswordError.obvious
}

if password.count < 8 {
return "OK"
} else if password.count < 10 {
return "Good"
} else {
return "Excellent"
}
}

Let’s break that down…

1. If a function is able to throw errors without handling them itself, you must mark the
function as throws before the return type.
2. We don’t specify exactly what kind of error is thrown by the function, just that it can throw
errors.
3. Being marked with throws does not mean the function must throw errors, only that it might.
4. When it comes time to throw an error, we write throw followed by one of our
PasswordError cases. This immediately exits the function, meaning that it won’t return a
string.
5. If no errors are thrown, the function must behave like normal – it needs to return a string.

That completes the second step of throwing errors: we defined the errors that might happen,

www.hackingwithswift.com 115
Functions

then wrote a function using those errors.

The final step is to run the function and handle any errors that might happen. Swift
Playgrounds are pretty lax about error handling because they are mostly meant for learning,
but when it comes to working with real Swift projects you’ll find there are three steps:

1. Starting a block of work that might throw errors, using do.


2. Calling one or more throwing functions, using try.
3. Handling any thrown errors using catch.

In pseudocode, it looks like this:

do {
try someRiskyWork()
} catch {
print("Handle errors here")
}

If we wanted to write try that using our current checkPassword() function, we could write
this:

let string = "12345"

do {
let result = try checkPassword(string)
print("Password rating: \(result)")
} catch {
print("There was an error.")
}

If the checkPassword() function works correctly, it will return a value into result, which is
then printed out. But if any errors are thrown (which in this case there will be), the password
rating message will never be printed – execution will immediately jump to the catch block.

116 www.hackingwithswift.com
How to jump
rating message will never be printed – execution will immediately handle errors
to the in block.
catch functions

There are a few different parts of that code that deserve discussion, but I want to focus on the
most important one: try. This must be written before calling all functions that might throw
errors, and is a visual signal to developers that regular code execution will be interrupted if an
error happens.

When you use try, you need to be inside a do block, and make sure you have one or more
catch blocks able to handle any errors. In some circumstances you can use an alternative
written as try! which does not require do and catch, but will crash your code if an error is
thrown – you should use this rarely, and only if you’re absolutely sure an error cannot be
thrown.

When it comes to catching errors, you must always have a catch block that is able to handle
every kind of error. However, you can also catch specific errors as well, if you want:

let string = "12345"

do {
let result = try checkPassword(string)
print("Password rating: \(result)")
} catch PasswordError.short {
print("Please use a longer password.")
} catch PasswordError.obvious {
print("I have the same combination on my luggage!")
} catch {
print("There was an error.")
}

As you progress you’ll see how throwing functions are baked into many of Apple’s own
frameworks, so even though you might not create them yourself much you will at least need to
know how to use them safely.

Tip: Most errors thrown by Apple provide a meaningful message that you can present to your
user if needed. Swift makes this available using an error value that’s automatically provided

www.hackingwithswift.com 117
Functions

inside your catch block, and it’s common to read error.localizedDescription to see exactly
what happened.

118 www.hackingwithswift.com
Summary: Functions
We’ve covered a lot about functions in the previous chapters, so let’s recap:

• Functions let us reuse code easily by carving off chunks of code and giving it a name.
• All functions start with the word func, followed by the function’s name. The function’s
body is contained inside opening and closing braces.
• We can add parameters to make our functions more flexible – list them out one by one
separated by commas: the name of the parameter, then a colon, then the type of the
parameter.
• You can control how those parameter names are used externally, either by using a custom
external parameter name or by using an underscore to disable the external name for that
parameter.
• If you think there are certain parameter values you’ll use repeatedly, you can make them
have a default value so your function takes less code to write and does the smart thing by
default.
• Functions can return a value if you want, but if you want to return multiple pieces of data
from a function you should use a tuple. These hold several named elements, but it’s limited
in a way a dictionary is not – you list each element specifically, along with its type.
• Functions can throw errors: you create an enum defining the errors you want to happen,
throw those errors inside the function as needed, then use do, try, and catch to handle them
at the call site.

www.hackingwithswift.com 119
Checkpoint 4
With functions under your belt, it’s time to try a little coding challenge. Don’t worry, it’s not
that hard, but it might take you a while to think about and come up with something. As always
I’ll be giving you some hints if you need them.

The challenge is this: write a function that accepts an integer from 1 through 10,000, and
returns the integer square root of that number. That sounds easy, but there are some catches:

1. You can’t use Swift’s built-in sqrt() function or similar – you need to find the square root
yourself.
2. If the number is less than 1 or greater than 10,000 you should throw an “out of bounds”
error.
3. You should only consider integer square roots – don’t worry about the square root of 3
being 1.732, for example.
4. If you can’t find the square root, throw a “no root” error.

As a reminder, if you have number X, the square root of X will be another number that, when
multiplied by itself, gives X. So, the square root of 9 is 3, because 3x3 is 9, and the square root
of 25 is 5, because 5x5 is 25.

I’ll give you some hints in a moment, but as always I encourage you to try it yourself first –
struggling to remember how things work, and often having to look them up again, is a
powerful way to make progress.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 4. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

• This is a problem you should “brute force” – create a loop with multiplications inside,
looking for the integer you were passed in.
• The square root of 10,000 – the largest number I want you to handle – is 100, so your loop
should stop there.

120 www.hackingwithswift.com
Checkpoint 4

• If you reach the end of your loop without finding a match, throw the “no root” error.
• You can define different out of bounds errors for “less than 1” and “greater than 10,000” if
you want, but it’s not really necessary – just having one is fine.

www.hackingwithswift.com 121
Chapter 6
Closures

122 www.hackingwithswift.com
How to create and use closures
Functions are powerful things in Swift. Yes, you’ve seen how you can call them, pass in
values, and return data, but you can also assign them to variables, pass functions into
functions, and even return functions from functions.

For example:

func greetUser() {
print("Hi there!")
}

greetUser()

var greetCopy = greetUser


greetCopy()

That creates a trivial function and calls it, but then creates a copy of that function and calls the
copy. As a result, it will print the same message twice.

Important: When you’re copying a function, you don’t write the parentheses after it – it’s var
greetCopy = greetUser and not var greetCopy = greetUser(). If you put the parentheses
there you are calling the function and assigning its return value back to something else.

But what if you wanted to skip creating a separate function, and just assign the functionality
directly to a constant or variable? Well, it turns out you can do that too:

let sayHello = {
print("Hi there!")
}

sayHello()

Swift gives this the grandiose name closure expression, which is a fancy way of saying we just

www.hackingwithswift.com 123
Closures

created a closure – a chunk of code we can pass around and call whenever we want. This one
doesn’t have a name, but otherwise it’s effectively a function that takes no parameters and
doesn’t return a value.

If you want the closure to accept parameters, they need to be written in a special way. You see,
the closure starts and ends with the braces, which means we can’t put code outside those
braces to control parameters or return value. So, Swift has a neat workaround: we can put that
same information inside the braces, like this:

let sayHello = { (name: String) -> String in


"Hi \(name)!"
}

I added an extra keyword there – did you spot it? It’s the in keyword, and it comes directly
after the parameters and return type of the closure. Again, with a regular function the
parameters and return type would come outside the braces, but we can’t do that with closures.
So, in is used to mark the end of the parameters and return type – everything after that is the
body of the closure itself. There’s a reason for this, and you’ll see it for yourself soon enough.

In the meantime, you might have a more fundamental question: “why would I ever need these
things?” I know, closures do seem awfully obscure. Worse, they seem obscure and
complicated – many, many people really struggle with closures when they first meet them,
because they are complex beasts and seem like they are never going to be useful.

However, as you’ll see this gets used extensively in Swift, and almost everywhere in SwiftUI.
Seriously, you’ll use them in every SwiftUI app you write, sometimes hundreds of times –
maybe not necessarily in the form you see above, but you’re going to be using it a lot.

To get an idea of why closures are so useful, I first want to introduce you to function types.
You’ve seen how integers have the type Int, and decimals have the type Double, etc, and now
I want you to think about how functions have types too.

Let’s take the greetUser() function we wrote earlier: it accepts no parameters, returns no
value, and does not throw errors. If we were to write that as a type annotation for greetCopy,

124 www.hackingwithswift.com
How to create and use closures

we’d write this:

var greetCopy: () -> Void = greetUser

Let’s break that down…

1. The empty parentheses marks a function that takes no parameters.


2. The arrow means just what it means when creating a function: we’re about to declare the
return type for the function.
3. Void means “nothing” – this function returns nothing. Sometimes you might see this
written as (), but we usually avoid that because it can be confused with the empty
parameter list.

Every function’s type depends on the data it receives and sends back. That might sound
simple, but it hides an important catch: the names of the data it receives are not part of the
function’s type.

We can demonstrate this with some more code:

func getUserData(for id: Int) -> String {


if id == 1989 {
return "Taylor Swift"
} else {
return "Anonymous"
}
}

let data: (Int) -> String = getUserData


let user = data(1989)
print(user)

That starts off easily enough: it’s a function that accepts an integer and returns a string. But
when we take a copy of the function the type of function doesn’t include the for external

www.hackingwithswift.com 125
Closures

parameter name, so when the copy is called we use data(1989) rather than data(for: 1989).

Cunningly this same rule applies to all closures – you might have noticed that I didn’t actually
use the sayHello closure we wrote earlier, and that’s because I didn’t want to leave you
questioning the lack of a parameter name at the call site. Let’s call it now:

sayHello("Taylor")

That uses no parameter name, just like when we copy functions. So, again: external parameter
names only matter when we’re calling a function directly, not when we create a closure or
when we take a copy of the function first.

You’re probably still wondering why all this matters, and it’s all about to become clear. Do
you remember I said we can use sorted() with an array to have it sort its elements?

It means we can write code like this:

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]


let sortedTeam = team.sorted()
print(sortedTeam)

That’s really neat, but what if we wanted to control that sort – what if we always wanted one
person to come first because they were the team captain, with the rest being sorted
alphabetically?

Well, sorted() actually allows us to pass in a custom sorting function to control exactly that.
This function must accept two strings, and return true if the first string should be sorted before
the second, or false if the first string should be sorted after the second.

If Suzanne were the captain, the function would look like this:

func captainFirstSorted(name1: String, name2: String) -> Bool {


if name1 == "Suzanne" {
return true

126 www.hackingwithswift.com
How to create and use closures

} else if name2 == "Suzanne" {


return false
}

return name1 < name2


}

So, if the first name is Suzanne, return true so that name1 is sorted before name2. On the other
hand, if name2 is Suzanne, return false so that name1 is sorted after name2. If neither name is
Suzanne, just use < to do a normal alphabetical sort.

Like I said, sorted() can be passed a function to create a custom sort order, and as long as that
function a) accepts two strings, and b) returns a Boolean, sorted() can use it.

That’s exactly what our new captainFirstSorted() function does, so we can use it straight
away:

let captainFirstTeam = team.sorted(by: captainFirstSorted)


print(captainFirstTeam)

When that runs it will print ["Suzanne", "Gloria", "Piper", "Tasha", "Tiffany"], exactly
as we wanted.

We’ve now covered two seemingly very different things. First, we can create closures as
anonymous functions, storing them inside constants and variables:

let sayHello = {
print("Hi there!")
}

sayHello()

And we’re also able to pass functions into other functions, just like we passed

www.hackingwithswift.com 127
Closures

captainFirstSorted() into sorted():

let captainFirstTeam = team.sorted(by: captainFirstSorted)

The power of closures is that we can put these two together: sorted() wants a function that will
accept two strings and return a Boolean, and it doesn’t care if that function was created
formally using func or whether it’s provided using a closure.

So, we could call sorted() again, but rather than passing in the captainFirstTeam() function,
instead start a new closure: write an open brace, list its parameters and return type, write in,
then put our standard function code.

This is going to hurt your brain at first. It’s not because you’re not smart enough to
understand closures or not cut out for Swift programming, only that closures are really hard.
Don’t worry – we’re going to look at ways to make this easier!

Okay, let’s write some new code that calls sorted() using a closure:

let captainFirstTeam = team.sorted(by: { (name1: String, name2:


String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}

return name1 < name2


})

That’s a big chunk of syntax all at once, and again I want to say it’s going to get easier – in the
very next chapter we’re going to look at ways to reduce the amount of code so it’s easier to see
what’s going on.

But first I want to break down what’s happening there:

128 www.hackingwithswift.com
How to create and use closures
But first I want to break down what’s happening there:

1. We’re calling the sorted() function as before.


2. Rather than passing in a function, we’re passing a closure – everything from the opening
brace after by: down to the closing brace on the last line is part of the closure.
3. Directly inside the closure we list the two parameters sorted() will pass us, which are two
strings. We also say that our closure will return a Boolean, then mark the start of the
closure’s code by using in.
4. Everything else is just normal function code.

Again, there’s a lot of syntax in there and I wouldn’t blame you if you felt a headache coming
on, but I hope you can see the benefit of closures at least a little: functions like sorted() let us
pass in custom code to adjust how they work, and do so directly – we don’t need to write out a
new function just for that one usage.

Now you understand what closures are, let’s see if we can make them easier to read…

www.hackingwithswift.com 129
How to use trailing closures and
shorthand syntax
Swift has a few tricks up its sleeve to reduce the amount of syntax that comes with closures,
but first let’s remind ourselves of the problem. Here’s the code we ended up with at the end of
the previous chapter:

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]

let captainFirstTeam = team.sorted(by: { (name1: String, name2:


String) -> Bool in
if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}

return name1 < name2


})

print(captainFirstTeam)

If you remember, sorted() can accept any kind of function to do custom sorting, with one rule:
that function must accept two items from the array in question (that’s two strings here), and
return a Boolean set to true if the first string should be sorted before the second.

To be clear, the function must behave like that – if it returned nothing, or if it only accepted
one string, then Swift would refuse to build our code.

Think it through: in this code, the function we provide to sorted() must provide two strings
and return a Boolean, so why do we need to repeat ourselves in our closure?

130 www.hackingwithswift.com
How to use trailing closures and shorthand syntax

The answer is: we don’t. We don’t need to specify the types of our two parameters because
they must be strings, and we don’t need to specify a return type because it must be a Boolean.
So, we can rewrite the code to this:

let captainFirstTeam = team.sorted(by: { name1, name2 in

That’s already reduced the amount of clutter in the code, but we can go a step further: when
one function accepts another as its parameter, like sorted() does, Swift allows special syntax
called trailing closure syntax. It looks like this:

let captainFirstTeam = team.sorted { name1, name2 in


if name1 == "Suzanne" {
return true
} else if name2 == "Suzanne" {
return false
}

return name1 < name2


}

Rather than passing the closure in as a parameter, we just go ahead and start the closure
directly – and in doing so remove (by: from the start, and a closing parenthesis at the end.
Hopefully you can now see why the parameter list and in come inside the closure, because if
they were outside it would look even weirder!

There’s one last way Swift can make closures less cluttered: Swift can automatically provide
parameter names for us, using shorthand syntax. With this syntax we don’t even write name1,
name2 in any more, and instead rely on specially named values that Swift provides for us: $0
and $1, for the first and second strings respectively.

Using this syntax our code becomes even shorter:

let captainFirstTeam = team.sorted {

www.hackingwithswift.com 131
Closures

if $0 == "Suzanne" {
return true
} else if $1 == "Suzanne" {
return false
}

return $0 < $1
}

I left this one to last because it’s not as clear cut as the others – some people see that syntax
and hate it because it’s less clear, and that’s okay.

Personally I wouldn’t use it here because we’re using each value more than once, but if our
sorted() call was simpler – e.g., if we just wanted to do a reverse sort – then I would:

let reverseTeam = team.sorted {


return $0 > $1
}

So, in is used to mark the end of the parameters and return type – everything after that is the
body of the closure itself. There’s a reason for this, and you’ll see it for yourself soon enough.

There I’ve flipped the comparison from < to > so we get a reverse sort, but now that we’re
down to a single line of code we can remove the return and get it down to almost nothing:

let reverseTeam = team.sorted { $0 > $1 }

There are no fixed rules about when to use shorthand syntax and when not to, but in case it’s
helpful I use shorthand syntax unless any of the following are true:

1. The closure’s code is long.


2. $0 and friends are used more than once each.
3. You get three or more parameters (e.g. $2, $3, etc).

132 www.hackingwithswift.com
How to use trailing closures and shorthand syntax

If you’re still unconvinced about the power of closures, let’s take a look at two more examples.

First up, the filter() function lets us run some code on every item in the array, and will send
back a new array containing every item that returns true for the function. So, we could find all
team players whose name begins with T like this:

let tOnly = team.filter { $0.hasPrefix("T") }


print(tOnly)

That will print ["Tiffany", "Tasha"], because those are the only two team members whose
name begins with T.

And second, the map() function lets us transform every item in the array using some code of
our choosing, and sends back a new array of all the transformed items:

let uppercaseTeam = team.map { $0.uppercased() }


print(uppercaseTeam)

That will print ["GLORIA", "SUZANNE", "PIPER", "TIFFANY", "TASHA"], because


it has uppercased every name and produced a new array from the result.

Tip: When working with map(), the type you return doesn’t have to be the same as the type
you started with – you could convert an array of integers to an array of strings, for example.

Like I said, you’re going to be using closures a lot with SwiftUI:

1. When you create a list of data on the screen, SwiftUI will ask you to provide a function that
accepts one item from the list and converts it something it can display on-screen.
2. When you create a button, SwiftUI will ask you to provide one function to execute when
the button is pressed, and another to generate the contents of the button – a picture, or some
text, and so on.
3. Even just putting stacking pieces of text vertically is done using a closure.

www.hackingwithswift.com 133
Closures

Yes, you can create individual functions every time SwiftUI does this, but trust me: you won’t.
Closures make this kind of code completely natural, and I think you’ll be amazed at how
SwiftUI uses them to produce remarkably simple, clean code.

134 www.hackingwithswift.com
How to accept functions as
parameters
There’s one last closure-related topic I want to look at, which is how to write functions that
accept other functions as parameters. This is particularly important for closures because of
trailing closure syntax, but it’s a useful skill to have regardless.

Previously we looked at this code:

func greetUser() {
print("Hi there!")
}

greetUser()

var greetCopy: () -> Void = greetUser


greetCopy()

I’ve added the type annotation in there intentionally, because that’s exactly what we use when
specifying functions as parameters: we tell Swift what parameters the function accepts, as well
its return type.

Once again, brace yourself: the syntax for this is a little hard on the eyes at first! Here’s a
function that generates an array of integers by repeating a function a certain number of times:

func makeArray(size: Int, using generator: () -> Int) -> [Int]


{
var numbers = [Int]()

for _ in 0..<size {
let newNumber = generator()
numbers.append(newNumber)

www.hackingwithswift.com 135
Closures

return numbers
}

Let’s break that down…

1. The function is called makeArray(). It takes two parameters, one of which is the number of
integers we want, and also returns an array of integers.
2. The second parameter is a function. This accepts no parameters itself, but will return one
integer every time it’s called.
3. Inside makeArray() we create a new empty array of integers, then loop as many times as
requested.
4. Each time the loop goes around we call the generator function that was passed in as a
parameter. This will return one new integer, so we put that into the numbers array.
5. Finally the finished array is returned.

The body of the makeArray() is mostly straightforward: repeatedly call a function to generate
an integer, adding each value to an array, then send it all back.

The complex part is the very first line:

func makeArray(size: Int, using generator: () -> Int) -> [Int]


{

There we have two sets of parentheses and two sets of return types, so it can be a bit of a
jumble at first. If you split it up you should be able to read it linearly:

1. We’re creating a new function.


2. The function is called makeArray().
3. The first parameter is an integer called size.
4. The second parameter is a function called generator, which itself accepts no parameters
and returns an integer.

136 www.hackingwithswift.com
How to accept functions as parameters

5. The whole thing – makeArray() – returns an array of integers.

The result is that we can now make arbitrary-sized integer arrays, passing in a function that
should be used to generate each number:

let rolls = makeArray(size: 50) {


Int.random(in: 1...20)
}

print(rolls)

And remember, this same functionality works with dedicated functions too, so we could write
something like this:

func generateNumber() -> Int {


Int.random(in: 1...20)
}

let newRolls = makeArray(size: 50, using: generateNumber)


print(newRolls)

That will call generateNumber() 50 times to fill the array.

While you’re learning Swift and SwiftUI, there will only be a handful of times when you need
to know how to accept functions as parameters, but at least now you have an inkling of how it
works and why it matters.

There’s one last thing before we move on: you can make your function accept multiple
function parameters if you want, in which case you can specify multiple trailing closures. The
syntax here is very common in SwiftUI, so it’s important to at least show you a taste of it here.

To demonstrate this here’s a function that accepts three function parameters, each of which
accept no parameters and return nothing:

www.hackingwithswift.com 137
Closures

func doImportantWork(first: () -> Void, second: () -> Void,


third: () -> Void) {
print("About to start first work")
first()
print("About to start second work")
second()
print("About to start third work")
third()
print("Done!")
}

I’ve added extra print() calls in there to simulate specific work being done in between first,
second, and third being called.

When it comes to calling that, the first trailing closure is identical to what we’ve used already,
but the second and third are formatted differently: you end the brace from the previous closure,
then write the external parameter name and a colon, then start another brace.

Here’s how that looks:

doImportantWork {
print("This is the first work")
} second: {
print("This is the second work")
} third: {
print("This is the third work")
}

Having three trailing closures is not as uncommon as you might expect. For example, making a
section of content in SwiftUI is done with three trailing closures: one for the content itself, one
for a head to be put above, and one for a footer to be put below.

138 www.hackingwithswift.com
Summary: Closures
We’ve covered a lot about closures in the previous chapters, so let’s recap:

• You can copy functions in Swift, and they work the same as the original except they lose
their external parameter names.
• All functions have types, just like other data types. This includes the parameters they
receive along with their return type, which might be Void – also known as “nothing”.
• You can create closures directly by assigning to a constant or variable.
• Closures that accept parameters or return a value must declare this inside their braces,
followed by the keyword in.
• Functions are able to accept other functions as parameters. They must declare up front
exactly what data those functions must use, and Swift will ensure the rules are followed.
• In this situation, instead of passing a dedicated function you can also pass a closure – you
can make one directly. Swift allows both approaches to work.
• When passing a closure as a function parameter, you don’t need to explicitly write out the
types inside your closure if Swift can figure it out automatically. The same is true for the
return value – if Swift can figure it out, you don’t need to specify it.
• If one or more of a function’s final parameters are functions, you can use trailing closure
syntax.
• You can also use shorthand parameter names such as $0 and $1, but I would recommend
doing that only under some conditions.
• You can make your own functions that accept functions as parameters, although in practice
it’s much more important to know how to use them than how to create them.

Of all the various parts of the Swift language, I’d say closures are the single toughest thing to
learn. Not only is the syntax a little hard on your eyes at first, but the very concept of passing a
function into a function takes a little time to sink in.

So, if you’ve read through these chapters and feel like your head is about to explode, that’s
great – it means you’re half way to understanding closures!

www.hackingwithswift.com 139
Checkpoint 5
With closures under your belt, it’s time to try a little coding challenge using them.

You’ve already met sorted(), filter(), map(), so I’d like you to put them together in a chain –
call one, then the other, then the other back to back without using temporary variables.

Your input is this:

let luckyNumbers = [7, 4, 38, 21, 16, 15, 12, 33, 31, 49]

Your job is to:

1. Filter out any numbers that are even


2. Sort the array in ascending order
3. Map them to strings in the format “7 is a lucky number”
4. Print the resulting array, one item per line

So, your output should be as follows:

7 is a lucky number
15 is a lucky number
21 is a lucky number
31 is a lucky number
33 is a lucky number
49 is a lucky number

If you need hints they are below, but honestly you should be able to tackle this one either from
memory or by referencing recent chapters in this book.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 5. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

140 www.hackingwithswift.com
Checkpoint 5

1. You need to use the filter(), sorted(), and map() functions.


2. The order you run the functions matters – if you convert the array to a string first, sorted()
will do a string sort rather than an integer sort. That means 15 will come before 7, because
Swift will compare the “1” in “15” against “7”.
3. To chain these functions, use luckyNumbers.first { }.second { }, obviously putting the real
function calls in there.
4. You should use isMultiple(of:) to remove even numbers.

www.hackingwithswift.com 141
Chapter 7
Structs

142 www.hackingwithswift.com
How to create your own structs
Swift’s structs let us create our own custom, complex data types, complete with their own
variables and their own functions.

A simple struct looks like this:

struct Album {
let title: String
let artist: String
let year: Int

func printSummary() {
print("\(title) (\(year)) by \(artist)")
}
}

That creates a new type called Album, with two string constants called title and artist, plus an
integer constant called year. I also added a simple function that summarizes the values of our
three constants.

Notice how Album starts with a capital A? That’s the standard in Swift, and we’ve been using
it all along – think of String, Int, Bool, Set, and so on. When you’re referring to a data type,
we use camel case where the first letter is uppercased, but when you’re referring to something
inside the type, such as a variable or function, we use camel case where the first letter is
lowercased. Remember, for the most part this is only a convention rather than a rule, but it’s a
helpful one to stick with.

At this point, Album is just like String or Int – we can make them, assign values, copy them,
and so on. For example, we could make a couple of albums, then print some of their values and
call their functions:

let red = Album(title: "Red", artist: "Taylor Swift", year:


2012)

www.hackingwithswift.com 143
Structs

let wings = Album(title: "Wings", artist: "BTS", year: 2016)

print(red.title)
print(wings.artist)

red.printSummary()
wings.printSummary()

Notice how we can create a new Album as if we were calling a function – we just need to
provide values for each of the constants in the order they were defined.

As you can see, both red and wings come from the same Album struct, but once we create
them they are separate just like creating two strings.

You can see this in action when we call printSummary() on each struct, because that function
refers to title, artist, and year. In both instances the correct values are printed out for each
struct: red prints “Red (2012) by Taylor Swift” and wings prints out “Wings (2016) by BTS” –
Swift understands that when printSummary() is called on red, it should use the title, artist,
and year constants that also belong to red.

Where things get more interesting is when you want to have values that can change. For
example, we could create an Employee struct that can take vacation as needed:

struct Employee {
let name: String
var vacationRemaining: Int

func takeVacation(days: Int) {


if vacationRemaining > days {
vacationRemaining -= days
print("I'm going on vacation!")
print("Days remaining: \(vacationRemaining)")
} else {

144 www.hackingwithswift.com
How to create your own structs

print("Oops! There aren't enough days remaining.")


}
}
}

However, that won’t actually work – Swift will refuse to build the code.

You see, if we create an employee as a constant using let, Swift makes the employee and all its
data constant – we can call functions just fine, but those functions shouldn’t be allowed to
change the struct’s data because we made it constant.

As a result, Swift makes us take an extra step: any functions that only read data are fine as they
are, but any that change data belonging to the struct must be marked with a special mutating
keyword, like this:

mutating func takeVacation(days: Int) {

Now our code will build just fine, but Swift will stop us from calling takeVacation() from
constant structs.

In code, this is allowed:

var archer = Employee(name: "Sterling Archer",


vacationRemaining: 14)
archer.takeVacation(days: 5)
print(archer.vacationRemaining)

But if you change var archer to let archer you’ll find Swift refuses to build your code again –
we’re trying to call a mutating function on a constant struct, which isn’t allowed.

We’re going to explore structs in detail over the next few chapters, but first I want to give
some names to things.

• Variables and constants that belong to structs are called properties.

www.hackingwithswift.com 145
Structs

• Functions that belong to structs are called methods.


• When we create a constant or variable out of a struct, we call that an instance – you might
create a dozen unique instances of the Album struct, for example.
• When we create instances of structs we do so using an initializer like this: Album(title:
"Wings", artist: "BTS", year: 2016).

That last one might seem a bit odd at first, because we’re treating our struct like a function and
passing in parameters. This is a little bit of what’s called syntactic sugar – Swift silently
creates a special function inside the struct called init(), using all the properties of the struct as
its parameters. It then automatically treats these two pieces of code as being the same:

var archer1 = Employee(name: "Sterling Archer",


vacationRemaining: 14)
var archer2 = Employee.init(name: "Sterling Archer",
vacationRemaining: 14)

We actually relied on this behavior previously. Way back when I introduced Double for the
first time, I explained that you can’t add an Int and a Double and instead need to write code
like this:

let a = 1
let b = 2.0
let c = Double(a) + b

Now you can see what’s really happening here: Swift’s own Double type is implemented as a
struct, and has an initializer function that accepts an integer.

Swift is intelligent in the way it generates its initializer, even inserting default values if we
assign them to our properties.

For example, if our struct had these two properties

let name: String

146 www.hackingwithswift.com
How to create your own structs

var vacationRemaining = 14

Then Swift will silently generate an initializer with a default value of 14 for
vacationRemaining, making both of these valid:

let kane = Employee(name: "Lana Kane")


let poovey = Employee(name: "Pam Poovey", vacationRemaining:
35)

Tip: If you assign a default value to a constant property, that will be removed from the
initializer entirely. To assign a default but leave open the possibility of overriding it when
needed, use a variable property.

www.hackingwithswift.com 147
How to compute property values
dynamically
Structs can have two kinds of property: a stored property is a variable or constant that holds a
piece of data inside an instance of the struct, and a computed property calculates the value of
the property dynamically every time it’s accessed. This means computed properties are a blend
of both stored properties and functions: they are accessed like stored properties, but work like
functions.

As an example, previously we had an Employee struct that could track how many days of
vacation remained for that employee. Here’s a simplified version:

struct Employee {
let name: String
var vacationRemaining: Int
}

var archer = Employee(name: "Sterling Archer",


vacationRemaining: 14)
archer.vacationRemaining -= 5
print(archer.vacationRemaining)
archer.vacationRemaining -= 3
print(archer.vacationRemaining)

That works as a trivial struct, but we’re losing valuable information – we’re assigning this
employee 14 days of vacation then subtracting them as days are taken, but in doing so we’ve
lost how many days they were originally granted.

We could adjust this to use computed property, like so:

struct Employee {
let name: String

148 www.hackingwithswift.com
How to compute property values dynamically

var vacationAllocated = 14
var vacationTaken = 0

var vacationRemaining: Int {


vacationAllocated - vacationTaken
}
}

Now rather than making vacationRemaining something we can assign to directly, it is instead
calculated by subtracting how much vacation they have taken from how much vacation they
were allotted.

When we’re reading from vacationRemaining, it looks like a regular stored property:

var archer = Employee(name: "Sterling Archer",


vacationAllocated: 14)
archer.vacationTaken += 4
print(archer.vacationRemaining)
archer.vacationTaken += 4
print(archer.vacationRemaining)

This is really powerful stuff: we’re reading what looks like a property, but behind the scenes
Swift is running some code to calculate its value every time.

We can’t write to it, though, because we haven’t told Swift how that should be handled. To fix
that, we need to provide both a getter and a setter – fancy names for “code that reads” and
“code that writes” respectively.

In this case the getter is simple enough, because it’s just our existing code. But the setter is
more interesting – if you set vacationRemaining for an employee, do you mean that you want
their vacationAllocated value to be increased or decreased, or should vacationAllocated stay
the same and instead we change vacationTaken?

www.hackingwithswift.com 149
Structs

I’m going to assume the first of those two is correct, in which case here’s how the property
would look:

var vacationRemaining: Int {


get {
vacationAllocated - vacationTaken
}

set {
vacationAllocated = vacationTaken + newValue
}
}

Notice how get and set mark individual pieces of code to run when reading or writing a value.
More importantly, notice newValue – that’s automatically provided to us by Swift, and stores
whatever value the user was trying to assign to the property.

With both a getter and setter in place, we can now modify vacationRemaining:

var archer = Employee(name: "Sterling Archer",


vacationAllocated: 14)
archer.vacationTaken += 4
archer.vacationRemaining = 5
print(archer.vacationAllocated)

SwiftUI uses computed properties extensively – you’ll see them in the very first project you
create!

150 www.hackingwithswift.com
How to take action when a
property changes
Swift lets us create property observers, which are special pieces of code that run when
properties change. These take two forms: a didSet observer to run when the property just
changed, and a willSet observer to run before the property changed.

To see why property observers might be needed, think about code like this:

struct Game {
var score = 0
}

var game = Game()


game.score += 10
print("Score is now \(game.score)")
game.score -= 3
print("Score is now \(game.score)")
game.score += 1

That creates a Game struct, and modifies its score a few times. Each time the score changes, a
print() line follows it so we can keep track of the changes. Except there’s a bug: at the end the
score changed without being printed, which is a mistake.

With property observers we can solve this problem by attaching the print() call directly to the
property using didSet, so that whenever it changes – wherever it changes – we will always run
some code.

Here’s that same example, now with a property observer in place:

struct Game {
var score = 0 {
didSet {

www.hackingwithswift.com 151
Structs

print("Score is now \(score)")


}
}
}

var game = Game()


game.score += 10
game.score -= 3
game.score += 1

If you want it, Swift automatically provides the constant oldValue inside didSet, in case you
need to have custom functionality based on what you were changing from. There’s also a
willSet variant that runs some code before the property changes, which in turn provides the
new value that will be assigned in case you want to take different action based on that.

We can demonstrate all this functionality in action using one code sample, which will print
messages as the values change so you can see the flow when the code is run:

struct App {
var contacts = [String]() {
willSet {
print("Current value is: \(contacts)")
print("New value will be: \(newValue)")
}

didSet {
print("There are now \(contacts.count) contacts.")
print("Old value was \(oldValue)")
}
}
}

var app = App()

152 www.hackingwithswift.com
How to take action when a property changes

app.contacts.append("Adrian E")
app.contacts.append("Allen W")
app.contacts.append("Ish S")

Yes, appending to an array will trigger both willSet and didSet, so that code will print lots of
text when run.

In practice, willSet is used much less than didSet, but you might still see it from time to time
so it’s important you know it exists. Regardless of which you choose, please try to avoid
putting too much work into property observers – if something that looks trivial such as
game.score += 1 triggers intensive work, it will catch you out on a regular basis and cause all
sorts of performance problems.

www.hackingwithswift.com 153
How to create custom initializers
Initializers are specialized methods that are designed to prepare a new struct instance to be
used. You’ve already seen how Swift silently generates one for us based on the properties we
place inside a struct, but you can also create your own as long as you follow one golden rule:
all properties must have a value by the time the initializer ends.

Let’s start by looking again at Swift’s default initializer for structs:

struct Player {
let name: String
let number: Int
}

let player = Player(name: "Megan R", number: 15)

That creates a new Player instance by providing values for its two properties. Swift calls this
the memberwise initializer, which is a fancy way of saying an initializer that accepts each
property in the order it was defined.

Like I said, this kind of code is possible because Swift silently generates an initializer
accepting those two values, but we could write our own to do the same thing. The only catch
here is that you must be careful to distinguish between the names of parameters coming in and
the names of properties being assigned.

Here’s how that would look:

struct Player {
let name: String
let number: Int

init(name: String, number: Int) {


self.name = name
self.number = number

154 www.hackingwithswift.com
How to create custom initializers

}
}

That works the same as our previous code, except now the initializer is owned by us so we can
add extra functionality there if needed.

However, there are a couple of things I want you to notice:

1. There is no func keyword. Yes, this looks like a function in terms of its syntax, but Swift
treats initializers specially.
2. Even though this creates a new Player instance, initializers never explicitly have a return
type – they always return the type of data they belong to.
3. I’ve used self to assign parameters to properties to clarify we mean “assign the name
parameter to my name property”.

That last point is particularly important, because without self we’d have name = name and
that doesn’t make sense – are we assigning the property to the parameter, assigning the
parameter to itself, or something else? By writing self.name we’re clarifying we mean “the
name property that belongs to my current instance,” as opposed to anything else.

Of course, our custom initializers don’t need to work like the default memberwise initializer
Swift provides us with. For example, we could say that you must provide a player name, but
the shirt number is randomized:

struct Player {
let name: String
let number: Int

init(name: String) {
self.name = name
number = Int.random(in: 1...99)
}
}

www.hackingwithswift.com 155
Structs

let player = Player(name: "Megan R")


print(player.number)

Just remember the golden rule: all properties must have a value by the time the initializer ends.
If we had not provided a value for number inside the initializer, Swift would refuse to build
our code.

Important: Although you can call other methods of your struct inside your initializer, you
can’t do so before assigning values to all your properties – Swift needs to be sure everything is
safe before doing anything else.

You can add multiple initializers to your structs if you want, as well as leveraging features
such as external parameter names and default values. However, as soon as you implement your
own custom initializers you’ll lose access to Swift’s generated memberwise initializer unless
you take extra steps to retain it. This isn’t an accident: if you have a custom initializer, Swift
effectively assumes that’s because you have some special way to initialize your properties,
which means the default one should no longer be available.

156 www.hackingwithswift.com
How to limit access to internal
data using access control
By default, Swift’s structs let us access their properties and methods freely, but often that isn’t
what you want – sometimes you want to hide some data from external access. For example,
maybe you have some logic you need to apply before touching your properties, or maybe you
know that some methods need to be called in a certain way or order, and so shouldn’t be
touched externally.

We can demonstrate the problem with an example struct:

struct BankAccount {
var funds = 0

mutating func deposit(amount: Int) {


funds += amount
}

mutating func withdraw(amount: Int) -> Bool {


if funds >= amount {
funds -= amount
return true
} else {
return false
}
}
}

That has methods to deposit and withdraw money from a bank account, and should be used
like this:

var account = BankAccount()

www.hackingwithswift.com 157
Structs

account.deposit(amount: 100)
let success = account.withdraw(amount: 200)

if success {
print("Withdrew money successfully")
} else {
print("Failed to get the money")
}

But the funds property is just exposed to us externally, so what’s stopping us from touching it
directly? The answer is nothing at all – this kind of code is allowed:

account.funds -= 1000

That completely bypasses the logic we put in place to stop people taking out more money than
they have, and now our program could behave in weird ways.

To solve this, we can tell Swift that funds should be accessible only inside the struct – by
methods that belong to the struct, as well as any computed properties, property observers, and
so on.

This takes only one extra word:

private var funds = 0

And now accessing funds from outside the struct isn’t possible, but it is possible inside both
deposit() and withdraw(). If you try to read or write funds from outside the struct Swift will
refuse to build your code.

This is called access control, because it controls how a struct’s properties and methods can be
accessed from outside the struct.

Swift provides us with several options, but when you’re learning you’ll only need a handful:

158 www.hackingwithswift.com
How to limit access to internal data using access control

• Use private for “don’t let anything outside the struct use this.”
• Use fileprivate for “don’t let anything outside the current file use this.”
• Use public for “let anyone, anywhere use this.”

There’s one extra option that is sometimes useful for learners, which is this: private(set). This
means “let anyone read this property, but only let my methods write it.” If we had used that
with BankAccount, it would mean we could print account.funds outside of the struct, but
only deposit() and withdraw() could actually change the value.

In this case, I think private(set) is the best choice for funds: you can read the current bank
account balance at any time, but you can’t change it without running through my logic.

If you think about it, access control is really about limiting what you and other developers on
your team are able to do – and that’s sensible! If we can make Swift itself stop us from making
mistakes, that’s always a smart move.

Important: If you use private access control for one or more properties, chances are you’ll
need to create your own initializer.

www.hackingwithswift.com 159
Static properties and methods
You’ve seen how we can attach properties and methods to structs, and how each struct has its
own unique copy of those properties so that calling a method on the struct won’t read the
properties of a different struct from the same type.

Well, sometimes – only sometimes – you want to add a property or method to the struct itself,
rather than to one particular instance of the struct, which allows you to use them directly. I use
this technique a lot with SwiftUI for two things: creating example data, and storing fixed data
that needs to be accessed in various places.

First, let’s look at a simplified example of how to create and use static properties and methods:

struct School {
static var studentCount = 0

static func add(student: String) {


print("\(student) joined the school.")
studentCount += 1
}
}

Notice the keyword static in there, which means both the studentCount property and add()
method belong to the School struct itself, rather than to individual instances of the struct.

To use that code, we’d write the following:

School.add(student: "Taylor Swift")


print(School.studentCount)

I haven’t created an instance of School – we can literally use add() and studentCount directly
on the struct. This is because those are both static, which means they don’t exist uniquely on
instances of the struct.

160 www.hackingwithswift.com
Static properties and methods

This should also explain why we’re able to modify the studentCount property without
marking the method as mutating – that’s only needed with regular struct functions for times
when an instance of struct was created as a constant, and there is no instance when calling
add().

If you want to mix and match static and non-static properties and methods, there are two rules:

1. To access non-static code from static code… you’re out of luck: static properties and
methods can’t refer to non-static properties and methods because it just doesn’t make sense
– which instance of School would you be referring to?
2. To access static code from non-static code, always use your type’s name such as
School.studentCount. You can also use Self to refer to the current type.

Now we have self and Self, and they mean different things: self refers to the current value of
the struct, and Self refers to the current type.

Tip: It’s easy to forget the difference between self and Self, but if you think about it it’s just
like the rest of Swift’s naming – we start all our data types with a capital letter (Int, Double,
Bool, etc), so it makes sense for Self to start with a capital letter too.

Now, that sound you can hear is a thousand other learners saying “why the heck is this
needed?” And I get it – this can seem like a rather redundant feature at first. So, I want to show
you the two main ways I use static data.

First, I use static properties to organize common data in my apps. For example, I might have a
struct like AppData to store lots of shared values I use in many places:

struct AppData {
static let version = "1.3 beta 2"
static let saveFilename = "settings.json"
static let homeURL = "https://www.hackingwithswift.com"
}

Using this approach, everywhere I need to check or display something like my app’s version

www.hackingwithswift.com 161
Structs

number – an about screen, debug output, logging information, support emails, etc – I can read
AppData.version.

The second reason I commonly use static data is to create examples of my structs. As you’ll
see later on, SwiftUI works best when it can show previews of your app as you develop, and
those previews often require sample data. For example, if you’re showing a screen that
displays data on one employee, you’ll want to be able to show an example employee in the
preview screen so you can check it all looks correct as you work.

This is best done using a static example property on the struct, like this:

struct Employee {
let username: String
let password: String

static let example = Employee(username: "cfederighi",


password: "hairforceone")
}

And now whenever you need an Employee instance to work with in your design previews, you
can use Employee.example and you’re done.

Like I said at the beginning, there are only a handful of occasions when a static property or
method makes sense, but they are still a useful tool to have around.

162 www.hackingwithswift.com
Summary: Structs
Structs are used almost everywhere in Swift: String, Int, Double, Array and even Bool are all
implemented as structs, and now you can recognize that a function such as isMultiple(of:) is
really a method belonging to Int.

Let’s recap what else we learned:

• You can create your own structs by writing struct, giving it a name, then placing the
struct’s code inside braces.
• Structs can have variable and constants (known as properties) and functions (known as
methods)
• If a method tries to modify properties of its struct, you must mark it as mutating.
• You can store properties in memory, or create computed properties that calculate a value
every time they are accessed.
• We can attach didSet and willSet property observers to properties inside a struct, which is
helpful when we need to be sure that some code is always executed when the property
changes.
• Initializers are a bit like specialized functions, and Swift generates one for all structs using
their property names.
• You can create your own custom initializers if you want, but you must always make sure
all properties in your struct have a value by the time the initializer finishes, and before you
call any other methods.
• We can use access to mark any properties and methods as being available or unavailable
externally, as needed.
• It’s possible to attach a property or methods directly to a struct, so you can use them
without creating an instance of the struct.

www.hackingwithswift.com 163
Checkpoint 6
Structs sit at the core of every SwiftUI app, so it’s really important you take some extra time to
make sure you understand what they do and how they work.

To check your knowledge, here’s a small task for you: create a struct to store information
about a car, including its model, number of seats, and current gear, then add a method to
change gears up or down. Have a think about variables and access control: what data should be
a variable rather than a constant, and what data should be exposed publicly? Should the gear-
changing method validate its input somehow?

As always I’ll write some hints below, but first I’m going to leave some space so you don’t see
the hints by accident. As always, it’s a really good idea to try this challenge yourself before
looking at the hints – it’s the fastest way to identify parts you feel less confident with.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 6. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

• A car’s model and seat count aren’t going to change once produced, so they can be
constant. But its current gear clearly does change, so make that a variable.
• Changing gears up or down should ensure such a change is possible – there is no gear 0,
for example, and it’s safe to assume a maximum of 10 gears should cover most if not all
cars.
• If you use private access control, you will probably also need to create your own
initializer. (Is private the best choice here? Try it for yourself and see what you think!)
• Remember to use the mutating keyword on methods that change properties!

164 www.hackingwithswift.com
Chapter 8
Classes

www.hackingwithswift.com 165
How to create your own classes
Swift uses structs for storing most of its data types, including String, Int, Double, and Array,
but there is another way to create custom data types called classes. These have many things in
common with structs, but are different in key places.

First, the things that classes and structs have in common include:

• You get to create and name them.


• You can add properties and methods, including property observers and access control.
• You can create custom initializers to configure new instances however you want.

However, classes differ from structs in five key places:

1. You can make one class build upon functionality in another class, gaining all its properties
and methods as a starting point. If you want to selectively override some methods, you can
do that too.
2. Because of that first point, Swift won’t automatically generate a memberwise initializer for
classes. This means you either need to write your own initializer, or assign default values to
all your properties.
3. When you copy an instance of a class, both copies share the same data – if you change one
copy, the other one also changes.
4. When the final copy of a class instance is destroyed, Swift can optionally run a special
function called a deinitializer.
5. Even if you make a class constant, you can still change its properties as long as they are
variables.

On the surface those probably seem fairly random, and there’s a good chance you’re probably
wondering why classes are even needed when we already have structs.

However, SwiftUI uses classes extensively, mainly for point 3: all copies of a class share the
same data. This means many parts of your app can share the same information, so that if the
user changed their name in one screen all the other screens would automatically update to

166 www.hackingwithswift.com
How to create your own classes

reflect that change.

The other points matter, but are of varying use:

1. Being able to build one class based on another is really important in Apple’s older UI
framework, UIKit, but is much less common in SwiftUI apps. In UIKit it was common to
have long class hierarchies, where class A was built on class B, which was built on class C,
which was built on class D, etc.
2. Lacking a memberwise initializer is annoying, but hopefully you can see why it would be
tricky to implement given that one class can be based upon another – if class C added an
extra property it would break all the initializers for C, B, and A.
3. Being able to change a constant class’s variables links in to the multiple copy behavior of
classes: a constant class means we can’t change what pot our copy points to, but if the
properties are variable we can still change the data inside the pot. This is different from
structs, where each copy of a struct is unique and holds its own data.
4. Because one instance of a class can be referenced in several places, it becomes important to
know when the final copy has been destroyed. That’s where the deinitializer comes in: it
allows us to clean up any special resources we allocated when that last copy goes away.

Before we’re done, let’s look at just a tiny slice of code that creates and uses a class:

class Game {
var score = 0 {
didSet {
print("Score is now \(score)")
}
}
}

var newGame = Game()


newGame.score += 10

Yes, the only difference between that and a struct is that it was created using class rather than

www.hackingwithswift.com 167
Classes

struct – everything else is identical. That might make classes seem redundant, but trust me: all
five of their differences are important.

I’ll be going into more detail on the five differences between classes and structs in the
following chapters, but right now the most important thing to know is this: structs are
important, and so are classes – you will need both when using SwiftUI.

168 www.hackingwithswift.com
How to make one class inherit
from another
Swift lets us create classes by basing them on existing classes, which is a process known as
inheritance. When one class inherits functionality from another class (its “parent” or “super”
class), Swift will give the new class access (the “child class” or “subclass”) to the properties
and methods from that parent class, allowing us to make small additions or changes to
customize the way the new class behaves.

To make one class inherit from another, write a colon after the child class’s name, then add the
parent class’s name. For example, here is an Employee class with one property and an
initializer:

class Employee {
let hours: Int

init(hours: Int) {
self.hours = hours
}
}

We could make two subclasses of Employee, each of which will gain the hours property and
initializer:

class Developer: Employee {


func work() {
print("I'm writing code for \(hours) hours.")
}
}

class Manager: Employee {


func work() {

www.hackingwithswift.com 169
Classes

print("I'm going to meetings for \(hours) hours.")


}
}

Notice how those two child classes can refer directly to hours – it’s as if they added that
property themselves, except we don’t have to keep repeating ourselves.

Each of those classes inherit from Employee, but each then adds their own customization. So,
if we create an instance of each and call work(), we’ll get a different result:

let robert = Developer(hours: 8)


let joseph = Manager(hours: 10)
robert.work()
joseph.work()

As well as sharing properties, you can also share methods, which can then be called from the
child classes. As an example, try adding this to the Employee class:

func printSummary() {
print("I work \(hours) hours a day.")
}

Because Developer inherits from Employee, we can immediately start calling


printSummary() on instances of Developer, like this:

let novall = Developer(hours: 8)


novall.printSummary()

Things get a little more complicated when you want to change a method you inherited. For
example, we just put printSummary() into Employee, but maybe one of those child classes
wants slightly different behavior.

This is where Swift enforces a simple rule: if a child class wants to change a method from a

170 www.hackingwithswift.com
How to make one class inherit from another

parent class, you must use override in the child class’s version. This does two things:

1. If you attempt to change a method without using override, Swift will refuse to build your
code. This stops you accidentally overriding a method.
2. If you use override but your method doesn’t actually override something from the parent
class, Swift will refuse to build your code because you probably made a mistake.

So, if we wanted developers to have a unique printSummary() method, we’d add this to the
Developer class:

override func printSummary() {


print("I'm a developer who will sometimes work \(hours) hours
a day, but other times spend hours arguing about whether code
should be indented using tabs or spaces.")
}

Swift is smart about how method overrides work: if your parent class has a work() method that
returns nothing, but the child class has a work() method that accepts a string to designate
where the work is being done, that does not require override because you aren’t replacing the
parent method.

Tip: If you know for sure that your class should not support inheritance, you can mark it as
final. This means the class itself can inherit from other things, but can’t be used to inherit from
– no child class can use a final class as its parent.

www.hackingwithswift.com 171
How to add initializers for classes
Class initializers in Swift are more complicated than struct initializers, but with a little
cherrypicking we can focus on the part that really matters: if a child class has any custom
initializers, it must always call the parent’s initializer after it has finished setting up its own
properties, if it has any.

Like I said previously, Swift won’t automatically generate a memberwise initializer for classes.
This applies with or without inheritance happening – it will never generate a memberwise
initializer for you. So, you either need to write your own initializer, or provide default values
for all the properties of the class.

Let’s start by defining a new class:

class Vehicle {
let isElectric: Bool

init(isElectric: Bool) {
self.isElectric = isElectric
}
}

That has a single Boolean property, plus an initializer to set the value for that property.
Remember, using self here makes it clear we’re assigning the isElectric parameter to the
property of the same name.

Now, let’s say we wanted to make a Car class inheriting from Vehicle – you might start out
writing something like this:

class Car: Vehicle {


let isConvertible: Bool

init(isConvertible: Bool) {
self.isConvertible = isConvertible

172 www.hackingwithswift.com
How to add initializers for classes

}
}

But Swift will refuse to build that code: we’ve said that the Vehicle class needs to know
whether it’s electric or not, but we haven’t provided a value for that.

What Swift wants us to do is provide Car with an initializer that includes both isElectric and
isConvertible, but rather than trying to store isElectric ourselves we instead need to pass it on
– we need to ask the super class to run its own initializer.

Here’s how that looks:

class Car: Vehicle {


let isConvertible: Bool

init(isElectric: Bool, isConvertible: Bool) {


self.isConvertible = isConvertible
super.init(isElectric: isElectric)
}
}

super is another one of those values that Swift automatically provides for us, similar to self: it
allows us to call up to methods that belong to our parent class, such as its initializer. You can
use it with other methods if you want; it’s not limited to initializers.

Now that we have a valid initializer in both our classes, we can make an instance of Car like
so:

let teslaX = Car(isElectric: true, isConvertible: false)

Tip: If a subclass does not have any of its own initializers, it automatically inherits the
initializers of its parent class.

www.hackingwithswift.com 173
How to copy classes
In Swift, all copies of a class instance share the same data, meaning that any changes you make
to one copy will automatically change the other copies. This happens because classes are
reference types in Swift, which means all copies of a class all refer back to the same
underlying pot of data.

To see this in action, try this simple class:

class User {
var username = "Anonymous"
}

That has just one property, but because it’s stored inside a class it will get shared across all
copies of the class.

So, we could create an instance of that class:

var user1 = User()

We could then take a copy of user1 and change the username value:

var user2 = user1


user2.username = "Taylor"

I hope you see where this is going! Now we’ve changed the copy’s username property we can
then print out the same properties from each different copy:

print(user1.username)
print(user2.username)

…and that’s going to print “Taylor” for both – even though we only changed one of the
instances, the other also changed.

174 www.hackingwithswift.com
How to copy classes

This might seem like a bug, but it’s actually a feature – and a really important feature too,
because it’s what allows us to share common data across all parts of our app. As you’ll see,
SwiftUI relies very heavily on classes for its data, specifically because they can be shared so
easily.

In comparison, structs do not share their data amongst copies, meaning that if we change class
User to struct User in our code we get a different result: it will print “Anonymous” then
“Taylor”, because changing the copy didn’t also adjust the original.

If you want to create a unique copy of a class instance – sometimes called a deep copy – you
need to handle creating a new instance and copy across all your data safely.

In our case that’s straightforward:

class User {
var username = "Anonymous"

func copy() -> User {


let user = User()
user.username = username
return user
}
}

Now we can safely call copy() to get an object with the same starting data, but any future
changes won’t impact the original.

www.hackingwithswift.com 175
How to create a deinitializer for a
class
Swift’s classes can optionally be given a deinitializer, which is a bit like the opposite of an
initializer in that it gets called when the object is destroyed rather than when it’s created.

This comes with a few small provisos:

1. Just like initializers, you don’t use func with deinitializers – they are special.
2. Deinitializers can never take parameters or return data, and as a result aren’t even written
with parentheses.
3. Your deinitializer will automatically be called when the final copy of a class instance is
destroyed. That might mean it was created inside a function that is now finishing, for
example.
4. We never call deinitializers directly; they are handled automatically by the system.
5. Structs don’t have deinitializers, because you can’t copy them.

Exactly when your deinitializers are called depends on what you’re doing, but really it comes
down to a concept called scope. Scope more or less means “the context where information is
available”, and you’ve seen lots of examples already:

1. If you create a variable inside a function, you can’t access it from outside the function.
2. If you create a variable inside an if condition, that variable is not available outside the
condition.
3. If you create a variable inside a for loop, including the loop variable itself, you can’t use it
outside the loop.

If you look at the big picture, you’ll see each of those use braces to create their scope:
conditions, loops, and functions all create local scopes.

When a value exits scope we mean the context it was created in is going away. In the case of
structs that means the data is being destroyed, but in the case of classes it means only one copy

176 www.hackingwithswift.com
How to create a deinitializer for a class

of the underlying data is going away – there might still be other copies elsewhere. But when
the final copy goes away – when the last constant or variable pointing at a class instance is
destroyed – then the underlying data is also destroyed, and the memory it was using is returned
back to the system.

To demonstrate this, we could create a class that prints a message when it’s created and
destroyed, using an initializer and deinitializer:

class User {
let id: Int

init(id: Int) {
self.id = id
print("User \(id): I'm alive!")
}

deinit {
print("User \(id): I'm dead!")
}
}

Now we can create and destroy instances of that quickly using a loop – if we create a User
instance inside the loop, it will be destroyed when the loop iteration finishes:

for i in 1...3 {
let user = User(id: i)
print("User \(user.id): I'm in control!")
}

When that code runs you’ll see it creates and destroys each user individually, with one being
destroyed fully before another is even created.

Remember, the deinitializer is only called when the last remaining reference to a class instance

www.hackingwithswift.com 177
Classes

is destroyed. This might be a variable or constant you have stashed away, or perhaps you
stored something in an array.

For example, if we were adding our User instances as they were created, they would only be
destroyed when the array is cleared:

var users = [User]()

for i in 1...3 {
let user = User(id: i)
print("User \(user.id): I'm in control!")
users.append(user)
}

print("Loop is finished!")
users.removeAll()
print("Array is clear!")

178 www.hackingwithswift.com
How to work with variables inside
classes
Swift’s classes work a bit like signposts: every copy of a class instance we have is actually a
signpost pointing to the same underlying piece of data. Mostly this matters because of the way
changing one copy changes all the others, but it also matters because of how classes treat
variable properties.

This one small code sample demonstrates how things work:

class User {
var name = "Paul"
}

let user = User()


user.name = "Taylor"
print(user.name)

That creates a constant User instance, but then changes it – it changes the constant value.
That’s bad, right?

Except it doesn’t change the constant value at all. Yes, the data inside the class has changed,
but the class instance itself – the object we created – has not changed, and in fact can’t be
changed because we made it constant.

Think of it like this: we created a constant signpoint pointing towards a user, but we erased that
user’s name tag and wrote in a different name. The user in question hasn’t changed – the
person still exists – but a part of their internal data has changed.

Now, if we had made the name property a constant using let, then it could not be changed –
we have a constant signpost pointing to a user, but we’ve written their name in permanent ink
so that it can’t be erased.

www.hackingwithswift.com 179
Classes

In contrast, what happens if we made both the user instance and the name property variables?
Now we’d be able to change the property, but we’d also be able to change to a wholly new
User instance if we wanted. To continue the signpost analogy, it would be like turning the
signpost to point at wholly different person.

Try it with this code:

class User {
var name = "Paul"
}

var user = User()


user.name = "Taylor"
user = User()
print(user.name)

That would end up printing “Paul”, because even though we changed name to “Taylor” we
then overwrote the whole user object with a new one, resetting it back to “Paul”.

The final variation is having a variable instance and constant properties, which would mean we
can create a new User if we want, but once it’s done we can’t change its properties.

So, we end up with four options:

1. Constant instance, constant property – a signpost that always points to the same user, who
always has the same name.
2. Constant instance, variable property – a signpost that always points to the same user, but
their name can change.
3. Variable instance, constant property – a signpost that can point to different users, but their
names never change.
4. Variable instance, variable property – a signpost that can point to different users, and those
users can also change their names.

180 www.hackingwithswift.com
How to work with variables inside classes

This might seem awfully confusing, and perhaps even pedantic. However, it serves an
important purpose because of the way class instances get shared.

Let’s say you’ve been given a User instance. Your instance is constant, but the property inside
was declared as a variable. This tells you not only that you can change that property if you
want to, but more importantly tells you there’s the possibility of the property being changed
elsewhere – that class you have could be a copy from somewhere else, and because the
property is variable it means some other part of code could change it by surprise.

When you see constant properties it means you can be sure neither your current code nor any
other part of your program can change it, but as soon as you’re dealing with variable properties
– regardless of whether the class instance itself is constant or not – it opens up the possibility
that the data could change under your feet.

This is different from structs, because constant structs cannot have their properties changed
even if the properties were made variable. Hopefully you can now see why this
happens: structs don’t have the whole signpost thing going on, they hold their data directly.
This means if you try to change a value inside the struct you’re also implicitly changing the
struct itself, which isn’t possible because it’s constant.

One upside to all this is that classes don’t need to use the mutating keyword with methods that
change their data. This keyword is really important for structs because constant structs cannot
have their properties changed no matter how they were created, so when Swift sees us calling a
mutating method on a constant struct instance it knows that shouldn’t be allowed.

With classes, how the instance itself was created no longer matters – the only thing that
determines whether a property can be modified or not is whether the property itself was
created as a constant. Swift can see that for itself just by looking at how you made the
property, so there’s no more need to mark the method specially.

www.hackingwithswift.com 181
Summary: Classes
Classes aren’t quite as commonly used as structs, but they serve an invaluable purpose for
sharing data, and if you ever choose to learn Apple’s older UIKit framework you’ll find
yourself using them extensively.

Let’s recap what we learned:

• Classes have lots of things in common with structs, including the ability to have properties
and methods, but there are five key differences between classes and structs.
• First, classes can inherit from other classes, which means they get access to the properties
and methods of their parent class. You can optionally override methods in child classes if
you want, or mark a class as being final to stop others subclassing it.
• Second, Swift doesn’t generate a memberwise initializer for classes, so you need to do it
yourself. If a subclass has its own initializer, it must always call the parent class’s initializer
at some point.
• Third, if you create a class instance then take copies of it, all those copies point back to the
same instance. This means changing some data in one of the copies changes them all.
• Fourth, classes can have deinitializers that run when the last copy of one instance is
destroyed.
• Finally, variable properties inside class instances can be changed regardless of whether the
instance itself was created as variable.

182 www.hackingwithswift.com
Checkpoint 7
Now that you understand how classes work, and, just as importantly, how they are different
from structs, it’s time to tackle a small challenge to check your progress.

Your challenge is this: make a class hierarchy for animals, starting with Animal at the top,
then Dog and Cat as subclasses, then Corgi and Poodle as subclasses of Dog, and Persian and
Lion as subclasses of Cat.

But there’s more:

1. The Animal class should have a legs integer property that tracks how many legs the animal
has.
2. The Dog class should have a speak() method that prints a generic dog barking string, but
each of the subclasses should print something slightly different.
3. The Cat class should have a matching speak() method, again with each subclass printing
something different.
4. The Cat class should have an isTame Boolean property, provided using an initializer.

I’ll provide some hints in a moment, but first I recommend you go ahead and try it yourself.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 7. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

1. You’ll need seven independent classes here, of which only one has no parent class.
2. To make one class inherit from another, write it like this: class SomeClass: OtherClass.
3. You can make subclasses have a different speak() method to their parent by using the
override keyword.
4. All our subclasses have four legs, but you still need to make sure you pass that data up to
the Animal class inside the Cat initializer.

www.hackingwithswift.com 183
Chapter 9
Protocols and
extensions

184 www.hackingwithswift.com
How to create and use protocols
Protocols are a bit like contracts in Swift: they let us define what kinds of functionality we
expect a data type to support, and Swift ensures that the rest of our code follows those rules.

Think about how we might write some code to simulate someone commuting from their home
to their office. We might create a small Car struct, then write a function like this:

func commute(distance: Int, using vehicle: Car) {


// lots of code here
}

Of course, they might also commute by train, so we’d also write this:

func commute(distance: Int, using vehicle: Train) {


// lots of code here
}

Or they might travel by bus:

func commute(distance: Int, using vehicle: Bus) {


// lots of code here
}

Or they might use a bike, an e-scooter, a ride share, or any number of other transport options.

The truth is that at this level we don’t actually care how the underlying trip happens. What we
care about is much broader: how long might it take for the user to commute using each option,
and how to perform the actual act of moving to the new location.

This is where protocols come in: they let us define a series of properties and methods that we
want to use. They don’t implement those properties and methods – they don’t actually put any
code behind it – they just say that the properties and methods must exist, a bit like a blueprint.

www.hackingwithswift.com 185
Protocols and extensions

For example, we could define a new Vehicle protocol like this:

protocol Vehicle {
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}

Let’s break that down:

• To create a new protocol we write protocol followed by the protocol name. This is a new
type, so we need to use camel case starting with an uppercase letter.
• Inside the protocol we list all the methods we require in order for this protocol to work the
way we expect.
• These methods do not contain any code inside – there are no function bodies provided
here. Instead, we’re specifying the method names, parameters, and return types. You can
also mark methods as being throwing or mutating if needed.

So we’ve made a protocol – how has that helped us?

Well, now we can design types that work with that protocol. This means creating new structs,
classes, or enums that implement the requirements for that protocol, which is a process we call
adopting or conforming to the protocol.

The protocol doesn’t specify the full range of functionality that must exist, only a bare
minimum. This means when you create new types that conform to the protocol you can add all
sorts of other properties and methods as needed.

For example, we could make a Car struct that conforms to Vehicle, like this:

struct Car: Vehicle {


func estimateTime(for distance: Int) -> Int {
distance / 50
}

186 www.hackingwithswift.com
How to create and use protocols

func travel(distance: Int) {


print("I'm driving \(distance)km.")
}

func openSunroof() {
print("It's a nice day!")
}
}

There are a few things I want to draw particular attention to in that code:

1. We tell Swift that Car conforms to Vehicle by using a colon after the name Car, just like
how we mark subclasses.
2. All the methods we listed in Vehicle must exist exactly in Car. If they have slightly
different names, accept different parameters, have different return types, etc, then Swift
will say we haven’t conformed to the protocol.
3. The methods in Car provide actual implementations of the methods we defined in the
protocol. In this case, that means our struct provides a rough estimate for how many
minutes it takes to drive a certain distance, and prints a message when travel() is called.
4. The openSunroof() method doesn’t come from the Vehicle protocol, and doesn’t really
make sense there because many vehicle types don’t have a sunroof. But that’s okay,
because the protocol describes only the minimum functionality conforming types must
have, and they can add their own as needed.

So, now we’ve created a protocol, and made a Car struct that conforms to the protocol.

To finish off, let’s update the commute() function from earlier so that it uses the new methods
we added to Car:

func commute(distance: Int, using vehicle: Car) {


if vehicle.estimateTime(for: distance) > 100 {
print("That's too slow! I'll try a different vehicle.")
} else {

www.hackingwithswift.com 187
Protocols and extensions

vehicle.travel(distance: distance)
}
}

let car = Car()


commute(distance: 100, using: car)

That code all works, but here the protocol isn’t actually adding any value. Yes, it made us
implement two very specific methods inside Car, but we could have done that without adding
the protocol, so why bother?

Here comes the clever part: Swift knows that any type conforming to Vehicle must implement
both the estimateTime() and travel() methods, and so it actually lets us use Vehicle as the
type of our parameter rather than Car. We can rewrite the function to this:

func commute(distance: Int, using vehicle: Vehicle) {

Now we’re saying this function can be called with any type of data, as long as that type
conforms to the Vehicle protocol. The body of the function doesn’t need to change, because
Swift knows for sure that the estimateTime() and travel() methods exist.

If you’re still wondering why this is useful, consider the following struct:

struct Bicycle: Vehicle {


func estimateTime(for distance: Int) -> Int {
distance / 10
}

func travel(distance: Int) {


print("I'm cycling \(distance)km.")
}
}

188 www.hackingwithswift.com
How to create and use protocols

let bike = Bicycle()


commute(distance: 50, using: bike)

Now we have a second struct that also conforms to Vehicle, and this is where the power of
protocols becomes apparent: we can now either pass a Car or a Bicycle into the commute()
function. Internally the function can have all the logic it wants, and when it calls
estimateTime() or travel() Swift will automatically use the appropriate one – if we pass in a
car it will say “I’m driving”, but if we pass in a bike it will say “I’m cycling”.

So, protocols let us talk about the kind of functionality we want to work with, rather than the
exact types. Rather than saying “this parameter must be a car”, we can instead say “this
parameter can be anything at all, as long as it’s able to estimate travel time and move to a new
location.”

As well as methods, you can also write protocols to describe properties that must exist on
conforming types. To do this, write var, then a property name, then list whether it should be
readable and/or writeable.

For example, we could specify that all types that conform Vehicle must specify how many
seats they have and how many passengers they currently have, like this:

protocol Vehicle {
var name: String { get }
var currentPassengers: Int { get set }
func estimateTime(for distance: Int) -> Int
func travel(distance: Int)
}

That adds two properties:

1. A string called name, which must be readable. That might mean it’s a constant, but it might
also be a computed property with a getter.
2. An integer called currentPassengers, which must be read-write. That might mean it’s a

www.hackingwithswift.com 189
Protocols and extensions

variable, but it might also be a computed property with a getter and setter.

Type annotation is required for both of them, because we can’t provide a default value in a
protocol, just like how protocols can’t provide implementations for methods.

With those two extra requirements in place, Swift will warn us that both Car and Bicycle no
longer conform to the protocol because they are missing the properties. To fix that, we could
add the following properties to Car:

let name = "Car"


var currentPassengers = 1

And these to Bicycle:

let name = "Bicycle"


var currentPassengers = 1

Again, though, you could replace those with computed properties as long as you obey the rules
– if you use { get set } then you can’t conform to the protocol using a constant property.

So now our protocol requires two methods and two properties, meaning that all conforming
types must implement those four things in order for our code to work. This in turn means Swift
knows for sure that functionality is present, so we can write code relying on it.

For example, we could write a method that accepts an array of vehicles and uses it to calculate
estimates across a range of options:

func getTravelEstimates(using vehicles: [Vehicle], distance:


Int) {
for vehicle in vehicles {
let estimate = vehicle.estimateTime(for: distance)
print("\(vehicle.name): \(estimate) hours to travel \
(distance)km")
}

190 www.hackingwithswift.com
How to create and use protocols

I hope that shows you the real power of protocols – we accept a whole array of the Vehicle
protocol, which means we can pass in a Car, a Bicycle, or any other struct that conforms to
Vehicle, and it will automatically work:

getTravelEstimates(using: [car, bike], distance: 150)

As well as accepting protocols as parameters, you can also return protocols from a function if
needed.

Tip: You can conform to as many protocols as you need, just by listing them one by one
separated with a comma. If you ever need to subclass something and conform to a protocol,
you should put the parent class name first, then write your protocols afterwards.

www.hackingwithswift.com 191
How to use opaque return types
Swift provides one really obscure, really complex, but really important feature called opaque
return types, which let us remove complexity in our code. Honestly I wouldn’t cover it in a
beginners course if it weren’t for one very important fact: you will see it immediately as soon
as you create your very first SwiftUI project.

Important: You don’t need to understand in detail how opaque return types work, only that
they exist and do a very specific job. As you’re following along here you might start to wonder
why this feature is useful, but trust me: it is important, and it is useful, so try to power through!

Let’s implement two simple functions:

func getRandomNumber() -> Int {


Int.random(in: 1...6)
}

func getRandomBool() -> Bool {


Bool.random()
}

Tip: Bool.random() returns either true or false. Unlike random integers and decimals, we
don’t need to specify any parameters because there are no customization options.

So, getRandomNumber() returns a random integer, and getRandomBool() returns a random


Boolean.

Both Int and Bool conform to a common Swift protocol called Equatable, which means “can
be compared for equality.” The Equatable protocol is what allows us to use ==, like this:

print(getRandomNumber() == getRandomNumber())

Because both of these types conform to Equatable, we could try amending our function to
return an Equatable value, like this:

192 www.hackingwithswift.com
How to use opaque return types
return an Equatable value, like this:

func getRandomNumber() -> Equatable {


Int.random(in: 1...6)
}

func getRandomBool() -> Equatable {


Bool.random()
}

However, that code won’t work, and Swift will throw up an error message that is unlikely to be
helpful at this point in your Swift career: “protocol 'Equatable' can only be used as a generic
constraint because it has Self or associated type requirements”. What Swift’s error means is
that returning Equatable doesn’t make sense, and understanding why it doesn’t make sense is
the key to understanding opaque return types.

First up: yes, you can return protocols from functions, and often it’s a really helpful thing to
do. For example, you might have a function that finds car rentals for users: it accepts the
number of passengers that it needs to carry, along with how much luggage they want, but it
might send back one of several structs: Compact, SUV, Minivan, and so on.

We can handle this by returning a Vehicle protocol that is adopted by all those structs, and so
whoever calls the function will get back some kind of car matching their request without us
having to write 10 different functions to handle all car varieties. Each of those car types will
implement all the methods and properties of Vehicle, which means they are interchangeable –
from a coding perspective we don’t care which of the options we get back.

Now think about sending back an Int or a Bool. Yes, both conform to Equatable, but they
aren’t interchangeable – we can’t use == to compare an Int and a Bool, because Swift won’t
let us regardless of what protocols they conform to.

Returning a protocol from a function is useful because it lets us hide information: rather than
stating the exact type that is being returned, we get to focus on the functionality that is
returned. In the case of a Vehicle protocol, that might mean reporting back the number of
seats, the approximate fuel usage, and a price. This means we can change our code later

www.hackingwithswift.com 193
Protocols and extensions

without breaking things: we could return a RaceCar, or a PickUpTruck, etc, as long as they
implement the properties and methods required by Vehicle.

Hiding information in this way is really useful, but it just isn’t possible with Equatable
because it isn’t possible to compare two different Equatable things. Even if we call
getRandomNumber() twice to get two integers, we can’t compare them because we’ve
hidden their exact data type – we’ve hidden the fact that they are two integers that actually
could be compared.

This is where opaque return types come in: they let us hide information in our code, but not
from the Swift compiler. This means we reserve the right to make our code flexible internally
so that we can return different things in the future, but Swift always understands the actual
data type being returned and will check it appropriately.

To upgrade our two functions to opaque return types, add the keyword some before their return
type, like this:

func getRandomNumber() -> some Equatable {


Int.random(in: 1...6)
}

func getRandomBool() -> some Equatable {


Bool.random()
}

And now we can call getRandomNumber() twice and compare the results using ==. From our
perspective we still only have some Equatable data, but Swift knows that behind the scenes
they are actually two integers.

Returning an opaque return type means we still get to focus on the functionality we want to
return rather than the specific type, which in turn allows us to change our mind in the future
without breaking the rest of our code. For example, getRandomNumber() could switch to
using Double.random(in:) and the code would still work great.

194 www.hackingwithswift.com
How to use opaque return types

But the advantage here is that Swift always knows the real underlying data type. It’s a subtle
distinction, but returning Vehicle means "any sort of Vehicle type but we don't know what",
whereas returning some Vehicle means "a specific sort of Vehicle type but we don't want to
say which one.”

At this point I expect your head is spinning, so let me give you a real example of why this
matters in SwiftUI. SwiftUI needs to know exactly what kind of layout you want to show on
the screen, and so we write code to describe it.

In English, we might say something like this: “there’s a screen with a toolbar at the top, a tab
bar at the bottom, and in the middle is a scrolling grid of color icons, each of which has a label
below saying what the icon means written in a bold font, and when you tap an icon a message
appears.”

When SwiftUI asks for our layout, that description – the whole thing – becomes the return type
for the layout. We need to be explicit about every single thing we want to show on the screen,
including positions, colors, font sizes, and more. Can you imagine typing that as your return
type? It would be a mile long! And every time you changed the code to generate your layout,
you’d need to change the return type to match.

This is where opaque return types come to the rescue: we can return the type some View,
which means that some kind of view screen will be returned but we don’t want to have to write
out its mile-long type. At the same time, Swift knows the real underlying type because that’s
how opaque return types work: Swift always knows the exact type of data being sent back, and
SwiftUI will use that create its layouts.

Like I said at the beginning, opaque return types are a really obscure, really complex, but
really important feature, and I wouldn’t cover them in a beginners course if it weren’t for the
fact that they are used extensively in SwiftUI.

So, when you see some View in your SwiftUI code, it’s effectively us telling Swift “this is
going to send back some kind of view to lay out, but I don’t want to write out the exact thing –
you figure it out for yourself.”

www.hackingwithswift.com 195
How to create and use extensions
Extensions let us add functionality to any type, whether we created it or someone else created
it – even one of Apple’s own types.

To demonstrate this, I want to introduce you to a useful method on strings, called


trimmingCharacters(in:). This removes certain kinds of characters from the start or end of a
string, such as alphanumeric letters, decimal digits, or, most commonly, whitespace and new
lines.

Whitespace is the general term of the space character, the tab character, and a variety of other
variants on those two. New lines are line breaks in text, which might sound simple but in
practice of course there is no single one way of making them, so when we ask to trim new lines
it will automatically take care of all the variants for us.

For example, here’s a string that has whitespace on either side:

var quote = " The truth is rarely pure and never simple "

If we wanted to trim the whitespace and newlines on either side, we could do so like this:

let trimmed =
quote.trimmingCharacters(in: .whitespacesAndNewlines)

The .whitespacesAndNewlines value comes from Apple’s Foundation API, and actually so
does trimmingCharacters(in:) – like I said way back at the beginning of this course,
Foundation is really packed with useful code!

Having to call trimmingCharacters(in:) every time is a bit wordy, so let’s write an extension
to make it shorter:

extension String {
func trimmed() -> String {
self.trimmingCharacters(in: .whitespacesAndNewlines)

196 www.hackingwithswift.com
How to create and use extensions

}
}

Let’s break that down…

1. We start with the extension keyword, which tells Swift we want to add functionality to an
existing type.
2. Which type? Well, that comes next: we want to add functionality to String.
3. Now we open a brace, and all the code until the final closing brace is there to be added to
strings.
4. We’re adding a new method called trimmed(), which returns a new string.
5. Inside there we call the same method as before: trimmingCharacters(in:), sending back its
result.
6. Notice how we can use self here – that automatically refers to the current string. This is
possible because we’re currently in a string extension.

And now everywhere we want to remove whitespace and newlines we can just write the
following:

let trimmed = quote.trimmed()

Much easier!

That’s saved some typing, but is it that much better than a regular function?

Well, the truth is that we could have written a function like this:

func trim(_ string: String) -> String {


string.trimmingCharacters(in: .whitespacesAndNewlines)
}

Then used it like this:

let trimmed2 = trim(quote)

www.hackingwithswift.com 197
Protocols and extensions

That’s less code than using an extension, both in terms of making the function and using it.
This kind of function is called a global function, because it’s available everywhere in our
project.

However, the extension has a number of benefits over the global function, including:

1. When you type quote. Xcode brings up a list of methods on the string, including all the
ones we add in extensions. This makes our extra functionality easy to find.
2. Writing global functions makes your code rather messy – they are hard to organize and hard
to keep track of. On the other hand, extensions are naturally grouped by the data type they
are extending.
3. Because your extension methods are a full part of the original type, they get full access to
the type’s internal data. That means they can use properties and methods marked with
private access control, for example.

What’s more, extensions make it easier to modify values in place – i.e., to change a value
directly, rather than return a new value.

For example, earlier we wrote a trimmed() method that returns a new string with whitespace
and newlines removed, but if we wanted to modify the string directly we could add this to the
extension:

mutating func trim() {


self = self.trimmed()
}

Because the quote string was created as a variable, we can trim it in place like this:

quote.trim()

Notice how the method has slightly different naming now: when we return a new value we
used trimmed(), but when we modified the string directly we used trim(). This is intentional,

198 www.hackingwithswift.com
How to create and use extensions

and is part of Swift’s design guidelines: if you’re returning a new value rather than changing it
in place, you should use word endings like ed or ing, like reversed().

Tip: Previously I introduced you to the sorted() method on arrays. Now you know this rule,
you should realize that if you create a variable array you can use sort() on it to sort the array in
place rather than returning a new copy.

You can also use extensions to add properties to types, but there is one rule: they must only be
computed properties, not stored properties. The reason for this is that adding new stored
properties would affect the actual size of the data types – if we added a bunch of stored
properties to an integer then every integer everywhere would need to take up more space in
memory, which would cause all sorts of problems.

Fortunately, we can still get a lot done using computed properties. For example, one property I
like to add to strings is called lines, which breaks the string up into an array of individual lines.
This wraps another string method called components(separatedBy:), which breaks the string
up into a string array by splitting it on a boundary of our choosing. In this case we’d want that
boundary to be new lines, so we’d add this to our string extension:

var lines: [String] {


self.components(separatedBy: .newlines)
}

With that in place we can now read the lines property of any string, like so:

let lyrics = """


But I keep cruising
Can't stop, won't stop moving
It's like I got this music in my mind
Saying it's gonna be alright
"""

print(lyrics.lines.count)

www.hackingwithswift.com 199
Protocols and extensions

Whether they are single lines or complex pieces of functionality, extensions always have the
same goal: to make your code easier to write, easier to read, and easier to maintain in the long
term.

Before we’re done, I want to show you one really useful trick when working with extensions.
You’ve seen previously how Swift automatically generates a memberwise initializer for
structs, like this:

struct Book {
let title: String
let pageCount: Int
let readingHours: Int
}

let lotr = Book(title: "Lord of the Rings", pageCount: 1178,


readingHours: 24)

I also mentioned how creating your own initializer means that Swift will no longer provide the
memberwise one for us. This is intentional, because a custom initializer means we want to
assign data based on some custom logic, like this:

struct Book {
let title: String
let pageCount: Int
let readingHours: Int

init(title: String, pageCount: Int) {


self.title = title
self.pageCount = pageCount
self.readingHours = pageCount / 50
}
}

200 www.hackingwithswift.com
How to create and use extensions

If Swift were to keep the memberwise initializer in this instance, it would skip our logic for
calculating the approximate reading time.

However, sometimes you want both – you want the ability to use a custom initializer, but also
retain Swift’s automatic memberwise initializer. In this situation it’s worth knowing exactly
what Swift is doing: if we implement a custom initializer inside our struct, then Swift disables
the automatic memberwise initializer.

That extra little detail might give you a hint on what’s coming next: if we implement a custom
initializer inside an extension, then Swift won’t disable the automatic memberwise initializer.
This makes sense if you think about it: if adding a new initializer inside an extension also
disabled the default initializer, one small change from us could break all sorts of other Swift
code.

So, if we wanted our Book struct to have the default memberwise initializer as well as our
custom initializer, we’d place the custom one in an extension, like this:

extension Book {
init(title: String, pageCount: Int) {
self.title = title
self.pageCount = pageCount
self.readingHours = pageCount / 50
}
}

www.hackingwithswift.com 201
How to create and use protocol
extensions
Protocols let us define contracts that conforming types must adhere to, and extensions let us
add functionality to existing types. But what would happen if we could write extensions on
protocols?

Well, wonder no more because Swift supports exactly this using the aptly named protocol
extensions: we can extend a whole protocol to add method implementations, meaning that any
types conforming to that protocol get those methods.

Let’s start with a trivial example. It’s very common to write a condition checking whether an
array has any values in, like this:

let guests = ["Mario", "Luigi", "Peach"]

if guests.isEmpty == false {
print("Guest count: \(guests.count)")
}

Some people prefer to use the Boolean ! operator, like this:

if !guests.isEmpty {
print("Guest count: \(guests.count)")
}

I’m not really a big fan of either of those approaches, because they just don’t read naturally to
me “if not some array is empty”?

We can fix this with a really simple extension for Array, like this:

extension Array {
var isNotEmpty: Bool {

202 www.hackingwithswift.com
How to create and use protocol extensions

isEmpty == false
}
}

Tip: Xcode’s playgrounds run their code from top to bottom, so make sure you put that
extension before where it’s used.

Now we can write code that I think is easier to understand:

if guests.isNotEmpty {
print("Guest count: \(guests.count)")
}

But we can do better. You see, we just added isNotEmpty to arrays, but what about sets and
dictionaries? Sure, we could repeat ourself and copy the code into extensions for those, but
there’s a better solution: Array, Set, and Dictionary all conform to a built-in protocol called
Collection, through which they get functionality such as contains(), sorted(), reversed(), and
more.

This is important, because Collection is also what requires the isEmpty property to exist. So,
if we write an extension on Collection, we can still access isEmpty because it’s required. This
means we can change Array to Collection in our code to get this:

extension Collection {
var isNotEmpty: Bool {
isEmpty == false
}
}

With that one word change in place, we can now use isNotEmpty on arrays, sets, and
dictionaries, as well as any other types that conform to Collection. Believe it or not, that tiny
extension exists in thousands of Swift projects because so many other people find it easier to
read.

www.hackingwithswift.com 203
Protocols and extensions
read.

More importantly, by extending the protocol we’re adding functionality that would otherwise
need to be done inside individual structs. This is really powerful, and leads to a technique
Apple calls protocol-oriented programming – we can list some required methods in a protocol,
then add default implementations of those inside a protocol extension. All conforming types
then get to use those default implementations, or provide their own as needed.

For example, if we had a protocol like this one:

protocol Person {
var name: String { get }
func sayHello()
}

That means all conforming types must add a sayHello() method, but we can also add a default
implementation of that as an extension like this:

extension Person {
func sayHello() {
print("Hi, I'm \(name)")
}
}

And now conforming types can add their own sayHello() method if they want, but they don’t
need to – they can always rely on the one provided inside our protocol extension.

So, we could create an employee without the sayHello() method:

struct Employee: Person {


let name: String
}

But because it conforms to Person, we could use the default implementation we provided in
our extension:

204 www.hackingwithswift.com
How to create and use protocol extensions

let taylor = Employee(name: "Taylor Swift")


taylor.sayHello()

Swift uses protocol extensions a lot, but honestly you don’t need to understand them in great
detail just yet – you can build fantastic apps without ever using a protocol extension. At this
point you know they exist and that’s enough!

www.hackingwithswift.com 205
How to get the most from protocol
extensions
Protocol extensions are a power feature in Swift, and you’ve already learned enough to be able
to use them in your own apps. However, if you were curious and have some time to spare, we
could go off on a little tangent and explore them further.

Note: This is definitely edging beyond beginner-level Swift, so please don’t feel under any
pressure to continue on – you’re welcome to skip to the next chapter at any point!

Still here? Okay, let’s try another protocol extension, this time a little more complicated. First,
we’ll write it as a simple extension on Int:

extension Int {
func squared() -> Int {
self * self
}
}

let wholeNumber = 5
print(wholeNumber.squared())

That adds a squared() method, which multiplies the number by itself to get the square so that
our above code will print 25.

If we wanted that same method on a Double without just copying the code across, we could
follow the same pattern we did with Collection: find a protocol that both Int and Double
adopt, and extend that.

Here both types share the Numeric protocol, because they are both numbers, so we could try
to extend that:

extension Numeric {

206 www.hackingwithswift.com
How to get the most from protocol extensions

func squared() -> Int {


self * self
}
}

However, that code won’t work any more, because self * self can now be any kind of numbers,
including Double, and if you multiply a Double by a Double you definitely don’t get an Int
back.

To solve this we can just use the Self keyword – I introduced that briefly when we looked at
referring to static properties and methods, because it lets us refer to the current data type. It’s
useful here because it means “whatever conforming type this method was actually called on”,
and looks like this:

extension Numeric {
func squared() -> Self {
self * self
}
}

Remember, self and Self mean different things: self refers to the current value, and Self refers
to the current type. So, if we had an integer set to 5, our squared() function would effectively
work like this:

func squared() -> Int {


5 * 5
}

There Self is effectively Int, and self is effectively 5. Or if we had a decimal set to 3.141,
squared() would work like this:

func squared() -> Double {


3.141 * 3.141

www.hackingwithswift.com 207
Protocols and extensions

Want to go further?
If you’re still here, I think it’s safe to say you want to keep exploring protocol extensions even
more, and who am I to disappoint? This is definitely just for very curious folks, though – you
really do not need to know the following in order to start building apps with SwiftUI.

Still here? Okay, let’s start with a built-in protocol called Equatable, which is what Swift uses
to compare two objects using == and !=.

We can make our User struct conform to Equatable like this:

struct User: Equatable {


let name: String
}

And now we can compare two users:

let user1 = User(name: "Link")


let user2 = User(name: "Zelda")
print(user1 == user2)
print(user1 != user2)

We don’t need to do any special work here, because Swift can make the Equatable
conformance for us – it will compare all the properties of one object against the same
properties in the other object.

We can go further: there’s a Swift protocol called Comparable, which allows Swift to see if
one object should be sorted before another. Swift can’t automatically implement this in our
custom types, but it’s not hard: you need to write a function called < that accepts two instances
of your struct as its parameter, and returns true if the first instance should be sorted before the
second.

208 www.hackingwithswift.com
How to get the most from protocol extensions

So, we could write this:

struct User: Equatable, Comparable {


let name: String
}

func <(lhs: User, rhs: User) -> Bool {


lhs.name < rhs.name
}

Tip: As you learn more about Swift, you’ll learn that the < function can be implemented as a
static function that helps keep your code a little more organized.

Our code is enough to let us create two User instances and compare them using <, like this:

let user1 = User(name: "Taylor")


let user2 = User(name: "Adele")
print(user1 < user2)

That’s neat, but the really clever thing is that Swift uses protocol extensions to make the
following work too:

print(user1 <= user2)


print(user1 > user2)
print(user1 >= user2)

That code is possible because Equatable lets us know whether user1 is equal to user2, and
Comparable lets us know whether user1 should be sorted before user2, and with those two
pieces Swift can figure out the rest automatically.

Even better, we don’t even need to add Equatable to our struct in order to get == to work.
This alone is fine:

struct User: Comparable {

www.hackingwithswift.com 209
Protocols and extensions

let name: String


}

Behind the scenes, Swift uses protocol inheritance so that Comparable automatically also
means Equatable. This works similarly to class inheritance, so when Comparable inherits
from Equatable it also inherits all its requirements.

Anyway, this has been a massive tangent – don’t worry if it left your head spinning a little, it’s
just for curiosity and it will make more sense when you’re further on with your Swift journey!

210 www.hackingwithswift.com
Summary: Protocols and
extensions
In these chapters we’ve covered some complex but powerful features of Swift, but don’t feel
bad if you struggled a bit – these really are hard to grasp at first, and they’ll only really sink in
once you’ve had time to try them out in your own code.

Let’s recap what we learned:

• Protocols are like contracts for code: we specify the functions and methods that we
required, and conforming types must implement them.
• Opaque return types let us hide some information in our code. That might mean we want to
retain flexibility to change in the future, but also means we don’t need to write out gigantic
return types.
• Extensions let us add functionality to our own custom types, or to Swift’s built-in types.
This might mean adding a method, but we can also add computed properties.
• Protocol extensions let us add functionality to many types all at once – we can add
properties and methods to a protocol, and all conforming types get access to them.

When we boil it down to that these features seem easy, but they aren’t. You need to know
about them, to know that they exist, but you need to use them only superficially in order to
continue your learning journey.

www.hackingwithswift.com 211
Checkpoint 8
Now that you understand how protocols and extensions work, it’s time to pause our learning
and take on a challenge so you can put it all into practice.

Your challenge is this: make a protocol that describes a building, adding various properties and
methods, then create two structs, House and Office, that conform to it. Your protocol should
require the following:

1. A property storing how many rooms it has.


2. A property storing the cost as an integer (e.g. 500,000 for a building costing $500,000.)
3. A property storing the name of the estate agent responsible for selling the building.
4. A method for printing the sales summary of the building, describing what it is along with its
other properties.

I’ll provide some hints in a moment, but first I recommend you go ahead and try it yourself.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 8. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

1. Start by designing the protocol fully before you go near any structs.
2. Remember you can’t provide implementations for methods in your protocol.
3. You get to decide whether your properties should be get-only or have getters and setters.
For example, maybe the number of rooms is constant, but perhaps you also want to take
into account someone adding an extension.
4. You can control the read-write status of a property using { get } or { get set }.

212 www.hackingwithswift.com
Chapter 10
Optionals

www.hackingwithswift.com 213
How to handle missing data with
optionals
Swift likes to be predictable, which means as much as possible it encourages us to write code
that is safe and will work the way we expect. You’ve already met throwing functions, but
Swift has another important way of ensuring predictability called optionals – a word meaning
“this thing might have a value or might not.”

To see optionals in action, think about the following code:

let opposites = [
"Mario": "Wario",
"Luigi": "Waluigi"
]

let peachOpposite = opposites["Peach"]

There we create a [String: String] dictionary with two keys: Mario and Luigi. We then
attempt to read the value attached to the key “Peach”, which doesn’t exist, and we haven’t
provided a default value to send back in place of missing data.

What will peachOpposite be after that code runs? This is a [String: String] dictionary, which
means the keys are strings and the values are strings, but we just tried to read a key that doesn’t
exist – it wouldn’t make sense to get a string back if there isn’t a value there.

Swift’s solution is called optionals, which means data that might be present or might not. They
are primarily represented by placing a question mark after your data type, so in this case
peachOpposite would be a String? rather than a String.

Optionals are like a box that may or may not have something inside. So, a String? means there
might be a string waiting inside for us, or there might be nothing at all – a special value called
nil, that means “no value”. Any kind of data can be optional, including Int, Double, and Bool,
as well as instances of enums, structs, and classes.

214 www.hackingwithswift.com
How to handle missing data with optionals
as well as instances of enums, structs, and classes.

You’re probably thinking “so… what has actually changed here? Before we had a String, and
now we have a String?, but how does that actually change the code we write?”

Well, here’s the clincher: Swift likes our code to be predictable, which means it won’t let us
use data that might not be there. In the case of optionals, that means we need to unwrap the
optional in order to use it – we need to look inside the box to see if there’s a value, and, if there
is, take it out and use it.

Swift gives us two primary ways of unwrapping optionals, but the one you’ll see the most
looks like this:

if let marioOpposite = opposites["Mario"] {


print("Mario's opposite is \(marioOpposite)")
}

This if let syntax is very common in Swift, and combines creating a condition (if) with creating
a constant (let). Together, it does three things:

1. It reads the optional value from the dictionary.


2. If the optional has a string inside, it gets unwrapped – that means the string inside gets
placed into the marioOpposite constant.
3. The condition has succeeded – we were able to unwrap the optional – so the condition’s
body is run.

The condition’s body will only be run if the optional had a value inside. Of course, if you want
to add an else block you can – it’s just a normal condition, so this kind of code is fine:

var username: String? = nil

if let unwrappedName = username {


print("We got a user: \(unwrappedName)")
} else {
print("The optional was empty.")

www.hackingwithswift.com 215
Optionals

Think of optionals a bit like Schrödinger’s data type: there might be a value inside the box or
there might not be, but the only way to find out is to check.

This might seem rather academic so far, but optionals really are critical for helping us produce
better software. You see, in the same way optionals mean data may or may not be present, non-
optionals – regular strings, integers, etc – mean data must be available.

Think about it: if we have a non-optional Int it means there is definitely a number inside,
always. It might be something like 1 million or 0, but it’s still a number and is guaranteed to be
present. In comparison, an optional Int set to nil has no value at all – it’s not 0 or any other
number, it’s nothing at all.

Similarly, if we have a non-optional String it means there is definitely a string in there: it


might be something like “Hello” or an empty string, but both of those are different from an
optional string set to nil.

Every data type can be optional if needed, including collections such as Array and
Dictionary. Again, an array of integers might contain one or more numbers, or perhaps no
numbers at all, but both of those are different from optional arrays set to nil.

To be clear, an optional integer set to nil is not the same as a non-optional integer holding 0, an
optional string set to nil is not the same as a non-optional string that is currently set to an
empty string, and an optional array set to nil is not the same as a non-optional array that
currently has no items – we’re talking about the absence of any data at all, empty or otherwise.

As Zev Eisenberg said, “Swift didn’t introduce optionals. It introduced non-optionals.”

You can see this in action if you try to pass an optional integer into a function that requires a
non-optional integer, like this:

func square(number: Int) -> Int {


number * number

216 www.hackingwithswift.com
How to handle missing data with optionals

var number: Int? = nil


print(square(number: number))

Swift will refuse to build that code, because the optional integer needs to be unwrapped – we
can’t use an optional value where a non-optional is needed, because if there were no value
inside we’d hit a problem.

So, to use the optional we must first unwrap it like this:

if let unwrappedNumber = number {


print(square(number: unwrappedNumber))
}

Before we’re done, I want to mention one last thing: when unwrapping optionals, it’s very
common to unwrap them into a constant of the same name. This is perfectly allowable in
Swift, and means we don’t need to keep naming constants unwrappedNumber or similar.

Using this approach, we could rewrite the previous code to this:

if let number = number {


print(square(number: number))
}

This style is a bit confusing when you first read it, because now it feels like quantum physics –
can number really be both optional and non-optional at the same time? Well, no.

What’s happening here is that we’re temporarily creating a second constant of the same name,
available only inside the condition’s body. This is called shadowing, and it’s mainly used with
optional unwraps like you can see above.

So, inside the condition’s body we have an unwrapped value to work with – a real Int rather
than an optional Int? – but outside we still have the optional.

www.hackingwithswift.com 217
Optionals
than an optional Int? – but outside we still have the optional.

218 www.hackingwithswift.com
How to unwrap optionals with
guard
You’ve already seen how Swift uses if let to unwrap optionals, and it’s the most common way
of using optionals. But there is a second way that does much the same thing, and it’s almost as
common: guard let.

It looks like this:

func printSquare(of number: Int?) {


guard let number = number else {
print("Missing input")
return
}

print("\(number) x \(number) is \(number * number)")


}

Like if let, guard let checks whether there is a value inside an optional, and if there is it will
retrieve the value and place it into a constant of our choosing.

However, the way it does so flips things around:

var myVar: Int? = 3

if let unwrapped = myVar {


print("Run if myVar has a value inside")
}

guard let unwrapped = myVar else {


print("Run if myVar doesn't have a value inside")
}

www.hackingwithswift.com 219
Optionals

So, if let runs the code inside its braces if the optional had a value, and guard let runs the code
inside its braces if the optional didn’t have a value. This explains the use of else in the code:
“check that we can unwrap the optional, but if we can’t then…”

I realize that sounds like a small difference, but it has important ramifications. You see, what
guard provides is the ability to check whether our program state is what we expect, and if it
isn’t to bail out – to exit from the current function, for example.

This is sometimes called an early return: we check that all a function’s inputs are valid right as
the function starts, and if any aren’t valid we get to run some code then exit straight away. If
all our checks pass, our function can run exactly as intended.

guard is designed exactly for this style of programming, and in fact does two things to help:

1. If you use guard to check a function’s inputs are valid, Swift will always require you to use
return if the check fails.
2. If the check passes and the optional you’re unwrapping has a value inside, you can use it
after the guard code finishes.

You can see both of these points in action if you look at the printSquare() function from
earlier:

func printSquare(of number: Int?) {


guard let number = number else {
print("Missing input")

// 1: We *must* exit the function here


return
}

// 2: `number` is still available outside of `guard`


print("\(number) x \(number) is \(number * number)")
}

220 www.hackingwithswift.com
How to unwrap optionals with guard

So: use if let to unwrap optionals so you can process them somehow, and use guard let to
ensure optionals have something inside them and exit otherwise.

Tip: You can use guard with any condition, including ones that don’t unwrap optionals. For
example, you might use guard someArray.isEmpty else { return }.

www.hackingwithswift.com 221
How to unwrap optionals with nil
coalescing
Wait… Swift has a third way of unwrapping optionals? Yep! And it’s really useful, too: it’s
called the nil coalescing operator and it lets us unwrap an optional and provide a default value
if the optional was empty.

Let’s rewind a bit:

let captains = [
"Enterprise": "Picard",
"Voyager": "Janeway",
"Defiant": "Sisko"
]

let new = captains["Serenity"]

That reads a non-existent key in our captains dictionary, which means new will be an optional
string to set to nil.

With the nil coalescing operator, written ??, we can provide a default value for any optional,
like this:

let new = captains["Serenity"] ?? "N/A"

That will read the value from the captains dictionary and attempt to unwrap it. If the optional
has a value inside it will be sent back and stored in new, but if it doesn’t then “N/A” will be
used instead.

This means no matter what the optional contains – a value or nil – the end result is that new
will be a real string, not an optional one. That might be the string from inside the captains
value, or it might be “N/A”.

222 www.hackingwithswift.com
How to unwrap optionals with nil coalescing

Now, I know what you’re thinking: can’t we just specify a default value when reading from the
dictionary? If you’re thinking that you’re absolutely correct:

let new = captains["Serenity", default: "N/A"]

That produces exactly the same result, which might make it seem like the nil coalescing
operator is pointless. However, not only does the nil coalescing operator work with
dictionaries, but it works with any optionals.

For example, the randomElement() method on arrays returns one random item from the array,
but it returns an optional because you might be calling it on an empty array. So, we can use nil
coalescing to provide a default:

let tvShows = ["Archer", "Babylon 5", "Ted Lasso"]


let favorite = tvShows.randomElement() ?? "None"

Or perhaps you have a struct with an optional property, and want to provide a sensible default
for when it’s missing:

struct Book {
let title: String
let author: String?
}

let book = Book(title: "Beowulf", author: nil)


let author = book.author ?? "Anonymous"
print(author)

It’s even useful if you create an integer from a string, where you actually get back an optional
Int? because the conversion might have failed – you might have provided an invalid integer,
such as “Hello”. Here we can use nil coalescing to provide a default value, like this:

let input = ""

www.hackingwithswift.com 223
Optionals

let number = Int(input) ?? 0


print(number)

As you can see, the nil coalescing operator is useful anywhere you have an optional and want
to use the value inside or provide a default value if it’s missing.

224 www.hackingwithswift.com
How to handle multiple optionals
using optional chaining
Optional chaining is a simplified syntax for reading optionals inside optionals. That might
sound like something you’d want to use rarely, but if I show you an example you’ll see why
it’s helpful.

Take a look at this code:

let names = ["Arya", "Bran", "Robb", "Sansa"]

let chosen = names.randomElement()?.uppercased() ?? "No one"


print("Next in line: \(chosen)")

That uses two optional features at once: you’ve already seen how the nil coalescing operator
helps provide a default value if an optional is empty, but before that you see optional chaining
where we have a question mark followed by more code.

Optional chaining allows us to say “if the optional has a value inside, unwrap it then…” and
we can add more code. In our case we’re saying “if we managed to get a random element from
the array, then uppercase it.” Remember, randomElement() returns an optional because the
array might be empty.

The magic of optional chaining is that it silently does nothing if the optional was empty – it
will just send back the same optional you had before, still empty. This means the return value
of an optional chain is always an optional, which is why we still need nil coalescing to provide
a default value.

Optional chains can go as long as you want, and as soon as any part sends back nil the rest of
the line of code is ignored and sends back nil.

To give you an example that pushes optional chaining harder, imagine this: we want to place
books in alphabetical order based on their author names. If we break this right down, then:

www.hackingwithswift.com 225
Optionals
books in alphabetical order based on their author names. If we break this right down, then:

• We have an optional instance of a Book struct – we might have a book to sort, or we might
not.
• The book might have an author, or might be anonymous.
• If it does have an author string present, it might be an empty string or have text, so we
can’t always rely on the first letter being there.
• If the first letter is there, make sure it’s uppercase so that authors with lowercase names
such as bell hooks are sorted correctly.

Here’s how that would look:

struct Book {
let title: String
let author: String?
}

var book: Book? = nil


let author = book?.author?.first?.uppercased() ?? "A"
print(author)

So, it reads “if we have a book, and the book has an author, and the author has a first letter,
then uppercase it and send it back, otherwise send back A”.

Admittedly it’s unlikely you’ll ever dig that far through optionals, but I hope you can see how
delightfully short the syntax is!

226 www.hackingwithswift.com
How to handle function failure with
optionals
When we call a function that might throw errors, we either call it using try and handle errors
appropriately, or if we’re certain the function will not fail we use try! and accept that if we
were wrong our code will crash. (Spoiler: you should use try! very rarely.)

However, there is an alternative: if all we care about is whether the function succeeded or
failed, we can use an optional try to have the function return an optional value. If the function
ran without throwing any errors then the optional will contain the return value, but if any error
was thrown the function will return nil. This means we don’t get to know exactly what error
was thrown, but often that’s fine – we might just care if the function worked or not.

Here’s how it looks:

enum UserError: Error {


case badID, networkFailed
}

func getUser(id: Int) throws -> String {


throw UserError.networkFailed
}

if let user = try? getUser(id: 23) {


print("User: \(user)")
}

The getUser() function will always throw a networkFailed error, which is fine for our testing
purposes, but we don’t actually care what error was thrown – all we care about is whether the
call sent back a user or not.

This is where try? helps: it makes getUser() return an optional string, which will be nil if any

www.hackingwithswift.com 227
Optionals

errors are thrown. If you want to know exactly what error happened then this approach won’t
be useful, but a lot of the time we just don’t care.

If you want, you can combine try? with nil coalescing, which means “attempt to get the return
value from this function, but if it fails use this default value instead.”

Be careful, though: you need to add some parentheses before nil coalescing so that Swift
understands exactly what you mean. For example, you’d write this:

let user = (try? getUser(id: 23)) ?? "Anonymous"


print(user)

You’ll find try? is mainly used in three places:

1. In combination with guard let to exit the current function if the try? call returns nil.
2. In combination with nil coalescing to attempt something or provide a default value on
failure.
3. When calling any throwing function without a return value, when you genuinely don’t care
if it succeeded or not – maybe you’re writing to a log file or sending analytics to a server,
for example.

228 www.hackingwithswift.com
Summary: Optionals
In these chapters we’ve covered one of Swift’s most important features, and although most
people find optionals hard to understand at first almost everyone agrees they are useful in
practice.

Let’s recap what we learned:

• Optionals let us represent the absence of data, which means we’re able to say “this integer
has no value” – that’s different from a fixed number such as 0.
• As a result, everything that isn’t optional definitely has a value inside, even if that’s just an
empty string.
• Unwrapping an optional is the process of looking inside a box to see what it contains: if
there’s a value inside it’s sent back for use, otherwise there will be nil inside.
• We can use if let to run some code if the optional has a value, or guard let to run some
code if the optional doesn’t have a value – but with guard we must always exit the function
afterwards.
• The nil coalescing operator, ??, unwraps and returns an optional’s value, or uses a default
value instead.
• Optional chaining lets us read an optional inside another optional with a convenient syntax.
• If a function might throw errors, you can convert it into an optional using try? – you’ll
either get back the function’s return value, or nil if an error is thrown.

Optionals are second only to closures when it comes to language features folks struggle to
learn, but I promise after a few months you’ll wonder how you could live without them!

www.hackingwithswift.com 229
Checkpoint 9
Now that you understand a little about optionals, it’s time to pause for a few minutes and try a
small coding challenge so you can see how much you’ve remembered.

Your challenge is this: write a function that accepts an optional array of integers, and returns
one randomly. If the array is missing or empty, return a random number in the range 1 through
100.

If that sounds easy, it’s because I haven’t explained the catch yet: I want you to write your
function in a single line of code. No, that doesn’t mean you should just write lots of code then
remove all the line breaks – you should be able to write this whole thing in one line of code.

I’ll provide some hints in a moment, but first I recommend you go ahead and try it yourself.

Hacking with Swift+ subscribers can get a complete video solution for this checkpoint here:
Solution to Checkpoint 9. If you don’t already subscribe, you can start a free trial today.

Still here? Okay, here are some hints:

1. Your function should accept an [Int]? – an array of integers that might be there, or might be
nil.
2. It needs to return a non-optional Int.
3. You can use optional chaining to call randomElement() on the optional array, which will
in turn return another optional.
4. Because you need to return a non-optional integer, you should use nil coalescing to pick a
random number from 1 through 100.

230 www.hackingwithswift.com
Chapter 11
Wrap up

www.hackingwithswift.com 231
Where now?
This course taught you the most important parts of Swift in a way that I hope was clear,
understandable, and practical. At this point you’re primed to build your own apps using Swift
and SwiftUI.

Of course, there are lots of Swift features this course hasn’t covered, but that’s okay – you
could spend months covering all the features of Swift without actually getting near building
your own apps for iOS, and honestly I’m not really sure such an approach would be
productive. After all, if you aren’t applying what you’ve learned, would you really remember
it?

From here, I recommend your next move ought to be my free 100 Days of SwiftUI course. By
working your way through Swift for Complete Beginners you’re already 15 days through the
100, and the remaining 85 teach you to apply what you’ve learned to build real apps.

Well done for making it to the end! I hope you enjoyed learning about the fundamentals of
Swift, and I look forward to seeing where you go next.

232 www.hackingwithswift.com

You might also like