Type Casting
Type casting lets you check the type of a value at runtime and treat it
as a different type within its own class hierarchy. It's how you ask
"is this value really a Circle?" and, if so, safely reach
for the members that only a Circle has. Swift makes this
safe with the is and as operators.
Defining a class hierarchy to play with
Type casting is most interesting when several types share a common
ancestor. Here is a small hierarchy we'll use throughout the page: a
base MediaItem with two subclasses.
class MediaItem {
var name: String
init(name: String) { self.name = name }
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
let library: [MediaItem] = [
Movie(name: "Dune", director: "Villeneuve"),
Song(name: "Blue Train", artist: "Coltrane"),
Movie(name: "Arrival", director: "Villeneuve")
]
Swift infers the type of library as [MediaItem], the common supertype.
Checking type with is
The type check operator is returns
true if a value is an instance of a given type (or one of
its subclasses), and false otherwise.
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Movies: \(movieCount), Songs: \(songCount)")
// → Movies: 2, Songs: 1
Downcasting with as? and as!
A constant or variable typed as a superclass may actually hold an instance of a subclass behind the scenes. Downcasting tries to treat it as that more specific subclass. Because the cast can fail, Swift offers two forms:
as?— the conditional form. Returns an optional of the target type: the value if the cast succeeds, ornilif it fails. Always safe.as!— the forced form. Casts and force-unwraps in one step. Use it only when you're certain the cast will succeed; a wrong cast triggers a runtime crash.
for item in library {
if let movie = item as? Movie {
print("Movie: \(movie.name), dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: \(song.name), by \(song.artist)")
}
}
// → Movie: Dune, dir. Villeneuve
// → Song: Blue Train, by Coltrane
// → Movie: Arrival, dir. Villeneuve
Watch out: casting does not modify the instance or
create a new one — the underlying object is the same. The cast only
changes how Swift lets you treat it. And reach for
as? by default; as! crashes the moment the
value isn't the type you claimed.
Upcasting
Going the other direction — treating a subclass instance as one of its
superclasses — always succeeds, so it uses the plain as
operator (no ? or !). This is also how you
hand a value to code that expects the more general type.
let specific = Movie(name: "Sicario", director: "Villeneuve")
let general = specific as MediaItem // upcast, always safe
print(general.name) // → Sicario
// general.director would NOT compile — only the MediaItem API is visible
Any and AnyObject
Swift provides two special types for working with non-specific values:
Anycan represent an instance of any type at all — including function types, structs, enums, and optionals.AnyObjectcan represent an instance of any class type.
var things: [Any] = []
things.append(0)
things.append(3.14)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Prisoners", director: "Villeneuve"))
things.append({ (name: String) in "Hi \(name)" })
Tip: if you assign an optional to an Any
value, Swift warns you, because it's easy to do by accident. If you
really mean it, silence the warning with an explicit
as Any cast: things.append(optionalNumber as Any).
Type casting with a switch
A switch statement with as patterns is the
cleanest way to discover and unwrap the concrete type of an
Any value in one place. Each case can bind
the value to a constant of the matched type.
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case let someDouble as Double:
print("a Double: \(someDouble)")
case let someString as String:
print("a String: \(someString)")
case let (x, y) as (Double, Double):
print("a point at (\(x), \(y))")
case let movie as Movie:
print("a Movie: \(movie.name)")
case let greet as (String) -> String:
print(greet("Ada"))
default:
print("something else")
}
}
The case ... as Type pattern combines a check and a downcast.
Casting collections
You can downcast an entire collection. Casting an array of supertypes to
an array of a subtype with as? succeeds only if every
element is that subtype; otherwise it returns nil.
let onlyMovies: [MediaItem] = [
Movie(name: "Dune", director: "Villeneuve"),
Movie(name: "Arrival", director: "Villeneuve")
]
if let movies = onlyMovies as? [Movie] {
print("All \(movies.count) items are Movies")
}
// → All 2 items are Movies
// library mixes Movie and Song, so this cast fails:
print(library as? [Movie] == nil) // → true
Wrapping up
Type casting is Swift's runtime answer to polymorphism: is
asks a yes/no question, as? safely tries a downcast,
as! forces one, and plain as upcasts. Combine
them with switch to branch on concrete types cleanly, and
lean on Any/AnyObject only when you genuinely
need to erase a type. Prefer the conditional forms — they keep your
programs crash-free.