Implementing Custom Subscripts in Swift
Learn how to extend your own types with subscripts, allowing you to index into them with simple syntax just like native arrays and dictionaries. By Mikael Konutgan.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Implementing Custom Subscripts in Swift
10 mins
Update 2/10/16: This tutorial has been updated for Xcode 8 and Swift 3.
Update 2/10/16: This tutorial has been updated for Xcode 8 and Swift 3.
Update note: This tutorial was updated for Swift 2.2 and Xcode 7.3 by Mikael Konutgan. Original post by Tutorial Team member Evan Dekhayser.
Subscripts are a powerful language feature that, when used properly, can significantly enhance the convenience factor and readability of your code.
Like operator overloading, subscripts let you use native Swift constructs: something like checkerBoard[2][3]
rather than the more verbose checkerBoard.objectAt(x: 2, y: 3)
.
In this tutorial, you’re going to explore subscripts by building the foundations for a basic checkers game in a playground. You’ll see how easy it is to use subscripting to move pieces around the board. When you’re done, you’ll be well on your way to building a new game to keep your fingers occupied during all of your spare time.
Oh, and you’ll know a lot more about subscripts too! :]
Getting Started
Create a new playground and add the following code:
struct Checkerboard {
enum Square: String {
case empty = "▪️"
case red = "🔴"
case white = "⚪️"
}
typealias Coordinate = (x: Int, y: Int)
fileprivate var squares: [[Square]] = [
[ .empty, .red, .empty, .red, .empty, .red, .empty, .red ],
[ .red, .empty, .red, .empty, .red, .empty, .red, .empty ],
[ .empty, .red, .empty, .red, .empty, .red, .empty, .red ],
[ .empty, .empty, .empty, .empty, .empty, .empty, .empty, .empty ],
[ .empty, .empty, .empty, .empty, .empty, .empty, .empty, .empty ],
[ .white, .empty, .white, .empty, .white, .empty, .white, .empty ],
[ .empty, .white, .empty, .white, .empty, .white, .empty, .white ],
[ .white, .empty, .white, .empty, .white, .empty, .white, .empty ]
]
subscript(coordinate: Coordinate) -> Square {
get {
return squares[coordinate.y][coordinate.x]
}
set {
squares[coordinate.y][coordinate.x] = newValue
}
}
}
extension Checkerboard: CustomStringConvertible {
var description: String {
return squares.map { row in row.map { $0.rawValue }.joined(separator: "") }
.joined(separator: "\n") + "\n"
}
}
Checkerboard
contains three definitions:
-
Square
represents the state of a square on the board..empty
represents an empty square while.red
and.white
represent the presence of a red or white piece on that square. -
Coordinate
is an alias for a tuple of two integers. You will use this type to access the squares on the board. -
squares
is the two-dimensional array that stores the state of the board.
Finally, there’s an extension to add conformance to CustomStringConvertible
that lets you print a checkerboard to the console.
Open the console using View/Debug Area/Show Debug Area, then enter the following lines at the bottom of the playground:
var checkerboard = Checkerboard()
print(checkerboard)
This code initializes an instance of Checkerboard
then prints the description
property of the CustomStringConvertible
implementation to the console. The output in your console should look like this:
▪️🔴▪️🔴▪️🔴▪️🔴 🔴▪️🔴▪️🔴▪️🔴▪️ ▪️🔴▪️🔴▪️🔴▪️🔴 ▪️▪️▪️▪️▪️▪️▪️▪️ ▪️▪️▪️▪️▪️▪️▪️▪️ ⚪️▪️⚪️▪️⚪️▪️⚪️▪️ ▪️⚪️▪️⚪️▪️⚪️▪️⚪️ ⚪️▪️⚪️▪️⚪️▪️⚪️▪️
Getting And Setting Pieces
Looking at the console, it’s pretty easy for you to know what piece occupies a given square, but your program doesn’t have those powers yet. It can’t know which player is at a specified coordinate because the squares
array is marked as private
. There’s an important point to make here: the squares
array is the implementation of the the board. However, the user of type Checkerboard
shouldn’t know anything about the implementation of that type.
A type should shield its users from its internal implementation details; that’s why the squares
array is kept private.
With that in mind, you’re going to add two methods to Checkerboard
to find and set a piece at a given coordinate.
Add the following methods to Checkerboard
, after the spot you assign the squares
array:
func piece(at coordinate: Coordinate) -> Square {
return squares[coordinate.y][coordinate.x]
}
mutating func setPiece(at coordinate: Coordinate, to newValue: Square) {
squares[coordinate.y][coordinate.x] = newValue
}
Notice how the squares
array is accessed – using a Coordinate
tuple – rather than accessing the array directly. The actual storage mechanism of an array-of-arrays is exactly the kind of implementation detail the user should be shielded from!
Defining Subscripts
You may have noticed these methods look an awful lot like a property getter and setter combination. Maybe they should be implemented as a computed property instead? Unfortunately, that won’t work. Your methods require a coordinate
parameter, and computed properties can’t have parameters. Does that mean you’re stuck with methods?
Well no – this special case is exactly what subscripts are for! :]
Look at how you define a subscript:
subscript(parameterList) -> ReturnType {
get {
// return someValue of ReturnType
}
set (newValue) {
// set someValue of ReturnType to newValue
}
}
Subscript definitions mix both function and computed property definition syntax:
- The first part looks a lot like a function definition, with a parameter list and a return type. Instead of the
func
keyword and function name, you use the specialsubscript
keyword. - The main body looks a lot like a computed property, with a getter and a setter.
This combination of function and property syntax highlights the power of subscripts: to provide a shortcut to accessing the elements of an indexed collection. You’ll learn more about that soon, but first, consider the following example.
Replace methods piece(at:)
and setPiece(at:to:)
with the following subscript:
subscript(coordinate: Coordinate) -> Square {
get {
return squares[coordinate.y][coordinate.x]
}
set {
squares[coordinate.y][coordinate.x] = newValue
}
}
The getter and setter of this subscript are implemented exactly like the methods they replace:
- Given a
Coordinate
, the getter returns the square at the column and row. - Given a
Coordinate
and value, the setter accesses the square at the column and row and replaces its value.
Give your new subscript a test drive by adding the following code to the end of the playground:
let coordinate = (x: 3, y: 2)
print(checkerboard[coordinate])
checkerboard[coordinate] = .white
print(checkerboard)
The playground will tell you the piece at (3, 2) is red. After changing it to white, the output in the console will be:
▪️🔴▪️🔴▪️🔴▪️🔴 🔴▪️🔴▪️🔴▪️🔴▪️ ▪️🔴▪️⚪️▪️🔴▪️🔴 ▪️▪️▪️▪️▪️▪️▪️▪️ ▪️▪️▪️▪️▪️▪️▪️▪️ ⚪️▪️⚪️▪️⚪️▪️⚪️▪️ ▪️⚪️▪️⚪️▪️⚪️▪️⚪️ ⚪️▪️⚪️▪️⚪️▪️⚪️▪️
You can now find out which piece is at a given coordinate, and set it, by using checkerboard[coordinate]
in both cases. A shortcut indeed!