타입 캐스팅 (Type Casting)

값의 런타임 타입을 확인하고, 더 구체적인 타입의 정보를 제공합니다.

*타입 캐스팅(Type casting)*은 인스턴스의 타입을 확인하거나 해당 인스턴스를 자신의 클래스 계층 구조의 다른 상위 클래스나 하위 클래스로 취급하는 방법입니다.

Swift에서 타입 캐스팅은 isas 연산자로 구현합니다. 이 두 연산자는 값의 타입을 확인하거나 값을 다른 타입으로 변환하는 간단하고 효과적인 방법을 제공합니다.

프로토콜 준수 여부 검사 (Checking for Protocol Conformance)에서 설명한대로 타입 캐스팅을 사용하여 타입이 프로토콜을 준수하는지 확인할 수도 있습니다.

타입 캐스팅을 위한 클래스 계층 정의 (Defining a Class Hierarchy for Type Casting)

클래스와 하위 클래스의 계층도와 함께 타입 캐스팅을 사용하여 특정 클래스 인스턴스의 타입을 확인하고 같은 계층도 내에서 다른 클래스로 인스턴스를 변환할 수 있습니다. 아래의 세 코드는 타입 캐스팅의 예시에서 사용하기 위해 클래스의 계층도와 해당 클래스의 인스턴스를 포함하는 배열을 정의합니다.

첫 번째 코드는 MediaItem이라는 새로운 기본 클래스를 정의합니다. 이 클래스는 디지털 미디어 라이브러리에 나타나는 모든 종류의 항목에 대한 기본 기능을 제공합니다. 특히 String 타입의 name 프로퍼티와 init(name:) 이니셜라이저를 선언합니다. (영화와 노래를 포함하여 모든 미디어 항목은 이름을 가지고 있다고 가정합니다.)

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

다음 코드는 MediaItem의 두 하위 클래스를 정의합니다. 첫 번째 햐위 클래스 Movie는 영화나 필름에 대한 추가 정보를 캡슐화합니다. 기본 MediaItem 클래스의 상위에 director 프로퍼티와 해당 이니셜라이저를 추가합니다. 두 번째 하위 클래스 Song은 기본 클래스의 상위에 artist 프로퍼티와 이니셜라이저를 추가합니다:

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라는 배열 상수를 생성합니다. library 배열의 타입은 배열 리터럴의 내용으로 초기화하여 추론됩니다. Swift의 타입 검사기는 MovieSongMediaItem의 상위 클래스를 공통으로 가지고 있으므로, library 배열에 대해 [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")
]
// the type of "library" is inferred to be [MediaItem]

library에 저장된 항목은 여전히 MovieSong 인스턴스 입니다. 그러나 이 배열의 항목을 반복하면, 항목은 MovieSong이 아닌 MediaItem 타입으로 받습니다. 기본 타입으로 작업을 하려면, 아래에 설명된대로 타입을 확인하거나 다른 타입으로 다운 캐스트(downcast) 해야합니다.

타입 검사 (Checking Type)

인스턴스가 특정 하위 클래스 타입인지 확인하기 위해 타입 검사 연산자(type check operator)(is)를 사용합니다. 이 타입 검사 연산자는 인스턴스가 하위 클래스 타입이면 true 아니면 false를 반환합니다.

아래의 예시는 library 배열의 MovieSong 인스턴스의 개수를 나타내는 두 변수 movieCountsongCount를 정의합니다:

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"

이 예시는 library 배열의 모든 항목을 반복합니다. 반복문 실행 시에 for-in 루프는 배열에서 다음 MediaItemitem 상수에 설정합니다.

item is Movie는 현재 MediaItemMovie 인스턴스이면 true를 반환하고 아니면 false를 반환합니다. 유사하게 item is Song은 항목이 Song 인스턴스인지 확인합니다. for-in 루프의 마지막에 movieCountsongCount의 값은 MediaItem 인스턴스에서 각 타입을 포함하는 개수를 나타냅니다.

다운 캐스팅 (Downcasting)

특정 클래스 타입의 상수나 변수는 하위 클래스의 인스턴스를 참조할 수 있습니다. 이것이 필요하다고 생각하는 경우, 타입 캐스트 연산자(type cast operator)(as? 또는 as!)를 사용하여 하위 클래스 타입으로 *다운 캐스트(downcast)*할 수 있습니다.

다운 캐스팅은 실패할 수 있으므로, 타입 캐스트 연산자는 두 가지 다른 형태로 제공됩니다. 조건부 형식 as?은 다운 캐스트를 하려고 할 때 타입의 옵셔널 값을 반환합니다. 강제 형식 as!은 다운 캐스트를 시도하고 단일 복합 동작으로 강제 언래핑합니다.

다운 캐스트가 성공할지 확신이 없을 때, 조건부 형식의 타입 캐스트 연산자(as?)를 사용합니다. 이 연산자의 형식은 항상 옵셔널 값을 반환하고, 다운 캐스트가 불가능하면 nil을 반환합니다. 이것은 다운 캐스트의 성공여부를 확인하는 용도로도 사용 가능합니다.

