In my previous post, Generic Functions in Swift, I used a type casting operator to check for inheritance. It was simple enough to mention there, but I felt I should cover type casting in Swift a bit more in-depth in its own post. Type casting is a way to convert an object of one type to another.
There is one term that is used a lot when talking about type casting, so it should probably be defined upfront, that term is downcast. According to Wikipedia, downcasting is the act of casting a reference of a base class to one of its derived classes. There is an opposite term to this one, the obviously named upcast. While this term is not used in Apple’s iBook, it is used in the WWDC video “Swift Interoperability In Depth.” It of course means to go the other way, casting a derived class back up to one of its base classes.
Swift’s Type Casting Operators
There are 3 type casting operators in Swift with extremely simple names: is, as?, and as!. One could make the argument that they are really 2, since the last two are the same operator with an optional and non-optional form. Nonetheless, here is a handy table to show them all in one place and what they return:
Operator | Return Type |
is | Bool, if the entity is indeed one of its subclasses or adopts that protocol |
as? | Optional of intended entity. |
as! | Forcefully unwrapped optional of intended entity. |
This is just for a quick reference, let’s get a bit more in depth on what these actually do.
The is Operator
The is operator has two related but technically distinct uses in Swift.
First, if the right-hand term is a class name, it returns a Bool regarding whether the left-hand term is indeed that subclass.
Secondly, if the right-hand term is a protocol, it returns a Bool regarding whether the left-hand term adopts that protocol.
Another way of describing the is operator is that it returns a Bool whether the left-hand term can be downcast to a specific type. Since protocols can also be treated as types (like making an array of Equatable objects), this works the same for the protocol form. So while what it does is semantically distinct between classes and protocols, they both boil down to whether you can downcast the left-hand term.
This is was actually a bit more nuanced than I thought. I originally thought is just checked whether the cast would be possible, but there was more to it. (Update: Since the original writing of this post, now the is operator checks whether the cast is possible) Take this example:
let controlArray = [UILabel(), UITextView(), UIButton()] for item in controlArray { if item is UILabel { print("UILabel") } else if item is UITextView { print("UITextView") } else if item is UIButton { print("UIButton") } else if item is UIDatePicker { print("UIDatePicker") } }
It’s pretty simple, we just make a Swift array of three objects, a UILabel, UITextView, and a UIButton. To hold all of these elements, the compiler had to look for their common superclass, so controlArray is actually an array of UIViews. Well, UIViews are actually subclasses of NSObject, so what if we add this case:
else if item is NSString { print("NSString") }
That should work, right? Nope, the compiler emits a warning that says “Cast from ‘UIView’ to unrelated type ‘NSString’ always fails.” It lets it compile, but tells you that it will never be true, so you might as well just take it out.
One thing to note though, if we add another object to that array that is a subclass of NSObject, then that error warning goes away, so if we changed the array to this:
let controlArray = [UILabel(), UITextView(), UIButton(), NSDate()]
Now controlArray is actually an array of NSObjects, so it is possible to downcast it to an NSString.
Now okay, that might be obvious, but an NSString is an NSObject, this should surely work:
var someString: NSString = "Old school" var typeCheck = someString is NSObject //Attempting to save a Bool to typeCheck //Warning: 'is' test is always true
Like the above test shows, this now works, but gives the compiler warning of ” ‘is’ test is always true “. I am leaving the text above for historical purposes on how things used to be in Swift 1, it was interesting given how Swift worked at the time. Now the is operator acts a bit more like my original interpretation, checking whether the cast is possible, as opposed to just testing for downcasts.
The as? Operator
I’m going to start the other way around for this one and start with the optional form. The is operator checks whether the downcast is possible, if we try to downcast the first element in the controlArray (a UILabel) to a UIDatePicker, that would fail, since while they both are UIViews, my UILabel was certainly not a UIDatePicker. As such, if it could fail, the program needs a way to signify that. Sounds like a great use for Swift optionals once more! So, let’s rewrite that controlArray for loop using some Optional Binding to take advantage of the unique aspects of these controls.
let controlArray = [UILabel(), UIButton(), UIDatePicker()] for item in controlArray { if let someLabel = item as? UILabel { let storeText = someLabel.text } else if let someDatePicker = item as? UIDatePicker { let storeDate = someDatePicker.date } }
So now, our for loop steps through our controlArray, and grabs pertinent data out of some controls we care about, and ignores the ones we don’t. It will grab the text from the UILabel, the date from the UIDatePicker, and just ignore the UIButton altogether. Now, I’m not doing anything with those values here to just keep the code concise to show off the Optional Binding, but you could store those elsewhere in more permanent variables if you wanted.
Nonetheless, that is how that works, the as? operator returns an optional, and then we use optional binding to assign it to a temporary constant, and then use that in the if clause.
The as! Operator
There is not too much more to say here, but as operator is the forcefully unwrapped optional form of the as? operator. To forcefully type something that you believe cannot fail, you use the as! operator, which is now consistent with how Swift’s Optional Syntax works in all other situations. As with any force unwrapping though, using the as! operator risks runtime errors that will crash your app should the unwrapping not succeed.
You should only use this one if you are certain that the downcast would be successful. You would use it like so:
let aControlThatShouldBeAButton: UIView = UIButton() let thatButton: UIButton = aControlThatShouldBeAButton as! UIButton
One thing to note, upcasting is often done implicitly. As the first line shows, the UIButton was just created, and then assigned directly to the explicitly typed UIView “aControlThatShouldBeAButton.” You can use as to upcast if you wish to not write the type on the left side, but it is probably best practice to write it with normal typing as shown on the first line in the above example for upcasting.
The original as operator does still exist, but it has a much more narrow use. The only times I’ve seen it somewhat required, is when you need to typecast to a bridged type when the typecasting is not implicit. For instance, a few releases ago NSString would no longer implicitly convert to a Swift String. Swift Strings would still implicitly convert to NSString when necessary, but you could not go the other way around implicitly. In that case, to convert an NSString to a Swift String, now you use the original as operator. It is basically used for type conversions that the compiler can be certain will succeed, like converting Bridged types. It also apparently can work for upcasts, though I have not seen that documented anywhere, but was confirmed in a playground in the version of Xcode mentioned at the bottom of this article. So the above statement is still correct, about using the original as operator to upcast a UIButton to a UIView, if you don’t want to explicitly type “aControlThatShouldBeAButton”.
Conclusion
I thought this post was going to be a lot simpler when I started it, but I learned quite a bit when testing this out in the playgrounds. My key take-aways from this article (that weren’t immediately obvious) are:
- The is operator checks whether an entity can be downcast to a type in the same hierarchy.
It does not check for upcasting. - Upcasting is done implicitly.
I hope you found this article helpful. If you did, please don’t hesitate to share this post on Twitter or your social media of choice, every share helps. Of course, if you have any questions, don’t hesitate to contact me on the Contact Page, or on Twitter @CodingExplorer, and I’ll see what I can do. Thanks!