型キャストとは継承ツリーの中のあるスーパークラスの変数を、そのサブクラスのインスタンスと看做せるか判定したり、変換したりする仕組みの事をいいます。
言語によっては、異なる型間での変換(例えば、整数と浮動小数点数等)も型キャストと呼ぶ場合がありますが、Swiftでは、基本的に同じベースクラスをスーパークラスにもつ型同士で扱われます。
例として、以下の様なGUIコントロールのベースクラスを継承する、ラベル、ボタン、テキストボックスを使って説明します。
/* GUIコントロール */
class GUIControl {
var x, y, width, height: Float // 位置と幅、高さ
// イニシャライザ
init(x: Float, y: Float, width: Float, height: Float) {
self.x = x
self.y = y
self.width = width
self.height = height
}
}
/* ラベル */
class Label: GUIControl {
var text: String // テキスト
// イニシャライザ
init(x: Float, y: Float, width: Float, height: Float, text: String = "") {
self.text = text
super.init(x: x, y: y, width: width, height: height)
}
}
/* ボタン */
class Button: Label {
var _pushed = false // 押された?
var isPushed: Bool {
return _pushed
}
}
/* テキストボックス */
class TextBox: Label {
var readOnly = false // 入力不可?
}
上の各クラスのインスタンスを適当に生成して配列に格納します。
// コントロールの配列
let controls = [
Label(x: 10, y: 10, width: 100, height: 30, text: "名前"),
TextBox(x: 120, y: 10, width: 100, height: 30),
Label(x: 10, y: 50, width: 100, height: 30, text: "住所"),
TextBox(x: 120, y: 50, width: 100, height: 30),
Button(x: 120, y: 50, width: 100, height: 30, text:"送信")
]
ここでは配列に格納する型を明示していませんが、全ての要素がLabelという共通のクラスを継承しているので、型は自動的に、[Label]と類推されます。
Labelが継承しているGUIControlも共通のクラスですが、継承ツリーの中でより詳細な下位の共通クラスが類推されます。
もし、この配列の中に共通のスーパークラスを持たないクラスのインスタンスが含まれていた場合、型は[AnyObject]となります。AnyObjectは、全てのクラスを表すことができる特別な型です。但し、明示的にAnyObjectを継承してクラスを定義することはできません。
また、配列の中に、クラスのインスタンスでない型(IntやString、構造体のインスタンス等)が含まれていた場合、型は[Any]と類推されます。Anyは、関数型を除く全ての型を表すことができる型です。
インスタンスが継承ツリーの中のあるサブクラスのインスタンスかどうかは、is演算子を使って判定できます。
次の例では、上で作成した配列をループしながら、それぞれのクラスのインスタンスの数を数えています。
var labelCount = 0 // ラベル数のカウント
var buttonCount = 0 // ボタン数のカウント
var textBoxCount = 0 // テキストボックスのカウント
for control in controls {
if control is Button {
++buttonCount
} else if control is TextBox {
++textBoxCount
} else {
++labelCount
}
}
println("ボタン:\(buttonCount)個、テキストボックス:\(textBoxCount)個、ラベル:\(labelCount)個")
/* 実行結果
ボタン:1個、テキストボックス:2個、ラベル:2個
*/
上の例では、配列は[Label]と類推されているので、control is Label は必ず真となります。常に真なのでループの中でこのコードを記述しても無意味なコードということでコンパイルエラーになります。
あるインスタンスが、あるクラス又はそのスーパークラスのインスタンスであることが明確な場合は、as!演算子を使ってその型へ変換(ダウンキャスト)することができます。
var control: GUIControl
control = controls[3]
let textBox = control as! TextBox
この時、型が合っておらずダウンキャストが失敗した場合、実行時エラーが発生します。
ダウンキャスト可能かどうか明確でない場合は、as?演算子を使って、オプショナル型を受け取るようにすることができます。この場合、ダウンキャストに失敗するとnilが返されます。
var control: GUIControl
control = controls[3]
// 下のbuttonはButton?型
// ダウンキャストに失敗したらnilになる
let button = control as? Button
as?演算子はオプショナル型を返すので、if文でオプショナルバインディングを使うことができます。
for control in controls {
if let button = control as? Button {
let isPushed = button.isPushed ? "押された" : "押されていない"
print("ボタン:\(button.text) \(isPushed)")
} else if let textBox = control as? TextBox {
let readOnly = textBox.readOnly ? "入力不可" : "入力可"
print("テキストボックス:\(textBox.text) \(readOnly)")
} else {
print("ラベル:\(control.text)")
}
}
/* 実行結果
ラベル:名前
テキストボックス: 入力可
ラベル:住所
テキストボックス: 入力可
ボタン:送信 押されていない
*/
AnyObjectはどのクラスにも適用できるので、次のようにAnyObject型の配列にLabelクラスのインスタンスを保持することもできます。
配列の要素は全てLabel型なので、as!演算子を使って型キャストしても実行時エラーはおきません。
let objects: [AnyObject] = [
Label(x: 10, y: 10, width: 100, height: 30, text: "名前"),
Label(x: 10, y: 50, width: 100, height: 30, text: "住所"),
Label(x: 10, y: 90, width: 100, height: 30, text: "電話番号"),
]
for object in objects {
let label = object as! Label
print("ラベル:\(label.text)")
}
上のループは次のように、配列自体を型キャストしてもう少し簡単に記述できます。
for label in objects as! [Label] {
print("ラベル:\(label.text)")
}
Anyは、関数型を除くあらゆる型を表すことができるので、次のようにAny型の配列にさまざまな値を保持させることができます。
let things: [Any] = [
100,
-23.56,
"吾輩は猫である",
("救急車", 119),
Label(x: 10, y: 10, width: 100, height: 30, text: "名前"),
TextBox(x: 120, y: 10, width: 100, height: 30),
Button(x: 120, y: 50, width: 100, height: 30, text: "送信")
]
switch文の中でas演算子(as!ではありません)やis演算子を使ってこれらの型を調べることができます。
for thing in things {
switch thing {
case 100 as Int:
print("整数:100")
case is Int:
print("100以外の整数")
case let d as Double where d < 0:
print("負の浮動小数点数:\(d)")
case is Double:
print("0以上の浮動小数点数")
case let s as String:
print("文字列:\(s)")
case let (contact, tel) as (String, Int):
print("\(contact)の電話番号:\(tel)")
case let button as Button:
print("ボタン:\(button.text)")
case let c as GUIControl:
print("ボタン以外のGUIコントロール:(x:\(c.x), y:\(c.y), width:\(c.width), height:\(c.height))")
default:
print("不明な型")
}
}
/* 実行結果
整数:100
負の浮動小数点数:-23.56
文字列:吾輩は猫である
救急車の電話番号:119
ボタン以外のGUIコントロール:(x:10.0, y:10.0, width:100.0, height:30.0)
ボタン以外のGUIコントロール:(x:120.0, y:10.0, width:100.0, height:30.0)
ボタン:送信
*/