Swift Type Casting

Type Casting consists of two things

  1. Type Checking
  2. Changing the Type

Swift Type Checking

Swift gives a lot of priority to the readability of the code. No wonder to check whether an instance belongs to a certain type, we use the is the keyword. For Example, the following code snippet would always print false.

var aString = "BaluTutorial.com"
print(aString.self is Int) // false

Let’s do the type checking in the class and its subclasses. For that, we’ve created the following three classes.

import Foundation

class University {
    
    var university: String
    
    init(university: String) {
        self.university = university
    }
}

class Discipline : University {
    
    var discipline: String
    
    init(university: String, discipline: String) {
        self.discipline = discipline
        super.init(university: university)
    }
}

class Student : University {
    
    var student: String
    
    init(student: String, university: String) {
        self.student = student
        super.init(university: university)
    }
}

In the following sections, we’ll be looking at Upcasting and Downcasting.

Swift Upcasting

Let’s create an object of each of the classes and combine them in an Array. Our goal is to know thy type!

var arrayObject = [University(university: "IIT"),
             Discipline(university: "IIT",discipline: "Computer Science"),
             Student(student: "Balu Naik",university: "IIT")]

print(arrayObject is [Student])       //false
print(arrayObject is [Discipline])    //false
print(arrayObject is [University])    //true

The array is of the type University. All the subclasses are always implicitly upcasted to the parent class. In the above code, the Swift Type Checker automatically determines the common superclass of the classes and sets the type to it.

type(of:) can be used to determine the type of any variable/constant.

var university = University(university: "IIT")
var student = Student(student: "BaluNaik",university: "IIT")
var discipline = Discipline(university: "IIT",discipline: "Computer Science")

print(student is University)    //true
print(discipline is University)  //true
print(university is Student)   //false
print(university is Discipline)  //false

Protocols are types Hence type checking and casting work the same way on them. The following code prints true.

protocol SomeProtocol {
    
    init(str: String)
    
}

class SomeClass : SomeProtocol {
    
    required init(str: String) {
        
    }
}

var myObject = SomeClass(str: "BaluTutorial.com")

print(myObject is SomeProtocol)  //true

Swift Downcasting

For downcasting a superclass to the subclass, we use the operator as.

  • as has two variants, as? and as! to handle scenarios when the downcasting fails.
  • as is typically used for basic conversions.
  • as? would return an optional value if the downcast succeeds and nil when it doesn’t.
  • as! force unwraps the value. Should be used only when you’re absolutely sure that the downcast won’t fail. Otherwise, it’ll lead to a runtime crash.
//Double to float

let floatValue = 2.35661312312312 as Float
print(floatValue)   //prints 2.356613

let compilerError = 0.0 as Int  //Compiler error

let crashes = 0.0 as! Int  //Runtime crash

Let’s look at the class hierarchy again. Let’s try to downcast the array elements to the subclass types now.

var arrayObject = [University(university: "IIT"),
             Discipline(university: "MIT",discipline: "Computer Science"),
             Student(student: "Balu",university: "BITS"),
             Student(student: "Naik",university: "MIT")]

for item in arrayObject {
    if let obj = item as? Student {
        print("Students Detail: \(obj.student), \(obj.university)")
    } else if let obj = item as? Discipline {
        print("Discipline Details: \(obj.discipline), \(obj.university)")
    }
}

We downcast the type University to the subclass types. Following results get printed in the console.

We’ve used if let statements to unwrap the optional gracefully.

Any and AnyObject

As per the Apple Docs:

  • Any can represent an instance of any type at all, including function types.
  • AnyObject can represent an instance of any class type.

AnyObject can be a part of Any type. Inside an AnyObject array, to set value types, we need to typecast them to the AnyObject using as an operator.

import Foundation

class AClass {
    
    var name = "Balu Naik"
}

func printHello()
{
    print("Hello Balututorial.com")
}

var anyType: [Any] = [1, "Hey", true, 1.25, AClass(), printHello()]

var anyObjectType: [AnyObject] = [AClass(), 1 as AnyObject, "Hello" as AnyObject]

print(anyType)
print(anyObjectType)

for item in anyType {
    if let obj = item as? AClass {
        print("Class AClass property name is: \(obj.name)")
    }
}

for item in anyObjectType {
    if let obj = item as? String {
        print("String type: \(type(of: obj))")
    }
}

 

The function that’s present in the anyType array, gets printed on the array’s declaration. Notice that for anyObjectType, the string gets printed without double-quotes. Besides AnyObject allows object types only. String and Int are value types.

Then how is it working?

On typecasting the string and int as AnyObject they both get converted into NSString and NSNumber class types which are not value types. Hence it is possible to set String, Int in AnyObject provided we use the as operator correctly.