jane1choi/TIL

[Swift] 타입 캐스팅 (Type Casting)

Closed this issue · 0 comments

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

타입캐스팅은 인스턴스의 타입을 확인하거나 인스턴스를 같은 계층(hierachy)에 있는 다른 superclass나 subclass로 취급하는 방법입니다. 타입캐스팅에는 isas 두 연산자를 사용합니다. 타입캐스팅을 이용하면 특정 프로토콜을 따르는지(conforms) 확인할 수도 있습니다.
출처: The Swift Language Guide

is 연산자: Checking Type

표현식 is Type

is연산자를 이용해 특정 인스턴스의 타입을 확인할 수 있습니다.
런타임 시점에 타입에 대한 체크가 이루어지는데, 반환 값은 Bool 형입니다.
표현식이 Type과 동일하거나 표현식이 Type의 서브 클래스인 경우 true 를 반환하고, 이외의 경우에는 false를 반환합니다.

  • 선언한 상수/변수의 타입 확인
let char: Character = "A"
 
char is Character       // true
char is String          // false
 
let bool: Bool = true
 
bool is Bool            // true
bool is Character       // false
  • class의 타입 확인
class Human { }
class Teacher: Human { }    // Human 클래스를 상속받기 때문에 Teacher은 Human의 서브클래스
 
let teacher: Teacher = .init()
teacher is Teacher      // true
teacher is Human        // true

as 연산자: Type Casting

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

표현식(의 타입)이 변환할 Type과 호환된다면, 변환할 Type으로 캐스팅된 인스턴스를 리턴합니다.
상속 관계인 업캐스팅(Upcasting)과 다운 캐스팅(Downcasting)에서 사용하지만,
Any와 AnyObject 타입을 사용할 경우, 상속 관계가 아니어도 예외적으로 사용할 수 있습니다.

as, as?, as! 의 차이를 이해하기 위해서는 먼저 업캐스팅과 다운캐스팅에 대해 알아야 할 것 같습니다!

업캐스팅 (Upcasting)

서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 사용할 수 있도록 컴파일러에게 타입 정보를 전환해주는 것입니다.
업캐스팅은 as연산자를 사용해서 할 수 있으며, 서브 클래스는 슈퍼 클래스의 부분집합이기 때문에 업캐스팅은 항상 성공합니다.
따라서, 보장된 변환(guaranteed conversion)이라고도 합니다.
업캐스팅은 암시적으로 처리되므로 꼭 필요한 경우가 아니라면 생략해도 무방합니다.

예제를 봅시다!

class Human {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class Teacher: Human { }
class Student: Human { }
 
 
let people: [Human] = [
    Teacher.init(name: "김태끼"),
    Student.init(name: "정으니"),
    Student.init(name: "은주초이"),
    Student.init(name: "김둥찬")
]

people 배열에는 Human 타입의 인스턴스만 들어갈 수 있습니다.
그런데 Teacher, Student 라는 타입의 인스턴스가 들어갈 수 있는 이유는?
Teacher, Student의 슈퍼 클래스가 Human 이기 때문입니다!
Teacher과 Student는 명백히 서로 다른 타입의 클래스이지만, Human이라는 클래스로 업캐스팅했기 때문에 Human 타입으로 취급될 수 있습니다.

위의 예제는 암시적으로 처리된 경우인데, as 연산자를 사용한 업캐스팅은

let human = Teacher.init() as Human

위와 같이 해주면 됩니다!

⚠️ 업캐스팅 시 주의할 점은, 접근 범위가 한정된다는 것입니다!
바로 위의 예시의 경우, human의 접근 범위가 Human의 멤버로 한정됩니다.
따라서 Human 클래스의 멤버에는 접근할 수 있지만, 서브 클래스인 Teacher의 멤버에는 접근할 수 없습니다!!

다운캐스팅 (Downcasting)

다운캐스팅의 업캐스팅의 반대겠죠??
슈퍼 클래스의 인스턴스를 서브 클래스 타입으로 사용할 수 있도록 컴파일러에게 타입 정보를 전환해주는 것입니다.
업캐스팅된 인스턴스를 다시 원래 서브 클래스 타입으로 참조할 때 사용하며,
다운 캐스팅은 실패할 수 있기 때문에 as?as! 연산자를 이용합니다.

위에서 업캐스팅을 할 경우, 접근 범위가 슈퍼 클래스의 멤버로 한정되기 때문에 서브 클래스의 멤버에 접근할 수 없다고 했습니다!
만약 제한된 서브 클래스의 멤버에 접근하고 싶다면? 다운캐스팅을 이용하면 됩니다!

var teacher: Teacher = human as! Teacher

위와 같이 Human 타입으로 업캐스팅된 human 변수를 다시 서브 클래스인 Teacher 타입으로 변환(==다운캐스팅)해서 teacher이라는 변수에 넣어주었습니다. 이렇게 다운캐스팅을 통해 다시 Teacher 클래스의 멤버에 접근할 수 있습니다!

업캐스팅은 성공이 보장되어있는 반면, 다운캐스팅은 실패 가능성이 있기 때문에 as?as!를 사용한다고 위에서 설명했습니다.
이 둘의 차이에 대해 알아봅시다!

만약, 위의 바로 위의 예제 코드를 아래와 같이 바꾸면 어떻게 될까요?

var teacher: Teacher = human as! Student

human 변수의 원래 형식은 Teacher 이었습니다.
그런데 위와 같이 잘못된 클래스인 Student로 다운캐스팅을 하려고 한다면 에러가 발생하게 됩니다.
이렇게 다운캐스팅에 실패할 수 있기 때문에 성공이 보장된 업캐스팅과 달리 as에 옵셔널(?, !)이 붙은 것입니다.

  • as? : 런타임 시점에 다운 캐스팅을 하며, 실패할 경우 nil을 리턴
  • as! : 런타임 시점에 타입 캐스팅을 하며, 실패할 경우 에러 발생
for human in people {
   if let teaher =  human as? Teacher {
        print("선생님 : \(teacher.name)")
    } else if let student =  human as? Student {
        print("제자  : \(student.name)")
    }
}

as! 연산자는 옵셔널 강제 언래핑과 같이 실패할 경우 에러를 발생시키기 때문에 확실한 경우가 아니라면
위의 예제와 같이 as? 를 이용해 안전하게 다운 캐스팅하는 것이 좋을 것 같습니다!!

+ as!, as?런타임에 실행되는 반면, as컴파일 타임에 실행된다는 차이점도 있다고 합니다!

참고 자료: https://jusung.gitbook.io/the-swift-language-guide/language-guide/18-type-casting, https://babbab2.tistory.com/127, https://zeddios.tistory.com/265