Chain unwrapping Optionals

The ability of if statements conditional unwrap Optionals can be chained together to produce some useful and concise code. The following example is a bit contrived, but it illustrates how we can use a single if statement to unwrap a chain of optional values.

When you play a game of pool, called a frame, the type of the first ball you pot becomes the type you need to pot for the rest of the frame, and your opponent has to pot the opposite type.

Let's define a frame of pool and say that we want to track what type of ball each player will be potting:

class PoolFrame { 
var player1BallType: PoolBallType?
var player2BallType: PoolBallType?
}

We will also create a PoolTable object that has an optional currentFrame property, which will contain information about the current frame if one is in progress:

class PoolTable { 
var currentFrame: PoolFrame?
}

So, we have a pool table that has an optional frame and a frame that has optional ball type for each player.

Now, let's write a function that prints the ball type for player 1 in the current frame. However, it is possible that the current frame is nil because there is no frame currently being played, or that player 1's ball type is nil because a ball hasn't been potted. Therefore, we need to account for either of those being nil:

func printBallTypeOfPlayer1(forTable table: PoolTable) { 
if let frame = table.currentFrame, let ballType = frame.player1BallType {
print(ballType.rawValue)
} else {
print("Player 1 doesn't yet have a ball type or there is no current frame")
}
}

Our function is given PoolTable, and to print player 1's ball type, we first need to check and unwrap the currentFrame property, and then we need to check and unwrap the current frame's player1BallType property.

We can do this by nesting our if statements as follows:

func printBallTypeOfPlayer1(forTable table: PoolTable) { 
if let frame = table.currentFrame {
if let ballType = frame.player1BallType {
print(ballType.rawValue)
} //... handle else
} //... handle else
}

Instead, we can handle this chained unwrapping in one if statement by performing the unwrapping statement sequentially, separated by commas, and each statement can access the unwrapped values from the previous statements:

func printBallTypeOfPlayer1(forTable table: PoolTable) { 
if let frame = table.currentFrame, let ballType = frame.player1BallType {
print("\(ballType)")
} //... handle else
}

The first statement unwraps the currentFrame property, and the second statement uses that unwrapped frame to unwrap player 1's ball type.

Let's use the functions we've just created:

//
// Table with no frame in play
//
let table = PoolTable()
table.currentFrame = nil
printBallTypeOfPlayer1(forTable: table)
// Player 1 doesn't yet have a ball type or there is no current frame

//
// Table with frame in play, but no balls potted
//
let frame = PoolFrame()
frame.player1BallType = nil
frame.player2BallType = nil
table.currentFrame = frame
printBallTypeOfPlayer1(forTable: table)
// Player 1 doesn't yet have a ball type or there is no current frame

//
// Table with frame in play, and a ball potted
//
frame.player1BallType = .solid
frame.player2BallType = .stripe
printBallTypeOfPlayer1(forTable: table)
// solid