[Swift] 타입캐스팅, 다운캐스팅, is, as란?
iOS/Swift

[Swift] 타입캐스팅, 다운캐스팅, is, as란?

Swift의 타입캐스팅에 대해 알아보아요. 😎

먼저 클래스의 상속 개념부터 이해하고 있어야 하는데......

이렇게 Coffee, Latte, Americano라는 클래스가 있습니다. 여기서 우리는 라떼와 아메리카노를 묶어서 커피라고 부르기도 하죠!??!? 

커피라는 개념을 포함하고 있는 라떼는, 여기에서는 커피를 상속받아 라떼가 탄생하게 됩니다! 마찬가지로 커피라는 개념을 포함하고 있는 아메리카노도 생성!

그러면 커피는 부모 Class, 라떼와 아메리카노는 자식 Class가 됨니다. '커피'는 '샷'이라는 항목을 갖고 있고, 이를 상속받은 자식들인 라떼와 아메리카노도 '샷'이라는 항목을 가지고 있슴니다~~ 초간단 상속 끗!

class Coffee {
    let shot: Int
    
    init(shot: Int) {
        self.shot = shot
    }
}

class Latte: Coffee {
    var flavor: String
    
    init(flavor: String, shot: Int) {
        self.flavor = flavor
        super.init(shot: shot)
    }
}

class Americano: Coffee {
    let iced: Bool
    
    init(shot: Int, iced: Bool) {
        self.iced = iced
        super.init(shot: shot)
    }
}

 

그러면 이제 '커피' 클래스의 인스턴스 걍 커피, '라떼' 클래스의 인스턴스인 바닐라라떼와, '아메리카노' 클래스의 인스턴스인 '아이스아메리카노(aka 아아)'를 생성해 보겠읍니다.

let coffee: Coffee = Coffee(shot: 1)
let vanillaLatte: Latte = Latte(flavor: "vanilla", shot: 2)
let icedAmericano: Americano = Americano(shot: 3, iced: true)

원 샷 커피, 투 샷 바닐라라떼, 쓰리 샷 아아를 생성했음! 위에서 말했듯 'shot' 이라는 변수는 Latte 클래스와 Americano 클래스가 Coffee 클래스를 상속받았기 때문에 저렇게 쓰일 수 있음!

 

📌 is 사용하기

/// 데이터 타입 확인
print("coffee is Coffee ->", coffee is Coffee) // true
print("coffee is Americano ->", coffee is Americano) // false
print("coffee is Latte ->", coffee is Latte) // false

is는 데이터 타입을 확인하는 연산자입니다! <인스턴스> is <클래스>  형태로 사용하는데, return 값으로 Boolean 타입의 값을 돌려 줍니다. 위의 코드에서, 부모 클래스 Coffee의 인스턴스인 걍 coffee는 is Coffee를 했을 때 true를 받지만, 자식 클래스로 is Americano와 is Latte를 했을 땐 false를 받습니다. 부모 클래스가 자식 클래스인 척을 할 수는 없는 것 ..

print("vanillaLatte is Coffee ->", vanillaLatte is Coffee) // true
print("vanillaLatte is Americano ->", vanillaLatte is Americano) // false
print("vanillaLatte is Latte ->", vanillaLatte is Latte) // true

print("icedAmericano is Coffee ->", icedAmericano is Coffee) // true
print("icedAmericano is Americano ->", icedAmericano is Americano) // true
print("icedAmericano is Latte ->", icedAmericano is Latte)// false

하지만! 바닐라라떼와 아아는 부모 클래스인 Coffee를 상속받아 만든 Americano, Latte 클래스의 인스턴스죠!??!? 부모처럼 shot이라는 변수도 갖고 있죠!??!? 그러면 부모인 척을 할 수 있습니다!!!! vanillaLatte is Coffee는 true를 반환합니다!!! icedAmericano도 마찬가지! 이렇게 자식 클래스가 부모 클래스인 척하는 걸 업캐스팅이라고 합니다~ '척한다'는 것은 참조한다는 것이져! 자식이 부모인 척하는 것은 항상 성공하기 때문에, 업캐스팅은 항상 성공합니다!

또 하지만... Latte 클래스만이 갖고 있을 수 있는 flavor라는 특징을 Americano가 가지고 있을 수 없듯(아뭐...헤이즐넛아아라던가 이런건  말고...) 자식끼리는 완전히 독립된 상태입니다! icedAmericano is Latte는 false를 반환하죠~!

 

+ 추가로, 아래 코드처럼 type(of: <인스턴스명>) 함수를 사용하면 정확한 클래스명을 return받을 수 있습니다! 바닐라라떼는 커피냐? 했을 때 true였지만, 정확히는 Latte라는 것이져. Coffee인 척을 할 수 있는 Latte와 Americano..

print(type(of: coffee)) // Coffee
print(type(of: vanillaLatte)) // Latte
print(type(of: icedAmericano)) // Americano

 

