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:

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:

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.