Downcasting Optionals in Swift: As? Type, or As! Type

Downcasting optionals in Swift: as? Type, or as! Type?

The practical difference is this:

var optionalString = dict["SomeKey"] as? String

optionalString will be a variable of type String?. If the underlying type is something other than a String this will harmlessly just assign nil to the optional.

var optionalString = dict["SomeKey"] as! String?

This says, I know this thing is a String?. This too will result in optionalString being of type String?, but it will crash if the underlying type is something else.

The first style is then used with if let to safely unwrap the optional:

if let string = dict["SomeKey"] as? String {
// If I get here, I know that "SomeKey" is a valid key in the dictionary, I correctly
// identified the type as String, and the value is now unwrapped and ready to use. In
// this case "string" has the type "String".
print(string)
}

Swift type casting / downcasting

These two patterns are very close, but are not technically the same:

  • The if let ... as! GameScene? {...} does the forced cast to GameScene? and the if let then safely unwraps the resulting optional.

  • The if let ... as? GameScene { ... } will gracefully downcast and unwrap the result.

Functionally these two are almost equivalent. The question is why the template would use the former syntax. One could argue that the as? pattern is little ambiguous because glancing at the code you cannot tell whether you're merely testing the success of a downcast or whether you're dealing with an optional, too. The template's code makes this more explicit.

All of that having been said, I would use if let ... as? GameScene { ... } pattern. It is customary.

Optional downcasting to another type if the first one fails

You are describing a protocol:

protocol MethodHolder {
func method()
}
class TypeOne: UIViewController, MethodHolder {
func method() {
}
}
class TypeTwo: UIViewController, MethodHolder {
func method() {
}
}
class ActualViewController : UIViewController {
var delegate : MethodHolder?
override func viewDidLoad() {
super.viewDidLoad()
self.delegate?.method() // no need to cast anything!
}
}

There is no need to cast anything, because typing the delegate as a MethodHolder guarantees to the compiler (and to you) that this object has a method method. Thus you can call that method without bothering to know whether this happens to be a TypeOne or a TypeTwo.

Swift optional downcasting as Any

There are 2 ways to handle nil in swift:

1. Use of if-let statement

    var obj: Person
if let name = obj.name {
var str = name
}

You can also check type of obj.name variable here as

    var obj: Person
if let name = obj.name as? String {
var str = name
}

2. Use of guard-let statement

    var obj: Person
guard let name = obj.name as? String else {
return
}

Difference between both condition is that with if-let you code will continue executing with next line of code after condition fails( if object is nil), but guard-let will stop execution and will throw return.

Note: Default operator ??

You can also implement default "operator: ??" for assigning a default value like:

var str = obj.name ?? ""

Swift: Downcast to known type

The existing answers correctly state that there is no built in functionality for this. But just to build on what's already been written here, it is possible to define an infix operator =? which implements the behavior you described in your question. For example:

infix operator =? {
associativity none
precedence 130
}

func =? <T>(inout lhs: T?, rhs: Any?) {
lhs = rhs as? T
}

var movie: Movie?
let item: Any? = Movie(name: "some movie")

movie =? item
movie?.name // "some movie"

What is the difference between Swift as and as! type casting operations?

as is compile time cast

as? and as! are runtime casts

  • as? will cast, if cast not possible will return Optional(nil)
  • as! will cast, if cast not possible will crash with runtime error

Example:

class Music { }
class Pop: Music { }
class Rock: Music { }

Pop() as Music // OK, some might disagree but Music *is* a super class of Pop
Pop() as Rock // Compile error: 'Pop' is not convertable to 'Rock'

let pop: AnyObject = Pop()

pop as Music // Compile error: 'AnyObject' is not convertible to 'Music'

pop as? Pop // Pop
pop as! Pop // Pop
pop as? Music // Pop
pop as! Music // Pop

pop as? Rock // nil
pop as! Rock // Runtime error signal SIGABRT

Cannot downcast from ... to a more optional type

You had mistake in if let syntax. Instead of force cast you want to have optional cast. And since you already have this array you don't want get optional array of pokemons

if let pokemonsList = objects as? [Pokemon] {
self.pokemons = pokemonsList
}

Treating a forced downcast as optional will never produce 'nil'

Let's take a closer look at your last line, and explode it to see what's happening:

let temporaryAnyObject = test()
let temporaryString = temporaryAnyObject as String
dict["test"] = temporaryString

The error is on the second line, where you are telling the compiler to enforce that temporaryAnyObject is definitely a String. The code will still compile (assuming you don't treat warnings as errors), but will crash if temporaryAnyObject is not actually a String.

The way as works, with no ?, is basically saying "from now on, treat the result of this expression as that type IF the result is actually of that type, otherwise we've become inconsistent and can no longer run.

The way as? works (with the ?) is saying "from now on, treat the result of this expression as that type IF the result is actually of that type, otherwise the result of this expression is nil.

So in my exploded example above, if test() does return a String, then the as downcast succeeds, and temporaryString is now a String. If test() doesn't return a String, but say an Int or anything else not subclassed from String, then the as fails and the code can no longer continue to run.

This is because, as the developer in complete control, you told the system to behave this way by not putting the optional ? indicator. The as command specifically means that you do not tolerate optional behavior and you require that downcast to work.

If you had put the ?, then temporaryString would be nil, and the third line would simple remove the "test" key/value pair from the dictionary.

This might seem strange, but that's only because this is the opposite default behavior of many languages, like Obj-C, which treat everything as optional by default, and rely on you to place your own checks and asserts.

Edit - Swift 2 Update

Since Swift 2, the forced, failable downcast operator as has been removed, and is replaced with as!, which is much Swiftier. The behavior is the same.



Related Topics



Leave a reply



Submit