Swift 3.0 - What is as, as? and as! operators?

We all have been there where language offers plethora of options. However, I have quite experienced that bunch of features sometimes cause confusion even though individual feature is easier to understand. When all the features come at once, it gets quite scary to remember what goes where.

In this post I will try to demystify the little concepts involving keywords which are not too long, but can cause bit of a confusion. Hopefully at the end of this article, things will become more clear than they were at the beginning of this article.

  • as Operator

As operator is used to convert one type to another when compiler is guaranteeing the success of desired cast. With this operator, the source and destination are convertible to each other, and if not compiler will produce an error asking to avoid the incompatible cast.

However, if you really want to force cast, you can use optional cast operators as? or as! which will produce successful casted value if cast is successful, otherwise will produce nil value and cause crash respectively.

as is an old operator. This can be used to convert primitive types which are interchangeable or converting Swift types to Objective-C and vice versa.

as operator can also be used for the upcasting. For example, when you want to cast derived type to the base type. Since compiler already knows whether the derived to base class conversion is possible based on the inheritance, you will get the feedback at the compile time. So if upcasting is not possible, code will fail to compile.

For example, following conversions are implicitly allowed by Swift by just using as keyword. No fancy as? or as! are required

let doubleVal = 0.0 as Double
let ObjCString = "asd" as NSString
let ObjCURL = URL(string: "") as NSURL?
let anyObj = 3 as AnyObject
let anyVal_1 = 3 as Any
let ObjCArray = [] as NSArray
let ObjCDictionary = [:] as NSDictionary
let floatVal = 0.0 as Float

Please note that as keyword cannot be used for conversions such as direct from Double to Int. It will result in the error

double_to_int_conversion_error

  • as? Operator

This operator is more sophisticated that is allows you to check if object of one type is castable to another. If cast is successful, it will return the casted value, otherwise returns nil.

Let's understand this keyword by the use of class hierarchy since this is where type conversions come handy.

I am going to use the same inheritance for next two operators, so you might want to look at this example as a base for further sample codes


class Developer {
    func printWhoAmI() {
        print("I am Developer")
    }
}

class Android: Developer {
    override func printWhoAmI() {
        print("I am an Android developer")
    }
}

class iOS: Developer {
    override func printWhoAmI() {
        print("I am an iOS developer")
    }
}

class CSharp: Developer {
    override func printWhoAmI() {
        print("I am a C Sharp developer")
    }
}

let devs: [Developer] = [Developer(), Android(), iOS(), CSharp()]

Here we have Developer class as a base and 3 other classes, Android, iOS and C-Sharp are inherited from it. We are also maintaining an array of objects of type Developer.

Note that, array may not only contain Developer types, but also any classes inherit from Developer

Now, let's play with some examples


for dev in devs {
    // Code below prints each line once for every type. The base class Developer does not print anything since it is a superclass.

    // The line below is valid since all the types stored in devs array are either of type Developer or derive from Developer type so this gets resolved at compile time
    
    // This is always true for given input
    let genericDev = dev as Developer

    // The lines below will get printed only once for every conforming type

    if let andDev = dev as? Android {
        print("Casted to Android")
    }

    if let iOSDev = dev as? iOS {
        print("Casted to iOS")
    }

    if let cSharpDev = dev as? CSharp {
        print("Casted to C Sharp")
    }
}

// Output
Developer
Developer
Casted to Android
Developer
Casted to iOS
Developer
Casted to C Sharp

// Note that below code will produce nil, since we are trying to cast Android type to iOS

let iOSDev = devs[1] as? iOS

// So will this, since attempt is made to cast iOS type to Android

let androidDev = devs[1] as? Android

// This code will successfully cast Android object into Developer, since it's upcasting

let genericDeveloper = devs[1] as? Developer

  • as! Operator

as! operator is very similar to its sibling as?. Difference is as! is more evil.

Where as? will produce nil value is cast is unsuccessful, as! will result in crash if cast fails.

// Forcefully casting Android type to iOS. 
// This will crash since devs[1] is of type Android
let forcediOSDveloper = devs[1] as! iOS

// Forcefully casting iOS type to Android
// This too will crash since since devs[2] is of type iOS and attempt is made to cast iOS type to Android
let forcedAndroidDveloper = devs[2] as! Android

// This code will cast successfully
let validCast = devs[1] as! Android

As it's evident from screenshot below, the code above will result in crash for incompatible types.

crash_by_invalid_cast

However, if cast if possible it will return the casted object back

  • is Operator

is operator is slightly different from rest of the operators mentioned in this post. While other operators actually perform the cast, this operator will check if given object is of specified type of conforms to given protocol.

To elaborate more on this, let's make a protocol and make a class to conform to it

protocol Prot {
    func doit()
}

protocol AnotherProt {
    func doitToo()
}

class Animal: Prot {
    func doit() {

    }
}

let animal = Animal()

if animal is Prot {
    print("Conforms to Prot")
}

if animal is Animal {
    print("Class is of type Animal")
}

if animal is AnotherProt {
    // This will not print since animal does not conform to type AnotherProt
    print("Class is of type AnotherProt")
}

// Past example with devs array
// let devs: [Developer] = [Developer(), Android(), iOS(), CSharp()]

for dev in devs {
    if dev is CSharp {
        print("Dev is C Sharp")
    } else if dev is iOS {
        print("Dev is iOS")
    } else if dev is Android {
        print("Dev is Android")
    } else {
        print("Default Developer")
    }
}

// Output of previous code
Default Developer
Dev is Android
Dev is iOS
Dev is C Sharp

As it is clear from above example, whenever object O is of type T or inherit from T, or conforms to protocol P, following statements are true

if O is T {

}

if O is P {
    
}




References and Further reading:

Apple Developer Portal

Up casting and down casting confusion in Swift