안녕하세요! 후르륵짭짭 입니다.
이번에는 아주아주 IOS 개발을 할 때 자주 사용하면서도
먼지 모르고 사용했던 TypeCasting에 대해서 알아보려고 합니다!
** TypeCasting이란? **
TypeCasting이란 말 그래도 타입 변환을 의미합니다.
하지만 Swift에서는 타입 변환에 대해 엄청 예민하게 반응합니다.
왜냐하면 Swift 자체가 안정성을 중시한 언어 이기 때문 입니다.
그래서 스위프트에서는 클래스(인스턴스)의 타입을 확인하거나
자신을 다른 타입의 인스턴스인 양 행세 할 수 있도록 TypeCasting이라는 것이 있습니다.
** TypeCasting 사용 방법 **
그럼 타입 캐스팅을 사용하는 방법에 대해 알아보도록 하겠습니다.
이렇게 되어 있다고 할 때, B와 C는 A를 상속 받은 것을 알 수 있다.
그리고 타입 캐스팅은 이렇게 상속 관계에서의 클래스들의 특징을 변환 시켜주거나 확인 시켜 줄 수 있다.
즉!! 이렇게 상속관계에 있을 때만 타입 캐스팅을 사용 할 수 있다.
class Coffee{
let name : String
let shot : Int
var description : String{
get{
return "\(shot) shot(s) \(name)"
}
}
func printName(){
print(name)
}
init(shot : Int){
self.shot = shot
self.name = "coffee"
}
}
class Latte : Coffee {
var flavor : String
override var description: String {
return "\(shot) shot(s) \(flavor) latte"
}
func printFlavor(){
print(flavor)
}
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"
}
func printIcedState(){
print(iced)
}
init(shot : Int , iced : Bool){
self.iced = iced
super.init(shot: shot)
}
}
이렇게 코드가 짜여져 있다고 할 때,
A는 Coffee 이고 B는 Latte 그리고 C는 Americano 입니다.
그리고 다음과 같이 코드를 작성한다면 각 클래스에 맞는 description이 작동 됩니다.
let coffee : Coffee = Coffee(shot: 1)
print(coffee.description) // 1 shot(s) coffee
let americano : Americano = Americano(shot: 2, iced: false)
print(americano.description) // 2 shot(s) hot americano
let latte : Latte = Latte(flavor: "green tea", shot: 3)
print(latte.description) // 3 shot(s) green tea latte
그럼 이 코드를 가지고 타입 캐스팅을 알아보도록 하겠습니다.
** IS **
타입 캐스팅에서 타입 확인 방법으로 "IS"가 있습니다.
IS는 단순히 타입을 확인하는 용도로 사용 됩니다.
그리고 사용 방법은 간단합니다.
단순히 IS를 넣어줘서 true , false 두 중의 하나로 결과 값이 나옵니다.
print(coffee is Coffee) // true
print(coffee is Americano) // false
print(coffee is Latte) // false
하지만 이런 오류가 발생하게 됩니다.
이러한 오류가 발생하는 이유는 동일한 타입이기너 서로 상속관계가 아닐 때 발생합니다.
그래서 컴파일 과정상 단순한 경고표시로만 알려주는 겁니다.
//클래스 인스턴스(AnyObject)를 판별을 IS로 판별하는 방법
func checkType(of item : AnyObject){
if item is Latte{
print("item is latte")
}
else if item is Americano{
print("item is Americano")
}
else if item is Coffee{
print("item is Coffee")
}
}
checkType(of: latte) // 이렇게 AnyObject이기 때문에 IS로 판별해준다. item is latte
checkType(of: americano) // item is Americano
그래서 IS 타입 캐스팅은 이러헥 Any 타입에 대해 주로 사용 됩니다.
그런데, 여기서 item은 Latte나 Americano로 타입이 변환 되는 것이 아니라 단순히 확인만 하는 것이라서
Americano나 Latte가 고유로 가진 프로퍼티나 메소드를 사용 할 수 없습니다.
** as **
그래서 등장한 것이 as 입니다.
as는 타입 확인 뿐만 아니라 변환 까지 가능하게 해줍니다.
변환과 확인 까지 해주기 때문에 as는 총 세가지 유형이 존재 합니다.
as! = 이 타입으로 무조건 될거야 (잘 사용하지 않습니다.)
as? = 이 타입일 수도 있어?
as = 이 타입이랑 동일하거나 부모도 확실해
if let downCasting = coffee as? Americano {
print(downCasting.printIcedState())
}else{
print("downCasting fail : " , coffee.description)
}
// downCasting fail : 1 shot(s) coffee
if let downCasting = coffee as? Latte {
print(downCasting.printFlavor())
}else{
print("downCasting fail : " , coffee.description)
}
//downCasting fail : 1 shot(s) coffee
이렇게 as? 를 사용해서 타입 변환을 할 수 있습니다.
그리고 coffee는 Americano와 Latte의 부모 클래스 이기 때문에
Coffee 클래스가 Americano 또는 Latte로 타입 변환이 가능하니?? 라고 물어보는 겁니다.
이것을 바로 DownCasting (다운 캐스팅)이라고 합니다.
즉 부모 클래스에서 자식 클래스로 타입 변환을 해주는 것을 다운 캐스팅 한다는 겁니다.
하지만 위에 모두 downCasting 실패가 되었습니다.
왜냐하면 coffee는 Coffee 클래스 이고 Latte나 Americano가 아니기 때문 입니다.
그럼 언제 사용 할 수 있는것인가?
바로 위장된 상태에서 다운 캐스팅을 사용 할 수 있습니다.
//Latte는 Coffee의 자식이다. 따라서 Latte는 Coffee처럼 위장할 수 있다.
let camouflageType : Coffee = Latte(flavor: "Pink", shot: 3)
예를 들어 이렇게 Latte로 생성 했지만 Latte는 Coffee의 자식이기 때문에 Coffee가 될 수 있습니다.
이렇게 된다면 Latte의 기능은 사용할 수 없게 되고 Coffee만 사용할 수 있게 됩니다.
따라서 이럴 때 다운 캐스팅을 사용하게 됩니다.
//camouflageType은 Coffee인데 원래 태생이 Latte이기 때문에 Latte로 타입 변경을 해준다.
if let downCasting = camouflageType as? Latte{
downCasting.printFlavor()
}
else{
print("downCasting fail : " , camouflageType.description)
}
//반면에 Americano도 Coffee 밑에 있기 때문에 이렇게 타입 케스팅이 되지만 결과는 else 문으로 빠지게 된다.
if let downCasting = camouflageType as? Americano{
downCasting.printIcedState()
}
else{
print("downCasting fail : " , camouflageType.description)
}
이렇게 다운 캐스팅을 사용 할 수 있게 됩니다.
하지만 관련이 없을 경우에는 당연히 오류가 발생하겠죠?
그리고 지금 까지는 as? 를 알아봤는데, 이제 as를 알아보도록 하겠습니다.
그냥 as는 확실히 알고 있는 부모로 타입을 변환하거나 동일한 타입일 경우에 사용합니다.
어차피 알 수 있는 것이죠!
//같은 타입일 경우에는 as? 가 아니라 보통 as로 해줘야 한다.
let downCasting = coffee as Coffee
downCasting.printName()
//부모 클래스 일 경우에도 as? 가 아니라 보통 as로 해준다.
let upCasting = latte as Coffee
그리고 Any 타입도 타입을 변형 시키고 기능을 사용 하기 할 때는 아래 처럼 해주면 됩니다.
//DownCasting function
func castTypeToAppropriate(item : Any){
if let castedItem : Latte = item as? Latte{
castedItem.printFlavor()
}
else if let castedItem : Americano = item as? Americano{
castedItem.printIcedState()
}
else if let castedItem : Coffee = item as? Coffee{
castedItem.printName()
}
else{
print("Unkown Type")
}
}
castTypeToAppropriate(item: coffee)
castTypeToAppropriate(item: latte)
castTypeToAppropriate(item: camouflageType) // camouflageType은 Coffee인덷 Latte로도 typeCasting이 되기 때문에 Pink라는 결과가 나왔다.
여기서 camouflageType은 Latte이지만 Coffee로 위장 했기 때문에
첫번째 Latte로 변형이 되었다.
** Meta Type **
Meta Type은 그냥 타입을 말합니다.
(참고로 Codable을 할 때 클래스 자체를 넣어주는 것이 아니라 타입을 넣어주는 것 입니다.)
예를 들어 Int 는 Int 타입이고
String은 String 입니다.
만약 클래스 이름이 A 라면 A 클래스의 타입은 A 입니다.
이렇게 그 클래스의 타입을 알고 싶다면 "타입".self를 해주면 타입을 반환하게 됩니다.
class SomeClass : SomeProtocol{}
//Int.Type은 Int의 타입을 저장하는 것이다.
let intType : Int.Type = Int.self
let stringType : String.Type = String.self
let classType : SomeClass.Type = SomeClass.self // 이렇게 하면 SomeClass 타입이 들어가게 된다.
그리고 타입의 형태는 .Type 입니다.
요약을 하자면 .self는 타입을 반환 해주고 그 반환되는 타입이 .Type 입니다.
반면 프로토콜은 .Type 이 아니라 .protocol 입니다.
let protocolProtocol : SomeProtocol.Protocol = SomeProtocol.self // Protocol은 Type이 아니라 Protocol이다.
그래서 아래 처럼 사용 할 수가 있습니다.
var someTypee : Any.Type // 아무런 타입이 들어 갈 수가 있다.
someTypee = intType
print(someTypee) //someType의 Type은? Int 타입이다.
someTypee = stringType
print(someTypee) //someType의 Type은? String 타입이다.
someTypee = classType
print(someTypee) //someType의 Type은? SomeClass 타입이다.
someTypee = protocolProtocol // 프로토콜 이여도 Type으로 들어 갈 수가 있다.
print(someTypee) //someType의 Type은? SomeProtocol 타입이다.
만약에 클래스의 인스턴스가 아니라 값을 알고 싶다면
print("StringValue".self) // 값 다음에 .self를 해주면 값이 나오고, 타입 뒤에 .self를 해주면 타입이 나오게 된다.
이렇게 "값".self를 해주면 됩니다.
참고 사이트 :
- 다운 캐스팅
'Xcode > Swift - PlayGround' 카테고리의 다른 글
PlayGround) Protocol에 대해서 좀 더 깊게 알아가기 (0) | 2021.02.14 |
---|---|
PlayGround) Method Chaining와 Optional Chaining이란! (0) | 2021.02.13 |
PlayGround) Required Init()이 무엇일까? (0) | 2021.02.09 |
PlayGround) Class func 와 Static func의 차이가 머지? (0) | 2021.01.24 |
PlayGround) 참조값으로 받을 수 있는 함수와 클로저의 놀라운 기능! (0) | 2021.01.14 |
댓글