[Swift] 타입캐스팅

나는 사과·2021년 3월 9일
0

TIL

목록 보기
6/17

타입캐스팅은 쉽게 말하면 타입을 변환하는 것이다.
먼저 Swift는 데이터 타입 안전을 위해서 다른 타입과의 값 교환을 제한한다. 그리고 다른 언어(C, Python 등..)에서 지원하는 암시적 타입 변환도 지원하지 않는다. 그럼 다 제한된다고 하는데 도대체 Swift에서의 타입캐스팅은 무엇일까?

타입캐스팅

Swift에서 타입캐스팅은 인스턴스의 타입을 확인하거나 다른 타입의 인스턴스인 것처럼 행세하는 방법으로 사용된다. 타입캐스팅을 사용하기 위해서는 isas연산자를 사용하면 된다. 이 연사자들을 이용해서 값의 타입을 확인하거나 다른 타입으로 전환, 프로토콜을 준수하는지도 확인할 수 있다.

  • 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 클래스를 BusTrain클래스에서 상속 받았다. 그럼 두 클래스가 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번과 같은데 방식에서 강제 변환을 하는 부분에 있어서 실패하면 런타임 오류가 발생하게 된다.

0개의 댓글