プロトコルの継承とクラス限定プロトコル
Swiftのプロトコルは、Objective-Cと同様に多重継承が可能です。継承させる際には、次のようにプロトコルを宣言します。
protocol プロトコル名 : プロトコル1, プロトコル2, ... { }
値型のストラクチャ、列挙型にプロトコルを準拠させたくない場合には、クラス限定のプロトコルであることを示すために、次のようにプロトコルを宣言します。
protocol プロトコル名 : class { }
クラス限定の方法で宣言されたプロトコルにストラクチャや列挙型に準拠させようとすると、コンパイルエラーが起こります。
クラス限定のプロトコルを多重継承する場合には、次のように書きます。
protocol プロトコル名 : class, プロトコル1, プロトコル2, ... { }
クラス限定のプロトコルを多重継承する例を見てみましょう。
protocol Respondable { var userInteractive: Bool { get set } } protocol Scrollable: class, Respondable { func scroll() } class ScrollableComponent: Scrollable { var userInteractive = true func scroll () { } }
これはスクロールできるコンポーネントをモデル化したものです。スクロールできるにはまず、タッチできるコンポーネントである必要があります。そのために、この例ではタッチできることをRespondableプロトコルで表現し、さらにそのプロトコルを継承したものとしてScrollableプロトコルを表現しています。このScrollableプロトコルはクラス限定のプロトコルとしているため、現在クラスとして宣言しているScrollableComponentをストラクチャとして宣言するとコンパイルエラーとなります。
エクステンションとプロトコルの併用
定義された既存のクラスを新しくプロトコルに準拠させたい場合、Objective-Cではカテゴリに対してプロトコルの実装を書くことでそれを実現できました。次の例を見てください。
@protocol Comparable <NSObject> - (NSComparisonResult)largerThan:(instancetype)otherObject; @end @interface NSString (Compare) <Comparable> - (NSComparisonResult)largerThan:(instancetype)otherObject; @end @implementation NSString (Compare) - (NSComparisonResult)largerThan:(instancetype)otherObject { return [self compare:otherObject]; } @end
この例では、自分自身で定義したComparableプロトコルに準拠するよう、Objective-CのNSStringインターフェイスにカテゴリを追加しています。このプロトコルを他のクラスにも実装することで、「比較できる」という機能を明示できます。
Swiftでも同様にエクステンションとプロトコルを併用することで、既存の型に対して新しい機能を追加できます。
extension 既存の型名: プロトコル1, プロトコル2, ... { プロトコルの実装 }
標準ライブラリの型StringとArrayに新しくプロトコルを追加する例を見てみましょう。
protocol Countable { var count: UInt { get } } extension String: Countable { var count: UInt { return UInt(countElements(self)) } } extension Array: Countable { var count: UInt { return UInt(countElements(self)) } }
ここで定義したCountableは「数えられる」という機能を明示したプロトコルで、準拠する型はcountプロパティを定義する必要があります。このように、カスタムで定義したプロトコルを既存の型に追加することで、機能のかたまりがさらに明確になります。
なお、標準APIのcountElements関数は、CollectionTypeプロトコルに準拠した型の引数を受け取ることが要求されています。
プロトコルを型として扱う
プロトコルに準拠した型は、変数宣言の後に「: プロトコル名」と型注釈を付けることで、プロトコルを型として扱えます。準拠している型を直接呼び出さず、プロトコルを型として扱うことのメリットには、次の点が挙げられます。
- メソッドや関数の引数や返り値としてプロトコルの型を宣言することで、具体的な実装を知らない、プロトコルに要求された機能のみを用いた柔軟な設計ができる
- 同一のプロトコルに準拠している型であれば、異なる型であっても、プロトコルを要素の型とすることでArrayなどでまとめて扱うことができる
- オプション機能を持つプロトコル(後の節で解説します)に準拠する型が、実際にその機能を持っているかどうか分からないとき、プロトコルとして扱うことで、オプション機能の有無をチェックしながらその型を扱うことができる
では、先ほど定義したCountableプロトコルを例に1つ目のメリットを確認してみましょう。
var str: String = "aaa" var arr: [Int] = [1,2,3,4] var countables: [Countable] = [str, arr] func printCounts(items: [Countable]) { for countable in countables { print("\(countable.count) ") } } printCounts(countables) // 3 4
Countableプロトコルに準拠した2つの型StringとArrayをひとまとめに配列に渡しています。 共通の機能を持った異なる型をあたかも同じ型のように見立てて扱えることが分かります。
プロトコルの型チェック
型がプロトコルに準拠しているかどうかをチェックするためには、タイプキャスト(本連載第5回を参照)と同じ文法が用いられます。ただし、プロトコル準拠のチェックのためには、チェックが必要なプロトコルの宣言の頭に @objc と記述します。@objcが先頭に付けられたプロトコルはクラス限定プロトコルとなり、値型に対して実装を要求することができません。
次の例を見てください。
@objc protocol Readable { func read() -> String } class Book: Readable { private var content: String init(content: String) { self.content = content } func read() -> String { return self.content } } class Pen { } var items: [AnyObject] = [Pen(), Book(content:"text")] for item in items { // ➊ if item is Readable { // ➋ let readableItem = item as Readable // ➌ println("Item content is \\(readableItem.read())") // ➌ } else { println("Item is not confirm to readable") // ➍ } if let readableItem = item as? Readable { // ➎ println("Item content is \\(readableItem.read())") // ➏ } else { println("Item can't be cast as readable") // ➐ } } // Item is not confirm to readable // Item can't be cast as readable // Item content is text // Item content is text
この例のReadableプロトコルは、readメソッドで内容を取得するような機能を示します。 items変数には、Readableプロトコルに準拠したBookオブジェクトと、準拠しないPenオブジェクトが代入されています。for in文(➊)では、items変数に代入されたオブジェクト1つ1つ1つを取り出して、Readableプロトコルに準拠しているかをis演算子とas?タイプキャストで調べています。
is演算子とas?タイプキャストの仕組みは次のとおりです。
isによる準拠チェック
「インスタンス名 is プロトコル名」で、インスタンスがプロトコルに準拠した型である場合にはtrueを、そうでない場合はfalseを返します。
asによるキャスト
具体サブクラスへのタイプキャストと同様に、as?ではオプショナル列挙型にくるんだ上でタイプキャストを実行、asでは単純なキャスト(ランタイムエラーの可能性あり)を実行します。
as?では、キャストに失敗するとnil(=Optional.None)が返りますから、オプショナルバインディングと組み合わせることで安全なキャストを実現することができます。
一方、asではプロトコルへのキャストに失敗するとランタイムエラーが発生します。そのため、インスタンスがプロトコルに確実に準拠している場面で使うようにするほうが安全です。
上の例では、➋のif文でitemに代入されたオブジェクトがReadableプロトコルに準拠しているかどうかを判定しています。準拠していれば➌部分のコードが実行され、準拠していなければ➍部分のコードが実行されます。➌部分では、itemがReadableプロトコルに準拠していることが確認済みであるため、オプショナル列挙型にくるまず、asタイプキャストでプロトコルに直接キャストしています。
2番目のif文(➎)ではas?によって、安全なタイプキャストを行っています。ここではキャストの成功、失敗にかかわらず、Optional<Readable>型のインスタンスが生成されます。これをオプショナルバインディングで扱うことにより、キャストが成功した場合は中身を取り出して➏部分を実行し、そうでない場合は➐部分を実行します。