티스토리 뷰

반응형

이번 글에서는 Swift 공식 문서의 18번째 단원인 Type Casting을 읽고 정리한 글을 쓰려고 한다.

 

Apple Swift 공식 문서 18단원 - Type Casting

Type Casting

타입 캐스팅은 인스턴스의 타입을 확인하거나 어떠한 클래스의 인스턴스를 해당 클래스 계층 구조의 슈퍼 클래스나 서브 클래스로 취급하는 방법이다. Swift의 타입 캐스팅은 is, as 연산자로 구현된다. 이 두 연산자는 타입을 확인하거나 다른 타입으로 캐스트 하는 것을 간단한 표현으로 제공한다.


타입 캐스팅을 사용하여 프로토콜을 준수하는지 확인할 수도 있다.


Defining a Class Hierarchy for Type Casting

클래스와 서브 클래스의 계층 구조와 함께 타입 캐스팅을 사용하여 특정 클래스의 인스턴스의 타입을 확인하고 해당 인스턴스를 동일한 계층 내의 다른 클래스로 캐스팅할 수 있다.

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)
    }
}

위의 코드에는 3개의 클래스가 정의되어있다. MediaItem이라는 슈퍼 클래스와 이를 상속받는 Movie, Song 클래스가 정의되어있는데 이를 사용하여 타입 캐스팅을 사용해보려고한다.

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]

library에는 Movie 클래스와 Song 클래스의 인스턴스들이 들어있다. 이렇게 되면 library의 타입은 Array<MediaItem>이 된다. 이때 만약 Array의 요소들을 Movie, Song 타입으로 사용하고 싶다면 타입을 확인하거나 해당 타입으로 다운 캐스트 하여 사용해야 한다. 이러한 방법은 다음 섹션에서 살펴보자.


Checking Type

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 구문에서 library라는 Array의 모든 항목에 접근한다. 하지만 library는 Array<MediaItem>타입이기 때문에 MediaItem 타입이 반환되게 되는데 이때 is 연산자를 사용하여 해당 항목이 MediaItem의 서브클래스인 Movie, Song 클래스인지 확인할 수 있다. 이 때 각자 클래스에 맞도록 조건문이 실행되게 된다.


Downcasting

특정 클래스 타입의 상수와 변수는 서브 클래스의 인스턴스를 참조할 수 있다. 만약 이러한 경우가 만족한다고 생각하는 경우 as?, as! 를 사용하여 서브 클래스 타입으로 다운 캐스트 할 수 있다.

 

이러한 경우 다운 캐스팅에 실패할 수 있기 때문에 옵셔널 값을 사용할 수 있다. as? 연산자는 다운 캐스트 하려는 타입의 옵셔널 값을 반환하고 as! 연산자는 다운 캐스트를 시도하고 반환된 옵셔널 결과를 강제로 언래핑한다.

 

다운 캐스트의 성공이 불확실하다면 as?를 사용하는 것이 좋다. 이 경우 만약 캐스팅에 실패하면 nil이 반환되는데 이러한 점 때문에 다운 캐스트에 성공했는지에 대한 여부를 확인할 수 있다.

 

만약 다운 캐스트가 확실하게 성공할 것이라고 생각하는 경우엔 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

아까 만든 세 가지 클래스를 다시 사용해보자. 위의 코드는 아까 만들어둔 Array<MediaType>의 각 항목들 즉 MediaType들을 다운 캐스트 해서 사용하는 방법이다. 이 경우 각각의 항목에 대하여 MediaType클래스인지 Movie 클래스인지 Song 클래스인지 확신할 수 없기 때문에 as?를 사용하는 게 좋다.

 

만약 위의 코드에서 item에 Movie 클래스 타입을 포함하는 타입이 있다면 movie 상수에 Movie 클래스 타입이 할당된다. 이러한 것을 다운 캐스트라고 한다. 캐스팅은 실제로 인스턴스를 수정하거나 값을 변경하지는 않는다. 인스턴스는 동일하게 유지되지만 캐스트 된 타입의 인스턴스로 처리된다.


Type Casting for Any and AnyObject

Swift는 타입이 정해지지 않은 두 개의 특별한 타입을 제공한다.

  • 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 Array에는 다양한 타입의 값들이 추가되었다. 이는 things의 타입이 Array<Any>이기 때문인데 이러한 경우 모든 항목에 대하여 특정 타입을 찾고 싶다면 switch문의 case에서 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

위의 코드처럼 things의 모든 항목에 접근할 때마다 switch 문으로 타입 캐스트를 진행하여 성공한 케이스의 코드를 실행한다.

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

Any 타입은 옵셔널 타입을 포함한 모든 타입의 값을 나타낸다. Swift는 Any 타입의 값으로 특정 타입의 옵셔널 값을 사용하려는 경우에 경고를 발생한다. 위의 코드처럼 Int? 형을 Any 타입으로 사용하면 오류를 발생시킨다는 것이다. 이렇게 옵셔널 값을 Any값으로 사용해야 하는 경우 as 연산자를 사용하여 특정 타입의 옵셔널 값을 Any로 캐스팅해서 사용할 수 있다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함