새로 배운 점
- 클래스와 서브클래스의 계층 구조가 있는 타입 캐스팅을 사용하여 특정 클래스 인스턴스의 타입을 확인하고 그 인스턴스를 같은 계층에 있는 다른 클래스로 캐스팅할 수 있습니다.
- Any 또는 AnyObject 타입에서 알고 있는 상수나 변수의 특정 타입을 찾으려면, switch문에서 is 또는 as 패턴을 사용할 수 있습니다.
Type Casting
타입 캐스팅(Type Casting)은 인스턴스의 타입을 체크하거나
해당 인스턴스를 인스턴스 자신의 클래스 계층에서 다른 슈퍼클래스 또는 서브클래스로 처리하는 방법입니다.
Swift에서 타입 캐스팅은 is와 as 연산자로 구현됩니다.
이 두 연산자는 값의 타입을 체크하거나 다른 타입으로 그 값을 캐스팅하는 간단하고 효과적인 방법입니다.
Defining a Class Hierarchy for Type Casting
클래스와 서브클래스의 게층 구조가 있는 타입 캐스팅을 사용하여
특정 클래스 인스턴스의 타입을 확인하고
그 인스턴스를 같은 계층에 있는 다른 클래스로 캐스팅할 수 있습니다.
아래의 3개 코드들은 클래스 계층과 이러한 클래스들의 인스턴스들을 포함하는 배열을 정의하여
타입 캐스팅의 예를 보여줍니다.
첫 번째 예제 코드는 MediaItem 이라는 기본 클래스를 정의합니다.
이 클래스는 digital media library에 표시되는 모든 종류의 아이템에 대한 기본 기능을 제공합니다.
여기서 구체적으로 String 타입의 이름과 init을 제공합니다.
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
다음 코드는 MediaItem의 두 서브클래스를 정의합니다.
첫 번째 서브클래스인 Movie는 movie에 대한 추가적인 정보를 캡슐화합니다.
여기에는 director 프로퍼티와 이에 대응되는 이니셜라이저가 추가됩니다.
두 번째 서브클래스, Song은 artist 프로퍼티와 이니셜라이저가 추가됩니다.
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라는 상수 배열을 생성하며,
이 배열은 2개의 Movie 인스턴스와 3개의 Song 인스턴스를 포함합니다.
library 배열의 타입은 array literal의 내용을 통해 초기화돼서 추론됩니다.
Swift의 Type checker는 Movie와 Song이 공통의 MediaItem의 서브클래스를 가지고 있다고 추론할 수 있으며,
이 library 배열의 타입을 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")
]
// the type of "library" is inferred to be [MediaItem]
library에 저장된 아이템들은 Movie와 Song 인스턴스입니다.
하지만 이 배열로 iteration 하면, 전달 받는 이 아이템들은 Movie나 Song이 아닌 MediaItem 타입입니다.
만약 이들을 native 타입으로 작업하려면 타입을 체크하거나 다른 타입으로 downcasting 해야 합니다.
Checking Type
Type check 연산자인 is를 사용하여 인스턴스가 어느 특정 서브클래스 타입인지 확인할 수 있습니다.
만약 지정한 서브클래스 타입이라면 true를 리턴하고 아니라면 false를 리턴합니다.
아래 예제 코드는 movieCount와 songCount라는 두 변수를 정의하고
위에서 정의한 libarary 배열에서 Movie와 Song의 인스턴스 개수를 카운트합니다.
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
movieCount += 1
} else if item is Song {
songCount += 1
}
}
print("Media library contains \(movieCount) movies and \(songCount) songs")
// Prints "Media library contains 2 movies and 3 songs"
만약 각 반복에서 MediaItem이 Movie 인스턴스라면
item is Movie는 true를 리턴하고 그렇지 않다면 false를 리턴합니다.
유사하게, item is Song은 for문에서의 item 상수가 Song 인스턴스인지 체크합니다.
Downcasting
특정 클래스 타입의 상수나 변수는 실제로 서브클래스의 인스턴스일 수 있습니다.
이러한 경우에 타입 캐스트 연산자인 as?나 as! 를 사용하여
서브클래스 타입으로 다운캐스팅할 수 있습니다.
다운캐스팅이 실패할 수 있기 때문에 타입캐스트 연산자에는 두 가지 형태가 있습니다.
하나는 as?이며 다운캐스팅하려는 타입의 옵셔널 값을 리턴합니다.
다른 하나는 as!이며 다운캐스팅을 시도하고 강제로 언래핑합니다.
조건 형태인 타입캐스트 연산자인 as? 는 다운캐스팅이 성공한다고 확신할 수 없을 때 사용합니다.
이 연산자는 항상 옵셔널 값을 반환하며 만약 다운캐스팅이 가능하지 않다면 그 값은 nil이 됩니다.
강제 언래핑 형태인 타입캐스트 연산자 as! 는 다운캐스팅이 항상 성공한다고 확신할 때만 사용합니다.
만약 올바르지 않은 클래스 타입으로 다운캐스팅하려고 하면 런타임 에러를 발생시킵니다.
아래 예제 코드는 for-in을 통해 위에서 정의한 library 배열을 순환하고
각 항목의 description을 출력합니다.
이를 위해서 각 항목을 MediaItem이 아닌 실제 Movie나 Song으로 접근해야 합니다.
이 예에서는 배열의 각 항목이 Movie일 수도 있고 Song일 수도 있습니다.
각 항목에 사용할 실제 클래스를 미리 알 수 없으므로
as? 연산자를 이용해 다운캐스트를 하는 것이 적절합니다.
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)")
}
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley
Movie로 다운캐스팅을 성공하면 Movie description이 출력됩니다.
만약 다운캐스팅에 실패하면 Song 타입으로 다운캐스팅을 시도하고 성공하면 Song description을 출력합니다.
캐스팅은 실제로 인스턴스를 수정하거나 인스턴스 값을 변경하지 않습니다.
기본 인스턴스는 그대로 유지되며 단순히 해당 인스턴스가 캐스팅된 유형의 인스턴스로 처리되고 참조됩니다.
Type Casting for Any and AnyObject
Swift는 지정되지 않은 타입들에 대해 작업을 할 수 있는 두 개의 특별한 타입을 제공합니다.
- Any : 함수 타입을 포함한 모든 타입의 인스턴스를 나타냅니다.
- AnyObject : 모든 클래스 타입의 인스턴스를 나타냅니다.
Any 및 AnyObject 타입은 제공된 동작과 기능이 명시적으로 필요한 경우에만 사용해야 합니다.
코드 내에서 작업할 수 있는 타입을 구체적으로 지정하는 것이 좋습니다.
다음은 Any 타입을 사용하여 다른 타입들을 섞어서 사용하는 예제 코드입니다.
var things: [Any] = []
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
things 배열은 클로저를 포함한 여러 타입을 모두 가지고 있습니다.
Any 또는 AnyObject 타입에서 알고 있는 상수나 변수의 특정 타입을 찾으려면,
switch문에서 is 또는 as 패턴을 사용할 수 있습니다.
아래 예제 코드는 for문으로 things 항목들을 순회하며 각 항목을 switch문으로 쿼리 합니다.
일부 switch문의 케이스는 그 값을 지정된 타입으로 바인딩하여 출력합니다.
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called \(movie.name), dir. \(movie.director)")
case let stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
Any 타입은 옵셔널 타입을 포함하여 모든 타입의 값을 나타냅니다.
Swift는 Any 타입의 값이 예상되는 옵셔널 값을 사용할 경우 경고를 표시합니다.
옵셔널 값을 Any 타입으로 사용해야 하는 경우
as 연산자를 사용하여 아래와 같이 옵셔널을 Any로 명시적으로 캐스팅할 수 있습니다.
let optionalNumber: Int? = 3 things.append(optionalNumber) // Warning things.append(optionalNumber as Any) // No warning
참고
https://docs.swift.org/swift-book/LanguageGuide/TypeCasting.html