Need Clarification of Type Casting Operator in Swift

Need clarification of Type Casting operator in Swift

You could have found the answer yourself if you read the note on the bottom:

The cases of a switch statement use the forced version of the type cast operator (as, not as?) to check and cast to a specific type. This check is always safe within the context of a switch case statement.

(emphasis mine)

Here is an Apple blog post which elaborates on the difference between as?, as and as!.

Need clarification on AnyObject in Swift

AnyObject is a protocol. If you type it in a Playground and command click on it the following pops up:

/// The protocol to which all classes implicitly conform.
///
/// When used as a concrete type, all known `@objc` methods and
/// properties are available, as implicitly-unwrapped-optional methods
/// and properties respectively, on each instance of `AnyObject`. For
/// example:
///
/// .. parsed-literal:
///
/// class C {
/// @objc func getCValue() -> Int { return 42 }
/// }
///
/// // If x has a method @objc getValue()->Int, call it and
/// // return the result. Otherwise, return nil.
/// func getCValue1(x: AnyObject) -> Int? {
/// if let f: ()->Int = **x.getCValue** {
/// return f()
/// }
/// return nil
/// }
///
/// // A more idiomatic implementation using "optional chaining"
/// func getCValue2(x: AnyObject) -> Int? {
/// return **x.getCValue?()**
/// }
///
/// // An implementation that assumes the required method is present
/// func getCValue3(x: AnyObject) -> **Int** {
/// return **x.getCValue()** // x.getCValue is implicitly unwrapped.
/// }
///
/// See also: `AnyClass`
@objc protocol AnyObject {
}

Apple Swift: Type Casting Generics

Suppose you have an array of buttons:

let views: [NSView] = [NSButton(), NSButton(), NSButton()]

You can use these casts:

let viewsAreButtons = views is [NSButton]  // returns true
let buttonsForSure = views as! [NSButton] // crashes if you are wrong
let buttonsMaybe = views as? [NSButton] // optionally set

If you try to use as in a switch case like below, it will not work. The compiler (Swift 1.2 Xcode 6.3b1) says: "Downcast pattern of type [NSButton] cannot be used."

switch views {
case let buttons as [NSButton]:
println("Buttons")
default:
println("something else")
}

Call it a limitation. File a radar with your use case. The Swift team really seams to be listening for feedback. If you really want to get it to work, you can define your own pattern matching operator. In this case it would be something like this:

struct ButtonArray { }
let isButtonArray = ButtonArray()

func ~=(pattern: ButtonArray, value: [NSView]) -> Bool {
return value is [NSButton]
}

Then this works:

switch views {
case isButtonArray:
println("Buttons") // This gets printed.
default:
println("something else")
}

Try it in a Playground. Hope it helps!

Can you overload type-casting operators in Swift?

No, the language doesn't provide such functionality for custom types. There is bridging between Objective-C collections and Swift collections but that's baked in and not customizable.

// Swift array of `String` elements
let swiftArray: [String] = ["Bob", "John"]

// Obj-C array of `NSString` elements, but element type information
// is not known to the compiler, so it behaves like an opaque NSArray
let nsArray: NSArray = ["Kate", "Betty"]

// Obj-C array of an `NSString` and an `NSNumber`, element type
// information is not known to the compiler
let heterogeneousNSArray: NSArray = ["World", 3]

// Casting with `as` is enough since we're going from Swift array to NSArray
let castedNSArray: NSArray = swiftArray as NSArray

// Force casting with `as!` is required as element type information
// of Obj-C array can not be known at compile time
let castedSwiftArray: [String] = nsArray as! [String]

// Obj-C arrays can not contain primitive data types and can only
// contain objects, so we can cast with `as` without requiring a
// force-cast with `!` if we want to cast to [AnyObject]
let heterogeneousCastedNSArray: [AnyObject] = heterogeneousNSArray as [AnyObject]

Documentation for type casting is available here.

I think you can achieve what you want to do with initializers.

extension X {
init(string: String) {
self = X(string)
}
}

extension String {
init(x: X) {
// toString is implemented elsewhere
self = x.toString
}
}

let x = X()
let string = "Bobby"

let xFromString: X = X(string: string)
let stringFromX: String = String(x: x)

Not directly related to your question but there is also a family of protocols that start with ExpressibleBy..., enabling you to do things like the following:
Let's say we want to initialize strings from integer literals. We can do that by conforming to and implementing ExpressibleByIntegerLiteral

