티스토리 뷰

728x90

타입 캐스팅은 인스턴스의 타입을 확인 하거나, 인스턴스를 같은 계층에 있는 다른 상위클래스나 하위클래스로 취급하는 방법입니다. 타입캐스팅에는 isas 두 연산자를 사용하고, 타입 캐스팅을 사용하면 프로토콜을 따르는지도 확인할 수 있습니다.


타입캐스팅을 위한 클래스 계층구조 선언
(Defining a Class Hierarchy for Type Casting)

 

아래는 타입캐스팅 확인을 위해 만든 클래스 입니다. 

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



다음은 두 클래스 들은 MediaItem의 하위클래스 입니다.

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

Movie 클래스와 Song 클래스 둘 다 MediaItem의 하위 클래스이므로 name을 상속 받고,

각각 director/ artist 의 프로퍼티를 갖고 있습니다. 

 

마지막으로 MovieSong의 인스턴스를 포함하고 있는 배열의 상수 library를 생성합니다. 

library는 타입추론에 의해 [MediaItem] 배열의 타입을 갖게 됩니다.

library를 순회 하면 배열의 아이템은 Movie, Song 타입이 아닌 MediaItem 타입인걸 확인할 수 있습니다. 

let libraryItemType = library.map { $0 }

 


타입 확인 (Checking Type)

 

is 연산자를 사용하여 인스턴스의 타입을 확인할 수 있습니다. 

아래 예제는 libarary 배열을 for-in 문으로 순회하고 item이 특정 타입일때 마다 해당 count를 증가시키는 코드입니다.

var movieCount = 0
var songCount = 0

for item in library {
    if item is Movie {
        movieCount += 1
    } else if item is Song {
        songCount += 1
    }
}

// Media library contains 2 movies and 3 songs

 


다운캐스팅 (Downcasting)

캐스팅: 실제 인스턴스를 지정한 타입으로 취급 하는것

 

as?as! 연산자를 사용해 어떤 타입의 인스턴스인지 확인할 수 있습니다.

as? 는 특정 타입이 맞는지 확신할 수 없을때 사용하며, 런타임에에 타입 캐스팅을 하며 만약 타입 캐스팅에 실패하게 되면 nil을 반환합니다.

as! 는 특정 타입이라는것이 확실할 경우에만 사용합니다. 만약 타입 캐스팅에 실패하게 되면 런타임에러가 발생합니다.

 

아래 예제는 library 배열의 mediaItem이 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

 

as!를 사용하게 되면 런타임에러가 발생하여,
사용자 입장에서는 앱이 꺼지는 경험을하기 때문에 가능하면 as? 를 사용하는걸 권장 합니다.

 

업 캐스팅 (Upcasting)

서로 상속 관계에 있는 클래스에서 자식클래스를 부모클래스로 타입캐스팅 하는것을 말하며,

as 키워드를 사용하여 업 캐스팅 할 수 있습니다.

 

컴파일 타임에 업 캐스팅이 가능한지 여부가 판별되기 때문에 컴파일이 되면, 항상 성공 합니다.

class Student {
    let name: String

    init(name: String) {
        self.name = name
    }
}

class HighSchoolStudent: Student {
    let gpa: Double

    init(name: String, gpa: Double) {
        self.gpa = gpa
        super.init(name: name)
    }
}

let peppo = HighSchoolStudent(name: "peppo", gpa: 4.5)
let upCasted = peppo as Student

print(peppo.name, peppo.gpa)
print(upCasted.name, upCasted.gpa) // compile error

 

마지막 줄의 경우는 upCasted가 Student 타입으로 업캐스팅 되었기 때문에,

프로퍼티는 이제 name만 갖게 됩니다. upCasted를 조회해보면 아래와 같이 나옵니다.


Any, AnyObject의 타입 캐스팅 (Type Casting for Any and AnyObject)

  • Any - 모든 타입을 나타냅니다.
  • AnyObject - 모든 클래스 타임의 인스턴스를 나타냅니다.

 

아래는 Any 타입의 예제 입니다. 

things라는 Any타입 배열을 선언하여 여러 타입의 값을 저장합니다.

여기에는 Int, String, 함수, 클로저까지 포함됩니다.

 

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를 순회하며 타입캐스팅이 되는지 switch case 문에 as 연산자로 확인해 타입캐스팅 되는 배열의 원소 값을 출력합니다.

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 123
// 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
NOTE

Any 타입은 옵셔널 타입을 포함합니다.
아래처럼 Any 타입을 사용해야 하는 곳에 옵셔널을 사용하면 경고가 나옵니다. (코드 2번째줄)
let optionalNumber: Int? = 3
things.append(optionalNumber)        // warning: Expression implicitly coerced from 'Int?' to 'Any'
things.append(optionalNumber as Any) // No warning
728x90