プロトコル(Protocols)
最終更新日: 2024/3/18 原文: https://docs.swift.org/swift-book/LanguageGuide/Protocols.html
準拠型が実装する必要がある要件を定義する。
_プロトコル_は、特定のタスクまたは機能に準拠するメソッド、プロパティ、およびその他の要件の設計図を定義します。クラス、構造体、または列挙型がプロトコルに準拠でき、それらの要件の実装を提供します。プロトコルの要件を満たす全ての型は、そのプロトコルに_準拠している_といいます。
プロトコルは、準拠する型が実装する必要がある要件を指定することに加えて、プロトコルを拡張して、これらの要件の一部を実装したり、準拠する型が利用できる追加機能を実装できます。
プロトコル構文(Protocol Syntax)
クラス、構造体、および列挙型と非常によく似た方法でプロトコルを定義します:
カスタム型は、定義の一部として、型名の後にプロトコル名をコロン(:
)で区切って配置することにより、特定のプロトコルに準拠することを示します。複数のプロトコルにも準拠でき、カンマ(,
)区切りで並べます:
クラスにスーパークラスがある場合は、準拠するプロトコルの前にスーパークラス名を記述し、その後にカンマを続けます:
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)
任意の型のインスタンスを変更することを目的としたプロトコルのインスタンスメソッドの要件を定義する場合は、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
修飾子と override
修飾子の両方でイニシャライザの実装をマークします:
失敗可能イニシャライザ要件(Failable Initializer Requirements)
失敗可能イニシャライザ要件は、準拠する型の失敗可能または失敗しないイニシャライザによって満たされます。失敗しないイニシャライザ要件は、失敗しないイニシャライザまたは暗黙的にアンラップされた失敗可能イニシャライザによって満たされます。
型としてのプロトコル(Protocols as Types)
プロトコルは、実際には機能を実装しません。 それにもかかわらず、コード内で型としてプロトコルを使用できます。
プロトコルを型として使用する最も一般的な方法は、プロトコルをジェネリック制約として使用することです。ジェネリック制約を持つコードは、プロトコルに準拠する任意の型を扱うことができ、特定の型は API を使用する側のコードで選択されます。例えば、引数を取る関数を呼び出したとき、その引数の型がジェネリックであれば、呼び出し元がその型を選びます。
Opaque 型を持つコードは、プロトコルに準拠した何らかの型を使って機能します。基本的な型はコンパイル時に判明し、API 実装はその型を選択しますが、その型の正体は API のクライアントから隠されています。例えば、ある関数の戻り値の型を隠し、その値があるプロトコルに準拠していることだけを保証します。
Box プロトコル型を持つコードは、プロトコルに準拠する、実行時に選択された任意の型で動作します。この実行時の柔軟性をサポートするために、Swift は必要なときに間接的なレイヤーを追加します。これは、ボックスとして知られており、これはパフォーマンスコストを伴います。この柔軟性が理由で、Swift はコンパイル時に基礎となる型を知らなりません。つまり、プロトコルによって必要とされるメンバのみにアクセスできることを意味します。基礎となる型で定義された他の API にアクセスするには、実行時にキャストが必要です。
委譲(Delegation)
_委譲_は、クラスまたは構造体がその責任の一部を別の型のインスタンスに引き渡す(または委譲する)ことを可能にするデザインパターンです。このデザインパターンは、委譲される責任をカプセル化するプロトコルを定義することによって実装され、準拠する型 (_デリゲート_と呼ばれる)が委譲された機能を提供することを保証します。委譲を使用して、特定のアクションに応答したり、その準拠した具体的な型を知らなくても外部ソースからデータを取得したりできます。
下記の例では、サイコロゲームと、ゲームの進行を追跡するデリゲートのネストされたプロトコルを定義しています:
DiceGame
クラスは、各プレイヤーが順番にサイコロを振り、一番高い数字を出したプレイヤーがそのラウンドに勝つゲームを実装しています。このクラスでは、サイコロを振るための乱数を生成するために、この章の前の例で使った線形合同生成器を使います。
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)
NOTE 型の既存のインスタンスは、extension でそのインスタンスの型がプロトコルに準拠すると、自動的にプロトコルに準拠します。
例えば、TextRepresentable
と呼ばれるプロトコルは、テキストとして表現できる任意の型で実装できます。これは、それ自体の説明や、現在の状態の説明などの可能性があります:
上記の Dice
クラスを拡張して、TextRepresentable
に準拠させることができます:
この extension は、Dice
の元の実装とまったく同じ方法で新しいプロトコルに準拠しています。プロトコル名を型名の後にコロン(:
)で区切って指定し、プロトコルの全ての要件の実装は extension の中括弧内({}
)に指定します。
これで、どの Dice
インスタンスも TextRepresentable
として扱えるようになりました:
同様に、SnakesAndLadders
クラスを拡張して、TextRepresentable
プロトコルに準拠させることができます:
条件付きでのプロトコルへの準拠(Conditionally Conforming to a Protocol)
次の拡張により、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
列挙型を定義しています。エキスパートは、持っている星の数によってさらにランク付けされます。
プロトコル型のコレクション(Collections of Protocol Types)
配列内のアイテムを繰り返し処理し、各アイテムの説明をテキストで出力できるようになりました:
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
に準拠しようとする構造体または列挙型の定義を作成すると、コンパイルエラーになります。
プロトコル合成(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)
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)?
になります。メソッドの戻り値ではなく、関数型全体がオプショナルになっていることに注目してください。
次の例では、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:)
を呼び出した後、それが返すオプショナルの 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)
例えば、要素が Equatable
プロトコルに準拠しているコレクションに適用される Collection
プロトコルの拡張を定義できます。コレクションの要素を Swift 標準ライブラリの Equatable
プロトコルに制約することで、==
および !=
演算子を使用して、2 つの要素間の等価性と不等価性をチェックできます:
allEqual()
メソッドは、コレクション内の全ての要素が等しい場合にのみ true
を返します。
1 つは全ての要素が同じで、もう 1 つは要素が同じでない、整数の 2 つの配列を考えます:
配列は Collection
に準拠し、整数は Equatable
に準拠しているため、 equalNumbers
および differentNumbers
は allEqual()
メソッドを使用できます:
NOTE 準拠する型が、複数の制約が付いた拡張の要件を満たす同じメソッドまたはプロパティを実装している場合、Swift は最も厳しい制約がついている実装を選択して使用します。
最終更新