Swift プログラミング

基本構文

Swift 言語でインスタンスが目的のプロトコルに準拠しているかを判定する方法です。

Swift 言語のプロトコルがどういった位置づけに変わっているかも想像してみています。

Swift でインスタンスが目的のプロトコルに準拠しているかを判定するには

インスタンスが格納された変数で、そのインスタンスがどのプロトコルに準拠しているかを判定するのに、Objective-C言語 ではNSObjectクラス に実装されているconformsToProtocol:メソッド を使って、いつでも判定できるようになっていました。

これがSwift言語 になると、プロトコル 判定できる場面は限られるようです。

目的のプロトコル に準拠しているかを判定するには

Swift言語 でインスタンスがどのプロトコルに準拠しているかを判定するには、文法的には次のようにis演算子 を使って行えます。

if object1 is ProtocolA {

}

複数のプロトコルに準拠しているかを調べたい時は、次のようにprotocol を使って一度に調べることも可能です。

if object1 is protocol<ProtocolA,ProtocolB> {

}

こちら と同じように、as演算子as?演算子 を使って、インスタンスをプロトコルの型にキャストすることもできます。プロトコル型の変数を宣言する方法はこちら で紹介しています。

プロトコル判定での注意事項

準拠判定は Objective-C 互換プロトコル限定の機能

ただし、この方法を使って純粋な Swift クラスで判定しようとすると、次のようなビルドエラーが発生します。

Cannot downcast from 'Class' to non-@objc protocol type 'Protocol'

どうやらSwift言語 では、Objective-C 互換のプロトコル でないと、インスタンスがそれに準拠しているかを判定できない仕様になっているようです。

そのため、プロトコルを定義する場合は、次のように@objcディレクティブ をつけて宣言する必要があります。

@objc protocol ProtocolA {

}

このようにすることで、たとえばこのProtocolAプロトコル に準拠したMyClassクラス のインスタンスで、それがどのプロトコルに準拠しているかをis演算子 で判定できるようになります。

let object:AnyObject = MyClass()

if object is ProtocolA {

}

ここで注意したいのは、複数のクラスを継承していて、継承したそれぞれでプロトコルを実装している場合、実際のクラス(dynamicType で得られるクラス型)そのものが準拠しているプロトコルだけしか、この方法で判定できない場合があるようです。

深い階層のプロトコルも考慮するには

あるインスタンスが複数のクラスを継承していて、それらのどこかで目的のプロトコルを準拠しているかを知りたい場合が普通だと思いますが、その場合は、そもそもクラスがNSObjectクラス を継承している必要があるようでした。

この仕様がXcode 6.0.1 に限ったものなのかはわかりませんが、たとえば次のようにクラスが定義されていたとします。

@objc class MyClass : ProtocolA {
}

@objc class MySubClass : MyClass, ProtocolB {
}

このとき、MySubClassクラス のインスタンスが格納された変数object に対してobject is ProtocolA と判定してもfalse と判定されます。

ちなみにobject is ProtocolB と判定した場合はtrue になります。


これを継承をさかのぼって判定するには、そもそも次のように、MyClassクラス の定義の時点でNSObjectクラス を継承した設計にしておく必要があります。NSObjectクラス を継承してしまえば@objcディレクティブ は不要なので消去してあります。

class MyClass : NSObject, ProtocolA {
}

class MySubClass : MyClass, ProtocolB {
}

このようにすることで、MySubClassクラス のインスタンスが格納された変数object に対してobject is ProtocolA と判定したときにtrue と判定されるようになります。

NSObject を継承したくない場合は

クラスをさかのぼってプロトコルを判定したいけれど、NSObject は継承したくない ような場合は、綺麗な方法ではないのですが、クラスを@objcディレクティブ をつけて定義した上で、派生先のクラスでもプロトコルを明示的に宣言する必要があるようです。

@objc class MyClass : ProtocolA {
}

@objc class MySubClass : MyClass, ProtocolA, ProtocolB {
}

このようにすることで、MySubClassクラス のインスタンスであっても、is演算子 などを使ってProtocolAプロトコル に準拠していることを判定できるようになるようでした。

