#Collections in Swift
Arrays and dictionaries are the most common data stores in many applications. Swift has brought us a new generation of these collections, so let's take a look at how they compare to the collections found in Objective-C.
##Greatest Differences
###Typing One of the most obvious differences between collections in Objective-C and Swift is that Swift collections are explicitly typed. Typed collections are useful for many reasons, primarily because the system requires you to be precise with the content type you are working with. Any type mismatches will throw errors, which will help you avoid common mistakes.
###Mutability
Perhaps the most convenient addition in Swift is that all collections are mutable by default. Whereas Objective-C uses custom subclasses for mutability, Swift determines mutability by the way a variable is declared. If you want to create a mutable collection, use the var
introducer. Conversely, you use the let
introducer to declare a constant. This is advantageous because both mutable and immutable collections can be created using the same set of initializers. For example:
var mutableArray = ["a", "b", "c"] // mutable
let constArray = ["d", "e", "f"] // immutable
###Generics
Generics deserve a post of their own, but it is important to discuss their affect on Swift collections. Both the Array
and Dictionary
collections in Swift are generic, meaning their implementations can be used to work with any data type. Additionally, many of the higher-order collection operators exist as global generics, such as contains
, enumerate
, equal
and join
. The distinction is important because these functions are not invoked on the collection itself; instead, the collection is passed as one of the parameters, for example:
var anArray = ["a", "b", "c"]
var result = contains(anArray, "a")
// true
This is definitely a new way of thinking about our code, especially for developers coming from Objective-C.
##Arrays
Just like Objective-C, there are many ways to make an Array
in Swift. The code below shows the most basic method:
var letters : String[] = ["a", "b"]
This example declares the variable letters
as an array of Strings, meaning it can only contain String
types. This example also initializes letters
to store two values, "a"
and "b"
.
Using type inference, the same example can be written in a shorter form:
var letters = ["a", "b”]
Since the elements provided in the array literal above are both of type String
, Swift can infer that letters
should be of type String [ ]
.
One of the neatest ways to create an Array
is by pre-populating it with default values.
var letters = Array(count: 3, repeatedValue: "a")
// [a, a, a]
###Interacting with an Array
The easiest way to read or modify elements in an Array
is with subscripting, e.g. letters[0] = 2
. However, Swift adds several new convenience methods for working with arrays:
Appending elements:
letters.append("c")
or
letters += "c"
Appending multiple elements:
letters += ["c", "d"]
Changing multiple elements:
letters[1...2] = ["e", "f”]
Additionally, when using the subscript syntax, the number of replacement values does not have to match the range provided.
###Enumeration
A standard for-in
loop is the easiest way to iterate over an Array
:
for letter in letters
{
print(letter)
}
// prints “abcdef"
If you need the index when enumerating, you can use the enumerate
function. It returns a tuple containing the index and value for each item in the collection.
for (index, letter) in enumerate(letters)
{
print("\(index)\(letter)")
}
// prints "0a1b2c3e4f"
##Dictionaries
Just like arrays, there are many ways to declare and initialize a Dictionary
in Swift. Below is the simplest example:
var inventory: Dictionary<String, Int> = ["Cars" : 1, "Trucks" : 2]
This example declares the variable inventory
as a Dictionary
that contains String
keys and Int
values. This example also initializes inventory
with two key-value pairs.
Once again, we can rely on Swift’s type inference when declaring a Dictionary
:
var inventory = ["Cars" : 1, "Trucks" : 2]
Since both keys are strings and both values are integers, Swift knows that inventory
should be of type Dictionary<String, Int>
.
###Interacting with a Dictionary
The easiest way to read or modify elements in a Dictionary
is with index subscripting, e.g.
inventory["SUVs"] = 5
Since collections are mutable by default, the code above will insert the key “SUVs”
(if necessary), and assign its value to 5
.
Just like Objective-C, a Dictionary
will return nil
if it does not contain the requested key. A common way to check for nil is:
if let num = inventory["SUVs"] {
println("The number of SUVs in \(num)")
}
else {
println("There are no SUVs")
}
Unlike a Swift Array
, a Dictionary
do not support the +=
operator for adding new elements.
###Enumeration
An example for-in
loop for Swift dictionaries is shown below. Note the (key, value)
tuple format provided by the loop.
for (key, value) in inventory {
println("key: \(key) value: \(value)")
}
If you need the index while enumerating a Dictionary
, you can use the enumerate function, just like we did with arrays.
for (index, (key, value)) in enumerate(inventory) {
println("index: \(index) key: \(key) value: \(value)")
}
In the example above, each item in the Dictionary
is returned as a nested tuple in the form of (index, (key, value))
.
##Assignment and Copy Behavior
In Objective-C, the NSArray
and NSDictionary
collection classes are passed by reference. Swift's corresponding collections, Array
and Dictionary
, are both implemented as structures. Because of this, Swift collections are pass-by-value, meaning they will be copied whenever they are assigned or passed as a parameter. This might cause performance concerns at first glance, but Swift documentation notes that while your code will behave as if a copy took place, a true copy is only performed when absolutely necessary.
###Dictionaries
Whenever you assign or pass a Dictionary
, it is immediately copied. Any keys/values that are value types are copied, and all keys/values that are objects will have their references copied. Since Strings
are values in Swift, it is worth noting that strings will be copies as well.
The code below is an example of how dictionaries are copied during assignment.
var inventory = ["Cars" : 1, "Trucks" : 2]
var inventoryCopy = inventory
inventoryCopy["Cars"] = 3
println(inventory["Cars"])
// prints "1"
Notice that changing the number of cars in the copied Dictionary
had no effect on the number of cars in the original Dictionary
. Similar code in Objective-C would return 3 cars in both dictionaries.
###Arrays
Swift has complex rules for copying its Array
type. Even though Swift will always appear to copy an Array
when it is passed or assigned, this is not always the case. Swift will avoid making a true copy of the Array
until you perform an action that can change the array’s length. These actions include inserting, deleting, appending, or replacement by ranged subscripts. When this copy is required, it follows the same rules as a Swift Dictionary
-- values are copied and objects are copied by reference. Of course, an array can always be force copied by calling the copy method.
The code below shows that arrays are not copied during simple manipulations, such as a single replacement.
var letters = ["a", "b", "c”]
var lettersCopy = letters
lettersCopy[0] = "b"
println(letters)
// prints "[b, b, c]”
However, operations that change the length of the array will trigger a copy. For example:
var letters = ["a", "b", "c"]
var lettersCopy = letters
lettersCopy.removeLast()
lettersCopy[0] = "b"
println(letters)
// prints "[a, b, c]"
While the above examples show that arrays are not always copied, the takeaway is that we should treat all Swift collections as pass-by-value. Expecting the system to not copy a collection could get you into trouble.