Swift Programming – Type Casting

In this tutorial you’ll learn about type casting in Swift. To follow along, it is recommended to have a Mac and the Xcode IDE installed on it. This tutorial makes use of the Xcode playground for compilation of the Swift codes. If you do not have a Mac, you may try using the open-source code editors which support different operating systems such as AtomSublimeText or VSCode.

Type Casting

Type casting allows you to check the type of a value using the type check operator is or cast a different type to that value using the type cast operator as.

Defining a Class Hierarchy for Type Casting

Type casting can be used to check the type of an instance of a class or to cast it to some other class within the same hierarchy. For a better understanding of this concept, let’s write some codes. We will define a base class Product with one property title. Then we will define two subclasses Smartphone and SmartTV which will inherit from our base class Product.

class Product {
    var title: String
    
    init(title: String) {
        self.title = title
    }
}

class Smartphone: Product {
    var storageCapacity: Int
    
    init(title: String, storageCapacity: Int) {
        self.storageCapacity = storageCapacity
        super.init(title: title)
    }
}

class SmartTV: Product {
    var resolution: String
    
    init(title: String, resolution: String) {
        self.resolution = resolution
        super.init(title: title)
    }
}

let products = [
    Smartphone(title: "iPhone X", storageCapacity: 256),
    SmartTV(title: "Samsung Q800T", resolution: "8k"),
    Smartphone(title: "iPhone 12 Pro", storageCapacity: 512),
    SmartTV(title: "Sony X950H", resolution: "4k")
]

We also defined a constant named products to store an array. Swift will infer the type of the constant products as Product. If we iterate over products, each item will be of type Product instead of its respective subclass, that is, Smartphone or SmartTV.

Checking Type

Let’s use the type check operator is for each product in products.

for product in products {
    if product is Smartphone {
        print("\(product.title) is a smartphone.")
    } else if product is SmartTV {
        print("\(product.title) is a smart tv.")
    } else {
        print("\(product.title) is just a cable.")
    }
}

Downcasting

In order to be able to work with the subclass instance of a product in our products array, we have to downcast it to its appropriate type. Downcasting can either succeed or fail, which is why we can use the type cast operator as in two forms, as? (downcasting as an optional value) or as! (downcasting as a forced-unwrapped value). If you are certain that downcasting an item will always succeed, you may use as!. However, if you’re unsure of whether the downcasting will succeed or fail, you should use as?.

for product in products {
    if let smartphone = product as? Smartphone {
        print("The \(smartphone.title) has a storage capacity of \(smartphone.storageCapacity)GB.")
    } else if let smartTV = product as? SmartTV {
        print("The \(smartTV.title) has a screen resolution of \(smartTV.resolution).")
    } else {
        print("\(product.title) is just a cable.")
    }
}

Type Casting for Any and AnyObject

There are two special types for non specific types in Swift. These are Any and AnyObject. The Any type can be used for an instance of any type and function type. The AnyObject type can used for an instance of any class type. Let’s see the Any type in action.

var items = [Any]()
// adding a value of type Int
items.append(15)

// adding a value of type Double
items.append(2.99)

// adding a value of type String
items.append("Hello World")

// adding an array of String
items.append(["Fruits", "Vegetables", "Tea", "Coffee", "Milk"])

// adding a dictionary of key type Int and value type String
items.append([1: "One", 2: "Two", 3: "Three", 4: "Four", 5: "Five"])

// adding a tuple of type Int and String
items.append((id: 1, name: "Zakhia"))

// adding an instance of Smartphone
items.append(Smartphone(title: "iPhone Xr", storageCapacity: 128))

// adding a closure expression that takes 2 parameters of type Int and returns an Int
items.append({ (a: Int, b: Int) -> Int in a + b} )

We can use a switch statement with type casting in the iteration of the array items.

for item in items {
    switch item {
    case let integer as Int:
        print("\(integer) is of type Int.")
    case let double as Double:
        print("\(double) is of type Double.")
    case let string as String:
        print("\(string) is of type String.")
    case let array as [String]:
        print("\(array) is an array of String.")
    case let dictionary as [Int: String]:
        print("\(dictionary) is a dictionary of key type Int and value type String.")
    case let tuple as (Int, String):
        print("\(tuple) is a tuple of type Int and String.")
    case let smartphone as Smartphone:
        print("Smartphone(title: \"\(smartphone.title)\", storageCapacity: \"\(smartphone.storageCapacity)\") is of type Smartphone.")
    case let sum as (Int, Int) -> Int:
        print("\(sum(14, 17)) is the result of a closure expression.")
    default:
        print("Unknown type.")
    }
}

Reference

https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html