1. Type Casting πŸ‘©β€πŸ’»

1. What is Type Casting?

Type Casting은 λ‹¨μˆœνžˆ νƒ€μž…μ„ λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ λ³€κ²½ν•˜λŠ” κ²ƒλ§Œμ„ μ˜λ―Έν•˜λŠ” 것이 μ•„λ‹ˆλ‹€. Instance 의 νƒ€μž…μ„ ν™•μΈν•˜κ±°λ‚˜, ν•΄λ‹Ή Instance λ₯Ό μžμ‹ μ˜ Class Hierarchy ꡬ쑰 μ•ˆμ—μ„œ Superclass λ˜λŠ” Subclass둜 닀루기 μœ„ν•΄ μ‚¬μš©ν•œλ‹€.

Swift μ—μ„œ Type Casting 은 is와 as operators λ₯Ό μ΄μš©ν•΄ κ΅¬ν˜„λœλ‹€. 이 두 operators λŠ” κ°„λ‹¨ν•˜λ©΄μ„œ λ¬Έμž₯을 ν‘œν˜„ν•˜λŠ” 것과 같은 μžμ—°μŠ€λŸ¬μš΄ λ°©λ²•μœΌλ‘œ Value에 λŒ€ν•œ Checking Typeκ³Ό Casting Another Types을 μ§€μ›ν•œλ‹€.

그리고 Checking Type은 ν•΄λ‹Ή Instance 의 νƒ€μž…μ„ ν™•μΈν•˜λŠ” 것 뿐 μ•„λ‹ˆλΌ κ·Έ νƒ€μž…μ΄ νŠΉμ • Protocols λ₯Ό λ”°λ₯΄κ³  μžˆλŠ”μ§€ ν™•μΈν•˜λŠ” 데 μ‚¬μš©λ˜κΈ°λ„ ν•œλ‹€.

2. Defining a Class Hierarchy for Type Casting

  • Base Class
class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}
  • Subclass of the MediaItem
class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

Movie 의 instance 만 μ €μž₯ν•  경우 λ°°μ—΄ movies λŠ” [Movie] νƒ€μž…μ˜ λ°°μ—΄λ‘œ μΆ”λ‘ λœλ‹€.

let movies = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Movie(name: "Citizen Kane", director: "Orson Welles")
]

print(type(of: movies))     // Array<Movie>

그리고 movies λŠ” λ‹€μŒκ³Ό 같이 반볡이 κ°€λŠ₯ν•˜λ‹€.

movies.forEach { print("The director of \($0.name) is \($0.director).") }
The director of Casablanca is Michael Curtiz.
The director of Citizen Kane is Orson Welles.


이제 MediaItem 을 Superclass 둜 κ°–λŠ” 또 λ‹€λ₯Έ Subclass Song 을 μΆ”κ°€ν•΄λ³΄μž.

  • Subclass of the MediaItem
class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

λ‹€μŒ λ°°μ—΄ library λŠ” 2개의 Movie instances 와 3개의 Song instances λ₯Ό 가지고 μžˆλ‹€.
Swift 의 Type Checker λŠ” 배열이 μ΄ˆκΈ°ν™” 될 λ•Œ library 의 elements κ°€ Movie 와 Song νƒ€μž…μ΄λΌλŠ” 것을 확인 ν›„, 이듀이 κ³΅ν†΅λœ Superclass MediaItem 의 Class Hierarchy κ΅¬μ‘°λΌλŠ” 것을 μΆ”λ‘ ν•œλ‹€. 그리고 두 νƒ€μž…μ„ λͺ¨λ‘ μ €μž₯ν•˜κΈ° μœ„ν•΄ 이 배열을 [MediaItem] νƒ€μž…μœΌλ‘œ μƒμ„±ν•œλ‹€.

let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]

print(type(of: library))    // Array<MediaItem>

An array is storing multiple types

