【Swift】Appleの新言語「Swift」のリファレンスを読む(14) - Type Casting、Nested Types
- (1) - The Basic
- (2) - Basic Operators、Strings and Characters
- (3) - Collection Types
- (4) - Control Flow
- (5) - Functions
- (6) - Closures、Enumerations
- (7) - Classes and Structures
- (8) - Properties
- (9) - Methods、Subscripts
- (10) - Inheritance
- (11) - Initialization
- (12) - Deinitialization、Automatic Reference Counting
- (13) - Optional Chaining
- (14) - Type Casting、Nested Types
- (15) - Extensions
- (16) - Protocols
- (17) - Generics
- (18) - Advanced Operators
- (19) - Dynamic Type、Attribute、Special Literals
注意
- あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
- コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。- 他言語にそっくりな部分でも指摘しない。(自戒)
Type Casting(キャスト)
あんまり面白い話でもないのでささっと終わらせましょう。
Swiftではis演算子を使ってインスタンスの型をチェックしたり、as演算子を使ってキャストすることが出来ます。
Defining a Class Hierarchy for Type Casting
まず基底クラスを作成します。
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) } }
この二つのクラスをまぜこぜにしたArrayを作ってみます。
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の型はArray<MediaItem>になりますね、と言うお話。
Checking Type
libraryをfor-in構文でぐるぐる回すとMediaItemのインスタンスがもらえるんですが、それぞれ実体はMovieだったりSongだったりします。型によって処理を変えたい場合はis演算子を使って判断します。
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"
Downcasting
親クラスのインスタンスを派生クラスとして扱うにはas演算子を使用します。
が、クラスのキャストと言うものはいつだって危険に満ち溢れています。かと言っていちいちisで判断するのもだるいので、なるべくas?演算子を使いましょう。
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
Type Casting for Any and AnyObject
Swiftでは型システムに対し中指を立てるための型が二つあります。以下の二つです。
- AnyObject
- Any
AnyObjectは全てのクラスに対する基底クラスのようなものです。Anyは更にその上で、あらゆる型はAnyで表現出来ます。(クロージャやファンクションの型もAnyで表現出来る。)
まずはAnyObjectの説明から。AnyObjectのArrayを作成すると、どんなクラスでも格納可能と言う旧世紀の遺物のようなArrayを作ることが出来ます。
let someObjects: AnyObject[] = [ Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"), Movie(name: "Moon", director: "Duncan Jones"), Movie(name: "Alien", director: "Ridley Scott") ] 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
for-in構文で使用するArrayはas演算子で事前にダウンキャスト可能です。
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です。これはもう見てもらえばわかると思います。
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")) 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
Anyの話はどうでもいいです。switchとas演算子を組み合わせることでパターンマッチをキメられることの方が大事です。何でswitchで説明しなかったのか謎です。
ifではas?の方を使いましたが、switchではasでそのまま判定出来ます。
Nested Types(型のネスト)
Swiftではある型の中に別の型を宣言することが出来ます。逆にオブジェクト指向のパラダイムを持ちながらその手のことが出来ない言語ってあるんでしょうか。
Nested Types in Action
なんだか若干複雑なサンプルが載っています。
struct BlackjackCard { // nested Suit enumeration enum Suit: Character { case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?" } // nested Rank enumeration enum Rank: Int { case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King, Ace struct Values { let first: Int, second: Int? } var values: Values { switch self { case .Ace: return Values(first: 1, second: 11) case .Jack, .Queen, .King: return Values(first: 10, second: nil) default: return Values(first: self.toRaw(), second: nil) } } } // BlackjackCard properties and methods let rank: Rank, suit: Suit var description: String { var output = "suit is \(suit.toRaw())," output += " value is \(rank.values.first)" if let second = rank.values.second { output += " or \(second)" } return output } }
型のネストにだけ着目すると、こんな構造です。
BlackjackCard ├Suit └Rank └Values
正直「こんなことが出来る」ってだけで十分な気がしますが、それぞれネストしている型が何をやっているのか細かく見てみましょう。
// nested Suit enumeration enum Suit: Character { case Spades = "?", Hearts = "?", Diamonds = "?", Clubs = "?" }
SuitはCharacterを継承したenumです。メンバとしてトランプの模様を持っています。
// nested Rank enumeration enum Rank: Int { case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten case Jack, Queen, King, Ace struct Values { let first: Int, second: Int? } var values: Values { switch self { case .Ace: return Values(first: 1, second: 11) case .Jack, .Queen, .King: return Values(first: 10, second: nil) default: return Values(first: self.toRaw(), second: nil) } } }
Rankはちょっと複雑です。TwoからTenまではそれぞれの単語に対応した2〜10の値が入っています。Intのenumはこう言う書き方が許されています。(参考)
Jack, Queen, King, Aceに関してはちょっと特別なルールがあります。と言ってもブラックジャックのルールなんですがね。それを吸収するためにValuesと言う構造体とvaluesと言うread onlyなプロパティが用意されています。
struct Values { let first: Int, second: Int? } var values: Values { switch self { case .Ace: return Values(first: 1, second: 11) case .Jack, .Queen, .King: return Values(first: 10, second: nil) default: return Values(first: self.toRaw(), second: nil) } }
valuesのgetの中を見ていきましょう。ここでのselfはRankです。自分自身がRankのどのメンバかで処理を分岐させたいのでしょう。
ブラックジャックではAceの値を1か11か選ぶことが出来ます。だから自分自身がAceだったらfirstが1、secondが11のValuesを返します。
また、Jack, Queen, Kingのような絵札は一律10として扱います。それ以外(TwoからTenまで)はそのままの数字です。これらは別の値を選ぶことが出来ないので、secondがnilなValuesを返します。
これらを踏まえた上で、BlackjackCardのプロパティを見てみましょう。そんなに難しくないはずです。
// BlackjackCard properties and methods let rank: Rank, suit: Suit var description: String { var output = "suit is \(suit.toRaw())," output += " value is \(rank.values.first)" if let second = rank.values.second { output += " or \(second)" } return output }
イニシャライザでrankとsuitに値を渡し、descriptionを呼び出すとこんな感じになります。
let theAceOfSpades = BlackjackCard(rank: .Ace, suit: .Spades) println("theAceOfSpades: \(theAceOfSpades.description)") // prints "theAceOfSpades: suit is ?, value is 1 or 11"
Referring to Nested Types
ネストしている型でも「.」でつなげれば普通に呼び出せます。
let heartsSymbol = BlackjackCard.Suit.Hearts.toRaw() // heartsSymbol is "?"