プロトコル(Protocols)
最終更新日: 2025/6/28 原文: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
準拠型が実装する必要がある要件を定義する。
_プロトコル_は、特定のタスクまたは機能に準拠するメソッド、プロパティ、およびその他の要件の設計図を定義します。クラス、構造体、または列挙型がプロトコルに準拠でき、それらの要件の実装を提供します。プロトコルの要件を満たす全ての型は、そのプロトコルに_準拠している_といいます。
プロトコルは、準拠する型が実装する必要がある要件を指定することに加えて、プロトコルを拡張して、これらの要件の一部を実装したり、準拠する型が利用できる追加機能を実装できます。
プロトコル構文(Protocol Syntax)
クラス、構造体、および列挙型と非常によく似た方法でプロトコルを定義します:
protocol SomeProtocol {
// プロトコルの定義をここに
}カスタム型は、定義の一部として、型名の後にプロトコル名をコロン(:)で区切って配置することにより、特定のプロトコルに準拠することを示します。複数のプロトコルにも準拠でき、カンマ(,)区切りで並べます:
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 構造体の定義をここに
}クラスにスーパークラスがある場合は、準拠するプロトコルの前にスーパークラス名を記述し、その後にカンマを続けます:
class SomeClass: SomeSuperclass, FirstProtocol, AnotherProtocol {
// クラスの定義をここに
}NOTE: プロトコルは型のため、 Swiftの他の型に一致するように(
Int、StringやDoubleなど) 名前は大文字から始めてください。(FullyNamedやRandomNumberGeneratorなど)
プロパティ要件(Property Requirements)
プロトコルは、特定の名前と型のインスタンスプロパティまたは型プロパティを要件にできます。プロトコルでは、格納プロパティか計算プロパティかを指定しません。必要なプロパティの名前と型を指定するだけです。また、プロトコルは、各プロパティが get のみか、get/set どちらも必要かどうかも指定します。
プロトコルが get/set を要求する場合、そのプロパティ要件は、定数格納プロパティまたは読み取り専用の計算プロパティでは満たされません。プロトコルが get のみを要求する場合でも、その要件はあらゆる種類のプロパティによって満たされ、必要ならば実装の方でプロパティの set を追加することもできます。
プロパティ要件は常に変数プロパティとして宣言され、前に var キーワードが付きます。get/set は型宣言の後に { get set } を記述することで示し、get は { get } で示します。
プロトコルで、型プロパティの要件の前には、必ず static キーワードを付けてください。クラスによって実装されるときは、class または static キーワードをプレフィックスを付けても要件を満たします:
単一のインスタンスプロパティを要件に持つプロトコルの例を次に示します:
完全修飾名を提供するために、FullyNamed プロトコルに準拠する型が必要です。プロトコルは、準拠する型の性質について他に何も指定していません。型が完全修飾名を提供する必要があることを指定するだけです。プロトコルでは、FullyNamed 型には、String 型の fullName という get インスタンスプロパティが宣言されています。
FullyNamed プロトコルに準拠するシンプルな構造体の例を次に示します:
この例では、特定の名前を持つ人物を表す Person という構造体を定義しています。その定義の最初の行の一部として FullyNamed プロトコルに準拠することが記述されています。
Person の各インスタンスには、String 型の fullName という 1 つの格納プロパティがあります。これは、FullyNamed プロトコルの単一の要件と一致し、Person がプロトコルに正しく準拠していることを意味します。(プロトコル要件が満たされていない場合、コンパイルエラーが発生します)
下記も FullyNamed プロトコルに準拠する、より複雑なクラスです:
このクラスは、宇宙船の読み取り専用計算プロパティとして fullName プロパティを実装します。各 Starship クラスのインスタンスは、必須の name とオプショナルの prefix を格納します。fullName プロパティは、プレフィックス値が存在する場合はそれを使用し、名前の先頭に追加して宇宙船のフルネームを作成します。
メソッド要件(Method Requirements)
プロトコルはインスタンスメソッドと型メソッドを要件にすることもできます。これらのメソッドは、通常のインスタンスおよび型メソッドとまったく同じ方法でプロトコルの定義の一部として記述されますが、中括弧({})やメソッド本文はありません。通常のメソッドと同じ規則に従って、可変長パラメータを使用できます。ただし、プロトコルの定義内のメソッドのパラメータにデフォルト値を指定することはできません。
型プロパティの要件と同様に、型メソッドをプロトコルで定義するときは、常に要件の前に static キーワードを付けます。これは、クラスによって実装されるときに、型メソッドの要件に class または static キーワードが付いている場合にも当てはまります:
次の例では、単一のインスタンスメソッド要件を持つプロトコルを定義しています:
このプロトコル RandomNumberGenerator に準拠するには random と呼ばれるインスタンスメソッドが必要です。このインスタンスメソッドは、呼び出される度に Double 値を返します。プロトコルでは指定されていませんが、この値は 0.0 から 1.0(ただし、1.0 を含まない) までの数値が想定されています。
RandomNumberGenerator プロトコルは、各乱数がどのように生成されるかについて何も指定していません。単に、ジェネレータが新しい乱数を生成する標準的な方法を要求するだけです。
下記は、RandomNumberGenerator プロトコルに準拠するクラスの実装です。このクラスは、線形合同法発生器として知られる疑似乱数発生器アルゴリズムを実装しています:
mutating メソッド要件(Mutating Method Requirements)
メソッドが属するインスタンスを変更する必要がある場合があります。値型(つまり、構造体と列挙型)のインスタンスメソッドの場合、メソッドの func キーワードの前に mutating キーワードを配置して、メソッドが属するインスタンスとそのインスタンスの全てのプロパティを変更できることを示します。このプロセスについては、Modifying Value Types from Within Instance Methods(インスタンスメソッド内からの値型の変更)で説明されています。
任意の型のインスタンスを変更することを目的としたプロトコルのインスタンスメソッドの要件を定義する場合は、mutating キーワードをメソッドにマークします。これにより、構造体と列挙型がそのメソッド要件を満たしてプロトコルに準拠することができます。
NOTE プロトコルのインスタンスメソッドの要件を
mutatingとしてマークする場合、クラスがそのメソッドを実装するときに、mutatingキーワードを記述する必要はありません。mutatingキーワードは、構造体と列挙型のみ使用できます。
下記の例では、Togglable というプロトコルが定義されています。これは、toggle という単一のインスタンスメソッドの要件を定義しています。その名前が示すように、toggle() メソッドは、通常、その型のプロパティを変更することによって、準拠する型の状態をトグル(反転)することを目的としています。
toggle() メソッドは、Togglable プロトコル定義の一部として mutating キーワードがマークされており、メソッドが呼び出されたときに準拠するインスタンスの状態を変更する可能性を示しています。
構造体または列挙型に対して Togglable プロトコルを実装する場合、その構造体または列挙型は、mutating がマークされている toggle() メソッドの実装を提供することにより、プロトコルに準拠できます。
下記の例では、OnOffSwitch という列挙型を定義しています。この列挙型は、on と off の 2 つのケースによって示される 2 つの状態を切り替えます。列挙型の toggle 実装は、Togglable プロトコルの要件を満たすように、mutating としてマークされています:
イニシャライザ要件(Initializer Requirements)
プロトコルでは、型に準拠するために特定のイニシャライザが必要な場合があります。このイニシャライザは、通常のイニシャライザとまったく同じ方法でプロトコルの定義の一部として記述できますが、中括弧({})やイニシャライザ本文はありません:
プロトコルイニシャライザ要件のクラス実装(Class Implementations of Protocol Initializer Requirements)
プロトコルのイニシャライザの要件は、準拠するクラスで指定イニシャライザまたは convenience イニシャライザとして実装できます。どちらの場合も、required 修飾子でイニシャライザの実装をマークする必要があります:
required 修飾子を使用すると、準拠するクラスの全てのサブクラスで、イニシャライザ要件を明示的に実装または継承して、プロトコルに準拠する必要があります。
required イニシャライザの詳細については、Required Initializers(必須イニシャライザ)を参照ください。
NOTE
finalクラスはサブクラス化できないため、final修飾子でマークされているクラスでは、プロトコルのイニシャライザの実装をrequired修飾子でマークする必要はありません。final修飾子の詳細については、Preventing Overrides(オーバーライドを防ぐ)を参照ください。
サブクラスがスーパークラスからの指定イニシャライザをオーバーライドし、そのイニシャライザがプロトコル要件も満たす場合、required 修飾子と override 修飾子の両方でイニシャライザの実装をマークします:
失敗可能イニシャライザ要件(Failable Initializer Requirements)
プロトコルは、Failable Initializers(失敗可能イニシャライザ)で定義されているように、失敗可能イニシャライザ要件を定義できます。
失敗可能イニシャライザ要件は、準拠する型の失敗可能または失敗しないイニシャライザによって満たされます。失敗しないイニシャライザ要件は、失敗しないイニシャライザまたは暗黙的にアンラップされた失敗可能イニシャライザによって満たされます。
セマンティック要件のみを持つプロトコル(Protocols that Have Only Semantic Requirements)
上記のすべてのプロトコル例では、いくつかのメソッドやプロパティが必要ですが、プロトコル宣言に要件を含める必要はありません。プロトコルを使用して、セマンティック要件、つまりそれらの型の値がどのように動作するか、およびそれらがサポートする操作に関する要件を記述することもできます。
Swift 標準ライブラリは、必須のメソッドやプロパティを持たないいくつかのプロトコルを定義しています。
Sendable: 並行処理ドメイン間で共有できる値用(Sendable型(Sendable Types)で説明)Copyable: 関数に渡すときに Swift がコピーできる値用(Borrowing と Consuming パラメータ(Borrowing and Consuming Parameters)で説明)BitwiseCopyable:ビット単位でコピーできる値用
これらのプロトコルの要件に関する情報については、それぞれのドキュメントの概要を参照してください。
これらのプロトコルを採用する場合も、他のプロトコルを採用する場合と同じ構文を使用します。唯一の違いは、プロトコルの要件を実装するメソッドやプロパティの宣言を含めないことです。次に例を示します。
上記のコードは新しい構造体を定義しています。Copyable にはセマンティック要件しかないため、構造体宣言にはプロトコルを採用するためのコードはありません。同様に、BitwiseCopyable にはセマンティック要件しかないため、そのプロトコルを採用する extension の本体は空です。
通常、これらのプロトコルへの準拠を記述する必要はありません。代わりに、プロトコルへの暗黙の準拠(Implicit Conformance to a Protocol)で説明されているように、Swift が暗黙的に準拠を追加します。
型としてのプロトコル(Protocols as Types)
プロトコルは、実際には機能を実装しません。 それにもかかわらず、コード内で型としてプロトコルを使用できます。
プロトコルを型として使用する最も一般的な方法は、プロトコルをジェネリック制約として使用することです。ジェネリック制約を持つコードは、プロトコルに準拠する任意の型を扱うことができ、特定の型は API を使用する側のコードで選択されます。例えば、引数を取る関数を呼び出したとき、その引数の型がジェネリックであれば、呼び出し元がその型を選びます。
Opaque 型を持つコードは、プロトコルに準拠した何らかの型を使って機能します。基本的な型はコンパイル時に判明し、API 実装はその型を選択しますが、その型の正体は API のクライアントから隠されています。例えば、ある関数の戻り値の型を隠し、その値があるプロトコルに準拠していることだけを保証します。
Box プロトコル型を持つコードは、プロトコルに準拠する、実行時に選択された任意の型で動作します。この実行時の柔軟性をサポートするために、Swift は必要なときに間接的なレイヤーを追加します。これは、ボックスとして知られており、これはパフォーマンスコストを伴います。この柔軟性が理由で、Swift はコンパイル時に基礎となる型を知らなりません。つまり、プロトコルによって必要とされるメンバのみにアクセスできることを意味します。基礎となる型で定義された他の API にアクセスするには、実行時にキャストが必要です。
プロトコルをジェネリック制約として使用することについては、ジェネリクス(Generics)を参照してください。Opaque 型と Box プロトコル型については、Opaque 型とBox プロトコル型(Opaque Types and Boxed Types)を参照してください。
委譲(Delegation)
_委譲_は、クラスまたは構造体がその責任の一部を別の型のインスタンスに引き渡す(または委譲する)ことを可能にするデザインパターンです。このデザインパターンは、委譲される責任をカプセル化するプロトコルを定義することによって実装され、準拠する型 (_デリゲート_と呼ばれる)が委譲された機能を提供することを保証します。委譲を使用して、特定のアクションに応答したり、その準拠した具体的な型を知らなくても外部ソースからデータを取得したりできます。
下記の例では、サイコロゲームと、ゲームの進行を追跡するデリゲートのネストされたプロトコルを定義しています:
DiceGame クラスは、各プレイヤーが順番にサイコロを振り、一番高い数字を出したプレイヤーがそのラウンドに勝つゲームを実装しています。このクラスでは、サイコロを振るための乱数を生成するために、この章の前の例で使った線形合同生成器を使います。
DiceGame.Delegate プロトコルは、サイコロゲームの進行を追跡するために導入できます。DiceGame.Delegate プロトコルは常にサイコロゲームのコンテキストで使用されるため、DiceGame クラスの内部にネストされます。プロトコルは、外側の宣言がジェネリックでない限り、構造体やクラスのような型宣言の内部にネストできます。型のネストについては、ネスト型(Nested Types) を参照してください。
_強参照_循環を防ぐために、デリゲートは_弱参照_として宣言されます。弱参照については、Strong Reference Cycles Between Class Instances(クラスインスタンス間の強循環参照)を参照ください。プロトコルをクラス専用としてマークすると、DiceGame クラスがそのデリゲートを弱参照で使用しなければならないことを宣言できるようになります。クラス専用プロトコルは、Class-Only Protocols(クラス専用プロトコル)で説明したように、AnyObject を継承します。
DiceGame.Delegate は、ゲームの進行状況を追跡するための 3 つのメソッドを提供しています。これらの 3 つのメソッドは、上記の play(rounds:) メソッド内のゲームロジックに組み込まれています。DiceGame クラスが、新しいゲームの開始、新しいターンの開始、またはゲームの終了時に、このデリゲートメソッドを呼びます。
delegate プロパティはオプショナルの DiceGame.Delegate のため、play(rounds:) メソッドはデリゲートメソッドを呼び出す度にオプショナルチェーンを使用します。delegate プロパティが nil の場合、これらのデリゲートの呼び出しはエラーを出力せずに失敗します。delegate プロパティが nil ではない場合、デリゲートメソッドが呼び出され、パラメータとして DiceGame インスタンスが渡されます。
次の例は、DiceGame.Delegate プロトコルに準拠する DiceGameTracker というクラスを示しています:
DiceGameTracker クラスは、DiceGame.Delegate プロトコルが要求する 3 つのメソッドをすべて実装しています。これらのメソッドを使用して、新しいゲームの開始時に両プレイヤーのスコアをゼロにし、各ラウンドの終了時にスコアを更新し、ゲームの終了時に勝者を発表します。
DiceGameTracker の実際の挙動は次のとおりです:
拡張機能を使ったプロトコル準拠の追加(Adding Protocol Conformance with an Extension)
既存の型のソースコードにアクセスできない場合でも、既存の型を拡張して新しいプロトコルに準拠させることができます。extension は、新しいプロパティ、メソッド、およびサブスクリプトを既存の型に追加できるため、プロトコルの要件を追加できます。拡張機能の詳細については、Extensions(拡張)を参照ください。
NOTE 型の既存のインスタンスは、extension でそのインスタンスの型がプロトコルに準拠すると、自動的にプロトコルに準拠します。
例えば、TextRepresentable と呼ばれるプロトコルは、テキストとして表現できる任意の型で実装できます。これは、それ自体の説明や、現在の状態の説明などの可能性があります:
上記の Dice クラスを拡張して、TextRepresentable に準拠させることができます:
この extension は、Dice の元の実装とまったく同じ方法で新しいプロトコルに準拠しています。プロトコル名を型名の後にコロン(:)で区切って指定し、プロトコルの全ての要件の実装は extension の中括弧内({})に指定します。
これで、どの Dice インスタンスも TextRepresentable として扱えるようになりました:
同様に、SnakesAndLadders クラスを拡張して、TextRepresentable プロトコルに準拠させることができます:
条件付きでのプロトコルへの準拠(Conditionally Conforming to a Protocol)
ジェネリック型は、型のジェネリックパラメータがプロトコルに準拠している場合など、特定の条件下でのみプロトコルの要件を満たすことができるようにします。型を拡張するときに制約を並べることで、ジェネリック型を条件付きでプロトコルに準拠させることができます。where 句を記述して、準拠するプロトコルの名前の後にこれらの制約を記述します。ジェネリック where 句の詳細については、Generic Where Clauses(ジェネリック where 句)を参照ください。
次の拡張により、Array インスタンスが TextRepresentable に準拠する型の要素を格納する場合は、常に TextRepresentable プロトコルに準拠するようになります:
拡張機能を使ったプロトコル準拠の宣言(Declaring Protocol Adoption with an Extension)
型がすでにプロトコルの全ての要件を満たしているものの、そのプロトコルに準拠することを表明していない場合は、空の extension でプロトコルに準拠することができます:
これで TextRepresentable が必要な型へ、Hamster のインスタンスを使用できるようになりました:
NOTE 要件を満たすだけで、型が自動的にプロトコルに準拠するわけではありません。プロトコルへの準拠を常に明示的に宣言する必要があります。
デフォルト実装を使用したプロトコル準拠(Adopting a Protocol Using a Synthesized Implementation)
Swift は、多くのシンプルなケースで、Equatable、Hashable、および Comparable のプロトコルへの準拠を自動的に提供できます。このデフォルト実装を使用すると、プロトコル要件を自分で実装するために、繰り返しコードを記述する必要がなくなります。
Swift は、次の種類の独自の型に対して Equatable のデフォルト実装を提供します。
Equatableプロトコルに準拠した型の格納プロパティのみで構成される構造体関連値が
Equatableプロトコルに準拠する型のみの列挙型関連値のない列挙型
== のデフォルト実装を受け取るには、自分で == 演算子を実装せずに、元の宣言を含むファイルで Equatable への準拠を宣言します。Equatable プロトコルは、!= のデフォルトの実装を提供しています。
下記の例では、Vector2D 構造体と同様に、3 次元位置ベクトル (x、y、z) の Vector3D 構造体を定義しています。x、y、z プロパティは全て Equatable に準拠した型なので、Vector3D は等価演算子のデフォルト実装を受け取ります。
Swift は、次の種類の独自の型に対して Hashable のデフォルト実装を提供します。
Hashableプロトコルに準拠した型の格納プロパティのみで構成される構造体関連値が
Hashableプロトコルに準拠する型のみの列挙型関連値のない列挙型
hash(into:) のデフォルト実装を受け取るには、hash(into:) メソッドを自分で実装せずに、元の宣言を含むファイルで Hashable への準拠を宣言します。
Swift は、Raw Value を持たない列挙型に Comparable のデフォルト実装を提供します。列挙型に関連値がある場合、それらは全て Comparable プロトコルに準拠している必要があります。< のデフォルト実装を受け取るには、自分で < 演算子を実装せずに、元の列挙宣言を含むファイルで Comparable への準拠を宣言します。残りの比較演算子(<=、>、および >=)は Comparable プロトコルがデフォルトで実装を提供してます。
下記の例では、初心者、中級者、および専門家向けのケースを含む SkillLevel 列挙型を定義しています。エキスパートは、持っている星の数によってさらにランク付けされます。
プロトコルへの暗黙の準拠(Implicit Conformance to a Protocol)
一部のプロトコルは非常によく使われるため、ほとんどの場合、新しい型を宣言するたびにそれらを記述することになるでしょう。以下のプロトコルについては、プロトコルの要件を実装する型を定義すると、Swift が自動的に準拠を推論するため、自分で記述する必要はありません。
明示的に準拠を記述することもできますが、それによってコードの動作が変わることはありません。暗黙的な準拠を抑制するには、準拠リストのプロトコル名の前にチルダ(~)を記述します。
上記のコードは、POSIX ファイルディスクリプタのラッパーの一部を示しています。FileDescriptor 構造体は Sendable プロトコルのすべての要件を満たしており、通常はこれにより Sendable になります。しかし、~Sendable と記述することで、この暗黙的な準拠が抑制されます。ファイルディスクリプタは開いているファイルを識別し操作するために整数を使用し、整数値は Sendable ですが、これを非 Sendable にすることで、特定種類のバグを回避するのに役立ちます。
暗黙的な準拠を抑制するもう 1 つの方法は、利用不可としてマークした extension を使用することです。
前の例のように、コードのある場所で ~Sendable と記述した場合でも、プログラムの他の場所のコードは FileDescriptor 型を拡張して Sendable 準拠を追加できます。対照的に、この例の unavailable extension は、Sendable への暗黙的な準拠を抑制し、さらにコードの他の場所にある extension がその型に Sendable 準拠を追加するのを防ぎます。
注:上記のプロトコルに加えて、分散(distributed)アクターは、暗黙的に
Codableプロトコルに準拠します。
プロトコル型のコレクション(Collections of Protocol Types)
プロトコルは、Protocols as Types(型としてのプロトコル)で説明されているように、配列や辞書などのコレクションに格納される型として使用できます。この例では、TextRepresentable の配列を作成します。
配列内のアイテムを繰り返し処理し、各アイテムの説明をテキストで出力できるようになりました:
thing 定数は TextRepresentable 型なことに注目してください。内部の実際のインスタンスがそれらの型の 1 つの場合でも、型は Dice、DiceGame、または Hamster ではありません。これは TextRepresentable 型で、TextRepresentable は全て textualDescription プロパティを持つことがわかっているため、ループ処理の中で安全に thing.textualDescription にアクセスできます。
プロトコル継承(Protocol Inheritance)
プロトコルは 1 つ以上の他のプロトコルを継承でき、継承する要件の上にさらに要件を追加できます。プロトコル継承の構文はクラス継承の構文に似ていますが、継承された複数のプロトコルをカンマ(,)で区切って並べます:
上の TextRepresentable プロトコルを継承するプロトコルの例を次に示します:
この例では、TextRepresentable から継承する新しいプロトコル PrettyTextRepresentable を定義しています。PrettyTextRepresentable に準拠するものは全て、TextRepresentable の全ての要件に加えて、PrettyTextRepresentable の追加の要件を満たす必要があります。この例では、PrettyTextRepresentable は、String を返す prettyTextualDescription というプロパティの get を提供するための 1 つの要件を追加します。
SnakesAndLadders クラスを拡張して、PrettyTextRepresentable に準拠させることができます:
この extension は、SnakesAndLadders 型が PrettyTextRepresentable プロトコルに準拠し、 prettyTextualDescription のプロパティの実装を提供していることを示しています。PrettyTextRepresentable に準拠するものは、必ず全て TextRepresentable なので、prettyTextualDescription の実装は、TextRepresentable プロトコルの textualDescription プロパティにアクセスして出力文字列を開始します。コロン(:)と改行()を追加し、これをテキストの開始表現として使用します。次に、ボードの正方形の配列を繰り返し処理し、各正方形の内容を表す幾何学的形状を追加します:
正方形の値が
0より大きい場合、それははしごの根本で、▲で表されますマスの値が
0未満の場合、それはヘビの頭で、▼で表されますそれ以外の場合、正方形の値は
0で、それは○で表される「自由な」正方形です
prettyTextualDescription プロパティを使用して、SnakesAndLadders インスタンスの説明をきれいに表示できるようになりました:
クラス専用プロトコル(Class-Only Protocols)
AnyObject プロトコルをプロトコル継承の一覧に追加することで、プロトコルへの準拠を(構造体や列挙型ではなく)クラス型に制限できます:
上記の例では、SomeClassOnlyProtocol はクラス型でのみ準拠できます。SomeClassOnlyProtocol に準拠しようとする構造体または列挙型の定義を作成すると、コンパイルエラーになります。
NOTE そのプロトコルの要件によって定義された動作が、準拠する型が値型のセマンティクスではなく参照型のセマンティクスであることを想定(要求)する場合は、クラス専用プロトコルを使用してください。参照型と値型のセマンティクスの詳細については、Structures and Enumerations Are Value Types(構造体と列挙型は値型)、Classes Are Reference Types(クラスは参照型)を参照ください。
プロトコル合成(Protocol Composition)
同時に複数のプロトコルに準拠すると便利な場合があります。プロトコル合成を使用して、複数のプロトコルを 1 つの要件に組み合わせることができます。プロトコル合成は、合成内の全てのプロトコルの要件を組み合わせた一時的なローカルプロトコルを定義したかのように動作します。プロトコル合成は、新しいプロトコル型を定義しません。
プロトコル合成は SomeProtocol & AnotherProtocol という形式で記述します。アンパサンド (&) で区切って、必要な数のプロトコルを並べることができます。プロトコルに加えて、プロトコル合成には、必要なスーパークラスを 1 つ含めることもできます。
Named と Aged という 2 つのプロトコルを組み合わせて、単一の関数パラメータに指定した例を次に示します:
この例では、Named プロトコルには、name という String 型のプロパティの get 要件が 1 つあります。Aged プロトコルには、age と呼ばれる Int 型のプロパティの get 要件が 1 つあります。どちらのプロトコルも、Person と呼ばれる構造体に準拠されています。
この例では、wishHappyBirthday(to:) 関数も定義されています。celebrator パラメータの型は Named & Aged です。これは、「Named プロトコルと Aged プロトコルの両方に準拠する任意の型」を意味します。必要なプロトコルの両方に準拠している限り、関数に渡される特定の型は問題ではありません。
この例では、birthdayPerson という名前の新しい Person インスタンスを作成し、この新しいインスタンスを wishHappyBirthday(to:) 関数に渡しています。Person は両方のプロトコルに準拠しているため、wishHappyBirthday(to:) 関数は誕生日の挨拶を出力できます。
先ほどの例の Named プロトコルと Location クラスを組み合わせた例を次に示します:
beginConcert(in:) 関数は、Location & Named 型のパラメータを受け取ります。これは、「Location のサブクラスで、Named プロトコルに準拠する任意の型」を意味します。この場合、City は両方の要件を満たしています。
Person は Location のサブクラスではないため、beginConcert(in:) 関数に birthdayPerson を渡すことは無効です。同様に、Named プロトコルに準拠していない Location のサブクラスを作成した場合、その型のインスタンスで beginConcert(in:) を呼び出すことも無効です。
プロトコル準拠チェック(Checking for Protocol Conformance)
Type Casting(型キャスト)で説明されている is および as 演算子を使用して、プロトコルの準拠を確認し、特定のプロトコルにキャストできます。プロトコルのチェックと型へのキャストは、型のチェックやキャストとまったく同じ構文を使用します。
is演算子は、インスタンスがプロトコルに準拠している場合はtrueを返し、準拠していない場合はfalseを返しますダウンキャスト演算子の
as?は、プロトコルの型のオプショナル値を返します。インスタンスがそのプロトコルに準拠していない場合、この値はnilですダウンキャスト演算子の
as!は、強制的にダウンキャストし、ダウンキャストが成功しない場合は実行時エラーを引き起こします
この例では、HasArea というプロトコルを定義しています。このプロトコルには、area という Double 型の単一のプロパティの get 要件があります:
Circle と Country の 2 つのクラスがあり、どちらも HasArea プロトコルに準拠しています:
Circle クラスは、radius という格納プロパティに基づいて、計算プロパティ area プロパティの要件を実装します。Country クラスは、格納プロパティとして area プロパティを直接実装します。どちらのクラスも、HasArea プロトコルに正しく準拠しています。
下記は、HasArea プロトコルに準拠していない Animal というクラスです:
Circle、Country、および Animal クラスには、共通の基本クラスがありません。とはいえ、それらは全てクラスなため、3 つ全ての型のインスタンスを使用して、AnyObject 型の値を格納する配列を初期化できます:
objects の配列は、半径 2 の Circle インスタンス、英国の表面積を平方キロメートル単位で初期化した Country インスタンス、そして 4 本の足を持つ Animal インスタンスを含む配列リテラルで初期化されています。
objects 配列を繰り返し処理して、配列内の各オブジェクトをチェックして、HasArea プロトコルに準拠しているかどうかを確認できます:
配列内のオブジェクトが HasArea プロトコルに準拠している場合、as? 演算子によって返されるオプショナル値はオプショナルバインディングを使用して objectWithArea という名前の定数にアンラップされます。objectWithArea 定数は HasArea 型であることが分かっているので、その area プロパティに型安全にアクセスして、表示することができます。
プロトコルに準拠したオブジェクトは、キャストで変更されないことに注目してください。それらはそれぞれ Circle、Country、Animal です。ただし、オブジェクトが objectWithArea 定数に格納されている時点では、HasArea 型とのみ認識されているため、アクセスできるのは area プロパティのみです。
オプショナルのプロトコル要件(Optional Protocol Requirements)
プロトコルにオプショナルの要件を定義できます。これらの要件は、プロトコルに準拠する型によって実装される必要はありません。オプショナルの要件には、プロトコルの定義の一部として optional 修飾子が付けられます。Objective-C と相互運用するコードを作成できるように、オプショナルの要件が利用可能です。プロトコルとオプショナルの要件の両方が @objc 属性でマークされている必要があります。@objc プロトコルは、構造体や列挙型で準拠することはできず、クラスでのみ準拠できることに注目してください。
オプショナルの要件でメソッドまたはプロパティを使用すると、その型は自動的にオプショナルになります。たとえば、(Int) -> String 型のメソッドは ((Int) -> String)? になります。メソッドの戻り値ではなく、関数型全体がオプショナルになっていることに注目してください。
プロトコルに準拠する型によって要件が実装されていない可能性を考慮して、オプショナルのプロトコル要件をオプショナルチェーンで呼び出すことができます。オプショナルメソッドの実装を確認するには、メソッドの呼び出し時にメソッド名の後に疑問符(?)を記述します(someOptionalMethod?(someArgument) など)。オプショナルチェーンについては、Optional Chaining(オプショナルチェーン)を参照ください。
次の例では、Counter という整数をカウントするクラスを定義しています。これは、外部データソースを使用して増分量を提供します。このデータソースは、次の 2 つのオプショナルの要件がある CounterDataSource プロトコルによって定義されます:
CounterDataSource プロトコルは、increment(forCount:) と呼ばれるオプショナルメソッドの要件と、fixedIncrement と呼ばれるオプショナルのプロパティの要件を定義しています。これらの要件は、データソースが Counter インスタンスに適切な増分量を提供するための 2 つの異なる方法を定義します。
NOTE 厳密に言えば、いずれのプロトコル要件を実装していなくても、
CounterDataSourceに準拠する独自クラスを作成できます。結局のところ、どちらもオプショナルです。技術的には許可されていますが、これはあまり有用なデータソースではありません。
以下で定義される Counter クラスには、CounterDataSource? 型のオプショナル値の dataSource プロパティがあります:
Counter クラスは、現在の値を count という変数プロパティに保存します。Counter クラスは、メソッドが呼び出されるたびに count プロパティを増加する increment というメソッドも定義します。
increment() メソッドは、最初に、データソースで increment(forCount:) メソッドの実装を呼び出して、増分量を取得しようとします。increment() メソッドは、オプショナルチェーンを使用して increment(forCount:) の呼び出しを試み、現在の count をメソッドの単一の引数として渡します。
ここでは、2 つの階層のオプショナルチェーンが行われていることに注目してください。まず、dataSource が nil の可能性があるため、dataSource にはその名前の後に疑問符(?)があり、dataSource が nil でない場合にのみ increment(forCount:) を呼び出す必要があることを示しています。次に、dataSource が存在する場合でも、increment(forCount:) がオプショナルのため、必ず実装されている保証はありません。ここで、increment(forCount:) が実装されない可能性も、オプショナルチェーンによって処理されます。increment(forCount:) の呼び出しは、increment(forCount:) が存在する場合、つまり nil でない場合にのみ発生します。これが increment(forCount:) にも名前の後に疑問符が付いている理由です。
increment(forCount:) の呼び出しはこれら 2 つの理由のいずれかで失敗する可能性があるため、呼び出しはオプショナルの Int 値を返します。これは、CounterDataSource の定義で increment(forCount:) がオプショナルではない Int 値を返すように定義されている場合でも当てはまります。オプショナルチェーンは 2 回行われていますが、結果は 1 つのオプショナルにラップされます。複数のオプショナルチェーンの詳細については、Linking Multiple Levels of Chaining(複数階層のチェーンへのリンク)を参照ください。
increment(forCount:) を呼び出した後、それが返すオプショナルの Int は、オプショナルバインディングを使用して、amount という定数にアンラップされます。オプショナルの Int に値が含まれている場合、つまり、デリゲートとメソッドの両方が存在し、メソッドが値を返した場合、count 格納プロパティに値が追加され、増加の動作が完了します。
increment(forCount:) メソッドから値を取得できない場合(dataSource が nil か、データソースが increment(forCount:) を実装していないため)、 increment() メソッドはデータソースの fixedIncrement プロパティから値を取得しようとします。fixedIncrement プロパティもオプショナルの要件のため、その値はオプショナルの Int 値です。ただし、fixedIncrement は、CounterDataSource プロトコル定義の一部として非オプショナルの Int プロパティとして定義されています。
下記は、fixedIncrement にアクセスされる度にデータソースが定数 3 を返す簡単な CounterDataSource の実装です。これは、オプショナルの fixedIncrement プロパティ要件を実装することでこれを行います。
ThreeSource のインスタンスを新しい Counter インスタンスのデータソースとして使用できます:
上記のコードは、新しい Counter インスタンスを作成します。データソースを新しい ThreeSource インスタンスに設定します。そして、カウンタの increment() メソッドを 4 回呼び出します。予想どおり、カウンタの count プロパティは、increment() が呼び出されるたびに 3 ずつ増加します。
下記は TowardsZeroSource と呼ばれるより複雑なデータソースで、Counter インスタンスを現在の count からゼロに向かってカウントアップまたはカウントダウンします:
TowardsZeroSource クラスは、CounterDataSource プロトコルからオプショナルの increment(forCount:) メソッドを実装し、count 引数を使用して、カウントする方向を決定します。count が既にゼロの場合、メソッドは 0 を返し、それ以上のカウントが行われないことを示します。
TowardsZeroSource のインスタンスを既存の Counter インスタンスとともに使用して、-4 から 0 までカウントできます。カウンタがゼロに達すると、それ以上のカウントは行われません:
プロトコル Extension(Protocol Extensions)
プロトコル Extension を使用して、準拠する型にメソッド、イニシャライザ、サブスクリプト、および計算プロパティの実装を提供できます。これにより、準拠する個々の型やグローバル関数ではなく、プロトコル自体に動作を定義できます。
例えば、RandomNumberGenerator プロトコルを拡張して、必須要件の random() メソッドを使用してランダムな Bool 値を返す randomBool() メソッドを提供できます:
プロトコル Extension を作成することにより、全ての準拠する型は、追加の変更なしでこのメソッドの実装を自動的に使用することができます。
プロトコル Extension は、準拠する型に実装を追加できますが、プロトコルにさらに定義を追加して拡張したり、別のプロトコルを継承することはできません。プロトコルの継承は、常にプロトコル宣言内で指定されます。
デフォルト実装の提供(Providing Default Implementations)
プロトコル Extension を使用して、そのプロトコルのメソッドまたは計算プロパティ要件にデフォルトの実装を提供できます。準拠する型が要件のメソッドまたはプロパティの実装を提供している場合、プロトコル Extension によって提供されるものの代わりに、準拠する型の実装が使用されます。
NOTE extension によって提供されるプロトコル要件のデフォルト実装は、オプショナルのプロトコル要件とは異なります。準拠する型は、独自の実装を提供する必要はありませんが、デフォルトの実装を持つ要件は、オプショナルチェーンなしで呼び出すことができます。
例えば、TextRepresentable プロトコルを継承する PrettyTextRepresentable プロトコルは、要件の prettyTextualDescription プロパティのデフォルト実装を提供して、textualDescription プロパティにアクセスした結果を返すことができます:
プロトコル Extensionに制約の追加(Adding Constraints to Protocol Extensions)
プロトコル Extension を定義するとき、準拠する型が拡張したメソッドとプロパティを使用できる前に、その型が満たす必要がある制約を指定できます。これらの制約は、拡張するプロトコルの名前の後のジェネリックの where 句によって記述されます。ジェネリック where 句の詳細については、Generic Where Clauses(ジェネリック where 句)を参照ください。
例えば、要素が Equatable プロトコルに準拠しているコレクションに適用される Collection プロトコルの拡張を定義できます。コレクションの要素を Swift 標準ライブラリの Equatable プロトコルに制約することで、== および != 演算子を使用して、2 つの要素間の等価性と不等価性をチェックできます:
allEqual() メソッドは、コレクション内の全ての要素が等しい場合にのみ true を返します。
1 つは全ての要素が同じで、もう 1 つは要素が同じでない、整数の 2 つの配列を考えます:
配列は Collection に準拠し、整数は Equatable に準拠しているため、 equalNumbers および differentNumbers は allEqual() メソッドを使用できます:
NOTE 準拠する型が、複数の制約が付いた拡張の要件を満たす同じメソッドまたはプロパティを実装している場合、Swift は最も厳しい制約がついている実装を選択して使用します。
最終更新