Table of Contents
Creating Tuples in Swift
Tuples in Swift are used to store and group multiple values into a single compound value.
You can create a tuple value using a pair of parentheses to surround the values that you want to form part of the tuple. This can include any number of values you like, including optionally having no values at all. There is a one caveat with this though which I’ll get to shortly.
Creating Empty Tuples
Let’s look at an empty tuple first. To define a tuple containing no values you write the following:
let emptyTuple = ()
The compiler will show you a warning but at this point but its actually perfectly legal and in fact the Void
type in Swift is just an alias for the this empty tuple.
Creating Tuples For a Single Value
The next logical step from creating an empty tuple is creating one with a single value. This is where the caveat I mentioned kicks in though.
As an optimization in Swift, if you write a tuple that contains a single value, the compiler interprets it not as a tuple but as the value contained within the tuple. Maybe an example would be better. Say I had written:
let myValue = (1.0)
On the face of it, this is a tuple containing a single Double
value. Swift however, would interpret the value assigned to myValue
as being a value of type Double
rather than a tuple containing a value of type Double
. See the difference?
The implication of this optimisation is that you essentially can’t create a tuple in Swift that contains a single value.
Creating Tuples Containing Multiple Values
If you choose to include more than one value in a tuple, you have to separate each value from the next using a comma.
For example, if I wanted to create a constant item
and initialize it with a tuple containing two string values, I would write:
let item = ("Square", "Red")
Valid Types to Use In Tuples
When it comes to the types of values you use within a tuple you can include values of any type you like.
We’ve already seen examples containing Double
and String
values but you are not constrained to just these types. You can create tuples containing any of the standard swift types as well as any custom type that you may have defined.
If you’re creating a tuple containing more than one value, the tuple doesn’t have to contain values that are all of the same type either.
It’s perfectly legal to have a tuple of type (String, Bool)
or one of type (Int, Float, String)
, in fact any combination of types you like is perfectly valid.
You can also create tuples that contain other tuples like so:
let item = ("Square", "Red", (1.0, 2.0, 1.5))
Type Inference and Tuples
As you may have noticed from the examples so far, you don’t have to include types annotations when declaring a tuple.
The good news here is that Swift’s in-built type inference mechanism extends to tuples and infers the types of the values in a tuple from the values you provided when you declare it.
This doesn’t mean you can’t include type annotations though.
I could also have written the example in the previous section as follows and it would have had exactly the same meaning:
let item:(String, String, (Double, Double, Double)) =
("Square", "Red", (1.0, 2.0, 1.5))
Unnamed Tuples
The examples I’ve used up until now are all examples of something called an unnamed tuples. In an unnamed tuple, we don’t give identifiers to the individual values within the tuple.
Accessing Values in Unnamed Tuples
When it comes to accessing the values in an unnamed tuple then, we have two options:
– Accessing the values by index.
– Decomposing the values in the tuple into new constants or variables.
Lets look at each of these in turn.
Accessing Tuple Values By Index
The first option we have for accessing values in an unnamed tuple is by index.
In this approach, the different values within the tuple are accessed using positional indices starting at 0. For example:
print("The shape is \(item.0)") // prints "The shape is Square"
print("The color is \(item.1)") // prints "The color is Red"
We can also access the values of a tuple inside another tuple as follows:
let item:(String, String, (Double, Double, Double)) =
("Square", "Red", (1.0, 2.0, 1.5))
print("The x coordinate is \(item.2.0)") // prints "The x coordinate is 1.0"
Now, although the access by index approach works it’s not a particularly clean way of doing things, mainly because it’s not that clear what the code is actually doing. We do have an alternative though, accessing values in a tuple by decomposing them into new variables or constants. Let’s look at that next.
Accessing Tuple Values Through Decomposition
When we decompose a tuple we essentially unpack the values within the tuple into a number of new constants or variables.
For example, I could decompose the tuple in the previous example into three new constants as follows:
let (shape, color, shapeCoordinate) = item
print("The shape is \(shape)")
// prints "The shape is Square"
print("The color is \(color)")
// prints "The color is Red"
print("the coordinate is \(shapeCoordinate)")
// prints "The coordinate is (1.0, 2.0, 1.5)"
Another example of using tuple decomposition is when iterating over the contents of a dictionary.
Say I had dictionary containing list of capital cities and their countries and I wanted to print out the country and the capital city. I could iterate over the dictionary using a for-in
loop and each key/value pair in the dictionary would be extracted as a tuple:
let cities = ["London": "United Kingdom", "Paris" : "France", "Washington D.C.": "United States of America", "Wellington" : "New Zealand"]
for (city, country) in cities {
print("Country: \(country)\tCapital city: \(city)")
}
// prints "Country: United Kingdom Capital city: London"
// prints "Country: France Capital City: Paris"
// etc...
Ignoring Values During Decomposition
When performing decomposition in this manner, we can also ignore one or more of tuple values we’re unpacking if we’re not interested in them.
To ignore a value, instead of providing a variable or constant name to hold the unpacked value, we provide an underscore (‘_’) character to act as a placeholder for the value we wish to ignore.
For example, if I was only interested in the coordinate value within our earlier example, I could write:
let (_, _, coordinate) = item
print("The coordinate is \(coordinate)")
// prints "The coordinate is (1.0, 2.0, 1.5)"
This ability to ignore values when unpacking a tuple makes tuples particularly powerful, especially when used in combination with control statements such as Swift’s switch
statement. Using tuples in these cases allows you to construct particularly powerful and complex conditionals whilst keeping your code clean, concise and expressive.
Tuple Tricks Using Decomposition
So, you can see how we can unpack values in a tuple using decomposition but there are also a couple of neat little tricks you can pull off using this technique. The first one is a way to initialise multiple variables or constants using a single line of code.
Initializing Multiple Variables on a Single Line
Say I wanted to declare three variables x
, y
, and z
to represent each of the components in a 3D coordinate.
Normally I would have to write something similar to the following:
var x = 1
var y = 2
var z = 4
Using tuples, I could instead initialize all three variables on a single line of code as follows:
var (x, y, z) = (1, 2, 4)
They don’t all need to be of the same type either:
var (name, age) = ("Andy", 39)
print("Name: \(name)") // prints "Name: Andy"
print("Age: \(age)") // prints "Age: 39"
Swapping Two Values
Tuples also allow us to perform another neat little trick where we swap the values stored in two variables without the need to define intermediate variables.
If we wanted to do this normally, we would need to define a temporary variable to hold one of the values whilst we swapped them. Using tuples however, we can do all this in just a single line:
var a = 1
var b = 2
print("a = \(a), b = \(b)") // prints "a = 1, b = 2"
(a, b) = (b, a)
print("a = \(a), b = \(b)") // prints "a = 2, b = 1"
Named Tuples
So we’ve seen how we can access the values in a tuple by index and also seen how we can decompose values within a tuple into new constants or variables, but in Swift there is actually a third option, one that can often be more convenient than either of the previous techniques. This third option is something called named tuples.
When we use named tuples, we can, at the point we define the tuple, assign individual identifiers to each of the values within the tuple.
For example, using named tuples I could re-write the example we’ve been using as follows:
let anotherItem =
(shape: "Square", color: "Red", coordinate:(1.0, 2.0, 1.5))
Or alternatively, if I wanted to write it using more explicit syntax:
let anotherItem:(shape: String, color: String, coordinate: (Double, Double, Double)) =
("Square", "Red", (1.0, 2.0, 1.5))
Note that when you use explicit syntax, naming the individual values within the tuple is optional on the right-hand side of the assignment.
Accessing Values in a Named Tuple
The biggest difference with named tuples comes when accessing values that make up the tuple.
Instead of having to use either indices or decomposition as we looked at previously, we can instead access the values in the tuple by name:
print("The shape is \(anotherItem.shape)") // prints "The shape is Square"
print("The color is \(anotherItem.color)") // prints "The color is Red"
This makes our code both easier to read and easier to understand.
Using Tuples With Functions and Methods
Returning Tuples from Functions or Methods
One of the primary uses for Tuples in Swift is returning multiple values from functions or methods so no article would be complete without taking a look at it.
As I mentioned previously, prior to Swift, returning multiple values from a function or method in Objective-C often meant defining a new type to aggregate the values before they were returned or alternatively packing them into a dictionary.
In Swift though, we can easily define tuple types on the fly so can use them to collect together and return multiple values from a function without the need for these intermediate formats:
func origin() -> (Double, Double, Double) {
//...
return (1.0, 2.0, 1.5)
}
As you can see, its pretty simple.
We declare that the function returns a tuple by writing the tuple and the types of values the tuple will contain after the return arrow. In the body of the function we then declare (on the fly) and return a tuple value that matches that signature.
When it comes to accessing the values returned from the function we can choose from the three approaches we’ve talked about already.
First, we can simply store the returned value as a tuple and then access the values within the tuple using indices:
let coor = origin()
print("The x coordinate is \(coor.0)") // prints "The x coordinate is 1.0"
print("The y coordinate is \(coor.1)") // prints "The y coordinate is 2.0"
print("The z coordinate is \(coor.2)") // prints "The z coordinate is 1.5"
Alternatively, we could decompose the returned values into new constants or variables at the point the tuple is returned:
let (xCoor, yCoor, zCoor) = origin()
print("The x coordinate is \(xCoor)") // prints "The x coordinate is 1.0"
print("The y coordinate is \(yCoor)") // prints "The y coordinate is 2.0"
print("The z coordinate is \(zCoor)") // prints "The z coordinate is 1.5"
Or finally, we could return a named tuple from the function and then access the values by name:
func anotherOrigin() -> (x: Double, y: Double, z: Double) {
//...
return (1.0, 2.0, 1.5)
}
let coor2 = anotherOrigin()
print("The x coordinate is \(coor2.x)") // prints "The x coordinate is 1.0"
print("The y coordinate is \(coor2.y)") // prints "The y coordinate is 2.0"
print("The z coordinate is \(coor2.z)") // prints "The z coordinate is 1.5"
Tuples as Function Parameters
When it comes to function parameters, tuples can also be pretty useful.
Just look at the signature of a function or method itself. See anything familiar?
When you call a function in Swift, the arguments you pass into a function essentially look like a tuple to the compiler.
For example you might have a function in Swift that looks something like this:
func doSomething(index: Int, _ description:String) {
print("\(index): \(description)")
}
doSomething(1, "Hello") // prints "1: Hello"
But you can also, maybe surprisingly, call the function like this:
let argumentTuple = (1, "Hello")
doSomething(argumentTuple) // prints "1: Hello"
So as you can see, this technique works with unnamed tuples, but what about named ones? Well let’s see:
func doSomethingElse(index index:Int, description:String) {
print("\(index): \(description)")
}
let argumentTuple = (1, "Hello")
// doSomethingElse(argumentTuple) // Unnamed tuple doesn't work...
let argumentTuple1 = (index: 1, description: "Hello")
doSomethingElse(argumentTuple1) // ...but a named one does!
As you can see, with a slight modification to the function declaration to match up the names, it also works with named tuples as well!
Using Tuples in varags
Functions
A more advanced usage of tuples is to use them as parameters in vararg
functions. vararg
functions are functions that take an unknown number of parameters.
Say we had a function that performed a particular transformation on one or more 3D coordinates. In this scenario, we could make use of a tuple by writing a function that accepted a variable number of tuples, with each tuple representing a 3D coordinate:
func transform(coordinates:(x: Double, y: Double, z: Double)...) -> [(x: Double, y: Double, z: Double)] {
return coordinates.map{($0.x, $0.y * 2, $0.z)}
}
transform((1.0, 2.0, 1.5), (2.0, 3.0, 4.0)) // returns [(1.0, 4.0, 1.5), (2.0, 6.0, 4.0)]
It’s debatable about whether using an array parameter in the first place would be a better option in this situation (If you have a view on this I’d love to hear it) but this approach does save you from having to define a separate Coordinate3D
type to use within the array so in a pinch it works.
Using Tuples To Represent Fixed Sequences of Values
As I said, whether Tuples were a better or worse choice as a parameter in the last example is debatable, but where they do come into their own is in representing sequences of fixed length.
Say we had a set of values that represented the number of sales we had for our app within a given period, traditionally we would have had to write something like:
var salesFigures1:[Int] = [1, 2, 3, 4, 5]
But in doing so, we would lack some clarity.
There is nothing in the declaration to indicate how many values we should be providing. Are we talking about the mid-week sales figures? What about a monthly? After all, it’s an array so why not a year?
Tuples provide us with a convenient way to bake a constraint directly into the declaration of our variable. For example:
var salesFigures2:(mon: Int, tue: Int, wed: Int, thu: Int, fri: Int, sat:Int, sun:Int) = (1, 2, 3, 4, 5, 6, 7)
As you can see, it’s much more explicit about what we’re expecting and as an added bonus it also allows the compiler to check that we’re actually providing the right number of values rather than having to perform some sort of run-time assertion checks to check the number of items in an array.
Tuples Are Value Types
Now, for those of you with object-oriented experience, one thing to bear in mind when you’re working with tuples is that they are value types not reference types.
This means that instead of being passed around by reference tuple values are copied. The implication of this is that if you pass a tuple into a function, return one from a function or create a new tuple by assigning an existing one, you are actually making copies of the tuple rather than passing a pointer to the same tuple object.
Let’s illustrate this with an example. If we take our 3D coordinate example for earlier and wanted to create a new coordinate from it:
var origin2 = (x: 0, y: 0, z: 0)
print(origin2) // prints (0, 0, 0)
var coordinate2 = origin2
// Change the `coordinate` tuple
coordinate2.x = 10
coordinate2.y = 20
coordinate2.z = 30
print(origin2) // prints (0, 0, 0)
print(coordinate2) // prints (10, 20, 30)
Notice how after this code, the values in the two coordinates are different. To reiterate, the origin
and coordinate
variables contain two different values rather than both pointing to the same object.
When to Use Tuples
So we’ve seen how to use tuples, and we’ve seen some of the tricks you can perform but one question remains, when should you use a tuple and when should you opt for using a struct or a class instead?
In general, tuples are particular useful for relatively simple, temporary groups of related values such as the values being returned from a function or method or when passing around information between different objects.
Although with careful management, they can be used for longer-term storage within your apps, if you need more complex data structures or are looking to store values in your app for extended periods of time, you should really be looking toward structs and classes rather than a tuple.
With that said though, you’ve seen some of the power that tuples bring to the Swift language, so keep them in mind for your next Swift project.