[TIL] 타입캐스팅

안녕하세요!

오늘은 타입캐스팅에 대해 배워보았습니다. 

 

타입캐스팅이란?

인스턴스의 타입을 확인하거나 인스턴스를 같은 계층에 있는 다른 상위 클래스나 하위 클래스로 취급하는 방법 (출처: 공식 문서)

인스턴스의 타입을 확인하거나 인스턴스 자신의 타입을 다른 타입의 인스턴스인 것처럼 사용할 때 활용되는 개념입니다.

 

쉽게 말해서, 1) 인스턴스의 타입을 확인할 수 있고 2) 인스턴스의 타입을 (일시적으로) 바꿔줄 수 있는 것이 타입캐스팅입니다!

 

공식 문서의 예제 코드로 복습해보겠습니다.

 

기본 코드 작성

우선 base 클래스 MediaItem을 정의해주고,

MediaItem을 상속받는 Movie, Song이라는 클래스를 정의해줍니다.

// base class 인 MediaItem을 정의해준다.
class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

// MediaItem 이라는 클래스를 상속받는 두 개의 하위 클래스를 만든다 - Movie, Song
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)
    }
}

// Movie와 Song 두 개의 클래스를 아이템으로 갖는 library 배열 선언
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")
]
// 이 때 "library"의 타입은 [MediaItem]

이때 Movie, Song 두 개의 클래스를 아이템으로 갖는 library 배열을 정의해주는데요,

Movie, Song 인스턴스의 공통 부모가 MediaItem이기 때문에 

library는 타입 추론에 의해 [MediaItem] 배열의 형태을 갖게 됩니다.

 

그런데, 각각 Movie, Song이라는 타입으로 바꾸어주고 싶을 때도 분명히 있을 거에요.


바로 이 때 타입 지정을 위해서 downcasting을 이용해야 합니다!

 


타입 검사 Checking Type

타입은 is 연산자를 이용해 확인할 수 있습니다.

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie { // item의 타입이 Movie가 맞다면 코드 실행
        movieCount += 1
    } else if item is Song { // item의 타입이 Song이라면 코드 실행
        songCount += 1
    }
}

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

library에 있는 item을 각각 보면서

Movie, Song일 때 1씩 더해주고 출력합니다.


다운캐스팅 Downcasting

다운캐스팅은 as?, as! 두 가지 키워드로 실행합니다.

  • as? : 특정 타입이 맞는지 확신할 수 없을 때 사용
    • 하위 클래스 타입으로 다운 캐스트를 시도할 때 옵셔널 값으로 반환
  • as! : 특정 타입이라는 것이 확실한 경우에 사용
    • 아닐 경우 런타임 에러
for item in library {
    if let movie = item as? Movie { // item의 type을 Movie로 다운캐스팅
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song { // item의 type을 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

* 캐스팅은 실제 인스턴스나 값을 바꾸는 것이 아니라, 지정한 타입으로 취급해 사용하는 것!!

 


Any와 AnyObject에 대한 타입 캐스팅 Type Casting for Any and AnyObject

  • Any는 모든 타입(함수 타입 포함)의 인스턴스를 나타낼 수 있음
  • AnyObject 모든 클래스 타입의 인스턴스를 나타낼 수 있음

Any 타입의 예제입니다.
things라는 Any 타입 배열을 선언해 여러 타입의 값을 저장할 수 있습니다.

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)" })
// 타입이 전부 다른 요소를 넣어줘도 괜찮다.

 

Any 또는 AnyObject 타입으로 정해진 상수 또는 변수의 타입을 알아보기 위해서는

switch 구문의 케이스로 is 또는 as 패턴을 사용해볼 수 있습니다.

for thing in things {
    switch thing {
    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 movie as Movie:
        print("a movie called \(movie.name), dir. \(movie.director)")
    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 movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael

Any 타입은 옵셔널 타입을 포함한다. 하지만 Swift에서는 Any 타입을 사용해야 하는 곳에 옵셔널을 사용하면 경고를 발생시킨다.
Any 값으로 옵셔널 값을 사용해야 한다면 as 연산자를 사용해 명시적으로 옵셔널을 Any로 캐스트할 수 있다.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning

 

 

예전에 velog에서 공식 문서를 정리해두었던 내용을 다시 꺼내보았습니다..

그 때는 개념을 거의 이해 못하고 예제만 겨우 이해했던 것 같은데,

역시 수업을 듣고 나서 공식 문서를 읽으니 머리에 쏙쏙 들어오네요 ㅠㅠ

TIL 더 열심히 해야지..

 

 

 

 

 

새싹에서 배우고 이해한 내용을 정리합니다.


참고

공식 문서 https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-ID342

 

'공부하자! > iOS' 카테고리의 다른 글

[TIL] Codable  (0) 2022.08.29
[TIL] CodeBase UI + 코드 예제  (0) 2022.08.18
[TIL] 모듈화, 프레임워크, 접근 제어  (0) 2022.08.17
[iOS] TableView 테이블뷰에 대하여  (0) 2022.08.11
[Swift] 자주 실수하는 오류 정리  (0) 2022.08.11