Swift 06 : 타입캐스팅 (Type Casting)

타입캐스팅이란

스위프트에서 타입캐스팅이란 변수나 상수의 데이터 타입을 확인하거나
다른 데이터 타입으로 변환하는 것을 말한다.
타입을 변환 한다는 건 쉽게말하면 데이터의 “모양"을 바꾸는 것..
어떤 데이터를 다른 데이터처럼 보이게 만드는 것이다.

타입 캐스팅에는 is,as,as!,as? 총 4가지 연산자가 있는데

is는 타입 검사(Type Checking)
as는 업캐스팅(Upcasting)
as!는 강제 다운캐스팅(Force Downcasting)
as?는 조건부 다운캐스팅(Optional Downcasting)

이라고 할 수 있다

문법은 다음과 같이 사용 :

변수명 is 타입
변수명 as 타입
변수명 as! 타입
변수명 as? 타입

아래와 같이

class Person {
  func brushHair() {
        print("머리를 빗습니다")
    }
}

class Gildong: Person {
	let age = 30
	let gender = "male"
  
	override func brushHair() {
		print("빗을 머리가 없습니다")
	}
    
	func hobby() {
    print("누워서 과자를 먹습니다")
	}
}

let unknownPerson = Gildong()

부모 클래스 Person과 Person을 상속하는 자식 클래스 Gildong이 있다고 치고
각각 예제와 함께 설명을 정리해 보았다


is : 객체가 특정 타입인지 여부를 검사하여 Boolean값, 즉 true 또는 false로 반환한다
unknownPerson is Person // true
unknownPerson is Gildong // true

unknownPersonGildong 클래스의 인스턴스이고
unknownPerson의 타입은 Gildong이지만, Gildong 클래스는 Person 클래스를 상속하고 있으므로
unknownPersonPerson 클래스의 인스턴스이기도 하다 그래서 Person타입과 Gildong타입에 둘 다 true를 반환한다

if unknownPerson.gender is Int {
    print("gender가 Int타입이라고?")
} else {
    print("역시.. gender는 Int가 아니였다능") // "역시.. gender는 Int가 아니였다능"
}

unknownPersongender프로퍼티는 문자열이므로 Int타입이 아니다.
따라서 else를 타서 “역시.. gender는 Int가 아니였다능"이 출력 되었다


as : 하위 클래스 객체를 상위 클래스로 변환하는 작업
let unknownPerson2 = unknownPerson as Person

unknownPerson은 위에서 만든 Gildong이란 클래스의 인스턴스이다.
unknownPersonas연산자를 사용하여 Person으로 업캐스팅 해주어 unknownPerson2에 집어넣었다

unknownPerson2.brushHair() // 빗을 머리가 없습니다
unknownPerson2.hobby() // Value of type 'Person' has no member 'hobby'

여기서 의문점..
unknownPerson2brushHair()는 Person의 brushHair(), “머리를 빗습니다"가 출력되는 것이 아닌
여전히 GildongbrushHair(), “빗을 머리가 없습니다"로 출력이 되는 걸까..?

이건 Swift의 메소드 호출이 실제 객체 타입을 기반으로 동적 처리되기 떄문이다
위에서 먼저unknownPersonGildong 클래스의 인스턴스로서 생성이 되었고
그런 다음 unknownPerson을 업캐스팅해서 unknownPerson2에 대입했다
unknownPerson2는 Person 타입의 변수이지만 여전히 Gildong 객체를 가리킨다.
따라서 오버라이딩한 메소드를 호출하면 해당 오버라이딩된 메소드가 실행된다
그래서 Gildong을 기반으로 호출이 되는 것이다.

반면 unknownPerson2hobby()라는 메소드를 실행시키려고 하면
오버라이딩되지 않아 Gildong 클래스에만 정의되어 있으므로 호출할 수 없는 것이다.

이런 업캐스팅은 언제 유용하게 사용할수 있을까?

let unknownPerson3 = Person()

let list = [unknownPerson, unknownPerson3]
list[0].hobby() // Value of type 'Person' has no member 'hobby'

만약 위에서 만든 Gildong타입의 unknownPerson
Person타입의 unknownPerson2를 하나의 리스트에 담으려고 한다고 해보자
원래 타입이 다르면 오류가 나야 할 텐데 잘 담긴다
그 이유는 swift에서 자동으로 Person으로 업캐스팅 해주었기 때문이다.
만약 둘이 생판 다른 타입이었다면 에러가 날것이다.


as! : 강제로 타입을 변환하려고 시도하고, 만약 변환이 실패할 경우 런타임 오류가 발생한다
let convertedAge = unknownPerson.age as! Int
print(convertedAge) // 30
let convertedGender = unknownPerson.gender as! Int // Abort() called 에러 발생
print(convertedGender)

as! 연산자를 이용해서 unknownPersonagegender 강제로 각각 Int로 형 변환하려고 한다

age는 30이 들어갔기 때문에 정상적으로 Int로 형 변환되어 문제없이 출력되었으나
gender는 문자열 “male” 이 들어갔기 때문에 Int로 형 변환할 수 없고 바로 런타임 에러가 발생하게 된다

이와 같이 as!연산자는 만약 변환할 수 없는 경우에는 바로 뻗어버리기 때문에
옵셔널 강제 언래핑처럼 반드시 변환 가능 여부가 확실한 경우에만 사용해야 하며
되도록 사용을 지양하는 것이 좋다


as? : 타입 변환을 시도하고, 변환이 실패할 경우에는 nil을 반환한다
let convertedGender = unknownPerson.gender as? Int
print(convertedGender) // nil

if let convertedAge = unknownPerson.age as? Int {
    print("값: \(convertedAge), 변환 성공~") // "값: 30, 변환 성공~"
} else {
    print("age는 Int로 다운캐스팅 못한다!!")
}

if let convertedGender = unknownPerson.gender as? Int {
    print("값: \(convertedGender), 변환 성공~")
} else {
    print("gender는 Int로 다운캐스팅 못한다!!") // "gender는 Int로 다운캐스팅 못한다!!"
}

위에서 사용한 as! 연산자 대신에 as? 연산자를 사용함으로써 조건부로 형 변환을 시도할 수 있다. 실패한다 해도 nil을 반환하기 때문에 최소한 에러는 방지되고 안전하게 다운 캐스팅을 시도하는 방법인 것이다