// Strings can not be initialized directly from integer literals
let s1: String = 3 // Error: Can not convert value of type 'Int' to specified type 'String'

// Conform to `ExpressibleByIntegerLiteral` and implement it
extension String: ExpressibleByIntegerLiteral {
public init(integerLiteral value: Int) {
// String has an initializer that takes an Int, we can use that to
// create a string
self = String(value)
}
}

// No error, s2 is the string "4"
let s2: String = 4

A nice use case for ExpressibleByStringLiteral can be found here.

Swift: What is the difference between casting using as / as? / as! keywords and a C-style cast?

Short answer :

The C-Style cast basically means the Swift compiler will just force your closure to be called as if it takes a (Int, Float) tuple as parameter whereas the as / as? / as! cast will first do some sanity checks on your cast to ensure that the types are compatible and so on.

Since the compiler believes (in certains versions, as seen on the comments on the other answer) that (Int, Float) -> () and ((Int, Float)) -> () are too far apart to be compatible, the sanity check will just return nil, therefore blocking your call.

What makes it work is that a function / closure taking a (Int, Float) tuple behaves exactly the same (in the current version of Swift) as a function / closure taking an Int and a Float parameter.

Long answer :

I compiled a snippet of code into assembly which I will be referencing from now on. That snippet can be found here : https://swift.godbolt.org/z/CaOb0s

For readability purposes, I used functions instead of actual closures here.

I've created two functions corresponding to the two cases we have :

func twoParamFunc(a: Int, b: Float)-> Void {
print(a, b)
}

func singleParamFunc(tuple: (a: Int, b: Float))-> Void {
print(tuple.a, tuple.b)
}

I then tried to cast those using your two different methods :

    let cCastFunction = ((((Int, Float)) -> Void)?(twoParamFunc))!
let asCastFunction = (twoParamFunc as? (((Int, Float)) -> Void))!

And when looking at the assembly code compiled by swift, we can see a lot of differences between the two.

When looking at the C-style cast, we can see that most of the code is basically just calling alloc/retain/release and moving pointers and values around. The only call to external code is through a failure case (the ! dereferencing a null reference), calling $ss18_fatalErrorMessage__4file4line5flagss5NeverOs12StaticStringV_A2HSus6UInt32VtF

Whereas in the swift-style cast, there are a lot of additional calls (the sanity checks I was talking about earlier).
We have for exemple

call    (type metadata accessor for (Swift.Int, Swift.Float) -> ())
...
call (type metadata accessor for ((Swift.Int, Swift.Float)) -> ())
...
call swift_dynamicCast@PLT

which clearly shows that the Swift compiler is doing some checks to the compatibility of the types being cast, and are nowhere to be found in the c-style cast.

So now that the C-style cast / Swift-style cast difference has been found, we can try to understand why the call to the C-style casted function works.

When looking at the assembly code generated by the two simple calls to the functions I made in the sample :

    twoParamFunc(a: a.0,b: a.1)
singleParamFunc(tuple: a)

We can see that those functions are actually compiled to be called identically :

singleParamFunc :

        mov     rdi, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
call (output.singleParamFunc(tuple: (a: Swift.Int, b: Swift.Float)) -> ())

Here we see that the value corresponding to the first value of the tuple is put into register rdi, and the second one is put into xmm0, and then the function is called

twoParamFunc :

        mov     rax, qword ptr [rip + (output.a : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.a : (Swift.Int, Swift.Float))+8]
...
mov rdi, rax
...
call (output.twoParamFunc(a: Swift.Int, b: Swift.Float) -> ())

In this function, it is not as straightforward, but now value 1 goes in rax register which itself is copied into rdi register, and value 2 still goes in xmm0, and the function is called.

But in this sample since we are doing other things, the assembly code is a bit messier, I've made another sample to test this cleanly : https://swift.godbolt.org/z/vDCZZV

In this sample (on which I've added another test with a struct) we can see that the assembly code created to called the 3 functions are exactly the same :

        mov     rdi, qword ptr [rip + (output.structValue : output.struct_test)]
movss xmm0, dword ptr [rip + (output.structValue : output.struct_test)+8]
call (output.test(value: output.struct_test) -> ())
        mov     rdi, qword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))]