그리고 이 배열을 λ°˜λ³΅μ„ 톡해 μ ‘κ·Όν•˜λ €κ³  ν–ˆμœΌλ‚˜ MediaItem 이 κ°–κ³  μžˆλŠ” name 을 μ œμ™Έν•œ Subclasses Movie, Song 의 director 와 artist μ—λŠ” 접근이 λΆˆκ°€λŠ₯ν•œ 것을 확인할 수 μžˆλ‹€. Swift κ°€ 두 νƒ€μž…μ„ λͺ¨λ‘ μ €μž₯ν•˜κΈ° μœ„ν•΄ κ³΅ν†΅λœ Superclass νƒ€μž…μœΌλ‘œ 배열을 μΆ”λ‘ ν–ˆκΈ° λ•Œλ¬Έμ΄λ‹€. μ΄λ•Œ ν•„μš”ν•œ 것이 λ°”λ‘œ Downcasting이닀.


2. Checking Type (Type Check Operator β€˜is’) πŸ‘©β€πŸ’»

Type Check Operator(is)λŠ” μΌμΉ˜ν•˜λŠ” νƒ€μž…μΈμ§€ 확인 ν›„ Bool을 λ°˜ν™˜ν•œλ‹€.

let aMedia = MediaItem(name: "Avatar")
let aMovie = Movie(name: "Casablanca", director: "Michael Curtiz")
print(aMedia is MediaItem)  // true
print(aMedia is Movie)      // false
print(aMedia is Song)       // false

print(aMovie is MediaItem)  // true
print(aMovie is Movie)      // true
print(aMovie is Song)       // false

Superclass 의 instance λŠ” Subclass 의 Members λ₯Ό λͺ¨λ‘ 갖지 λͺ»ν•˜λ―€λ‘œ Subclass νƒ€μž…μ΄ 될 수 μ—†λ‹€.
반면 Subclass 의 instance λŠ” Superclass 의 λͺ¨λ“  Members λ₯Ό λͺ¨λ‘ κ°–κ³  μžˆμœΌλ―€λ‘œ, Superclass νƒ€μž…μ΄ 될 수 μžˆλ‹€.


μœ„ library 에 각 νƒ€μž…μ΄ λͺ‡ κ°œμ”© μ €μž₯λ˜μ–΄ μžˆλŠ”μ§€ Type Check Operatorλ₯Ό μ‚¬μš©ν•΄ ν™•μΈν•΄λ³΄μž.

var (movieCount, songCount) = (0, 0)

library.forEach {
    switch $0 {
    case is Movie: movieCount += 1
    case is Song: songCount += 1
    default: break
    }
}

print("Media library contains \(movieCount) movies and \(songCount) songs")
Media library contains 2 movies and 3 songs

3. Downcasting (Type Cast Operator β€˜as?, as!’) πŸ‘©β€πŸ’»

νŠΉμ • Class Type 의 μƒμˆ˜λ‚˜ λ³€μˆ˜λŠ” κ²‰μœΌλ‘œ λ³΄μ΄λŠ” 것과 달리 μ‹€μ œλ‘œλŠ” Subclass Instance λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆλŠ” κ²½μš°λ„ μžˆλ‹€. μœ„μ—μ„œ library κ°€ 그런 κ²½μš°λ‹€. μ΄λ•Œ μ΄κ²ƒμ˜ Type 을 Subclass Type 으둜 Downcasting ν•  수 μžˆλ‹€.

Downcasting 은 μ‹€νŒ¨ν•  수 있기 λ•Œλ¬Έμ— 2κ°€μ§€μ˜ Operators κ°€ μ œκ³΅λœλ‹€. 쑰건뢀 ν˜•μ‹(conditional form)인 as?λŠ” Optional을 λ°˜ν™˜ν•˜λ―€λ‘œ Downcasting 의 성곡 μ—¬λΆ€λ₯Ό ν™•μΈν•˜λŠ” μš©λ„λ‘œ μ‚¬μš©ν•œλ‹€. λ§Œμ•½ Downcasting 성곡 μ—¬λΆ€λ₯Ό ν™•μ‹ ν•  수 μžˆμ„ κ²½μš°λŠ” κ°•μ œ ν˜•μ‹(forced form)인 as!λ₯Ό μ‚¬μš©ν•΄ Forced Unwrapping된 νƒ€μž…μ„ 얻을 수 μžˆλ‹€. 단, Downcasting 이 μœ νš¨ν•˜μ§€ μ•Šμ„ 경우 Runtime Error κ°€ trigger λ˜λ―€λ‘œ λ°˜λ“œμ‹œ 성곡함을 ν™•μ‹ ν•  수 μžˆμ„ λ•Œλ§Œ μ‚¬μš©ν•΄μ•Όν•œλ‹€.

