Can you guess what happens if you execute this code snippet in a playground? The playground crashes due to an index out of bounds exception. This is a problem I have run into more times than I care to admit.
import Foundation
let birds = [
"sparrow",
"starling",
"woodpecker"
]
birds[4]
We can easily avoid the index out of bounds exception by making sure the index isn't out of bounds before accessing the element.
import Foundation
let birds = [
"sparrow",
"starling",
"woodpecker"
]
let index = 3
if index >= 0 && index < birds.count {
birds[index]
} else {
print("Index Out of Bounds")
}
This works fine, but it gets old pretty fast. The good news is that Swift offers us a few options to implement a solution that is elegant and reduces code duplication. We start simple with an extension for Array
.
extension Array {
func safelyAccessElement(at index: Int) -> Element? {
guard index >= 0 && index < count else {
return nil
}
return self[index]
}
}
We define an instance method, safelyAccessElement(at:)
, that accepts the index of the element we would like to access. Notice that the return type of the instance method is Element?
. Element
is the type of element the array stores. The return type is of an optional type because the instance method returns nil
if the value of index
is out of bounds.
We can tidy up the implementation by using a half-open range. I feel it improves readability, but that is a personal preference.
extension Array {
func safelyAccessElement(at index: Int) -> Element? {
guard (0..<count).contains(index) else {
return nil
}
return self[index]
}
}
birds.safelyAccessElement(at: 2) // "woodpecker"
birds.safelyAccessElement(at: 3) // nil
We can simplify the implementation even more by asking the array for the indices that are valid for subscripting the array. The computed indices
property returns a range that includes the indices of the elements of the array. The result looks quite nice and readable.
extension Array {
func safelyAccessElement(at index: Int) -> Element? {
guard indices.contains(index) else {
return nil
}
return self[index]
}
}
birds.safelyAccessElement(at: 2) // "woodpecker"
birds.safelyAccessElement(at: 3) // nil
Subscript Syntax
The safelyAccessElement(at:)
instance method solves the problem we set out to solve, but there is an even better solution. We can define a subscript to safely access an element by its index. We need to make a few changes to the previous implementation. First, we remove the func
keyword. Second, we replcae the name of the instance method with the subscript
keyword. Third, we change the argument label from at
to safe
to make the intent of the subscript more obvious.
extension Array {
subscript(safe index: Int) -> Element? {
guard indices.contains(index) else {
return nil
}
return self[index]
}
}
birds[safe: 2] // "woodpecker"
birds[safe: 3] // nil
I define this subscript in every project I work on to avoid index out of range exceptions. The syntax looks nice and, thanks to the name of the argument label, there is no confusion or conflict with the default or unsafe subscript.
birds[2] // "woodpecker"
birds[safe: 2] // "woodpecker"
What's Next?
Subscripts have a lot more to offer than what I showed you in this episode. The example we implemented is read-only, but it is possible to define a getter and a setter to create a read-write subscript. Subscripts can even define multiple parameters if needed. Visit the documentation for more information.