[Swift5] Type Casting

Junyoung Park·2022년 4월 4일
0

Swift5 Docs

목록 보기
36/37
post-thumbnail
  • 다음은 Swift 5.6 Doc의 Type Casting 공부 내용을 정리했음을 밝힙니다.

Type Casting

타입 캐스팅을 통해 인스턴스 타입을 확인할 수 있다. 또는 인스턴스를 그 클래스 위계 구조 내부의 다른 부모/자식 클래스로 다룰 수 있다.

타입 캐스팅 오퍼레이터는 두 개다. is는 그 타입의 값을 확인하고, as는 다른 타입으로 인스턴스를 캐스팅한다. 타입 캐스팅을 특정 타입이 프로토콜에 부합하는지 확인하는 목적으로도 사용할 수 있다.

타입 캐스팅으로 클래스 하이어러키 정의

특정 클래스 인스턴스 타입을 확인하고, 같은 하이어러키 안에 있는 다른 클래스로 인스턴스 타입을 캐스팅하는 용도로 클래스 하이어러키로 타입 캐스팅할 수 있다.

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 = [
    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")
]
// the type of "library" is inferred to be [MediaItem]

Movie, Song 클래스가 각각 MediaItem 클래스를 상속한 자식 클래스로 이니셜라이저를 통해 감독 및 가수 이름을 커스텀하고 있다. library라는 배열에 두 클래스 인스턴스를 원소로 줄 때 이 클래스가 모두 MediaItem을 공통적으로 부모 클래스로 가진다는 점에서 자동으로 타입이 MediaItem으로 추론된다. 그렇기 때문에 이 배열에 값을 꺼내게 되면 MediaItem 타입이 된다.

타입 체크

is 오퍼레이터는 이 인스턴스가 특정 타입의 하위 클래스인지 확인한다. 맞다면 true를, 아니라면 false를 리턴한다.

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

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

for-in 반복문에서 꺼낸 각 item은 이때 MediaItem이고 is를 통해 Movie인지 Song인지 확인할 수 있다.

다운캐스팅

as 오퍼레이터로 인스턴스 타입을 다른 하위클래스 타입으로 다운캐스팅할 수 있다. 다운캐스팅이 실패할 수도 있기 때문에 옵셔널 as? / 강제 언래핑 기능 as! 두 개로 나뉜다.

다운캐스팅이 실패할 수도 있다면 옵셔널 값을 리턴하는 as?를 사용하자. 다운캐스팅이 실패하면 널 값을 리턴한다. 다운캐스팅 성공이 보장될 때에만 강제 언래핑 as!를 사용하자. 이때 다운캐스팅이 실패하면 런타임 에러가 난다.

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: 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

library 배열을 for-in 반복문으로 하나씩 원소를 꺼내 확인할 때 나오는 특정 인스턴스가 어떤 클래스인지 모르기 때문에 as?를 사용하자. Movie 클래스 인스턴스일 때, Song 클래스 인스턴스일 때 분기에 따라 코드 블록을 실행할 수 있다. if let movie = item as? Movie 옵셔널 바인딩이 특징.

특정 인스턴스를 캐스팅하는 건 인스턴스 타입을 바꾸거나 값을 바꾸는 게 아니다. 캐스팅을 시도한 특정 타입으로 이 인스턴스를 접근하는 방법이다. (그렇기 때문에 하위 클래스로 캐스팅하지 않으면 실패할 수 있다)

Any, AnyObject 타입 캐스팅

타입이 구체적으로 정해지지 않은 오브젝트에 대해 캐스팅하는 두 가지 방법이 있다. 모든 타입의 인스턴스를 가리킬 수 있는 Any, 모든 클래스 타입의 인스턴스를 가리킬 수 있는 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)" })

things 배열은 정수, 실수, 문자열, 튜플 등 모든 타입의 값을 담기 때문에 Any 타입 배열로 선언되었다. 컬렉션 내 아이템이 어떤 타입인지 구체적으로 확인하려면 앞의 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 타입으로 모든 타입의 데이터를 아이템으로 입력받아도 이를 하나씩 확인해 타입별로 코드 블록을 따로 실행하려면 위와 같이 스위치 케이스로 코드 블록을 실행할 필요가 있다.

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

옵셔널 정수 optionalNumberthings에 넣을 때 as 오퍼레이터로 Any로 캐스팅해야 한다.

profile
JUST DO IT

0개의 댓글