In the previous article, you learned about variables, constants, and some of the common data types, such as integers, floats, and strings. In this article, we zoom in on collections. Swift's standard library defines three collection types: sets, arrays, and dictionaries. Let's start with arrays.
1. Arrays
If you're familiar with Objective-C, JavaScript, or PHP, then it won't be difficult to understand arrays. An array is an ordered, zero-indexed collection of values. But there are a few important differences between arrays for Swift and other programming languages.
Type
The first important difference compared to arrays in Objective-C is that the values stored in an array are always of the same type. At first, this may seem like a significant limitation, but it actually isn't. In fact, this limitation has an important advantage. We know exactly what type we get back when we ask the array for one of its values.
Another key difference is the type of values an array can store. In Objective-C, an array can only store values of a class type. Swift doesn't have this limitation. An array in Swift can store strings, integers, floats, and class instances. How this works and why this is possible in Swift will become clear later in this series when we cover classes and structures.
Declaration
While there are several ways to create an array, you need to keep in mind that Swift needs to know what type of values you plan to store in the array. Create a new playground in Xcode, like we did in the previous article, and add the following lines to your playground.
var array1: Array<String> var array2: [String] var array3 = ["Apple", "Pear", "Orange"]
The first and second lines mean the same thing. The second line is just shorthand. The square brackets wrapping the String
keyword tell Swift that we're declaring an array that can only contain String
objects.
You could read the first line of code as: "We declare a variable named array1
of type Array
that can only contain String
objects." The colon signifies of type.
The third line shows us how to initialize an array using an array literal. Array literals look very similar to array literals in Objective-C. The main difference is the absence of the @
symbol preceding the square brackets and the string literals.
There's also a fancy way to initialize an array with a predefined number of default values. The syntax may be confusing at first, but take a moment to let it sink in.
var a = [String](repeating: "Test", count: 5)
The resulting array contains five strings, with each string being equal to "Test"
. To better understand the above initialization, take a look at the following two lines of code in which we initialize an empty array of strings.
var b = Array<String>() var c = [String]()
Don't worry if you're still confused. We'll explore the syntax in more detail once we start dealing with classes and functions. In this article, we're only focusing on collections.
Mutability
One aspect of Swift that you'll quickly come to appreciate is how to declare mutable collections. The above code snippet, for example, declares three mutable arrays. A mutable array is defined by using the var
keyword. It's that simple.
If you don't want an array to be mutable, then use the let
keyword instead. Swift aims to be intuitive and easy to use, and its implementation of mutability is a perfect example of that goal.
Getting and Setting Values
To access the values stored in an array, we use the same subscript syntax as in Objective-C. In the following example, we ask array3
for its second element, the string "Pear"
.
array3[1]
Replacing the value stored at index 1
is as simple as assigning a new value using the same subscript syntax. In the following example, we replace "Pear"
at index 1
with "Peach"
.
array3[1] = "Peach"
This is only possible because the array is mutable, that is, we used the var
keyword to declare the array. Mutating a constant array isn't possible. There are more advanced techniques for manipulating the contents of an array, but the underlying concept is the same.
Merging two arrays is as simple as adding them together. In the following example, we declare and merge two immutable arrays. Note that the resulting array, c
, doesn't need to be mutable for this to work.
let a = [1, 2, 3] let b = [4, 5, 6] let c = a + b
However, it is key that the values stored in a
and b
are of the same type. The reason should be obvious by now. As I mentioned earlier, the values stored in an array need to be of the same type. The following example results in an error.
let a = [1, 2, 3] let b = [1.5, 5.2, 6.3] let c = a + b
To append an array to a mutable array, we use the +=
operator. Note that the operand on the right is an array. This operation wouldn't work if we removed the square brackets surrounding 4
.
var a = [1, 2, 3] a += [4]
Operations
Arrays are objects on which you can perform a wide range of operations. Arrays expose a number of functions or methods. To invoke a method on an object, you use the dot notation. Add the following line to your playground to add an item to array3
.
array3.append("Cherry")
Let's see how many items array3
contains by inspecting the value of its count
property. This outputs 4
to the results pane.
array3.count
It's also possible to insert an item at a specific index by invoking the array's insert(_:at:)
method as shown below. The insert(_:at:)
method accepts more than one parameter, and the syntax may look a bit odd at first.
array3.insert("Prune", at: 2)
Like Objective-C, Swift supports named parameters to improve readability. The result is that code is easier to read and understand, and functions or methods don't need much explaining in terms of what they do. It is clear, for example, that the insert(_:at:)
method inserts an element at index 2
.
While Swift is more concise and less verbose than Objective-C, it supports named parameters. If you're coming from PHP, Ruby, or JavaScript, then this is certainly something that takes some getting used to.
Convenience Methods
What I really enjoy about Swift are the Ruby-like convenience properties and methods of Swift's standard library. An array, for example, has an isEmpty
property that tells you if the array contains any elements. This is nothing more than shorthand for checking the array's count
property. The result, however, is code that is more concise and easier to read.
array3.isEmpty
2. Dictionaries
Dictionaries behave very similarly to dictionaries in Objective-C. A dictionary stores an unordered collection of values. Each value in the dictionary is associated with a key. In other words, a dictionary stores a number of key/value pairs.
Type
As with arrays, the keys and values stored in a dictionary need to be of the same type. This means that if you ask a dictionary for the value of a particular key, you know what type the dictionary will return.
Declaration
Declaring a dictionary is similar to declaring an array. The difference is that you need to specify the type for both keys and values. The following example shows three ways to declare a dictionary.
var dictionary1: Dictionary<String, Int> var dictionary2: [String: Int] var dictionary3 = ["Apple": 3, "Pear": 8, "Orange": 11]
The second line is shorthand for the first line. The keys of these dictionaries need to be of type String
while the values are expected to be of type Int
. The var
keyword indicates that the dictionaries are mutable.
You could read the first line of code as: "We declare a variable named dictionary1
of type Dictionary
that can only contain keys of type String
and values of type Int
."
The third line illustrates how we can initialize a dictionary using a dictionary literal. This is similar to the syntax we use in Objective-C, but note that the curly braces are replaced by square brackets and the literal isn't prefixed with an @
symbol.
Getting and Setting Values
Accessing values is similar to accessing values of an array. The only difference is that you use the key instead of the index of the value you need to access. The following example illustrates this.
let value = dictionary3["Apple"] print(value)
You'll notice that Xcode tells us that the value of value
isn't 3
, but Optional(3)
. What does this mean? Swift uses optionals to wrap values that can be one of two things, a value or nil
. Don't worry about optionals at this point. We're going to focus on optionals in the next article of this series. Let me just tell you that optionals are another key concept of the Swift programming language.
It's interesting to point out that the syntax to access a value of a dictionary is identical to that of arrays if the keys of the dictionary are of type Int
. Take a look at the following example to see what I mean.
var dictionary4 = [0: "Apple", 1: "Pear", 2: "Orange"] let fruit = dictionary4[0]
Operations
As with arrays, the Swift standard library defines a wide range of operations you can perform on dictionaries. You can ask a dictionary for its number of key/value pairs through its count
property. Removing a key/value pair is easy and intuitive, as the next example illustrates. Of course, this is only possible if the dictionary is mutable.
dictionary4.removeValue(forKey: 0)
When you start learning Swift, you'll might run into code snippets that look odd or confusing. Take a look at the following line, in which we first declare a dictionary and then remove its key/value pairs.
var dictionary = [String: Int]() dictionary["Oranges"] = 2 dictionary["Apples"] = 10 dictionary["Pears"] = 5 dictionary = [:]
You have to admit that the last line looks a bit odd. Because Swift knows the types of the keys and values that can be stored in dictionary
, emptying the dictionary is as simple as assigning an empty dictionary to it.
There's no need to specify the types for the keys and values in this case, because we already did when we declared the dictionary in the first line. This points out another important detail, that is, the type of values you can store in arrays and dictionaries cannot change once the collection is declared.
3. Sets
Sets are very similar to arrays in that they store a collection of values of the same type. But there are several important differences. The elements stored in a set are unordered, and each item can only appear once in a set.
Declaration
Working with sets is a bit different from working with arrays. Take a look at the next examples in which we declare three sets. The set1
variable is of type Set<String>
, a set that can only contain values of type String
.
var set1: Set<String> var set2 = Set<String>() var set3: Set<String> = ["Apple", "Pear", "Orange"]
The set2
variable is an empty set, and we use an array literal in the third example to create and populate a mutable set that contains three values. Thanks to Swift's type inference, we can omit the type of the set.
var set3: Set = ["Apple", "Pear", "Orange"]
Manipulating Sets
Working with sets is similar to working with arrays. We can ask for the number of elements stored in a set by inspecting the set's count
property.
set3.count
Inserting an element is easy. Because the elements of a set are unordered, we don't need to specify the location of the new element.
set3.insert("Prune")
And the same applies to removing an element from a set.
set3.remove("Orange")
You can also ask a set if it contains a particular element.
set3.contains("Apple")
Arrays or Sets
I often refer to sets as lightweight versions of arrays. If the order of the elements is not important or you want to make sure each element can only appear once in a collection, then sets are the way to go.
4. Tuples
You are going to love tuples. Tuples aren't collections, but like collections, they also group multiple values. Similar to arrays and dictionaries, tuples can contain values of any type. The key difference, however, is that the values stored in a tuple don't need to be of the same type. Let's look at an example to explain this in more detail.
import Foundation var currency = ("EUR", 0.81) var time = (Date(), "This is my message.") var email = ("Bart Jacobs", "[email protected]")
The first example declares a tuple named currency
that is of type (String, Int)
. The second tuple, time
, contains a Date
instance and a string literal. The values stored in email
are both of type String
, which means email
is of type (String, String)
.
Accessing Values
Indexes
To access a value stored in a tuple, you use the index that corresponds with the value you're interested in.
var rate = currency.1 var message = time.1 var name = email.0
Xcode shows us the indexes of each value stored in a tuple in the results pane of the playground on the right.
To improve readability, you can name the values stored in a tuple. The result is that you can access the values of the tuple through their names instead of their indexes. Declaring a tuple is slightly different in that case.
var currency = (name: "EUR", rate: 0.81) let currencyName = currency.name let currencyRate = currency.rate
Decomposition
There's a second, more elegant way to work with the values stored in a tuple. Take a look at the following example in which we decompose the contents of currency
.
let (currencyName, currencyRate) = currency
The value of currency
at index 0
is stored in currencyName
, and the value at index 1
is stored in currencyRate
. There's no need to specify the type for currencyName
and currencyRate
since Swift infers the type from the values stored in currency
. In other words, currencyName
is of type String
, and currencyRate
is of type Float
.
If you're only interested in specific values of a tuple, you can use an underscore to tell Swift which values you're not interested in.
let (currencyName, _) = currency
Conclusion
Arrays and dictionaries are fundamental components of almost every programming language, and Swift is no different. While collections behave a little differently in Swift, it doesn't take long to become familiar with Swift's collection types if you've worked with arrays and dictionaries in other programming languages. In the next tutorial, we explore optionals and control flow.
If you want to get up and running with the Swift language quickly, check out our course on creating iOS apps with Swift.
Or check out some of our other tutorials and courses on Swift and iOS development!
Comments