# is연산자

is연산자는 인스턴스의 타입을 검사하는 연산자이다.

class Person{
    //...
}

class Student: Person{
    // ...
}

var me = Person()
var you = Student()
print(me is Person) // true
print(me is Student) // false
print(you is Student) // true
print(you is Person) // true

자식클래스의 인스턴스 타입은 부모클래스의 인스턴스 타입에 포함되므로 자식 인스턴스 is 부모 클래스 연산의 결과값이 true로 나오게 된다.

상속받는다는 것은 자식클래스가 부모클래스의 정보들을 포함하고 있다는 것을 생각하면 됨

# as 연산자

as연산자는 타입캐스팅 연산자이다. 타입캐스팅은 업캐스팅과 다운캐스팅이 있다.

Person -> Student -> Undergraduate로 상속이 이루어진다고 가정하자. 이때 Person의 객체는 Undergraduate클래스로 다운캐스팅을 한다. 반대의 경우를 업캐스팅이라고 한다.

다운캐스팅은 as?as! 두 가지 방법으로 할 수 있다. as?는 옵셔널 타입으로 리턴하기 때문에 언래핑 작업이 필요하다. (if let 바인딩 등)

as!의 경우 옵셔널타입을 강제 언래핑한 타입이므로 nil을 언래핑할 경우 에러가 발생한다.

자식클래스로부터 부모클래스로 업캐스팅하는 것은 as연산이 항상 성공하기 때문이다. 자식클래스 속성 몇가지를 가리고 부모클래스의 속성만 보이면 되기 때문이다.

부모클래스의 인스턴스를 생성하고 난 뒤에 다운캐스팅을 진행하려고 하면 메모리 구조를 바꿀 수 없기 때문에 타입캐스팅 실패로 nil을 리턴한다.

# 다운캐스팅에 대한 이해

