본문 바로가기

iOS/Swift

Swift) is, as - 타입 캐스팅 (Type Casting)

 

 

 

안녕하세요, 소들입니다!!!!

오늘 포스팅은 is와 as 즉, 타입 캐스팅에 대해 알아보려고 해요 :D

타입 캐스팅,,, 개발 하다보면 가끔 보이는..

 

is

as

as?

as!

 

막 ...  이렇게...  단어 하나갖고 장난질이여;;; 싶은 것들에 대해 

오늘은 짚고 넘어가보려고 합니당.. ...

 

저번 포스팅인 "상속"에 대해 안다는 가정 하에 글을 써요!

모든 포스팅은 편의 말투로 합니다~!!

 

 

 

 

1. 타입 캐스팅이란? (Type Casting)

 

타입 캐스팅은 인스턴스의 "타입"을 확인 하거나, 해당 인스턴스를 슈퍼 클래스나 하위 클래스로 취급하는 방법이다

Swift에서 타입 캐스팅은 "is"나 "as" 연산자로 구현하며,

타입 캐스팅을 사용하여 타입이 프로토콜에 적합한지 여부도 확인할 수 있다

 

이름 그대로 "타입" 하고 관련이 있는 것인데,

이 타입 캐스팅엔 is와 asㄱㅏ있는 것 같음!!!!!

 

그럼 is 와 as에 대해 알아보러 가자 :)

 

 

 

 

2. is : Checking Type 

 

 

표현식 is Type

 

 

타입을 체크하는 연산자로, 런타임 시점에 실제 체크가 이루어진다

표현식이 Type과 동일하거나, 표현식이 Type의 서브 클래스인 경우 ➡️ true

이 외의 경우엔 ➡️ false

 

is 연산자는 타입을 "체크"하는 연산자로, 반환 값은 Bool 형임!!

 다음과 같이 내가 선언한 상수/변수가

 

 

let charCharacter = "A"
 
char is Character       // true
char is String          // false
 
 
let boolBool = true
 
bool is Bool            // true
bool is Character       // false

 

 

내가 원하는 타입인지 확인할 때 쓸 수 있음!! 

이렇게 동일한 타입을 확인할 때도 쓸 수 있지만,

위에서는 표현식이 Type의 "서브 클래스"인 경우에도 true를 반환한다 했음

 

이게 무슨 말이냐면,

 

 

class Human { }
class TeacherHuman { }
 
let teacherTeacher = .init()
teacher is Teacher      // true
teacher is Human        // true

 

 

이렇게 Human 클래스를 Teacher이란 클래스가 "상속" 받을 경우,

teacher이란 인스턴스는 Human 클래스의 서브 클래스이기 때문에,

이런 경우엔 Human으로 타입 체크를 해도 true가 된단 것임!!!

 

애플 예제를 응용해서 보자면,

 

 

