참고사이트:
English: The swift programming language
타입 캐스팅(Type Casting)은 인스턴스의 타입(type)을 확인하거나 다른 슈퍼클래스 또는 서브 클래스로 변환하는 방법이다.
Swift에서 타입 캐스팅은 2가지 연산자 is
와 as
를 사용한다.
클래스의 계층구조(hierarchy), 서브 클래스에서 타입캐스팅을 사용하여 특정 클래스 인스턴스의 타입을 확인하고, 같은 계층구조에서 인스턴스의 타입을 다른 클래스 타입으로로 변환할 수 있다. 아래에 3개의 코드 블록은 타입 캐스팅의 설명을 위해 클래스들의 계층구조와 계층구조에 속한 클래스의 인스턴스를 담은 배열을 구현한다.
아래 첫 번째 코드 블록은 based class(계층구조의 최상위 클래스)
인 MediaItem
을 정의한다. 이 클래스는 디지털 미디어 라이브러리에 표시되는 모든 종류를 나타낸다. 또한 String 타입의 name
프로퍼티를 가지며 이니셜 라이저를 포함한다.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
다음 코드 블록은 MediaItem
클래스를 상속하는 두 개의 서브 클래스(Movie
, Song
)을 정의한다. 두 클래스 모두 디지털 라이브러리에 포함되는 종류로서, director
, artist
와 같이 추가적인 정보를 캡슐화(encapsulates)
한다.
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
배열의 타입은 array literal의 내용으로 초기화 됨으로써 이를 기반으로 추론된다. 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
타입의 인스턴스다. 그러나 배열의 각 항목을 순회하는 경우 각각의 element들은 MediaItem
타입으로 접근이 된다. 그러므로 MediaItem
타입이 아닌 원래의 타입인 Movie
, Song
타입으로 사용하려면, 타입 확인(Checking Type)
하거나 다운 캐스팅(Downcasting)
해야 한다. 이는 아래에 자세히 설명한다.
library
배열의 element 접근시 모습
인스턴스가 특정 타입의 서브 클래스인지 확인하기 위해 타입 확인 연산자인 is
를 사용한다. 타입 확인 연산자는 인스턴스가 특정 타입의 서브 클래스 이면 true
를 반환하며 아닌 경우에는 false
를 반환한다.
아래에 is
연산자의 사용방법을 확인하기 위해, 두 개의 변수 movieCount
와 songCount
를 선언하여 library
배열 안에 있는 Movie
와 Song
인스턴스의 수를 카운트한다.
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?
or as!
)를 사용하여 특정 타입의 서브 클래스로 downcast
해야 한다.
타입 캐스트 연산자가 두 가지 다른 형태인 이유는 실패
할 수 있기 때문이다. as?
형태는 다운 캐스트 하려는 타입의 옵셔널 타입을 반환한다. as!
형태는 다운캐스트와 강제적 언래핑(force-unwrap)을 모두 수행한다.
그렇기 때문에 다운캐스트의 성공이 보장되는 경우에 as!
를 사용하며, 확신이 없는 경우에는 as?
를 사용한다. as!
에 사용되는 !
는 옵셔널 타입의 forced unwrapping
과 마찬가지로 올바른 클래스 타입으로 다운캐스트 하지 않는 경우 런타임 에러를 트리거 한다.
as?
연산자는 올바른 클래스 타입으로 다운캐스트 한 경우 옵셔널 타입
을 반환하며, 아닌 경우에는 nil
을 반환한다.
아래 예제는 library
배열의 element를 순회하면서 각 항목에 대해 설명을 한다. 그러므로 MediaItem
타입이 아닌 Mobie
, Song
타입의 클래스 프로퍼티에 접근해야 한다. 이는 위에서 소개한 다운캐스트 연산자(as?
, as!
)를 사용하면 된다.
위에서 선언한 library
배열의 인스턴스는 Movie
나 Song
타입 둘 중 하나이나, 특정 타입이라고 확신할 수 없다 그러므로 for loop
에서 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
for loop
를 순회하는 도중, 만약 as?
연산자를 통해 현재 element의 인스턴스가 Movie
타입인 경우 첫 번째 조건문에서 nil
이 아닌 옵셔널 타입의 Movie?
를 반환하며 이는 optional binding
에 의해 접근된다. 그러므로 Movie
클래스의 프로퍼티인 director
에 접근하여 각 항목을 출력할 수 있다. 이는 Song
타입의 인스턴스인 경우도 마찬가지다.
NOTE
캐스팅(Casting)은 실제로 인스턴스나 타입이나 그 값을 바꾸지 않는다. 즉 캐스팅에 사용되는 기본 인스턴스는 그대로 유지된다.
Swift는 두가지 특이한 타입을 제공한다.
Any
: 함수 타입과 모든 인스턴스의 타입을 나타낼 수 있음
AnyObject
: 모든 클래스 타입을 나타낼 수 있음
Any
와 AnyObject
는 필요한 경우에만 사용하며 이보다 특정 타입을 명시하는 것이 항상 더 좋다고 한다.(the swift programming language에 기술되어 있음
)
아래 예제는 배열 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 문의 case에서 is
또는 as
패턴을 사용할 수 있다.
아래 예제는 switch 문에서의 사용 예시로, for loop
를 순회하면서 각 element에 대해 case에 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")
}
}
NOTE
Any
타입은 optional 타입을 포함하여 모든 타입의 값을 나타낼 수 있다. 그러나Any
타입이 필요한 곳에 optioanl 값을 사용하면 swift는 waring을 준다. 그러므로 optional 값의 사용이 반드시 필요한 경우,as
연산자를 사용하여Any
타입으로 casting하여 사용하면 된다.
Casting 예시
let optionalNumber: Int? = 3
things.append(optionalNumber) // Warning
things.append(optionalNumber as Any) // No warning