Casting은 μ‹€μ œ instance λ₯Ό μˆ˜μ •ν•˜κ±°λ‚˜ 값을 바꾸지 μ•ŠλŠ”λ‹€. instance λŠ” κ·ΈλŒ€λ‘œ μœ μ§€ν•˜λ©΄μ„œ, 단지 casting 된 νƒ€μž…μ˜ instance 둜 닀루고 μ ‘κ·Όν•  수 μžˆλ„λ‘ ν•œλ‹€.

library.forEach {
    if let movie = $0 as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = $0 as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}
Movie: Casablanca, dir. Michael Curtiz
Song: Blue Suede Shoes, by Elvis Presley
Movie: Citizen Kane, dir. Orson Welles
Song: The One And Only, by Chesney Hawkes
Song: Never Gonna Give You Up, by Rick Astley

4. Type Casting for Any and AnyObject(Upcasting Operator β€˜as’) πŸ‘©β€πŸ’»

Swift λŠ” λΆˆνŠΉμ • νƒ€μž…μ„ μœ„ν•œ 두 κ°€μ§€μ˜ νŠΉλ³„ν•œ νƒ€μž…μ„ μ œκ³΅ν•œλ‹€.

  • Any : Closure, Function, Class, Structure, Enumeration Types λ₯Ό ν¬ν•¨ν•œ λͺ¨λ“  νƒ€μž…μ˜ instance λ₯Ό λŒ€μ‹ ν•  수 μžˆλ‹€.
  • AnyObject : Class Types λ₯Ό λŒ€μ‹ ν•  수 μžˆλ‹€.

Any와 AnyObjectλŠ” 이것이 μ œκ³΅ν•˜λŠ” μž‘λ™ 및 κΈ°λŠ₯이 λͺ…μ‹œμ μœΌλ‘œ ν•„μš”ν•œ κ²½μš°μ—λ§Œ μ‚¬μš©ν•΄μ•Όν•œλ‹€. κ·Έ μ™Έ κ²½μš°λŠ” μ–Έμ œλ‚˜ λͺ…ν™•ν•œ νƒ€μž…μ„ μ§€μ •ν•˜λŠ” 것이 더 μ’‹λ‹€.

AnyλŠ” Optional 을 ν¬ν•¨ν•œ λͺ¨λ“  νƒ€μž…μ„ λŒ€μ‹ ν•  수 μžˆλ‹€.

예제λ₯Ό μœ„ν•΄ Structure 와 Enumeration 을 ν•˜λ‚˜μ”© μΆ”κ°€ν•˜μž.

struct Point {
    var x = 0.0, y = 0.0
}

enum CompassPoint {
    case east, west, south, north
}

1. Any

var things: [Any] = []

func testAnyTypes(_ things: [Any]) {
    for thing in things {
        switch thing {
        case 0 as Int:
            print("\(thing) : zero as an Int")
        case 0 as Double:
            print("\(thing) : zero as a Double")
        case let someInt as Int:
            print("\(thing) : an integer value of \(someInt)")
        case let someDouble as Double where someDouble > 0:
            print("\(thing) : 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("\(thing) : a string value of \"\(someString)\"")
        case let (x, y) as (Double, Double):
            print("\(thing) : an (x, y) point at \(x), \(y)")
        case let stringConverter as (String) -> String:
            print("\(thing) : \(stringConverter("Michael"))")
        case let movie as Movie:
            print("\(thing) : a movie called \(movie.name), dir. \(movie.director)")
        case let point as Point:
            print("\(thing) : a point is at (\(point.x), \(point.y))")
        case let direction as CompassPoint:
            print("\(thing) : a direction is \(direction)")
        default:
            print("\(thing) : something else")
        }
    }
}


