Typecasting with as in Swift

774 views Asked by At

I'm trying to understand of as typecasting.

Reading Type Casting chapter on Apple Documentation, I've two syntax for downcasting (as? and as! operators) but I didn't find anything about as. So I thought that I should never have used this kink of operator but yesterday when I was typing code with do-try-catch statement, I met this one:

catch let error as NSError {
      print(error)
}

Initially, error was type conforms to the Error protocol. Now using the as NSError casting, it has become an instance of a NSError class.

But my question is: What does as operator do? It's not a downcasting sure.. Could it be used for "convert" object?

EDIT I don't think it's a duplicate. In my case, the error variable, is not a class and doesn't inherit from a super class so I have not a upcasting. It is not even a pattern matching. I've already read both Swift Blog page and this thread on StackOverflow.

EDIT 2 From Swift Blog

Swift 1.2 separates the notions of guaranteed conversion and forced conversion into two distinct operators. Guaranteed conversion is still performed with the as operator, but forced conversion now uses the as! operator. The ! is meant to indicate that the conversion may fail. This way, you know at a glance which conversions may cause the program to crash.

The text above doesn't work for me because if I tried to use the as! operator instead of as, compiler complain me.

EDIT 3 Even in Using Swift with Cocoa and Obj-C documentation they use the let-as? syntax for checking and casting to a protocol. So, why in my case, I can't use it?

2

There are 2 answers

0
OOPer On

First of all, as suggested in the dasblinkenlight's comment, your code snippet is not using a type-casting-operator. Check the syntax of do-statement­ and you can find these:

catch-clausecatch­ pattern­opt ­where-clause­opt ­code-block­

patternvalue-binding-pattern­

value-binding-patternvar ­patternlet ­pattern­

patterntype-casting-pattern­

type-casting-patternis-pattern­ | as-pattern­

as-patternpattern ­as ­type­

So, your EDIT 2 has no meaning, there are no syntax accepting as! in catch-clause.


But this code (using type-casting-operator) works, so I try to explain how to use as-casting.

    enum MyError: Error {
        case bad
        //...
    }
    let error: Error = MyError.bad
    let nsError = error as NSError

As shown in the linked article in the EI Captain v2.0's comment, as-casting is used for Guaranteed conversion. I have collected some use cases of such conversions.

  • Upcasting

    class Animal {}
    class Dog: Animal {}
    let d = Dog()
    d as Animal     // upcast succeeds
    

    As shown in the article, upcasting always succeeds, so you can use as.

  • Specifying literal type

    let byte = 123 as UInt8
    let ch = "a" as UnicodeScalar
    

    In Swift, literals are typeless, so you can use as to specify the types of literals

    In case Swift can infer the type of the literal, you can omit such as-casting:

    let byte: UInt8 = 123
    let ch: UnicodeScalar = "a"
    
  • Disambiguating overloaded methods

    class MyClass {
        func aMethod(_ arg: String) {
            print(arg)
        }
        func aMethod(_ arg: Int) {
            print("\"\(arg)\"")
        }
    }
    let obj = MyClass()
    let theFunc = obj.aMethod as (String)->Void
    theFunc("abc") //->abc
    
  • Always-succeeds bridging

    let str: String = "a String"
    let nsStr = str as NSString
    
    let intArr: [Int] = [1,2,3]
    let nsArr = intArr as NSArray
    

    The example let nsError = error as NSError is included in this category, and you need to read this article carefully, to understand why this is an always-succeeds bridging.


For your EDIT 3.

You may need to distinguish these two syntaxes:

let a: Any = 1

//Using if-let -- Optional binding, `(a as? Int)` is an expression using type-casting-operator which generates an Optional result
if let intA = (a as? Int) {
    print("\(intA) is Int")
}

//Using if-case -- pattern matching, `(let intA as Int)` is a pattern using as-pattern
if case (let intA as Int) = a {
    print("\(intA) is Int")
}

As already noted, catch leads a pattern, and you cannot use as? in pattern.

0
Code Different On

as

Use as for types that Apple has done some work to handle the conversion in the background. These are usually Foundation types that Apple bridged into Swift and want you to have a quick way to convert back and forth from their ObjC equivalents, for example:

String <-> NSString
URL    <-> NSURL
Array  <-> NSArray
Data   <-> NSData

These casts always succeed and Xcode will warn you if you use as? or as!. In your specific case, Apple has done some meddling in the background to make the Error protocol and NSError to be castable to/from each other.

as!

Use as! when you know the object is castable to another type. as! will crash your app if the object is not castable (for example, when sender is actually a UITextField)

let button = sender as! UIButton    // you are sure that the sender is always
                                    // a UIButton or your app will crash

as?

Use as? when you not sure if the object is castable to the other type. In practice, this should be your preferred method and you should optional binding to check for success. as? produces nil if the object is not castable:

// Exit the function if the sender is not a UIButton
guard let sender = sender as? UIButton else {
    return
}

// Only execute the block if the sender is UIButton
if let button = sender as? UIButton {
    // ...
}