[Swift] 타입 캐스팅(Type Casting)

LEEHAKJIN-VV·2022년 7월 11일
0

Study-Swift 5.6

목록 보기
22/22

참고사이트:
English: The swift programming language


타입 캐스팅(Type Casting)

타입 캐스팅(Type Casting)은 인스턴스의 타입(type)을 확인하거나 다른 슈퍼클래스 또는 서브 클래스로 변환하는 방법이다.

Swift에서 타입 캐스팅은 2가지 연산자 isas를 사용한다.

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

클래스의 계층구조(hierarchy), 서브 클래스에서 타입캐스팅을 사용하여 특정 클래스 인스턴스의 타입을 확인하고, 같은 계층구조에서 인스턴스의 타입을 다른 클래스 타입으로로 변환할 수 있다. 아래에 3개의 코드 블록은 타입 캐스팅의 설명을 위해 클래스들의 계층구조와 계층구조에 속한 클래스의 인스턴스를 담은 배열을 구현한다.

아래 첫 번째 코드 블록은 based class(계층구조의 최상위 클래스)MediaItem을 정의한다. 이 클래스는 디지털 미디어 라이브러리에 표시되는 모든 종류를 나타낸다. 또한 String 타입의 name 프로퍼티를 가지며 이니셜 라이저를 포함한다.

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

다음 코드 블록은 MediaItem클래스를 상속하는 두 개의 서브 클래스(Movie, Song)을 정의한다. 두 클래스 모두 디지털 라이브러리에 포함되는 종류로서, director, artist와 같이 추가적인 정보를 캡슐화(encapsulates)한다.

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 클래스 인스턴스를 저장하는 상수 배열 library를 선언한다. library 배열의 타입은 array literal의 내용으로 초기화 됨으로써 이를 기반으로 추론된다. Swift의 타입 검사기는 MovieSong의 슈퍼클래스가 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타입의 인스턴스다. 그러나 배열의 각 항목을 순회하는 경우 각각의 element들은 MediaItem 타입으로 접근이 된다. 그러므로 MediaItem 타입이 아닌 원래의 타입인 Movie, Song타입으로 사용하려면, 타입 확인(Checking Type)하거나 다운 캐스팅(Downcasting)해야 한다. 이는 아래에 자세히 설명한다.

library 배열의 element 접근시 모습

타입 확인(Checking Type)

인스턴스가 특정 타입의 서브 클래스인지 확인하기 위해 타입 확인 연산자인 is를 사용한다. 타입 확인 연산자는 인스턴스가 특정 타입의 서브 클래스 이면 true를 반환하며 아닌 경우에는 false를 반환한다.

아래에 is연산자의 사용방법을 확인하기 위해, 두 개의 변수 movieCountsongCount를 선언하여 library 배열 안에 있는 MovieSong인스턴스의 수를 카운트한다.

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"

DownCasting(다운 캐스팅)

특정 클래스 타입의 상수나 변수는 서브 클래스의 인스턴스를 참조할 수 있다. 참조를 하기 위해서는 타입 캐스트 연산자(as?or as!)를 사용하여 특정 타입의 서브 클래스로 downcast해야 한다.

타입 캐스트 연산자가 두 가지 다른 형태인 이유는 실패할 수 있기 때문이다. as?형태는 다운 캐스트 하려는 타입의 옵셔널 타입을 반환한다. as! 형태는 다운캐스트와 강제적 언래핑(force-unwrap)을 모두 수행한다.

그렇기 때문에 다운캐스트의 성공이 보장되는 경우에 as!를 사용하며, 확신이 없는 경우에는 as?를 사용한다. as!에 사용되는 !는 옵셔널 타입의 forced unwrapping과 마찬가지로 올바른 클래스 타입으로 다운캐스트 하지 않는 경우 런타임 에러를 트리거 한다.

as?연산자는 올바른 클래스 타입으로 다운캐스트 한 경우 옵셔널 타입을 반환하며, 아닌 경우에는 nil을 반환한다.

아래 예제는 library 배열의 element를 순회하면서 각 항목에 대해 설명을 한다. 그러므로 MediaItem타입이 아닌 Mobie, Song타입의 클래스 프로퍼티에 접근해야 한다. 이는 위에서 소개한 다운캐스트 연산자(as?, as!)를 사용하면 된다.

위에서 선언한 library배열의 인스턴스는 MovieSong타입 둘 중 하나이나, 특정 타입이라고 확신할 수 없다 그러므로 for loop에서 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

for loop를 순회하는 도중, 만약 as?연산자를 통해 현재 element의 인스턴스가 Movie타입인 경우 첫 번째 조건문에서 nil이 아닌 옵셔널 타입의 Movie?를 반환하며 이는 optional binding에 의해 접근된다. 그러므로 Movie클래스의 프로퍼티인 director에 접근하여 각 항목을 출력할 수 있다. 이는 Song 타입의 인스턴스인 경우도 마찬가지다.

NOTE
캐스팅(Casting)은 실제로 인스턴스나 타입이나 그 값을 바꾸지 않는다. 즉 캐스팅에 사용되는 기본 인스턴스는 그대로 유지된다.

Any와 AnyObject의 타입 캐스팅

Swift는 두가지 특이한 타입을 제공한다.

  • Any: 함수 타입과 모든 인스턴스의 타입을 나타낼 수 있음

  • AnyObject: 모든 클래스 타입을 나타낼 수 있음

AnyAnyObject는 필요한 경우에만 사용하며 이보다 특정 타입을 명시하는 것이 항상 더 좋다고 한다.(the swift programming language에 기술되어 있음)

아래 예제는 배열 things프로퍼티를 선언하며 이는 [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)" })

Any 또는 AnyObject 타입으로 선언된 상수나 변수의 내부 타입을 알기 위해선 switch 문의 case에서 is 또는 as 패턴을 사용할 수 있다.

아래 예제는 switch 문에서의 사용 예시로, for loop를 순회하면서 각 element에 대해 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")
    }
}

NOTE
Any타입은 optional 타입을 포함하여 모든 타입의 값을 나타낼 수 있다. 그러나 Any타입이 필요한 곳에 optioanl 값을 사용하면 swift는 waring을 준다. 그러므로 optional 값의 사용이 반드시 필요한 경우, as연산자를 사용하여 Any타입으로 casting하여 사용하면 된다.

Casting 예시

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning

0개의 댓글