class MediaItem {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class Movie: MediaItem {
    var director: String
    init(name: String, director: String) {
        self.director = director
        super.init(name: name)
    }
}

class Song: MediaItem {
    var artist: String
    init(name: String, artist: String) {
        self.artist = artist
        super.init(name: name)
    }
}

// library 배열객체의 타입은 [MediaItem]임에 주의하자
// 각 원소가 MediaItem 타입이므로
let library = [
    Movie(name: "Casablanca", director: "Michael Curtiz"),
    Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
    Movie(name: "Citizen Kane", director: "Orson Welles"),
    Song(name: "The One And Only", artist: "Chesney Hawkes"),
    Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
for item in library {
    if let movie = item as? Movie {
        print("Movie: \(movie.name), dir. \(movie.director)")
    } else if let song = item as? Song {
        print("Song: \(song.name), by \(song.artist)")
    }
}

as 다운캐스팅은 이미 생성된 부모클래스의 인스턴스 타입을 강제 변경하기 위해 사용하는 것이 아니라 상속관계 그 중간 어디쯤에 위치한 인스턴스의 타입이 확실하지 않을때 해당 타입을 체크하기 위해 사용하는 것으로 보인다.

위의 예시에서 배열 내에 Song과 Movie클래스는 모두 MediaItem이라는 클래스를 상속하고 있으며 한 배열에 섞여있기 때문에 배열 자체는 [MediaItem]타입을 갖게 된다.

각 아이템 순회를 하며 MediaItem이라고 인식되었던 인스턴스가 실제 까보니 자신을 상속하는 자식클래스 Movie또는 Song클래스로 사용된 것을 확인하기 위해 as 다운캐스팅이 사용되는 것이라고 생각된다.

다운캐스팅은 다음 예시로 이해하면 된다.

let person: Person = Undergraduate()
if let newPerson = person as? Undergraduate{
    // ......
}

person 인스턴스의 근본을 생각해보면 Undergraduate 클래스이다. Undergraduate 클래스의 속성들 중 Person클래스에 해당하는 속성들만 활용하겠다고 첫줄에 선언한 뒤에 if let newPerson의 바인딩을 통해 person인스턴스를 다시 근본이었던 Undergraduate타입으로 되돌려 놓는 것이다.

다운캐스팅은 인스턴스의 근본을 생각하면 된다

as 연산자의 활용

두 타입이 서로 상호 호환의 관계에 있을때 as 연산자를 통해 캐스팅하여 쉽게 사용한다.

Objective-C 기반의 NSString과 스위프트 String 타입은 서로 상호 호환 관계에 있다.

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

두 타입이 상호 호환일 경우 타입 캐스팅이 실패할 가능성이 없기 때문에 as 키워드만 사용해도 된다.

# 상속과 다형성

다형성은 하나의 객체(인스턴스)가 여러 타입의 형태로 표현될 수 있음을 의미한다.

스택에 추가되는 변수 / 상수 타입에 따라 인스턴스가 여러 타입의 형태로 표현될 수 있다.

let person: Person = Student()로 선언된다면 Student타입에서 Person타입으로의 업캐스팅이 이루어진다. 이때 메모리 구조 자체는 데이터 영역의 Student타입을 따라 힙에 인스턴스가 생성되게 되지만 변수 person의 타입이 Person으로 지정되었기 때문에 Person내부 구성대로 따르는 것처럼 부분적인 속성 및 메서드에만 접근 가능하게 되는 것이다.

상속관계에서 타입의 저장 형태는 속성/메서드에 대한 접근가능 범위를 나타내는 것이다.

# Any와 AnyObject

var some: Any = "HELLO"

if let result = some as? String{
    print(result.count)
}

위와 같이 변수는 Any라는 타입으로도 선언할 수 있다.

Any타입으로 선언된 인스턴스는 내부 메모리 구조를 모르기 때문에 각종 메서드 및 속성들을 사용할 수 없게 된다. 이들을 사용하기 위해서는 타입캐스팅을 해줘야함.

let arr: [Any] = [1, 2.3, "HELLO"] // 여러 타입이 섞인 배열을 선언

AnyObject타입은 클래스의 인스턴스, 객체만 담을 수 있는 타입이다. 어떤 클래스의 타입이 됐던간에 담을 수 있다.

let objArr: [AnyObject] = [Person(), NSString()]

(objArr[0] as! Person).prop

스위치문 바인딩 정리본 참고 (opens new window)

let array: [Any] = [5, "안녕", 3.5, Person(), Superman(), {(name: String) in return name}]
for (index, item) in array.enumerated() {
    // (0,  5)
    // (1, "안녕")
    // (2, 3.5)
    // ...

    switch item {
    case is Int:                                  // item is Int
        print("Index - \(index): 정수입니다.")
    case let num as Double:                       // let num = item as Double
        print("Index - \(index): 소수 \(num)입니다.")
    case is String:                               // item is String
        print("Index - \(index): 문자열입니다.")
    case let person as Person:                    // let person = item as Person
        print("Index - \(index): 사람입니다.")
        print("이름은 \(person.name)입니다.")
        print("나이는 \(person.age)입니다.")
    case is (String) -> String:                   // item is (String) -> String
        print("Index - \(index): 클로저 타입입니다.")
    default:
        print("Index - \(index): 그 이외의 타입입니다.")
    }
}

옵셔널값의 Any변환

let optionalValue: Int? = 3
print(optionalValue) // Optional(3), 경고 발생
print(optionalValue as Any)

옵셔널값은 임시값이기 때문에 xcode에서 옵셔널 값을 사용하는 것이 맞는지 개발자에게 경고창을 띄운다.

이때 옵셔널값을 Any타입으로 캐스팅하면 옵셔널 값 자체를 사용하겠다는 의미이므로 경고창이 사라지게 된다.

AnyAnyObject로부터 특정 타입으로 캐스팅이 일어나는 것은 다운캐스팅이다.

반대의 경우는 업캐스팅이다.

# Reference

  1. 인프런 - 앨런 swift 문법 마스터 스쿨 (opens new window)
  2. Swift Document - Type Casting (opens new window)
  3. Switch문 바인딩 (opens new window)