Type Casting
Type casting lets you check an instance's runtime type and treat it as a more specific type, such as a subclass or superclass in its hierarchy.
It uses the is and as operators to check types or convert values to different types safely and expressively.
You can also use type casting to verify protocol conformance, as covered in the protocols section.
Creating a Class Hierarchy for Type Casting
Type casting works well with class hierarchies to identify an instance's type and cast it within the same hierarchy.
Here's an example hierarchy: A base class MediaItem for items in a media collection, with a name property and initializer.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}Two subclasses: Video adds a creator property; Track adds an performer property.
class Video: MediaItem {
var creator: String
init(name: String, creator: String) {
self.creator = creator
super.init(name: name)
}
}
class Track: MediaItem {
var performer: String
init(name: String, performer: String) {
self.performer = performer
super.init(name: name)
}
}An array collection holds instances of these classes. Swift infers the type as [MediaItem].
let collection = [
Video(name: "Epic Journey", creator: "Alex Rivera"),
Track(name: "Sunny Days", performer: "Jamie Lee"),
Video(name: "Mystery Tale", creator: "Jordan Kay"),
Track(name: "Echoes", performer: "Taylor Green"),
Track(name: "Rhythm Beat", performer: "Casey Blue")
]
// Type inferred as [MediaItem]Items in collection are typed as MediaItem during iteration, but you can check or downcast to their actual types.
Checking Types
The is operator checks if an instance matches a specific subclass, returning true or false.
Count videos and tracks in the collection:
var videoCount = 0
var trackCount = 0
for item in collection {
if item is Video {
videoCount += 1
} else if item is Track {
trackCount += 1
}
}
print("Collection has \(videoCount) videos and \(trackCount) tracks")
// Prints "Collection has 2 videos and 3 tracks"This loops through the array, using is to tally each type.
Downcasting
A value typed as a base class might reference a subclass instance. Downcast it using as? (conditional) or as! (forced).
as? returns an optional; it's nil if the cast fails. Use it when unsure.
as! assumes success and crashes if wrong. Use only when certain.
Print descriptions by downcasting:
for item in collection {
if let video = item as? Video {
print("Video: \(video.name), created by \(video.creator)")
} else if let track = item as? Track {
print("Track: \(track.name), by \(track.performer)")
}
}
// Video: Epic Journey, created by Alex Rivera
// Track: Sunny Days, by Jamie Lee
// Video: Mystery Tale, created by Jordan Kay
// Track: Echoes, by Taylor Green
// Track: Rhythm Beat, by Casey Blueas? attempts the cast; optional binding checks success and accesses subclass properties.
Note: Casting doesn't alter the instance— it just lets you access it as the cast type.
Type Casting with Any and AnyObject
Any holds any type, including functions. AnyObject holds any class instance.
Prefer specific types, but use these for flexibility.
Example: An array items of type [Any] with mixed values.
var items: [Any] = []
items.append(0)
items.append(0.0)
items.append(42)
items.append(3.14159)
items.append("hello")
items.append((3.0, 5.0))
items.append(Video(name: "Space Adventure", creator: "Sam Nova"))
items.append({ (name: String) -> String in "Hello, \(name)" })Use a switch with is and as to handle types:
for item in items {
switch item {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let video as Video:
print("a video called \(video.name), created by \(video.creator)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a video called Space Adventure, created by Sam Nova
// Hello, MichaelNote: For optionals in Any, cast explicitly with as Any to avoid warnings.
let optionalNumber: Int? = 3
items.append(optionalNumber as Any) // No warning