[Swift] Type Casting

상 원·2022년 7월 27일
0

Swift

목록 보기
29/31
post-thumbnail

C에서는 타입캐스팅을 굉장히 많이 해 봤는데 SwiftUI를 다루면서 타입캐스팅을 별로 안 해 본 것 같다. 한번 배워보자구~

Type Casting은 인스턴스의 타입을 체크하거나, 이 인스턴스의 해당 클래스를 다른 슈퍼클래스나 서브클래스로 다루는 것을 의미함.

스위프트에서 타입캐스팅은 isas 연산자로 구현돼 있다. 이 두 연산자가 타입을 체크하고 다른 타입으로 바꾸는 역할을 해 줌.

Defining a Class Hierachy for Type Casting

타입캐스팅을 클래스 간의 계층이 있을 때도 사용할 수 있음!
클래스 인스턴스를 같은 계층의 다른 클래스 인스턴스 타입으로 변환할 수가 있다.
무슨 말인지 모르겠지만... 예제를 보도록 하자.


베이스 클래스인 MediaItem 이다.

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

각각 프로퍼티를 추가하고 그에 맞는 이니셜라이저도 추가했다.

얘네들을 사용해 보도록 하자.

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 배열은 두 서브클래스들의 인스턴스를 원소로 갖는다. 그럼 library 의 타입은?!
Swift의 type checker가 추정하기로는 MovieSongMediaItem 이라는 동일한 슈퍼클래스를 갖고 있으므로 library 배열을 MediaItem 의 타입으로 인식해버린다.

배열 안에 있는 원소들은 Movie와 Song 인스턴스이긴 하지만, 이 배열을 쭉 iterate 해보면 MediaItem 타입의 값을 반환받는다.
원래 타입으로 작업하고 싶다면

  • 타입을 체크하거나
  • 다른 타입으로 downcast 해야 한다.

Checking Type

is 가 바로 type check 연산자임!
얘는 인스턴스가 특정 서브클래스 타입인지 체크하고 맞다면 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 와 Song 인스턴스가 몇 개인지 세는 코드임.

item is Movie 에서 지금 item은 MediaItem 타입인데, 이게 얘의 서브클래스인 Movie 인스턴스인지 판단하는 거임. Song도 마찬가지.

Downcasting

만약 클래스 인스턴스가 서브클래스의 인스턴스와 동일하다면, as 를 이용해서 서브클래스 타입으로 만들어줄 수 있음! as?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

여기서 배열의 각 아이템들은 Movie 이거나 Song 이여야 하는데, 아이템마다 어떤 클래스를 써야 하는지 확실하게 알지 못하기 때문에 as? 를 써서 다운캐스팅 해야 함!

item as? MovieMovie? 타입의 결과를 가지기 때문에 if let 구문으로 언래핑해서 movie에 넣어준 것!

참고

캐스팅은 인스턴스를 바꾸거나 값을 바꾸지 않는다. 기존 인스턴스는 동일하게 유지되고, 캐스팅된 인스턴스 타입으로 취급만 되는 것!

Type Casting for Any and AnyObject

Swift는 두 가지 특별한 타입을 갖구있다.

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

thing 배열은 Any 타입인데, 말그대로 함수를 포함한 모든 타입을 넣을 수 있음.

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

이런식으로 iterate할 수 있다.

profile
ios developer

0개의 댓글