class Human {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class TeacherHuman { }
class StudentHuman { }
 
 
let people: [Human= [
    Teacher.init(name: "김선생"),
    Student.init(name: "박제자"),
    Student.init(name: "유제자")
]

 

 

이렇게 Human이란 클래스가 있고

이 Human 클래스를 상속받는 서브클래스 Teacher, Student가 각각 존재함

그리고 people이란 배열에, Teacher 인스턴스 1개, Student 인스턴스 2개를 담았음

(타입에 민감한 Swift에서 어떻게 Teacher & Student 두 개의 타입의 인스턴스를 저장하나 싶겠지만,

이는 방금 뒤에 공부할 업캐스팅으로 가능한 것임!!!

Teacher 과 Student의 슈퍼 클래스가 Human으로 동일하기에, Human이란 클래스로 둘 다 업캐스팅한 것)

 

 

쨌든, 이렇게 업캐스팅 된 두 형식의 인스턴스를

 

 

for human in people {
    if human is Teacher {
        print("나는야 선생님 : \(human.name)")
    } else if human is Student {
        print("나는야 제자  : \(human.name)")
    }
}

 

 

이렇게 타입 캐스팅을 통해 확인하면서 조건문을 분기할 수 있음!!!!!

그럼 결과는 

 

 

 

 

어렵지 않읍니다

 

 

 

 

3. as: Type Casting

 

 

표현식 as  (변환 할)Type
표현식 as? (변환 할)Type
표현식 as! (변환 할)Type

 

 

표현식(의 타입)이 변환할 Type과 호환된다면, 변환할 Type으로 캐스팅된 인스턴스를 리턴한다

상속 관계인 업캐스팅(Upcasting)과 다운 캐스팅(Downcasting)에서 사용한다

Any와 AnyObject 타입을 사용할 경우, 상속 관계가 아니어도 예외적으로 사용할 수 있다

 

먼.. 소리.... 데스까..? 다운 캐스팅..?

이를 이해하기 위해서 먼저 업캐스팅과 다운 캐스팅에 대해 알아 보겠움

 

 

 

3-1. 업캐스팅과 다운캐스팅 (Upcasting / Downcasting)

 

 ① 업캐스팅(Upcasting) 

 

서브 클래스 인스턴스를 "슈퍼 클래스의 타입"으로 참조한다

업 캐스팅은 항상 성공한다

as 연산자를 사용해서 할 수도 있다 (컴파일 시점에 캐스팅 가능 여부를 결정한다)

 

자, 우린 아까 이상한 예제를 봤움

 

 

class Human {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class TeacherHuman { }
class StudentHuman { }
 
 
let people: [Human= [
    Teacher.init(name: "김선생"),
    Student.init(name: "박제자"),
    Student.init(name: "유제자")
]

 

 

바로 이 예제!!!!!

Swift는 타입에 민감한 언어이기 때문에, people이란 배열엔

우리가 Type Annotation으로 선언한 Human이란 타입의 인스턴스만 들어갈 수 있음!! 이는 당연함!!

 

근데 엥

Teacher , Student라는 타입의 인스턴스가 도대체 어떻게 들어간 것일까??

 

이것이 가능케 하는 것이 바로 "업캐스팅"인 것임

Teacher과 Student란 클래스는 분명 명백히 서로 다른 타입의 클래스임

하지만 이 둘은 뭐다?? 무슨 공통점이 있다????

 

부모 클래스가 같다!!

즉, 둘의 슈퍼 클래스가 Human으로 동일하기 때문에,

이 둘을 Human이란 클래스로 업캐스팅 해서 묶어버린 것

 

좀 더 이해하기 쉽게 위 클래스들의 멤버를 조금 더 손봐보겠움

 

 

class Human {
    let name: String = "Sodeul"
}
class TeacherHuman {
    let subject: String = "English"
}
class Student: Human {
    let grade: Int = 1
}

 

 

이렇게 존재할 때, 

 

 

let human = Teacher.init() as Humann

 

 

이런 요상해보이는 코드를 짰음

위에서 업캐스팅할 때 as를 쓸 수 있다고 했잖음!?!?! 위와 같이 사용함!!

따라서 위 코드의 의미는

 

Teacher 타입의 인스턴스를 생성하지만, 이를 Human 타입으로 업캐스팅해서 human에 저장하겠다

 

이런 말임!!!

그럼 어떻게 메모리에 올라갈까?

 

 

 

 

실제 human의 타입은 업캐스팅된 Human의 타입임!!

그치만 그렇다고 Human 인스턴스의 멤버만 메모리에 올라가는 것이 아님

우린 먼저 Teacher이란 인스턴스를 만들었기 때문에, Teacher이란 인스턴스가 온전히 메모리에 올라감

(실제 메모리에 이렇게 올라갈지 아닐진 몰라여!! 이해를 돕기 위해 첨부한 그림일 뿐)

 

다.만.

 human이 Teacher이란 서브 클래스를, Human이란 슈퍼클래스 타입으로 참조하는

"업캐스팅"을 한 것이기 때문에, human의 접근 범위가 "Human" 멤버로 한정되는 것

 

따라서, 

 

 

human.name             // Sodeul
human.subject          // Value of type 'Human' has no member 'subject'

 

 

이렇게, Human 클래스의 멤버인 name엔 접근할 수 있지만,

서브 클래스 Teacher의 멤버인 subject엔 접근할 수 없음

 

이렇듯, 서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 참조하는 것을

업캐스팅이라고 함

 

🌸업캐스팅은 항상 성공🌸하기 때문에

(상속 관계에서 슈퍼 클래스의 멤버는 서브 클래스가 당연히 포함하고 있으니까)

as를 써서해도 되고, 직접 타입을 명시해서 해도 됨

 

 

// 1. as를 사용한 업캐스팅
let human1 = Teacher.init() as Human
 
// 2. Type Annotation을 사용한 업캐스팅
let human2Human = Teacher.init()

 

 

이렇게!! 정리하자면

 

 as  : "컴파일 시점"에 타입 캐스팅(업캐스팅)을 하며, 실패할 경우 에러가 발생한다

패턴 매칭(switch)에도 사용한다

 

근데 .. 업캐스팅은 항상 성공인디..?

흠.. 근디 as는 String <-> NSString 이렇게 변환할 때도 사용함

또 as를 사용한 패턴 매칭은 다음 포스팅은 Any & AnyObject에서 볼 것임

일단 여기까지..

 

그럼, 다운 캐스팅은 이것의 반대겠지? 맞음!!

 

 

 ② 다운캐스팅(Downcasting) 

 

슈퍼 클래스 인스턴스를 "서브 클래스의 타입"으로 참조한다

업캐스팅된 인스턴스를 다시 원래 서브 클래스 타입으로 참조할 때 사용한다

다운 캐스팅은 실패할 수있기에 as?, as! 연산자를 이용한다

 

아깐 서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 참조하는 것이 업캐스팅이었다면,

이젠 슈퍼 클래스의 인스턴스를 서브 클래스의 타입으로 참조하는 것이 다운 캐스팅임

그리고, 이때 사용하는 것이 바로 우리가 공부하는 타입 캐스팅, as? 혹은 as! 라는 연산자임!

 

 

human.name             // Sodeul
human.subject          // Value of type 'Human' has no member 'subject'

 

 

자, 이것은 아까 업캐스팅을 할 경우, 슈퍼 클래스의 멤버만 접근 가능하다면서 본 것임

근데 human은 실제 Teacher이란 인스턴스를 생성했지만,

업캐스팅을 통해 Human 멤버에만 접근 가능하게 제한된거 잖음??

 

그럼 제한된 Teacher이란 서브 클래스의 subject 멤버에 접근하고 싶다면??

"다운 캐스팅"을 이용하자!!!

 

 

var teacherTeacher = human asTeacher

 

 

이렇게 as 연산자를 사용해서, (뒤에 !, ?가 붙고말고의 차이는 일단 무시)

Human 타입으로 업캐스팅된 human 변수를 다시 하위 클래스인Teacher 타입으로 변환해서 

넣어준 것임!!! 이것이 바로 다운 캐스팅

슈퍼 클래스인 Human 타입의 인스턴스를 서브 클래스인 Teacher 타입의 인스턴스로 참조하는 것

 

그럼 이제 

 

 

 

 

teacher이란 변수는 Teacher 클래스로 다운 캐스팅 됐기 때문에,

 

 

teacher.subject         // "English"

 

 

이렇게 Teacher 클래스의 멤버인 subject에도 접근이 가능함 :)

 

 

자, 근데 여기서 문제점 하나..!

업캐스팅은 항상 성공이랬는데,,,

다운 캐스팅은 실패할 가능성 때문에 as?, as! 뭐시기가 존재 한댔음

 

맞음!!

🌸 다운 캐스팅은 실패할 수 있음 🌸 

 

다시 위 예제를 돌아보면, 

 

 

let humanHuman = Teacher.init()

 

 

우리가 이렇게 Teacher이란 인스턴스를 생성 했지만,

human이란 변수는 슈퍼 클래스인 Human 인스턴스로 업캐스팅을 했음

 

자, 여기까진 문제 없었고!!!!!

이 human 변수를 다시 원래 형식은 Teacher 클래스로 다운 캐스팅 하는 것까지 문제 없었음!!!!

 

근데, 만약 잘못해갖꼬

Teacher이 아닌, Student로 다운 캐스팅을 해버린 것임

 

 

var student: Student = human as! Student

 

 

이렇게!!!!!!!!! 그럼 이것은 다운 캐스팅 실패!!!!!! 즉, 🚨에러🚨임!!!!!

하지만,,,,, Swift는 뭐다..? 안전한 언어다... 살려는 드릴게..

따라서, Swift는 다운캐스팅에 실패할까봐 업캐스팅과 달리 as에 옵셔널(?,!)이 붙은 것임..

 

 

 

 as?  : "런타임 시점"에 타입 캐스팅(다운 캐스팅)을 하며, 실패할 경우 nil을 리턴

 

 

 

 

아까처럼 타입 캐스팅을 실패한 경우, nil을 리턴함!!

컴파일 시점엔 성공/실패 여부를 알 수 없음

아주 안전한 대신, nil을 리턴하기 때문에 Optional-Type으로 선언해야 함!!

 

 

 

 as!  : "런타임 시점"에 타입 캐스팅(다운 캐스팅)을 하며, 실패할 경우 에러 발생

 

 

 

 

마치 옵셔널 강제 해제 연산과도 비슷함!!

Non-Optional Type으로 선언해도 돼서 간편 하지마는...

런타임 시점에 실패한 경우 에러를 발생하므로, 가능하면 as?를 사용하자 :)

 

 

따라서 as를 이용한 다운캐스팅 예제는 

 

 

for human in people {
   if let teaher =  human asTeacher {
        print("나는야 선생님 : \(teacher.name)")
    } else if let student =  human asStudent {
        print("나는야 제자  : \(student.name)")
    }
}

 

 

이렇게 if let 바인딩 구문을 통해 사용할 수 있고,

 

 

 

 

결과도 잘 나온다 :)

 

 

 

 

 

 

.

.

.

 

타입 캐스팅 공부 끝 :)

다음 포스팅에선 Any & AnyObject에 대해 알아볼 거예영

만약 잘못된 내용이나 피드백, 궁금증이 있다면 언제든 댓글 주세요~~!!



Calendar
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
최근 댓글
Visits
Today
Yesterday