movss xmm0, dword ptr [rip + (output.tupleValue : (Swift.Int, Swift.Float))+8]
call (output.test2(tuple: (Swift.Int, Swift.Float)) -> ())
        mov     ecx, 1
mov edi, ecx
movss xmm0, dword ptr [rip + .LCPI0_0]
call (output.test3(a: Swift.Int, b: Swift.Float) -> ())

To resume, in the current version of swift, any of these three functions could be c-casted into any other and still work.

This ended up being a lot longer than initially planned, but I thought this problem deserved it.

Casting to a type held in a variable

Swift (currently) requires the Type assignment at compile time. You can do some things like this, but you will need to write a converter for each type combination you want to use, e.g:

func convertType(from item: Int) -> Float {
return Float(item)
}

var item: Float = convertType(from: 1)

I would caution going down this road and try and get used to Swift's way of doing things. If you absolutely need it you should be able to use some generic functions with a protocol like FloatConvertable to handle this more simply.

Swift: Test class type in switch statement

You absolutely can use is in a switch block. See "Type Casting for Any and AnyObject" in the Swift Programming Language (though it's not limited to Any of course). They have an extensive example:

for thing in things {
switch thing {
case 0 as Int:
println("zero as an Int")
case 0 as Double:
println("zero as a Double")
case let someInt as Int:
println("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
println("a positive double value of \(someDouble)")
// here it comes:
case is Double:
println("some other double value that I don't want to print")
case let someString as String:
println("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
println("an (x, y) point at \(x), \(y)")
case let movie as Movie:
println("a movie called '\(movie.name)', dir. \(movie.director)")
default:
println("something else")
}
}

Why do we need to explicitly cast the optional to Any?

Every type can be implicitly promoted to an optional of that type. This means that when you cast T? to Any it is very hard to know whether it was originally T or originally T? (or even T?? or worse). Most confusing is that Any can be promoted to Any? and that Any? is of type Any, so telling the difference between Any, Any?, Any??, and Any??? (etc.) is very tricky, and sometimes impossible.

Any is a very tricky type in Swift and should almost never be used. Except for explicitly tricking the compiler (in some very fancy and fragile type-eraser), I don't know of any case where it really makes sense to have Any as a variable type, and definitely not in the form of [Any]. If you're created an [Any], you've gone down a bad path that isn't going to go well.

There are a very few cases where Any as a function parameter type makes sense (print() being the most famous), but they are extremely rare in app-level code. If you find yourself needing Any, you've probably done something wrong, and the compiler is going to fuss at you about it and often make you write extra as code to make sure you really mean the messy things you're saying.

Just to give some concrete versions of this, optionality tends to be lost when you enter Any. So consider this situation:

let number: Int = 3
let optionalNumber: Int? = 3
let nilNumber: Int? = nil

let anyNumber = number as Any
let anyOptional = optionalNumber as Any
let anyNil = nilNumber as Any

if anyNumber is Int { print("number is Int")} // yes
if anyOptional is Int { print("optional number is Int")} // yes
if anyNil is Int { print("nil is Int")} // no

if anyNil is Int? { print("nil is Int?")}
// -> Error: Cannot downcast from 'Any' to a more optional type 'Int?'

Rats.

We can't get our optional back the same way we put it in. We can promote it of course:

if (anyNil as Any?) is Int? { print("nil is Int?") }  // yes

But we can promote anything that way, since everything is implicitly an optional of itself:

if (anyNumber as Any?) is Int? { print("number is Int?")}  // also yes

So, um. Rats. We don't really know if it was originally optional or not. It's mess, and the compiler is warning you that it's going to be a mess if you go very far down this road. T->Any is a bit of magic. T->T? is also a bit of magic. Combine the two magics, and you had better know exactly what you're doing.

Type Casting in swift

The 'as' keyword is used for casting.

'as' example:

let calcVC = destinationViewController as CalculatorViewController

This line casts the destinationViewController to a CalculatorViewController. However, this would crash if destinationViewController was not a CalculatorViewController or a subclass thereof.

To protect against a crash, you can use 'if let' with 'as?'...

'as?' example:

if let calcVC = destinationViewController as? CalculatorViewController {
// ... write code to execute if destinationViewController is in fact a CalculatorViewController
}

You can even check before you even try to do 'as' with the 'is' keyword...

'is' example:

if destinationViewController is CalculatorViewController {
//...
}


Related Topics



Leave a reply



Submit