iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら。
今回は、Type Casting に関して。
Type Casting
Swift での Type Casting は、as
と is
オペレーターにて実装されます。また、Type Casting をそのタイプがあるプロトコルに準拠しているかを確認するためにも使用できるようです。
1. 準備編
まずは、この Type Casting をデモンストレイトするためにクラスを用意します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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) } } |
そして、初期化。
1 2 3 4 5 6 7 8 | 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 のインスタンスを持っています。これらのインスタンスのスーパークラスは、どちらも MediaItem なので、Swift はこの共通のタイプ(クラス)を推論してくれます。つまり、library 配列は、MediaItem[]
という型になります。
2. タイプチェック
あるタイプが(クラス)が、あるサブクラスであるかを確認するには、is
オペレータを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 | var movieCount = 0 var songCount = 0 for item in library { if item is Movie { ++movieCount } else if item is Song { ++songCount } } println("Media library contains \(movieCount) movies and \(songCount) songs") // prints "Media library contains 2 movies and 3 songs" |
このコードは、library 配列が保持する要素が Movie インスタンスか Song インスタンスかを確認して、それぞれ数を数えています。読みやすいと思います。
3. Downcasting
あるコンスタンス・変数が、あるサブクラスであることが推定される場合には、as
オペレータを使いキャストします。もちろん、キャスティングは失敗することがあります。そのため as?
という Optional にも対応しています。as
を使い間違ったキャストをするとランタイムエラーになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 | for item in library { if let movie = item as? Movie { println("Movie: '\(movie.name)', dir. \(movie.director)") } else if let song = item as? Song { println("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?
を使い条件分岐し、それぞれ対応しています。
4. Any と AnyObject
Swift は 2 つの特定の型に依存しないタイプエイリアスを持っています。AnyObject
と Any
があります。
まずは、AnyObject
からみていきます。AnyObject
はどんなクラスタイプでも表すことができます。Cocoa API を扱っているときに、AnyObject[]
を受け取ることがよくあります。Objective-C の id のようなものですかね。AnyObject がどんなタイプのインスタンスを持っているかわかっている時には、この AnyObject をキャストして使用できます。
1 2 3 4 5 | let someObjects: AnyObject[] = [ Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), Movie(name: "Moon", director: "Duncan Jones"), Movie(name: "Alien", director: "Ridley Scott") ] |
このように AnyObject[]
に Movie のインスタンスを格納しています。この場合には、問題なくキャスト(as
)して使用します。
1 2 3 4 5 6 7 | for object in someObjects { let movie = object as Movie println("Movie: '\(movie.name)', dir. \(movie.director)") } // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick // Movie: 'Moon', dir. Duncan Jones // Movie: 'Alien', dir. Ridley Scott |
このようにより簡潔に書くこともできます。
1 2 3 4 5 6 | for movie in someObjects as Movie[] { println("Movie: '\(movie.name)', dir. \(movie.director)") } // Movie: '2001: A Space Odyssey', dir. Stanley Kubrick // Movie: 'Moon', dir. Duncan Jones // Movie: 'Alien', dir. Ridley Scott |
次に Any
をみていきます。Any
は、どんなタイプのインスタンスも表すことができます。しかし、関数はできません。AnyObject
は、どんなクラスタイプでしたが、Any
は、文字通り関数以外何に対しても使えます。下記の例では、Int や Double, tuple 等を配列の中に保存しています。
1 2 3 4 5 6 7 8 9 | 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 変数は、どんなタイプでも格納しています。これを処理するために switch 文を使ってそれぞれ対応できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | for thing in things { switch thing { case 0 as Int: println("zero as an Int") case 0 as Double: println("zero as a Double") case let someInt as Int: println("an integer value of \(someInt)") case let someDouble as Double where someDouble > 0: println("a positive double value of \(someDouble)") case is Double: println("some other double value that I don't want to print") case let someString as String: println("a string value of \"\(someString)\"") case let (x, y) as (Double, Double): println("an (x, y) point at \(x), \(y)") case let movie as Movie: println("a movie called '\(movie.name)', dir. \(movie.director)") default: println("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 |
この switch 文では、as
(forced version of the type cast operator)を使っていますが、switch 文のコンテキストではいつも安全です。