타입캐스팅은 쉽게 말하면 타입을 변환하는 것이다.
먼저 Swift는 데이터 타입 안전을 위해서 다른 타입과의 값 교환을 제한한다. 그리고 다른 언어(C, Python 등..)에서 지원하는 암시적 타입 변환도 지원하지 않는다. 그럼 다 제한된다고 하는데 도대체 Swift에서의 타입캐스팅은 무엇일까?
Swift에서 타입캐스팅은 인스턴스의 타입을 확인하거나 다른 타입의 인스턴스인 것처럼 행세하는 방법으로 사용된다. 타입캐스팅을 사용하기 위해서는 is
와 as
연산자를 사용하면 된다. 이 연사자들을 이용해서 값의 타입을 확인하거나 다른 타입으로 전환, 프로토콜을 준수하는지도 확인할 수 있다.
is
연산자 : 타입 확인 연산자as
연산자 : 다른 타입으로 전환class Vehicle {
let name: String = ""
func horn() {
print("무슨 소리를 낼까~?")
}
}
class Bus: Vehicle {
var size: Int = 0
override func horn() {
print("뛰뛰빵빵~")
}
}
class Train: Vehicle {
var line: Int = 1
override func horn() {
print("칙칙폭폭~")
}
}
위 코드에서 Vehicle
클래스를 Bus
와 Train
클래스에서 상속 받았다. 그럼 두 클래스가 Vehicle
클래스인 척을 할 수 있다. 왜냐하면 자식클래스는 부모클래스의 특성을 모두 갖기 때문이다. 여기서 데이터 타입 확인 연산자인 is
를 사용할 수 있다.
is
연산자를 사용해서 인스턴스가 어떤 클래스(또는 자식클래스)의 인스턴스인지 타입을 확인할 수 있다. 인스턴스가 해당 클래스의 인스턴스거나 자식클래스의 인스턴스일 경우 true
를 반환하고 그렇지 않으면 false
를 반환한다.
위의 예시에 이어서..
let cityVehicle: Vehicle = Vehicle()
let cityBus: Bus = Bus()
print(cityVehicle is Vehicle) // true
print(cityVehicle is Bus) // false
print(cityVehicle is Train) // false
print(cityBus is Vehicle) // true
print(cityBus is Bus) // true
print(cityBus is Train) // false
자식클래스의 인스턴스는 부모클래스의 타입도 포함하고 있다는 것을 위의 예제에서 is
연산자를 사용해서 알 수 있다.
데이터의 타입을 확인하는 방법의 또 다른 방법으로 메타 타입 타입은 타입의 타입
을 뜻한다. .Type
을 클래스, 구조체, 열거형의 이름 뒤에 붙이거나 프로토콜 이름 뒤에 .Protocol
을 붙이면 된다.
let intType: Int.Type = Int.self // .self는 자신을 표현하는 값을 반환
let stringType: String.Type = String.self // .self 대신 type(of: )를 사용 가능
print(intType) // Int
print(stringType) // String
let someVehicle: Vehicle = Bus()
someVehicle.horn() // 뛰뛰빵빵~
위의 예제같은 경우가 있다.
someVehicle
상수는 Vehicle
인스턴스를 참조하도록 선언했지만 Vehicle
인 척하는 Bus
타입의 인스턴스를 참조하고 있다.
이 경우 Bus
타입에 정의된 프로퍼티, 메서드를 사용하려면 Bus
타입으로 변환해주어야 하는데 이때를 다운캐스팅이라고 한다.
-> 부모클래스의 타입을 자식클래스의 타입으로 캐스팅하기 때문이다.
이때는 as
연산자를 사용하면 된다. as
연산자는 다운캐스팅을 할 때 실패하는 경우가 있어서 ?
, !
을 붙여주어야 한다. 확실하게 다운캐스팅이 성공한다 생각할 경우에만 강제 연산자 !
을 붙여서 사용하고 그렇지 않은 경우에는 조건부 연산자 ?
를 사용해서 실패할 경우를 생각해야 한다.
물론, 둘 다 사용하지 않는 경우도 있다. 그럴때에는 그냥 as
만 사용하면 된다. 하지만 이 경우는 타입이 같거나 부모클래스 타입이라는 것을 아는 경우에만 사용하도록 해야한다.
let oneVehicle: Vehicle = Vehicle()
// 1번
if let downOne: Bus = oneVehicle as? Bus {
print("버스다!!")
} else {
oneVehicle.horn()
}
// 무슨 소리를 낼까~?
// 2번
if let downTwo: Vehicle = oneVehicle as? Vehicle {
print("어떤 종류지..?")
} else {
oneVehicle.horn()
}
// 어떤 종류지..?
// 3번
let downThree: Vehicle = oneVehicle as! Vehicle // 다운캐스팅 성공!
let downFour: Bus = oneVehicle as! Vehicle // 다운캐스팅 실패! -> 런타임 오류 발생!!
1번 코드는 oneVehicle
가 참조하는 인스턴스는 Vehicle
타입의 인스턴스인데 Bus
타입의 인스턴스일 경우 할당하라는 의미이기 때문에 false
가 되어서 else
부분이 실행이 된다.
반대로 2번 코드도 똑같은 의미인데 여기서는 true
가 되어서 if
부분의 코드가 실행이 된다.
3번도 1번, 2번과 같은데 방식에서 강제 변환을 하는 부분에 있어서 실패하면 런타임 오류가 발생하게 된다.