Swiftのプロトコル | プロトコル準拠のチェック(Checking for Protocol Conformance)

Swiftのプロトコル | プロトコル準拠のチェック(Checking for Protocol Conformance)

型キャスト(type casting)はプロトコルに対しても使用出来て、その場合プロトコルに準拠しているかどうかのチェックになります。型キャスト演算子はisasですが、基本的な使用方法は通常の型に使う場合と同じです。

ここでは、プロトコルに型キャスト演算子を使う場合の仕様を簡単にまとめた後に、具体例を使ってプロトコル準拠をチェックする方法を見ていきます。

プロトコルに対する型キャスト演算子

Swiftでは型キャスト演算子(isasを使って、インスタンスの型をチェックしたり、特定の型にキャストする(型を割り当てる)ことが出来ます。この機能は、プロトコルを対象とすることも可能ですが、その場合はインスタンスがプロトコルに準拠しているかが基準となります。

仕様を表にまとめると、

型キャスト演算子 プロトコルに準拠している プロトコルに準拠していない
is true false
as? オプショナル型プロトコル nil
as! プロトコル 実行時エラー

is演算子は自明で、プロトコルに準拠していればtrue、していなければfalseを返します。

as?as!演算子も、基本的には通常のダウンキャスト(downcasting)と同じですが、as?演算子の戻り値はオプショナル(optional)なプロトコル型インスタンス、as!演算子の戻り値はプロトコル型インスタンスになります。

型キャスト演算子を使ってプロトコル準拠をチェックする具体例

公式マニュアルと似たような具体例ですが、惑星プロトコルを使って型キャスト演算子の働きを確認します。

// 惑星プロトコル
protocol Planet {
     var name: String { get }
}

// 水星から火星まで
class Mercury: Planet { let name = "水星" }
class Venus: Planet { let name = "金星" }
class Earth: Planet {
    let name = "地球"
    let radius = 6371 // km
}
class Mars: Planet { let name = "火星" }

// 月は惑星じゃない
class Moon { let name = "月" }

// AnyObjectでひとまとめ
let solarSystem: [AnyObject] = [Mercury(), Venus(), Earth(), Mars(), Moon()]

// as?演算子でプロトコル準拠のチェック
for object in solarSystem {
    if let planet = object as? Planet {
        print("\(planet.name)は惑星です")
    }
}

順番に見ていきます。

プロトコルを用意

// 惑星プロトコル
protocol Planet {
     var name: String { get }
}

プロトコルPlanetプロパティname(名前)を要求しています。

プロトコルを採用したクラス、採用していないクラス

Planetを採用したクラスを色々作ってみると、

// 水星から火星まで
class Mercury: Planet { let name = "水星" }
class Venus: Planet { let name = "金星" }
class Earth: Planet {
    let name = "地球"
    let radius = 6371 // km
}
class Mars: Planet { let name = "火星" }

// 月は惑星じゃない
class Moon { let name = "月" }

このような感じになります。後で型キャスト演算子を使う時の比較対象として、惑星ではない月(Moonクラス)を用意しています。

AnyObjectで配列にまとめてas?でプロトコル準拠のチェック

AnyObject = あらゆるクラスに対応した型

型キャストのページで紹介しましたが、あらゆるクラスに対応した型としてAnyObjectが使えます。

// AnyObjectでひとまとめ
let solarSystem: [AnyObject] = [Mercury(), Venus(), Earth(), Mars(), Moon()]

上記のように配列(Arrays)の型をAnyObjectに指定し、配列要素として異なる型のクラスインスタンスを生成します。この場合、インスタンス本体の型は元々の型(例えばEarthとか)ですが、配列要素として取り出した時の型はAnyObjectになります。上記の例でも分かりますが、AnyObjectとして扱えるのは「クラスであること」だけが条件なので、そのクラスが基底クラスであるとか、派生クラスである、ということは関係ありません。

as?演算子でダウンキャスト

// as?演算子でプロトコル準拠のチェック
for object in solarSystem {
    if let planet = object as? Planet {
        print("\(planet.name)は惑星です")
    }
}
//水星は惑星です
//金星は惑星です
//地球は惑星です
//火星は惑星です

今作った配列に対してfor-in文でループを回して要素を取り出し、その配列要素に対してas?演算子でPlanetプロトコルにダウンキャストしようとしています。結果は、Moonクラス以外は全てダウンキャストに成功しているのが分かります。

型キャストのページでも説明しましたが、

if let planet = object as? Planet {

という構文は、ダウンキャスト演算とoptional bindingが組み合わさった構文になっています。

ダウンキャストされたインスタンスはプロトコル型として扱われる

上記の例でダウンキャストされたplanetは、表面上はPlanetプロトコルとして扱われます。したがって、例えばEarthクラスのみで定義されているradiusプロパティにはアクセス出来ません。

// クラス固有のプロパティ、メソッドにはアクセス出来ない
if let earth = solarSystem[2] as? Planet {
    print(earth.name) // これはOK
    print(earth.radius) // これはダメ
}
//error: value of type 'Planet' has no member 'radius'

プロトコル準拠が自明な場合はas!で強制的にダウンキャスト

今、配列solarSystemの要素は、最後の要素以外Planetプロトコルに準拠していることは知っているので、

// as!で強制的にダウンキャスト
let mercury = solarSystem[0] as! Planet
print(mercury.name)
//"水星"と表示

このように、as!演算子で直接Planetにダウンキャストすることも出来ます。as!でダウンキャストした場合は、オプショナル型ではなく通常のインスタンスになります。

当然ですが、Moonインスタンスに対して、準拠していないPlanetプロトコルにダウンキャストしようとするとエラーになります。

// Planetプロトコルに準拠していないMoonを無理やりダウンキャスト
let moon = solarSystem[4] as! Planet
// 実行時エラー
//error: Execution was interrupted, ....
//Could not cast value of type 'Moon' to 'Planet'.

まとめ

  • プロトコルに準拠しているかどうかのチェックに型キャスト演算子が使える
  • is演算子はプロトコルに準拠していればtrue、していなければfalse
  • as?演算子はプロトコルに準拠していれば、オプショナルプロトコル型としてダウンキャスト。準拠していない場合はnil
  • as!演算子はプロトコルに準拠していれば、プロトコル型としてダウンキャスト。準拠していない場合は実行エラー
  • ダウンキャストした場合はプロトコルとして扱われ、クラス等が持つ固有のプロパティなどは使えないので注意が必要

Swiftのプロトコル | オプショナルプロトコル要求