# 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
타입으로 캐스팅하면 옵셔널 값 자체를 사용하겠다는 의미이므로 경고창이 사라지게 된다.
Any
및 AnyObject
로부터 특정 타입으로 캐스팅이 일어나는 것은 다운캐스팅이다.
반대의 경우는 업캐스팅이다.