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 toGameScene?
and theif 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
Swift Optional Escaping Closure Parameter
Swift - Spritekit Cgpoint Alignment
Get HTML from Wkwebview in Swift
Accessing Code in Swift 3 Error
Can You Execute an Applescript Script from a Swift Application
How to Remove Diacritics from a String in Swift
Transparent Background For Modally Presented Viewcontroller
Custom Back Button For Navigationview'S Navigation Bar in Swiftui
Swift 3 For Loop With Increment
How to Encode Enum Using Nscoder in Swift
Removeobjectsatindexes For Swift Arrays