다른 프로그래밍 언어(C, Java 등등..) 에서는 암묵적 타입 캐스팅
을 지원한다.
하지만 스위프트는 암묵적 타입 캐스팅을 지원하지 않을 뿐더러, 다른 타입간의 변환을 매우 엄격하게 제한하기 때문에 스위프트에서 타입캐스팅
은 조금 다른 의미로 쓰인다.
우리가 다른 프로그래밍에서 쓰는 명시적 형변환은 스위프트에서는 이니셜라이저(String(abc)
)로 구혀하기때문에 이를 타입캐스팅이라고 하기에는 무리가 있다.
스위프트의 타입캐스팅은 인스턴스의 타입을 확인하거나, 자신을 다른 타입의 인스턴스인 양 행세할 수 있도록 해준다.
class Coffee{
let name: String
let shot: Int
var description: String{
return "\(shot) shot(s) \(name)"
}
init(shot: Int){
self.shot = shot
self.name = "coffee"
}
}
class Latte: Coffee{
var flavor: String
override var description: String{
return "\(shot) shots \(flavor) latte"
}
init(flavor: String, shot: Int){
self.flavor = flavor
super.init(shot: shot)
}
}
class Americano: Coffee{
let iced: Bool
override var description: String{
return "\(shot) shot(s) \(iced ? "iced" : "hot") americano"
}
init(shot: Int, iced: Bool){
self.iced = iced
super.init(shot: shot)
}
}
상위클래스의 coffee 인스턴스는 Americano 클래스와 Latte 클래스의 인스턴스라고 할 수는 없지만 반대의 경우엔는 가능하다. 이는 is
연산자를 통해 확인 가능하다.
let coffee: Coffee = Coffee(shot: 1)
let myCoffee: Americano = Americano(shot: 2, iced: false)
let yourCoffee: Latte = Latte(flavor: "green", shot: 3)
coffee is Coffee // true
coffee is Americano // false
coffee is Latte // false
myCoffee is Coffee // true
yourCoffee is Coffee // true
myCoffee is Latte // false
yourCoffee is Latte // true
메타 타입 타입
은 타입의 타입을 뜻한다. 클래스 타입, 구조체 타입, 열거형 타입, 프로토콜 타입 등의 타입의 타입이다. 즉, 타입 자체가 하나의 타입으로 또 표현할 수 있다는 것이다.
타입의 이름 뒤에 .Type
를 붙이면, 이는 메타 타입을 나타낸다.
protocol SomeProtocol() {}
class SomeClass: SomeProtocol {}
let intType: Int.Type = Int.self
let stringType: String.Type = String.self
let classType: SomeClass.Type = SomeClass.self
let protocolType: SomeProtocol.Protocol = someProtocol.self
var someType: Any.Type
someType = intType
someType = stringType
type(of: )
함수를 사용한다. 그래서 type(of: instance).self
라고 표현하면 someInstance의 타입을 값으로 표현한 값을 반환한다.type(of: coffee) == Coffee.self // true
type(of: coffee) == Americano.self // false
type(of: coffee) == Latte.self // false
type(of: coffee) == Americano.self // false
type(of: myCoffee) == Americano.self // true
type(of: yourCoffee) == Americano.self // false
type(of: coffee) == Latte.self // false
type(of: myCoffee) == Latte.self // false
type(of: yourCoffee) == Latte.self // true
type(of: myCoffee) == Coffee.self // false
어떤 클래스 타입의 변수 또는 상수가 정말로 해당 클래스의 인스턴스를 참조하지 않을 수도 있다. 예를 들어 Latte 클래스의 인스턴스가 Coffee 클래스의 인스턴스인양 Coffee 행세를 할 수 있다.
let actingConstant: Coffee = Latte(flavor: "vanila", shot: 2)
print(actingConstant.description) // 2 shot(s) vanila latte
타입캐스트 연산자에는 as?
와 as!
두 가지가 있다. as?
같은 경우에는 다운 캐스팅시 실패할 경우 nil
을 반환하며, 성공할 경우 옵셔널로 반환해준다. as!
같은 경우에는 실패할 경우 런타임에러가 발생하며, 성공할 경우 옵셔널이 아닌 인스턴스를 반환해준다.
let actingOne: Americano = coffee as? Americano // 실패
let castedAmericano: Americano = coffee as! Americano // 실패
actingOne의 경우 만약 coffee가 가리키는 인스턴스가 Americano 타입의 인스턴스라면 actingOne 임시변수에 할당하라
이고,
castedAmericano의 경우, 만약 coffee가 가리키는 인스턴스 Americano 타입의 인스턴스던지 아니던지CastedAmericano 임시변수에 할당하라
라는 뜻이다.
만약, 컴파일러가 다운캐스팅을 확신할 수 있는 경우에는 as!나 as?대신 as를 사용할 수 있다. 항상 성공하는 것을 아는 경우는 캐스팅하려는 타입이 같은 타입이거나, 부모클래스의 타입이라는 것을 알 때이다.
let castedCoffee: Coffee = yourCoffee as Coffee
스위프트에는 특정 타입을 지정하지않고, 여러 타입의 값을 할당할 수 있는 Any와 AnyObject 타입이 존재한다.
Any타입의 경우에는 구조체,열거형,클래스 타입의 값을 할당할 수 있으며, AnyObject는 클래스의 인스턴스만 할당할 수 있다.
func castTypeAppropriate(item: AnyObject){
if let castedItem: Latte = item as? Latte{
print(castedItem.description)
}else if let castedItem: Americano = item as? Americano{
print(castedItem.description)
}else if let castedItem: Coffee = item as? Coffee{
print(castedItem.description)
}else{
print("Unknown Type")
}
}
castTypeAppropriate(item: coffee)
castTypeAppropriate(item: myCoffee)
castTypeAppropriate(item: yourCoffee)
Any 타입은 모든 값 타입을 표현할 수 있기때문에 옵셔널 타입도 표현할 수 있다. 그런데도 Any 타입에 옵셔널 타입이 들어간다면 스위프트 컴파일러는 경고를 표시한다. 의도적으로 옵셔널 값을 Any 타입의 값으로 사용하고자 한다면 zs
연산자를 통해 타입캐스팅을 해주어야 한다.
let optionalValue: Int? = 100
print(optionalValue) // 컴파일러 경고 발생
print(optionalValue as Any) // 경고 없음