Lesson 67Classes

Type Casting

What is Type Casting?

Type casting lets you check an instance's type at runtime and treat it as a different class type within its hierarchy. Essential for working with class inheritance!

Type Casting Operators

is

Check if instance is of a certain type

if item is Movie { }

as?

Optional downcast (safe)

let m = item as? Movie

as!

Forced downcast (crashes if wrong)

let m = item as! Movie

as

Upcast to parent type

let media = movie as MediaItem

Any and AnyObject

Any - Can represent any type (value or reference)
AnyObject - Can represent any class type only

Best Practices

  • Prefer as? over as! for safety
  • Use is for type checking without casting
  • Avoid Any when possible - use generics instead
main.swift
// TYPE CASTING
// Check and convert between types at runtime

// CLASS HIERARCHY FOR EXAMPLES
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)
    }
}

// MIXED ARRAY
let library: [MediaItem] = [
    Movie(name: "Inception", director: "Christopher Nolan"),
    Song(name: "Bohemian Rhapsody", artist: "Queen"),
    Movie(name: "The Matrix", director: "Wachowskis"),
    Song(name: "Imagine", artist: "John Lennon"),
    Song(name: "Yesterday", artist: "Beatles")
]

// TYPE CHECKING WITH 'is'
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: 3

// DOWNCASTING WITH 'as?' (Optional)
for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), Director: \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), Artist: \(song.artist)")
    }
}

// FORCED DOWNCASTING WITH 'as!'
// Use only when you're CERTAIN of the type!
let firstItem = library[0]
let movie = firstItem as! Movie  // We know index 0 is a Movie
print("\(movie.name) by \(movie.director)")

// UPCASTING WITH 'as'
let inception = Movie(name: "Inception", director: "Nolan")
let media: MediaItem = inception as MediaItem  // Always succeeds
print(media.name)

// TYPE CASTING WITH ANY AND ANYOBJECT
var things: [Any] = []

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })

for thing in things {
    switch thing {
    case 0 as Int:
        print("zero as Int")
    case 0 as Double:
        print("zero as Double")
    case let someInt as Int:
        print("an integer: \(someInt)")
    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("movie: \(movie.name)")
    case let stringConverter as (String) -> String:
        print(stringConverter("World"))
    default:
        print("something else")
    }
}

// PRACTICAL EXAMPLE: JSON-LIKE PARSING
func processData(_ data: Any) {
    if let dict = data as? [String: Any] {
        if let name = dict["name"] as? String {
            print("Name: \(name)")
        }
        if let age = dict["age"] as? Int {
            print("Age: \(age)")
        }
    }
}

let userData: [String: Any] = ["name": "Alice", "age": 25, "active": true]
processData(userData)

Try It Yourself!

Create a Vehicle hierarchy with Car and Bike subclasses. Make an array and use type casting to print details about each!