このようにしてもProtocolAプロトコル の実装はMyClassクラス で既に済んでいるので、MySubClassクラス で改めて何かを追加実装しなければいけないことはありません。

Swift のプロトコルについての考察

Objective-C言語 では動的解決が自然な方法

このようにSwift言語 では、プロトコルを使った柔軟な動的判定を行うためにはNSObjectクラス を継承したObjective-C 準拠のオブジェクトを使うことが前提になっている様子です。

これはもしかすると、Swift言語 が掲げるSafe というコンセプトに依るものなのかもしれません。

Objective-C言語 の場合は動的言語な性質が強く、何かとconformsToProtocol:メソッドrespondsToSelector:メソッドperformSelector:メソッド といった動的にメソッドを解決する仕組みが多用されがちで、これはとても便利な反面、下手に使うと実行時エラーを生みやすいという問題も秘めていました。

とはいえObjective-C言語 はそんな動的解決が前提な言語だったため、プロトコルでの実装を任意とする@optionalディレクティブ 指定も自然なことでした。

Swift言語 では動的解決を嫌う傾向

そんな常識だった動的解決を、Swift言語 は嫌う傾向が見て取れます。

純粋なSwift言語 のプロトコルではoptional 指定ができなくなっています。つまり@objcディレクティブ を付けない限りは、プロトコルに任意実装のメソッドを定義することはできません。また、任意の実装を持っているかを判定するにもNSObjectクラスrespondsToSelector:メソッド が必要になります。

このことから、純粋なSwift プロトコルでは、そのプロトコルに準拠したからには、かならずそのプロトコルに定義されたメソッドが実装されていることになります。つまり、クラスさえ定まればプロトコルも実装されるメソッドも固定される訳で、プロトコルに準拠しているかを柔軟に知る必要がない言語とも言うことができます。

インスタンスがどのクラスに属しているかを判定する方法はSwift言語 でもしっかり用意されていて、それについてはこちら で説明した通りです。

Swift言語 のプロトコルはジェネリックと合わせて使う

では、Swift言語 では何のためにプロトコルが存在しているかというと、ジェネリックと併用するためと考えられます。

ジェネリックとは任意の型で共通の処理を実装するための新しい仕組みですが、汎用化するといってもどのような機能を持っているかが前提となることはよくあります。

たとえば、総和を求めるジェネリック関数であれば、渡される値の型で「加算」ができなくては話になりません。そういった「取り得る値の制限」にプロトコルがとても効果を発揮します。


もう少し踏み込んだ例を挙げると、Swift言語 では配列(Array)も文字列(String)も、CollectionType というプロトコルに準拠したクラスになっています。

このCollectionTypeプロトコル というのはある型(任意)をひとつの要素として、それを複数まとめて扱う機能を提供する型 といった意味合いで、配列であれば指定した型を要素としてまとめて扱う型、文字列であれば文字を要素としてまとめて扱う型、といった存在になります。

そしてcountElements関数 という、引数にCollectionTypeプロトコル を取る関数があります。この関数に、CollectionTypeプロトコル に準拠しているArray型String型 を渡すことでその要素の文字列、すなわち配列の要素数や文字数を取得できるようになっています。

同様に、このCollectionTypeプロトコル に準拠したクラスを作りさえすれば、このcountElements関数 を使って要素数を自由に取得してもらえるようにもなるということです。

Swift言語 のプロトコルは静的解決

そして興味深いのは、このようなプロトコルの使い方をしたとき、全てがコンパイルのタイミングで解決されるというところです。

これにより、これまでのObjective-C言語 のような動的解決ではないので、コンパイルのタイミングでより正確にコーディングミスを検出できるようになります。きっとこれがSwift言語 の掲げる「Safe」のひとつなのでしょう。

このように、Swift言語 のプロトコルというものは、デリゲートのような実行時の柔軟性を高めるためのものではなくて、ジェネリックと組み合わせてコードの汎用性を高めるためのものに変わったのだと言えそうです。