📌 다운캐스팅, as? as!

다음은 자주 쓰는 as? as! as지만,... 정확히 왜 이것을 쓰는지는 모르고... 대충 ?! 이거는 엑스코드가 바꿔 주는 대로 쓰던... as? as! as의 차이점에 대해 알아보겠습니다...

if let a: Americano = coffee as? Americano {
    print("This is Americano")
} else {
    print("FAIL")
} // FAIL

if let a: Latte = coffee as? Latte {
    print("This is Latte")
} else {
    print("FAIL")
} // FAIL

if let a: Coffee = coffee as? Coffee {
    print("This is Coffee")
} else {
    print("FAIL")
} // This is Coffee

이 코드를 볼까여. 첫 번째 if let 구문에서는 a라는 Americano 타입의 변수에 위에서 만들었던 걍 커피 coffee(Coffee) 변수를 Americano 타입으로 바꿔서 넣으려고 합니다. 타입 변경에 성공해서 a에 정상적으로 들어가면 This is Americano를, 아니면 FAIL을 리턴합니다. 하지만 FAIL이 나죠... 이유는 Coffee 타입(부모 타입)을 Americano 타입(자식 타입)으로 변환하려고 해서! 그렇슴니다. 부모는 자식인 척을 할 수 없으니까! 두 번째 if let 구문에서도 에서도 마찬가지입니다! 부모 타입이 자식인 라떼인 척을 할 수가 없어요~!

 

그렇담 세 번째 구문에서는, 원래 Coffee 타입이었던 coffee 인스턴스를 그대로 Coffee로 변환해서 넣으려고 하니 당연히 if let 구문이 true가 되겠죠~~~

 

여기서 사용하는 as는 타입을 변환하는 연산자입니다. 부모 타입인 Coffee의 인스턴스 coffee를 Americano 타입으로 변환할 수 있냐? 하는 구문이 coffee as? Americano 인 것이죠... 당연히 실패할 가능성이 있으니 as?를 사용합니다! 위의 코드에서는 대실패했군여. 부모가 자식인 척을 할 수 있냐? 위에서 아래로 내려갈 수 있냐? 이게 바로 다운캐스팅! 다운캐스팅은 실패할 가능성이 있으니 as? 혹은 as!를 사용합니다! 당연히 as!는 '아그냥강제로시켜!'라는 뜻. 컴파일 오류 날 수도 있어요.

 

if let b: Americano = icedAmericano as? Americano {
    print("This is Americano")
} else{
    print("FAIL")
} // This is Americano

if let b: Latte = icedAmericano as? Latte {
    print("This is Latte")
} else {
    print("FAIL")
} // FAIL

if let b: Coffee  = icedAmericano as? Coffee {
    print("This is Coffee")
} else {
    print("FAIL")
} // This is Coffee

이번엔 이 코드를 봅시다! 첫 번째 구문은 Americano의 인스턴스 아.아를 Americano로 캐스팅 ㅎㅏ니까 당연히 성공하겠죠?? 이거는 컴파일러도 알고 있어요. 저런 구문을 쓰면.. 

사람 머쓱하게,, 알려 주네여

두 번째 구문에서는! Coffee의 자식 클래스 Americano의 인스턴스인 icedAmericano를 또다른 자식 클래스 Latte로 바꿀 수 없으니까 else로 들어갑니다.

이것도 알려 줍니다!

세 번째 구문에서는 Americano의 인스턴스를 Coffee 타입에 참조해서 사용하려고 하는데, 컴파일러가 항상 성공한다고 알려 주네요! 자식은 부모인 척이 가능하니까요!

 

그렇다면 as!를 써서 강제 다운캐스팅을 해 볼까요???

let castedAmericano: Americano = coffee as! Americano // 강제 다운캐스팅 실패..

부모 클래스 Coffee의 인스턴스 coffee를 자식 클래스로 강제 다운캐스팅하려고 하면, 컴파일 과정에서 에러가 납니다. 그러면 as! 를 as? 로 바꿔서 실패할 경우의 처리를 해 주어야 안전한 코드가 되겠죠~~~?? 😵‍💫

 

 

항상 성공하는 다운캐스팅도 있어요!

let castedCoffee: Coffee = vanillaLatte as! Coffee // 항상 성공하는 다운캐스팅

vanillaLatte의 타입인 Latte가 Coffee 클래스를 상속받는다는 것을 컴파일러도 알고 있기 때문에, 앞에서처럼 if let 구문으로 감싸지 않은 이런 코드를 작성하면

너 그거... 항상 성공할걸? 강제로 ! 붙여 가면서 쓸 필요 없을걸? 걍 'as'로 바꾸는 게 어때???? 라고 친절하게 알려 줍니다! 😇

 

 

 

레퍼런스

스위프트 프로그래밍 3판, 야곰 

https://babbab2.tistory.com/127