타입(Type)은 어떤 데이터를 해석하는 방법입니다. 같은 데이터도 타입에 따라 전혀 다른 의미와 기능을 가지게 할 수 있습니다. 그리고 이러한 타입을 바꾸어 데이터를 해석하는 방법을 바꾸는 것이 타입 캐스팅입니다. 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)
    }
}

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")
] // library의 타입은 [MediaItem]으로 swift의 타입 시스템이 추론해줍니다.  

위 코드를 기반으로 아래에서 타입 관련 연산자를 살펴보도록 하겠습니다.

  • 캐스팅 가능성 검사 - 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")
      //"Media library contains 2 movies and 3 songs"
    
  • 캐스팅 - as, as?, as!
    as 연산은 타입 캐스팅을 하는데에 사용되는 연산입니다. 아무 타입이나 허용하는 것은 아니고, 계층 관계를 가지는 타입 간에만 가능합니다. 계층 관계가 없는 타입간에는 무조건 캐스팅이 실패하게 됩니다.

    1. as - 컴파일 타임에 성공 여부를 체크할 수 있는(즉, 무조건 성공할 수 밖에 없는) 캐스팅을 수행합니다. 이는 자신의 부모 클래스로 캐스팅하는 업캐스팅(Upcasting), Swift의 기본타입들을 그에 대응하는 Foundation 클래스 타입으로 변환하는 브릿징(Bridging)을 의미합니다.

    2. as?, as! - 컴파일 타임에 성공 여부를 체크할 수 없고, 런타임에 실행해봐야만 결과를 알 수 있는 캐스팅에 사용합니다. (하지만 전혀 무관한 타입으로의 캐스팅은 컴파일러가 경고를 합니다) 해당하는 캐스팅은 자식 클래스로 캐스팅하는 다운캐스팅(DownCasting)입니다. 다운캐스팅은 실패할 가능성이 있기 때문에, 옵셔널을 반환하도록 되어 있으며(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
    }
    
  • Any, AnyObject
    Swift에서는 두가지의 특별한 타입을 제공해줍니다.

    • Any: Swift에 존재하는 모든 타입(값 타입, 참조 타입, 함수 타입)을 대변합니다. 모든 타입은 Any 타입으로 업캐스팅이 가능합니다.

    • AnyObject: 모든 참조 타입(클래스)을 대변합니다. 모든 참조 타입은 AnyObject로 업캐스팅이 가능합니다. 다만 참조 타입이 아닌 다른 타입은 AnyObject로 업캐스팅 할 수 없습니다.

    Any와 AnyObject는 모든 타입을 담을 수 있다는 유연성을 부여해주지만, 사용할때는 실제 타입으로의 다운캐스팅이 거의 필수적이기 때문에 런타임 성능에 좋지 못한 영향을 주고, 설계상으로도 바람직하지 않습니다. 가능하면 구체 타입을 사용하는 것이 좋습니다. 하지만 파운데이션과 코코아 등 Objective-C의 영향이 남아 있는 곳에서는 여전히 이렇게 사용하는 부분이 있기 때문에 알고는 있어야 합니다.

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

    이 콜렉션의 원소들을 사용할때는 switch문과 is, as 구문을 조합하여 사용할 수 있습니다. 이때 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 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