스터디를 진행하며 처음부터 다시 Swift를 공부하고 있습니다.
오늘 작성할 파트는 타입캐스팅 입니다.
본래 순서라면.. 초기화를 다하고 하려했지만
초기화의 기초는 익혔기 때문에 다음 개념을 먼저 공부하고자 타입캐스팅을 작성하게 되었습니다.
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)
}
}
먼저 MediaItem
클래스와 그 클래스를 상속한 Movie
와 Song
클래스가 있다.
let library : [MediaItem] = [
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
의 슈퍼 클래스가 MediaItem
으로 동일하기에,
MediaItem
이란 클래스로 둘 다 업캐스팅한 것이다.
기본 타입으로 작업을 하려면 타입을 확인하거나 다른 타입으로 다운캐스트 해야한다.
인스턴스가 특정 하위 클래스 타입인지 확인하기 위해 타입 검사 연산자(is
)를 사용한다.
리턴타입은 Bool
이다.
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"
library
에 저장된 Movie
와 Song
이 몇개인지 루프와 조건을 돌려 이를 출력한다.
업캐스팅은 서브 클래스의 인스턴스를 슈퍼 클래스의 타입으로 참조하는 것을 말한다.
해당 개념을 설명하기 위해 예제를 들자면,
let theory = Movie.init(name: "cats", director: "Risk") as MediaItem
print("Movie : \(theory.name), dir : \(theory.director)")
해당 theory
를 프린트하면 Value of type 'MediaItem' has no member 'director' 에러가 뜬다.
위 코드는 Movie
인스턴스를 생성하지만, 이를 as
를 사용하여 MediaItem
으로 업캐스팅해서 theory
상수에 저장하겠다는 의미이기 때문이다.
좀 더 풀어서 설명하자면, theory
는 Movie
란 서브클래스를 MediaItem
이란 슈퍼클래스로 업캐스팅을 했기 때문에, theory
는 MidiaItem
에 있는 멤버밖에 접근을 하지 못하게 된 것이다.
다운캐스팅은 슈퍼 클래스 인스턴스를 서브 클래스의 타입으로 참조한다.
( 보통은 업캐스팅된 변수를 다시 서브클래스로 참조하고자 다운캐스팅을 쓴다고 함. )
다운 캐스트가 성공할지 확신이 없을 때 조건부 형식의 타입 캐스트 연산자 (as?
)를 사용하고,
다운 캐스트가 항상 성공할 것이라는 확신이 있을 때만 강제 형식의 타입 캐스트 연산자 (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
와 Song
두 가지 선택지가 있어, 각 항목에 사용할 실제 클래스를 미리 알지 못하므로,
as?
를 사용하는 것이 적절하다.
Any
는 모든 타입의 인스턴스를 나타낼 수 있고,
AnyObject
는 모든 클래스 타입의 인스턴스를 나타낼 수 있다.
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)" })
as는 업캐스팅 말고도 패턴 매칭에도 사용되는데,
switch 문을 활용해서, 해당 타입일 경우(캐스팅에 성공한 경우) case문이 실행될 수 있게 할 수 있다.
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와 AnyObject는 런타임 시점에 타입이 결정되기 때문에 컴파일 시점에는 해당 타입을 알 수 없다.
때문에 해당 타입의 멤버에 접근할 수 없다. 접근하려면 다운캐스팅을 사용하여야 한다.
제공하는 동작과 기능이 명시적으로 필요한 경우에만 Any 와 AnyObject 를 사용해야하고,
코드에서 작업할 것으로 예상되는 타입에 대해 구체적으로 지정하는 것이 좋다.
( Swift가 타입에 민감한 언어라 그런 것도 있긴함. )
if let movie = item as? Movie {
movie.append("credit_")
}
뭐.. 이런식으로..