다운 캐스트가 항상 성공할 것이라는 확신이 있을 때만, 강제 형식의 타입 캐스트 연산자(as!)를 사용해야 합니다. 이 연산자의 형식은 유효하지 않은 클래스 타입으로 다운 캐스트를 시도하면 런타임 오류가 발생합니다.

아래의 예시는 library의 각 MediaItem을 반복하고 각 항목에 대한 적절한 설명을 출력합니다. 이렇게 하려면 MediaItem이 아닌, MovieSong으로 각 항목에 접근해야 합니다. 이렇게 해야 설명에 사용할 MovieSongdirectorartist 프로퍼티에 접근할 수 있도록 하기위해 필요합니다.

이 예시는 배열의 각 항목은 MovieSong일 수 있습니다. 각 항목에 사용할 실제 클래스를 미리 알지 못하므로, 루프를 통해 매번 다운 캐스트를 확인하기 위해 타입 캐스트 연산자(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

이 예시는 Movie로 현재 item을 다운 캐스트 하는 것으로 시작합니다. itemMediaItem 인스턴스 이므로, Movie일 수도 있고; 마찬가지로 Song일 수 있습니다. 또는 기본 MediaItem일 수도 있습니다. 이 불확실성 때문에, 타입 캐스트 연산자의 as? 형식은 하위 클래스 타입으로 다운 캐스트를 시도할 때 옵셔널 값으로 반환합니다. item as? Movie의 결과는 Movie? 타입이거나 "옵셔널 Movie"입니다.

라이브러리 배열에 Song 인스턴스로 적용할 때 Movie로 다운 캐스팅은 실패합니다. 이것을 대응하기 위해 위의 예시는 옵셔널 Movie가 실제 값이 포함되어 있는지 확인하기 위해 옵셔널 바인딩을 사용합니다. (다운 캐스트가 성공 되었는지 확인합니다.) 이 옵셔널 바인딩은 "if let movie = item as? Movie"로 작성되고, 다음과 같이 읽을 수 있습니다:

"Movieitem을 접근하려고 합니다. 성공하면 반환된 옵셔널 Moviemovie라는 새로운 임시 상수에 설정합니다."

다운 캐스팅이 성공하면, movie의 프로퍼티는 director의 이름을 포함하여 Movie 인스턴스에 대해 설명을 출력하는데 사용합니다. 유사한 원칙을 사용하여 Song 인스턴스를 확인하고, 라이브러리에서 Song을 찾을 때마다 artist 이름을 포함하여 적절한 설명을 출력하는데 사용합니다.

Note: 캐스팅은 실제로 인스턴스를 수정하거나 값을 변경하지 않습니다. 기본 인스턴스는 동일하게 유지됩니다; 캐스트된 타입의 인스턴스로 처리하고 접근합니다.

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

Swift는 비특정 타입 작업을 위해 두 가지 특별한 타입을 제공합니다:

  • Any는 함수 타입을 포함하여 모든 타입의 인스턴스를 나타낼 수 있습니다.

  • AnyObject는 모든 클래스 타입의 인스턴스를 나타낼 수 있습니다.

제공하는 동작과 기능이 명시적으로 필요한 경우에만 AnyAnyObject를 사용합니다. 코드에서 작업할 것으로 예상되는 타입에 대해 구체적으로 지정하는 것이 항상 좋습니다.

다음은 함수 타입과 비 클래스 타입을 포함하여 다른 타입에 혼합으로 작업하기 위해 Any를 사용하는 예시입니다. 이 예시는 Any 타입의 값을 저장할 수 있는 things라는 배열을 생성합니다:

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 배열은 두 Int 값, 두 Double 값, 하나의 String 값, 하나의 (Double, Double) 타입의 튜플, "Ghostbusters" 영화, String 값과 다른 String 값을 반환하는 클로저 표현식을 포함합니다.

AnyAnyObject 타입으로만 알려진 상수나 변수의 특정 타입을 알아보려면 switch 구문의 케이스로 isas 패턴을 사용할 수 있습니다. 아래의 예시는 things 배열에 항목을 반복하고 switch 구문으로 각 항목의 타입을 조회합니다. 몇몇의 switch 구문의 케이스는 일치된 값을 지정된 타입의 상수에 바인딩하여 해당 값을 출력할 수 있도록 합니다:

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

Note: Any 타입은 옵셔널 타입을 포함하여 모든 타입의 값을 나타냅니다. Swift는 Any 타입의 값이 기대되는 곳에 옵셔널 값을 사용하면 경고를 줍니다. Any 값으로 옵셔널 값을 사용하는 것이 필요하다면 아래와 같이 as 연산자를 사용하여 명시적으로 옵셔널을 Any로 변환할 수 있습니다.

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

Beta Software:

This documentation contains preliminary information about an API or technology in development. This information is subject to change, and software implemented according to this documentation should be tested with final operating system software.

Learn more about using Apple's beta software.

Last updated

Was this helpful?