Using Switch Statements And Case Let for Type Checking in Swift

Posted 9/8/2018.

One thing that bothers many developers about using if statements for type checking in Objective-C is that it suggests a specific order of importance. For example, here's how you might use type checking in an Objective-C prepareForSegue method.

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.destinationViewController isKindOfClass:[TKDetailViewController class]]) {
        //prepare TKDetailViewController
    } else if ([segue.destinationViewController isKindOfClass:[TKModalViewController class]]) {
        //prepare TKModalViewController
    }
}

The order of TKDetailViewController and TKModalViewController doesn't really matter here. The purpose of the code is just to check the type of the destination view controller, and the order in which it checks possible types is arbitrary. Here's an equivalent version in Swift:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.destination is DetailViewController {
        //prepare DetailViewController
    } else if segue.destination is ModalViewController {
        //prepare ModalViewController
    }
}

This Swift version is much more concise, but we still have the same problem. In Swift, however, we can now use a switch statement for type checking:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case is DetailViewController:
        //prepare DetailViewController
    case is ModalViewController:
        //prepare ModalViewController
    default:
        break
    }
}

While it's unavoidable to list the types to be checked in a particular order, the intent of this code is clearer. Also notice that this version is even more concise since we don't have to repeat "if segue.destinationViewController is" each time.

But what about the code for handling each case? As it's written, we would have to do the following:

class DetailViewController: UIViewController {

    func configureDetail() {}

}

class ModalViewController: UIViewController {

    func configureModal() {}
    
}

class MasterViewController: UIViewController {

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        switch segue.destination {
        case is DetailViewController:
            let detailViewController = segue.destination as! DetailViewController
            detailViewController.configureDetail()
        case is ModalViewController:
            let modalViewController = segue.destination as! ModalViewController
            modalViewController.configureModal()
        default:
            break
        }
    }

}

This works, but it's more verbose than necessary and we need to use the dreaded exclamation point. Here it's safe to force cast, but the syntax still doesn't feel right. Using case let syntax we can do better:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    switch segue.destination {
    case let destination as DetailViewController:
        destination.configureDetail()
    case let destination as ModalViewController:
        destination.configureModal()
    default:
        break
    }
}

Another situation where this is useful is in checking types that conform to a protocol. Here's an example using a version of the Drawable protocol from Apple's WWDC 2015 talk Protocol-Oriented Programming in Swift.

protocol Drawable {
    func draw()
}

struct Circle: Drawable { ... }
struct Polygon: Drawable { ... }
struct Diagram: Drawable { ... }

let circle = Circle()
let triangle = Polygon()
let pentagon = Polygon()
let diagram = Diagram()
let drawables: [Drawable] = [circle, triangle, pentagon, diagram]
for drawable in drawables {
    switch drawable {
    case let drawable as Circle:
        print("This is a circle.")
    case let drawable as Polygon:
        print("This is a polygon.")
    case let drawable as Diagram:
        print("This is a diagram.")
    default:
        break
    }
}

Again, using the switch statement makes it clearer that order doesn't matter here, makes the code more concise. The case let syntax makes the code even more concise and allows us to cast the value we're switching on as part of the condition.