본문 바로가기

iOS 관련 공부

타입 캐스팅 (Type Casting)

is, as 타입 캐스팅에 대해 알아보쟈

역시나 소들이님 블로그 발췌 항상 감사드립니다.

소들이가 내 공식문서야...

 

 

타입 캐스팅이란

타입 캐스팅은 인스턴스의 "타입"을 확인하거나, 해당 인스턴스를 자신의 클래스 계층에 있는 상위 혹은 하위 클래스로 처리하는 방법이다.

Swift에서 타입 캐스팅은 "is"나 "as" 연산자로 구현하며, 이 두 연산자는 값의 타입을 확인하거나 값을 다른 타입으로 지정한다.

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

이름대로 "타입"하고 관련이 있음

 

 

타입 캐스팅 연산자

타입 캐스팅 연산자는 총 4가지

expression is type
expression as type
expression as? type
expression as! type
  • is 연산자는 런타임에 expression이 특정 type으로 캐스팅 되는지 체크해준다. 가능하면 true 불가능하면 false 를 반환한다.
  • as 연산자는 컴파일 단계에서 캐스팅이 실행된다. 그러므로 언제나 특정 type으로 캐스팅이 성공할 때만 사용이 가능하다. 업캐스팅(Upcasting) 혹은 브릿징(Bridging)에 사용된다.
  • as? 연산자는 런타임에 캐스팅하여 특정 type의 옵셔널을 반환한다. 성공하면 옵셔널 값을 반환하고 실패하면 nil을 반환한다.
  • as! 연산자는 런타임에 특정 type으로 강제 캐스팅한다. 캐스팅에 실패할 경우 런타임 에러가 발생할 수 있다.

브릿징(Bridging): NSString과 같은 Foundation 타입을 String과 같은 스위프트 표준 라이브러리로 캐스팅

 

 

 

is : Checking Type

표현식 is Type

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

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

이 외 경우에는 -> false

 

is 연산자는 타입을 체크하는 연산자로, 반환 값은 Bool 형이다.

let char: Character = "A"

char is Character      // true
char is String         // false

let bool: Bool = true

bool is Bool           // true
bool is Character      // false

이렇게 내가 선언한 상수/변수 가

내가 원하는 타입인지 확인할 때 쓸 수 있다.

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

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

class Human { }
class Teacher: Human { }

let teacher: Teacher = .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 Teacher: Human { }
class Student: Human { }

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)")
    }
}

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

 

그럼 결과는

 

 

as: Type Casting

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

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

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

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

업캐스팅과 다운캐스팅을 먼저 알아보자.,,

 

업캐스팅과 다운캐스팅 (Upcasting / Downcasting)

 

1. 업캐스팅(Upcasting)

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

업 캐스팅은 항상 성공한다.

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

 

아까 사용했던 예제를 보면

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: "유제자")
]

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

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

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

 

이걸 가능하게 하는 것이 바로 "업캐스팅"이다.

Teacher과 Student란 클래스는 서로 다른 타입의 클래스인데 이 둘은 부모 클래스가 같다는 공통점이 있다.

즉, 둘의 슈퍼 클래스가 Human으로 동일하기 때문에, 이 둘을 Human이란 클래스로 업캐스팅해서 묶어버린 것이다.

 

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

이렇게 존재할 때,

let human = Teacher.init() as Human

이런 코드를 짰을 때

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

 

실제 human의 타입은 업캐스팅 된 Human의 타입이다.

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

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

 

다만

human이 Teacher이란 서브 클래스를, Human이란 슈퍼클래스 타입으로 참조하는 "업캐스팅"을 . 한것이기 때문에,

human의 접근 범위가 "Human" 멤버로 한정되는 것이다.

 

human.name 에서는 Kaia가 출력

 

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

서브 클래스 Teacher의 멤버인 subject에는 접근할 . 수없다.

 

이렇게 서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 참조하는 것을 업캐스팅이라고 한다.

 

업캐스팅은 항상 성공하기 때문에(상속 관계에서 슈퍼 클래스의 멤버는 서브 클래스가 당연히 포함하고 있어서)

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

 

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

// 2. Type Annotation을 사용한 업캐스팅
let human2: Human = Teacher.init()

 

정리하면

 

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

 

 

2. 다운캐스팅(Downcasting)

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

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

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

 

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

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

이때 사용하는 것이 as?, as! 연산자 이다.

 

아까 업캐스팅을 할 경우에 슈퍼 클래스의 멤버만 접근 가능하다고하면서 봤던 것인데

human은 실제 Teacher이란 인스턴스를 생성했지만, 업캐스팅을 통해 Human 멤버에만 접근 가능하게 제한이 되었다.

 

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

"다운 캐스팅"을 이용한다.

 

var teacher: Teacher = human as! Teacher

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

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

이게 다운캐스팅인데

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

 

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

teacher.subject      // "English"

Teacher 클래스의 멤버인 subject에도 접근이 가능하다.

 

 

업캐스팅은 항상 성공이라했는데 다운 캐스팅은 실패할 가능성 때문에 as?, as! 존재한다고 했었다.

let human: Human = Teacher.init()

 

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

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

여기까지는 문제가 없고 이 human 변수를 다시 원래 형식인 Teacher 클래스로 다운 캐스팅 하는 것도 문제가 없었다.

 

만약 다운캐스팅을 잘못해서

Teacher이 아닌, Student로 다운캐스팅을 하면

var student: Student = human as! Student

이렇게 하면 다운캐스팅 실패, 즉 에러가 발생한다.

Swift 는 안전한 언어이기 때문에 다운캐스팅에 실패할까봐 업캐스팅과 달리 as에 옵셔널(?, !)이 붙은 것

 

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

var student: Student? = human as? Student     // nil

타입 캐스팅을 실패할 경우, nil을 리턴한다.

컴파일 시점에는 성공/실패 여부를 알 . 수없다.

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

 

 

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

 

마치 옵셔널 강제 해제 연산과 비슷하다.

Non-Optional Type으로 선언해도 되서 간편하지만 런타임 시점에 실패할 경우 에러를 발생하므로, 가능하면 as?를 사용

 

 

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

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: "유제자")
]

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

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

결과가 나온다.

 

 

 

 

 

일단 작성해놓고 다시 공부...

중간중간 이해안가는 부분 추가이해되는부분 작성하기

 

 

추가 브릿징

 

브릿징 (Bridging)

swift에서 브릿징이란 서로 호환되는 형식을 캐스팅해서 쉽게 사용하는 것으로

swift가 나오기 전까지 사용되었던 Objective-C로 작성된 데이터와 서로 교환하기 위해 만들어졌다.

swift에서는 내부적으로 여전히 Objective-C의 프레임워크를 사용하는 것이 많기 때문에

서로 완전히 상호 호환이 가능하도록 설계해놨다.(completely interchangeable)

타입 캐스팅을 강제(as!)할 필요 없이 업캐스팅(as)만으로도 이루어진다.

let str: String = "Hello"
let otherStr = str as NSString // Hello

 

 

 

 

 

 

 

 


+ Any , Generic