タイプチェックとタイプキャスト
Swiftでも、動作時におけるタイプチェック(型チェック)に関しての機能が提供されています。
AnyとAnyObject
Swiftでは、デフォルトの方法で宣言したクラスはすべて基底クラスとして扱われると前回述べました。包括的にすべての型を扱うための手段として、Objective-CにおけるNSObject型のような一般的なクラスに対する基底クラスは用いることがSwiftではできません。しかし、代わりにAnyObjectとAnyというタイプエイリアス[2]が用意されています。このタイプエイリアスの実態はプロトコルです。
[2] タイプエイリアスとは既存の型に他の名前を付けたものです。Swiftではtypealiasキーワードで他の型を別の名前の型として扱うことができます。
AnyObject
すべてのクラスはAnyObjectのプロトコルに準拠した型として扱うことができます。
class Solid {}
class Fluid {}
class Gas {}
let states: [AnyObject] = [Solid(), Fluid(), Gas()]
この例では、それぞれ基底クラスとして宣言されたSolid、Fluid、Gasクラスのオブジェクトを共通のAnyObjectとして扱い、それらを要素とする配列に代入しています。
Any
すべてのクラス、ストラクチャ、列挙型、タプルはAnyのプロトコルに準拠した型として扱うことができます。
class Val {}
var some: Int? = 1
let values: [Any] = [1, "string", Val(), some, (1, "string")]
この例では様々な型Int、String、Val、Optional>int<、(Int, String)をAnyとして扱い、それを要素とする配列に代入しています。
タイプチェックのis
Objective-Cでは動作時のクラスタイプチェックを、NSObjectのインスタンスメソッドisKindOfClass:で行うことができました。
NSString *string = @"string"; BOOL isString = [string isKindOfClass:[NSString class]]; // インスタンスがNSString またはそのサブクラスであるかどうか判定
Swiftでもis演算子を用いてタイプチェックを行うことができます。
class Place {
var discount: UInt
init(_ discount: UInt) {
self.discount = discount
}
}
class Factory : Place{}
class Store : Place{}
let places: [Place] = [Factory(400), Store(200), Factory(700), Store(100), Store(150)]
var discountOfFactories: UInt = 0
var discountOfStores: UInt = 0
for place in places {
if place is Factory {
discountOfFactories += place.discount
} else if place is Store {
discountOfStores += place.discount
}
}
discountOfFactories // 1100
discountOfStores // 450
is演算子は左辺にインスタンス、右辺に指定の型を置くことでインスタンスが指定の型であるかどうかをBool値で返します。
ただし、型の情報がコンパイル時にはっきりわかっている場合、結果がtrueであるかfalseあるかにかかわらずコンパイルエラーになります。
class Food {}
class Fish : Food {}
let someFish = Fish()
let someFood = Food()
someFish is Fish // コンパイルエラー
someFood is Fish // コンパイルエラー
この例の場合、someFish、someFoodはコンパイル時にいずれも型がはっきりしているために、双方isでタイプチェックを行おうとするとコンパイルエラーとなります。
タイプキャストのas
Objective-Cでは次の例のように、値の前に括弧を付けることでタイプキャストを行うことができました。
NSArray *values = @[@"string", @(2), [NSURL new]]; // 様々な型の値を持った配列を用意 NSInteger lengthOfString = ((NSString *)values[0]).length // 配列0番目のStringの長さを取り出す
Swiftではas演算子を用いて、より安全に値をタイプキャストすることが可能です。
Objective-Cでは動的なキャスト時に、インスタンス本来の型とキャスト先の型に不一致があっても、何も起きませんでした。Swiftでは、タイプキャスト時に型の不一致を検出した時点での挙動が定められています。
まず、as演算子には2種類の形式があります。is演算子の評価時と同様に、as演算子も左辺にインスタンス、右辺に指定の型を置くことで次の2種類の形式に応じた結果を返します。
as?形式
タイプキャストの結果をオプショナル列挙型にくるむことで、タイプキャストの安全性を高めることができます。
タイプキャストが成功した場合
指定の型とした値をオプショナル列挙型にくるんで返す。
タイプキャストが失敗した場合
nilとして値を返す。
as形式
タイプキャストが失敗しないという確信があるときにのみ利用します。as?形式とは異なり、ランタイムエラーが起きる可能性があるため、使用時には注意を要します。
タイプキャストが成功した場合
指定の型として値をそのまま返す。
タイプキャストが失敗した場合
ランタイムエラーを起こす。
as?形式とas形式の使用例を見てみましょう。
var values: [Any] = [1, 2, 3, "string", 5]
/* as?形式 */
var index = 0
for value in values {
if let string = value as? String {
break
}
index++
}
index // 3
/* as形式 */
for value in values {
var integer = value as Int // ループ4回目でランタイムエラー
println(integer)
}
この例では、1つ目のループでas?を用いています。本連載第3回で解説したオプショナルバインディングを用いることで、
value as? String
の結果がnilではない場合に、breakしてループを抜けます。配列valuesを初めから走査していき、最初のString型の値の番号がindexによってわかるという仕組みです。
2つ目のループでは、as形式を用いて変数valueをInt型にタイプキャストして変数integerに代入しています。4回目のループでは変数valueに"string"が代入されるため、タイプキャストが失敗してランタイムエラーが発生します。
また、AnyやAnyObjectの配列がより具体的な型の配列であるとわかっている場合には、配列ごとタイプキャストすることも可能です。
class Solid {}
class Fluid {}
class Gas {}
let states: [AnyObject] = [Solid(), Solid(), Solid()]
for value in states as [Solid] {
// この中でvalueはSolid型として扱うことができる
}
switch文でのisとas
is演算子によるタイプチェックや、as演算子によるタイプキャストは、次の例のようにして、switch文のcase条件内で行うことができます。
class Animal{}
class Plant{}
var values: [Any] = ["name", 1, 1.3, Animal(), Plant()]
for value in values {
switch value {
case 1 as Int: println("one") // (1)
case 1.0 as Double: println("one point zero") // (2)
case is Plant: println("plant") // (3)
case let string as String: println("string" + string) // (4)
default: println("other")
}
}
stringname one other other plant
この例のcaseでは、それぞれ次の条件が成立したときに、コロン以降の処理が実行されます。
(1) valueの値をInt型へタイプキャストでき、かつvalueの値が1である。
(2) valueの値をDouble型へタイプキャストでき、かつvalueの値が1.0である。
(3) valueの値の型がPlantである。
(4) valueの値をString型へタイプキャストでき、かつstringへvalueの値を代入できる。
