型キャスト

型キャストとは継承ツリーの中のあるスーパークラスの変数を、そのサブクラスのインスタンスと看做せるか判定したり、変換したりする仕組みの事をいいます。

言語によっては、異なる型間での変換(例えば、整数と浮動小数点数等)も型キャストと呼ぶ場合がありますが、Swiftでは、基本的に同じベースクラスをスーパークラスにもつ型同士で扱われます。

例として、以下の様なGUIコントロールのベースクラスを継承する、ラベル、ボタン、テキストボックスを使って説明します。

type_cast.png

/* 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)
ボタン:送信
*/