타입 캐스팅(type casting)
은 인스턴스의 타입을 확인하거나 클래스 계층의 다른 부모 클래스/자식 클래스로 취급하는 방법이다.
Swift의 타입 캐스팅은 is
나 as
연산자로 구현된다. 이 두 연산자는 값의 타입을 확인하거나 다른 타입으로 바꾸는 간단하고 표현적인 방법을 제공한다.
타입이 프로토콜을 따르는지 확인하는 데에도 타입 캐스팅을 사용할 수 있다.
특정 클래스 인스턴스의 타입을 확인하거나 같은 계층 내 다른 클래스로 인스턴스를 변환하기 위해 클래스의 계층에서 타입 캐스팅을 사용할 수 있다. 아래 세 가지 코드는 클래스 계층과 이 클래스 인스턴스를 포함하는 배열을 정의한다.
첫 번째로 MediaItem
기반 클래스를 정의한다. String
타입의 name
프로퍼티와 init(name:)
이니셜라이저를 선언한다.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
다음으로 MediaItem
의 두 가지 자식 클래스를 정의한다. Movie
는 영화에 대한 추가적인 정보를 캡슐화한다. 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의 타입 체커는 Movie
와 Song
이 같은 부모 클래스 MediaItem
을 갖는다고 추론할 수 있다. 따라서 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
배열은 Movie
와 Song
인스턴스를 저장한다. 하지만 배열의 내용을 순회할 때 반환되는 아이템의 타입은 MediaItem
이 된다. 때문에 타입을 확인하고 서로 다른 타입으로 다운캐스트(downcast) 해야만 한다.
타입 확인 연산자 is
를 사용해 인스턴스가 특정 자식 클래스 타입인지 확인한다. 타입 확인 연산자는 그 자식 클래스의 인스턴스면 true
를, 아니면 false
를 반환한다.
아래 예시는 Movie
인스턴스와 Song
인스턴스의 개수를 세는 movieCount
와 songCount
두 변수를 정의한다.
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"
특정 클래스 타입의 상수나 변수는 자식 클래스의 인스턴스를 참조하고 있을 수 있다. 타입 변환 연산자 as?
와 as!
를 사용해 자식 클래스로 다운캐스트 할 수 있다.
다운캐스팅은 실패할 수 있기 때문에 타입 변환 연산자는 두 가지 형태를 지닌다. 조건적 형태 as?
는 다운캐스팅을 시도한 타입의 옵셔널 값을 반환한다. 강제적 형태 as!
는 다운캐스트를 시도하고 결과를 강제 언래핑 한다.
다운캐스트가 성공한다는 보장이 없을 때 as?
를 사용한다. 이 형태의 연산자는 항상 옵셔널 값을 반환하고, 다운캐스트가 불가능하면 값은 nil
이 된다. 이는 다운캐스트가 성공했는지 확인할 수 있게 해 준다.
다운캐스트가 항상 성공한다는 확신이 있을 때 as!
를 사용한다. 이 형태의 연산자는 다운 캐스트가 실패하면 런타임 에러를 발생시킨다.
아래 예시는 library
의 각 MediaItem
을 순회하고 적절한 문자열로 출력한다. 이를 위해 Movie
나 Song
으로 접근해야만 한다. director
와 artist
프로퍼티에 접근할 필요성이 생긴다.
배열의 각 아이템이 Movie
인지 Song
인지 확신할 수 없기 때문에 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
캐스팅은 인스턴스를 수정하거나 값을 바꿀 수 없다. 기저의 인스턴스는 동일하게 유지된다. 변환을 시도한 타입의 인스턴스로써 간단히 취급하고 접근하는 것이다.
Swift는 두 가지의 특별한 비지정 타입을 제공한다.
Any
는 함수 타입을 포함하여 모든 타입의 인스턴스를 대표한다.AnyObject
는 모든 클래스 타입을 대표한다.꼭 필요할 때만 Any
와 AnyObject
를 사용한다. 코드에서 작동할 것이라 예상하는 타입을 지정하는 것이 항상 좋다.
다음은 여러 타입이 섞여 있는 작업을 하기 위해 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
값에 정말 옵셔널을 사용하길 원한다면 옵셔널을Any
로 명시적으로 변환하기 위해as
연산자를 사용할 수 있다.let optionalNumber: Int? = 3 things.append(optionalNumber) // Warning things.append(optionalNumber as Any) // No warning