dev._.note

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

Dev/SWIFT

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

Laena 2023. 12. 5. 20:42

📌 타입 캐스팅(Type Casting)

인스턴스의 타입을 확인하거나, 해당 인스턴스를 자신의 클래스 계층에 있는 상위 혹은 하위 클래스로 처리하는 방법

 

  1. 타입 캐스팅은 is 와 as 연산자로 구현.
  2. 이 두 연산자는 값의 타입을 확인하거나 값을 다른 타입으로 지정.
  3. 단순하면서도 보고 이해하기 쉬운 표현 방식.
  4. 해당 타입이 프로토콜을 따르는지(Protocol Conformance)도 확인가능.

타입 캐스팅 연산자 4종류

expression is type
expression as type
expression as? type
expression as! type
  • is 연산자는 런타임에 expression 이 특정 type 으로 캐스팅 되는지 체크. bool타입 return.
  • as 연산자는 컴파일 단계에서 캐스팅이 실행. 언제나 특정 type 으로 캐스팅이 성공할 때만 사용이 가능. 업캐스팅(Upcasting) 혹은 브릿징(Bridging) 에 사용.
  • as? 연산자는 런타임에 캐스팅하여 특정 type 의 옵셔널을 반환. 성공하면 옵셔널 값을 반환하고 실패하면 nil 을 반환.
  • as! 연산자는 런타임에 특정 type 으로 강제 캐스팅. 캐스팅에 실패할 경우 런타임 에러가 발생할 수 있으므로 사용권장 안함.

업캐스팅(Upcasting): 해당 타입의 상위 타입의 인스턴스로 캐스팅

브릿징(Bridging): NSString 과 같은 Foundation 타입을 String 과 같은 스위프트 표준 라이브러리로 캐스팅


타입 체크와 다운캐스팅 (Checking Type & Downcasting)

타입 캐스팅을 위해 클래스 계층 예시를 만들어 보겠습니다.

먼저, 가장 상위 클래스인 MediaItem 을 정의.

class MediaItem { 
    var name: String

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

 

다음 MediaItem 클래스를 상속받는 Movie  Song 클래스 두 개를 선언.

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 클래스를 상속받기 때문에 공통적으로 name 을 받아 초기화하며 Movie 클래스의 경우 추가적으로 director 를 받아 초기화하고 Song 클래스의 경우 artist 를 받아 초기화.

 

마지막으로, Movie  Song 인스턴스를 담는 library 배열을 생성.

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]

 

스위프트의 타입 체커는 Movie  Song 이 공통적으로 MediaItem 을 부모클래스로 가지는 것을 알고 library 의 타입을 [MediaItem] 으로 추론함. 그렇기 때문에 실제로 해당 배열에서 값을 꺼내서 쓸 때는 MediaItem으로 추출이 되기 때문에 Movie나 Song으로 사용하려면 다운캐스팅을 해주어야 함.


is 연산자로 타입 체크하기

타입 체크를 할 때는 is 연산자를 활용하면 인스턴스가 특정 하위 클래스 타입인지 확인.

인스턴스가 하위 클래스 유형이면 true 를 반환하고 아니면 false 를 반환. 

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 인스턴스는 2개, Song 인스턴스는 3개가 들어있는 것을 확인가능.


as 연산자로 다운캐스팅 하기

특정 클래스 타입의 상수나 변수가 실제로는 해당 인스턴스의 하위클래스를 나타내고 있을 수 있음.

이 때 as?  as! 연산자를 사용하여 하위 클래스로 다운캐스팅하여 하위클래스로 해당 인스턴스를 사용가능.

 

다운캐스팅은 실패할 수도 있기때문에, 두 가지 연산자가 있음. 

as? 의 경우 옵셔널 값을 반환.

as! 의 경우 강제 추출(force-unwrap)한 값을 반환.

 - as! 사용시 실패했을 때 런타임 에러의 위험이 있음.

 

다운캐스팅 성공에 대한 확신이 있을 때는 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

 

as? 연산자를 이용하여 다운캐스팅 하고 반환된 옵셔널 값을 if-let 구문으로 처리하는 방법. 다운캐스팅 하는 가장 일반적인 방법.

캐스팅은 실제로 인스턴스를 수정하거나 그 값을 변경하지 않음. 단순히 캐스팅 된 타입으로 해당 인스턴스를 처리하고 접근.


Any나 AnyObject를 타입 캐스팅 하는 방법

스위프트는 불특정한 타입을 다루기 위해 두 가지 특별한 타입을 제공.

  • 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 배열에서 특정 타입의 값을 찾아내기 위해서는 is  as 를 이용한 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 타입은 옵셔널 타입을 포함한 모든 타입을 나타낼 수 있음. 값 타입이 Any 타입으로 기대되는 곳에서 만약 옵셔널 값을 사용한다면 스위프트는 warning 을 나타냄. 이 때는 아래 처럼 as 연산자를 사용하여 옵셔널 타입을 Any 로 명시적 캐스팅하여 사용하면 warning 은 사라짐.

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

정리

  • is  as 연산자를 이용하여 타입 캐스팅을 할 수 있다.
  • is 연산자를 이용하여 타입 체크를 할 수 있다.
  • 다운캐스팅을 할 때는 as?  as! 연산자를 이용할 수 있다.

'Dev > SWIFT' 카테고리의 다른 글

[Swift] 키오스크 과제  (1) 2023.12.07
[Swift] 접근 제한자  (0) 2023.12.06
[Swift] didSet 과 willSet  (0) 2023.12.04
[Swift] readLine()  (2) 2023.12.02
[Swift] 계산기 만들기  (0) 2023.12.01