Swift: Tuple
Also known as Struct Lite™ (but not really)
Tuple types are somewhat a foreign concept to Objective-C developers, and can sometimes be confused for either a struct
or class
. But in fact, they’re much more basic than both of those. I like to think of them as a miniature struct or Struct Lite™ that should be used within a minimal scope then discarded.
According to Apple documentation on Swift Types:
A tuple type is a comma-separated list of zero or more types, enclosed in parentheses.
They’re just a compound of other different types. Tuples may contain zero or more types, generally of a String
, Integer
, Bool
as well as other tuples. Also please note that tuples are passed by value, not reference.
let foo = (0, false, “Hello”)
Above is a tuple type that contains three separate elements, if we were being verbose in our tuple declaration, we would have defined the tuple above as:
let foo: (Int, Bool, String) = (0, false, “Hello”)
If we wanted to access these individual elements from the tuple, we would have to use an array-like index accessor:
print(foo.0) // print: “0”print(foo.1) // print: “false”print(foo.2) // print: “Hello”
So hopefully by now you have a good understanding of the tuple syntax, so we’re gonna mix it up by creating a tuple which contains another tuple. Inception style:
let bar: (Int, (Bool, String)) = (1, (false, “Hello”))print(bar.0) // print: “0”print(bar.1.0) // print: “false”print(bar.1.1) // print: “Hello”
This is absolute madness, the dot point syntax begins to start looking like some sort of semantic versioning for an application. The reason it looks like this is because the value type at index 1 is another tuple, so it gets its own accessors.
But instead of using indexes and having to remember which corresponds to which value, it’s best practice to name your tuple’s properties wherever possible.
let person = (age: 28, isTall: false, name: “Andyy”)print(person.age) // print: “28”print(person.isTall) // print: “false”print(person.name) // print: “Andyy”
Where to use tuples
The best place to use a tuple would be when you want a function that can return multiple types. If you’re an Objective-C developer, this concept sounds outlandish and perhaps even impossible. Previously we’ve had to overcome such problem by either return an NSArray
or NSDictionary
. But even that resolution can be dangerous. Because what if we try typecasting the wrong object of the NSArray
and call a particular function on it? That could create a crash at runtime.
So instead, let’s look at how we can make a function return multiple values, instead of coming up with shortstop hacky solutions for Objective-C’s limitations:
typealias Person = (age: Int, isTall: Bool, name: String)func getMultipleValues() -> Person { let person: Person = (age: 28, isTall: false, name: “Andyy”) return person}let andyy = getMultipleValues()print(andyy.age) // print: "28"
print(andyy.isTall) // print: "false"
print(andyy.name) // print: "Andyy"
For code readability, I’ve used a typealias
to pre-define the tuple’s structure, and perhaps you should in your code too, it prevents you from accidentally redefining it’s structure incorrectly throughout your code.
But as you can see above, there is a function call getMultipleValues()
which returns a tuple of three separate and different data types, each with its own corresponding variable name.
Struct instantiation (deprecated)
Update: This will be deprecated Swift 3.0
Proposal: SE-0029
Remove implicit tuple splat behavior from function applications
At the beginning of this post, I jokingly referred to tuples as Struct Lite™. A little known fact is that tuples can be used to instantiate structs.
let size = CGSize(width: 100, height: 100)
The CGSize
struct takes two arguments, either of which can be a CGFloat
, Double
or Int
. But hey, what if we pass a tuple into this argument?
let arguments: (width: CGFloat, height: CGFloat) = (100, 100)var size = CGSize(arguments)
Pretty cool, huh? But there is one caveat… The tuple’s values must match exactly to the struct’s constructor arguments. Changing the order, argument labels or amount, will cause prevent the Xcode from compiling.
let arguments: (height: CGFloat, width: CGFloat) = (100, 100)❗️ var size = CGSize(arguments)
“Cannot invoke initializer for type ‘CGSize’ with an argument list of type ‘(height: CGFloat, width:CGFloat)’ “ — Xcode
Do you see what I did there? I changed the arrangment of the arguments by swapping the height
and width
locations. Even though they’re both CGFloat
, the compiler cannot infer what you’re trying to do.
Interesting Fact
If you’ve been using Swift previously, chances are that you’ve actually already used tuples for return types, you just don’t even know it. Below we have three separate functions, all of which do the exact same thing (nothing) and none of which return any value.
func doNothingA() -> Void { }func doNothingB() -> () { }func doNothingC() { }
doNothingA()
uses the more modern standard of declaring a void function, whilst doNothingB()
uses the original declaration for a void function, and doNothingC()
uses the shorthand declaration for a void function by omitting it return type completely.
The cool thing is, Void
is just a typealias
for ()
, which actually just an empty tuple. Because if you remember at the beginning of this post, I mentioned that tuples can contain zero or more types.
Mind. Blown. You’re welcome.
Choose your pronunciation
There is great confusion on how people pronounce the word “tuple”. Some like to say “Two-pull”, whilst others say “Tup-pl”, and the craziest ones say “Choo-pl”. Whatever your preference is, be prepared to get called out on it.
Sample code of this post can be found on GitHub. It’s just a simple Playgrounds file to help you understand a tad better.
If you like what you’ve read today you can check our my other articles or want to get in touch, please send me a tweet or follow me on Twitter, it really makes my day. I also organise Playgrounds Conference in Melbourne, Australia and would to see you at the next event.