[Any]에 μ—¬λŸ¬ νƒ€μž…μ„ μ €μž₯ν•˜κ³ , 이λ₯Ό Upcasting을 톡해 λ‹€μ‹œ ν™•μΈν•΄λ³΄μž.

  • Int and Double
things.append(0)            // Int
things.append(0.0)          // Double
things.append(42)           // Int
things.append(3.14159)      // Double

testAnyTypes(things)
0 : zero as an Int
0.0 : zero as a Double
42 : an integer value of 42
3.14159 : a positive double value of 3.14159


  • String, Tuple and Closure
things.append("hello")      // String
things.append((3.0, 5.0))   // Tuple of type (Double, Double)
things.append({ (name: String) -> String in "Hello, \(name)" }) // Closure of type (name: Stirng) -> String

testAnyTypes(things)
hello : a string value of "hello"
(3.0, 5.0) : an (x, y) point at 3.0, 5.0
(Function) : Hello, Michael


  • Class, Structure and Enumeration
things.append(Movie(name: "Avatar", director: "James Francis Cameron")) // Class
things.append(Point(x: 5.2, y: 3.0))                                    // Structure
things.append(CompassPoint.east)                                        // Enumeration

testAnyTypes(things)
__lldb_expr_81.Movie : a movie called Avatar, dir. James Francis Cameron
Point(x: 5.2, y: 3.0) : a point is at (5.2, 3.0)
east : a direction is east

2. AnyObject

AnyObject only represent class types

AnyObjectλŠ” Any와 달리 였직 Class Types만 λŒ€μ‹ ν•  수 μžˆλ‹€.

var things: [AnyObject] = []
things.append(Movie(name: "Avatar", director: "James Francis Cameron")) // Class

if let aMovie = things[0] as? Movie {
    print("\(aMovie) : a movie called \(aMovie.name), dir. \(aMovie.director)")
}
__lldb_expr_92.Movie : a movie called Avatar, dir. James Francis Cameron

3. Do Explicit Casting Optional to Any

λ‹€μŒ μ½”λ“œλ₯Ό 보자. Optional 을 κ·ΈλŒ€λ‘œ μ‚¬μš©ν•˜λ©΄ μž‘λ™μ€ λ˜μ§€λ§Œ Swift λŠ” 이λ₯Ό ν•΄κ²°ν•  것을 κ²½κ³ ν•œλ‹€.

let optionalNumber3: Int? = 3

things.append(optionalNumber3)          // Warning: Expression implicitly coerced from 'Int?' to 'Any'

testAnyTypes(things)
Optional(3) : an integer value of 3

μ΄λ•Œ κ²½κ³ λ₯Ό μ œκ±°ν•˜κΈ° μœ„ν•΄ Nil-Coalescing Operator(??)λ‚˜ Forced Unwrapping(!)을 μ‚¬μš©ν•  수 μžˆλ‹€.

let optionalNumber5: Int? = 5
let optionalNumber7: Int? = 7

things.append(optionalNumber5 ?? 0)
things.append(optionalNumber7!)

testAnyTypes(things)
5 : an integer value of 5
7 : an integer value of 7


사싀 Error κ°€ μ•„λ‹Œ Warning μ΄λ―€λ‘œ μž‘λ™μ— λ¬Έμ œλŠ” μ—†λ‹€. ν•˜μ§€λ§Œ κ²½κ³ λŠ” μ œκ±°ν•˜κ³ , Unwrapping 은 ν•˜μ§€ μ•Šμ€ 채 Optional μƒνƒœλ₯Ό μœ μ§€ν•  μˆ˜λŠ” μ—†μ„κΉŒ?

Any casting 은 이λ₯Ό κ°€λŠ₯ν•˜κ²Œ ν•œλ‹€.

let optionalNumber9: Int? = 9

things.append(optionalNumber9 as Any)

testAnyTypes(things)
Optional(9) : an integer value of 9




Reference

  1. β€œType Casting.” The Swift Programming Language Swift 5.7. accessed Jan. 14, 2023, Swift Docs Chapter 18 - Type Casting.