Swift 5.8 日本語化計画 : Swift 5.8


ジェネリック(汎用)


複数の型で機能するコードを記述し、それらの型の要件を指定します。

汎用(Generic)コード を使用すると、定義する要件の対象として全ての型を扱うことができ、柔軟で再利用可能な関数と型を書くことができます。重複を避け、明確な、抽象化の方法でその意図を表現するコードを書くことができます。


ジェネリック (汎用)は、Swift の最も強力な機能の一つであり、そして Swift の標準ライブラリの多くは汎用(ジェネリック)コードでビルドされています。実際に、それを意識していない場合でも、言語ガイド 全体を通して汎用を使用してきました。たとえば、Swift の ArrayDictionary 型は、両方とも汎用のコレクションです。Int 値を保持する配列、または String 値を保持する配列、または実際に他の全ての型用の配列を Swift では作成できます。同様に、指定した全ての型の値を格納するための dictionary を作成でき、その型が何であろうと制限はありません。


汎用が解決する問題


ここで 2つの Int 値を入れ替える、swapTwoInts(_:_:) と言う標準の、汎用でない関数を挙げます。


  1. func swapTwoInts(_ a: inout Int, _ b: inout Int) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }


この関数は、In-Out パラメータ で説明したように、ab の値を入れ替えるのに in-out パラメータを使用しています。


swapTwoInts(_:_:) 関数は、b の元の値を、a と入れ替え、a の元の値を b と入れ替えます。2つの Int 変数の値を交換するために、この関数を呼び出せます。


  1. var someInt = 3
  2. var anotherInt = 107
  3. swapTwoInts(&someInt, &anotherInt)
  4. print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
  5. // prints "someInt is now 107, and anotherInt is now 3"


swapTwoInts(_:_:) 関数は有用ですが、それは Int 値でしか使用できません。2つの String 値、または2つの Double 値を入れ替えたい場合は、以下に示す様に、swapTwoStrings(_:_:)swapTwoDoubles(_:_:) 関数など多くの関数を書かなければなりません:


  1. func swapTwoStrings(_ a: inout String, _ b: inout String) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }
  6. func swapTwoDoubles(_ a: inout Double, _ b: inout Double) {
  7. let temporaryA = a
  8. a = b
  9. b = temporaryA
  10. }


swapTwoInts(_:_:)、swapTwoStrings(_:_:)、および swapTwoDoubles(_:_:) 関数の本体が同一であることに気づいたかもしれません。唯一の違いは、それらが受け入れる値 (Int、String、および Double) の型だけです。


全て の型の2つの値を入れ替える一つの関数を書けば、はるかに有用で、著しく柔軟になります。汎用コードを使用すると、そのような関数を書くことができます。(これらの関数の汎用バージョンは後で定義します。)


注意: これら3つのすべての関数で、ab の型は同じでなければなりません。ab が同じ型ではなかった場合には、それらの値を入れ替えることは不可能です。Swift は、型安全な言語であり、(例えば) String 型の変数と Double 型の変数を互いに値を入れ替えるのは許可されていません。これを実行しようとすると、コンパイル時エラーになります。


汎用関数


汎用関数 は、全ての型を扱うことができます。ここで上に挙げた swapTwoInts(_:_:) 関数の汎用バージョンで、swapTwoValues(_:_:) と言う例を挙げます:


  1. func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
  2. let temporaryA = a
  3. a = b
  4. b = temporaryA
  5. }


swapTwoValues(_:_:) 関数の本体は swapTwoInts(_:_:) 関数の本体と同一です。しかし、swapTwoValues(_:_:) の最初の行は swapTwoInts(_:_:) とは少し異なります。ここで最初の行を比較してみましょう。


  1. func swapTwoInts(_ a: inout Int, _ b: inout Int)
  2. func swapTwoValues<T>(_ a: inout T, _ b: inout T)


関数の汎用 (ジェネリック) バージョンは、(この場合は、T と言う) プレースホルダ 型の名を、実際の 型の名 (Int、String、または Double など) の代わりに使用しています。プレースホルダ型の名は、T が何でなければならないとは言っていませんが、ab の両方が、 T が表すのが何であれ、同じ型 T でなければならないと 告げ ています。T の代わりに使われる実際の型は swapTwoValues(_:_:) 関数が呼び出されるたびに決定されます。


汎用関数と汎用でない関数の間の他の違いは、汎用関数の名前 (swapTwoValues(_:_:)) に山括弧 (<T>) 内にプレースホルダ型の名 (T) が続いているということです。括弧は TswapTwoValues(_:_:) 関数定義内のプレースホルダ型の名だと Swift に教えています。T はプレースホルダであるため、Swift は T と言う実際の型は検索しません。


これらの値の両者が互いに同じ型のものである限り、全て の型の2つの値を渡すことができることを除いて、swapTwoValues(_:_:) 関数は、swapTwoInts と同じ方法で呼び出すことができます。swapTwoValues(_:_:) が呼び出されるたびに、T に使用する型は、関数に渡された値の型から推測されます。


以下の2つの例では、T は、それぞれ IntString であることが推測されます。


  1. var someInt = 3
  2. var anotherInt = 107
  3. swapTwoValues(&someInt, &anotherInt)
  4. // someInt is now 107, and anotherInt is now 3
  5. var someString = "hello"
  6. var anotherString = "world"
  7. swapTwoValues(&someString, &anotherString)
  8. // someString is now "world", and anotherString is now "hello"


注意: 上記で定義された swapTwoValues(_:_:) 関数は、Swift の標準ライブラリの一部であり、そしてあなたのアプリで自動的に利用可能となる swap と言う汎用関数に触発されています。独自のコードで swapTwoValues(_:_:) 関数の動作が必要な場合は、独自の実装を提供するよりも、Swift の既存の swap(_:_:) 関数を使用できます。


型パラメータ


上記の swapTwoValues(_:_:) の例では、プレースホルダの型 T は、型パラメータ の例です。型パラメータは、プレースホルダ型を指定し、名前を付け、一致する山括弧の対の間に、関数名の直後に書かれています(<T> のように)。


型パラメータを指定するやいなや、関数のパラメータの型を定義するためにそれを使用でき(例えば swapTwoValues(_:_:) 関数の ab のパラメータのように)、また関数の戻り値の型として、また関数の本体内の型注釈として使用できます。それぞれの場合、型パラメータは関数が呼び出されるたびに 実際の 型に置き換えられます。(上記の swapTwoValues(_:_:) の例では、T は、関数が呼び出された最初の時は Int 型で置き換えられ、呼び出された二度目には String に置き換えられます。)


カンマで区切り、山括弧内で複数の型パラメータ名を書くことによって、1つ以上の型パラメータを提供できます。



型パラメータの命名


ほとんどの場合、型パラメータおよび汎用型またはそれが中で使用されている関数との関係について、読者に伝える Dictionary<key, Value> での KeyValueArray<Element> での Element などの説明的な名前を型パラメータは持っています。しかし、それらの間の意味のある関係がない場合、それは上記の swapTwoValues(_:_:) 関数内の T のような、 TUV のような単一の文字を使用して、それらに名前を付けるのが、従来のやり方です。


注意: 常にそれらが のプレースホルダであり、値のものではないことを示すために、(例えば、TMyTypeParameter のように) upper camel case の名前を型パラメータに与えて下さい。


汎用の型


汎用の関数に加えて、Swift では、自分で独自の 汎用の型 を定義できます。これらは、ArrayDictionary と同様に、全て の型を扱うことができる、カスタムクラス、構造体、および列挙型です。


このセクションでは、Stack と言う汎用のコレクション型を書く方法を説明します。スタックは配列に似た、値の順序付き集合ですが、Swift の Array 型より操作の制限が厳密に設定されています。配列には新しい項目が挿入でき、配列内の任意の場所から除去できます。スタックは、しかし、新しい項目はコレクションの最後にのみ追加することができ (スタック上に新たな値を プッシュする として知られてい) ます。同様に、スタックは、項目をコレクションの末尾からのみ削除する (スタックから値を ポップする として知られています)ことができます。



注意: スタックの概念は、そのナビゲーション階層における View Controller をモデル化する UINavigationController クラスによって使用されています。UINavigationController クラスの pushViewController(_:animated:) メソッドを呼び出してナビゲーションスタック上にビューコントローラを追加 (またはプッシュ) するのに使い、その popViewControllerAnimated(_:) メソッドを呼び出してナビゲーションスタックからビューコントローラを削除 (または ポップ) します。コレクションを管理するためのアプローチとして厳しい "ラストイン、ファーストアウト" が必要なときにはいつでも、スタックは便利なコレクションモデルです。


下のイラストは、スタックのプッシュ/ポップ動作を示しています。





  1. スタック上には現在3つの値があります。
  2. 4番目の値が、スタックの最上部へ "プッシュ" されます。
  3. スタックは最上部に最新のものを乗せ、現在4つの値を保持します。
  4. スタック内の最上部の項目が "ポップ" されます。
  5. 値をポップした後、スタックは再び3つの値を保持します。

ここで Int 値のスタック用に、スタックの汎用でないバージョンを書く方法は以下のとおりです。


  1. struct IntStack {
  2. var items : [Int] =[]
  3. mutating func push(_ item: Int) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> Int {
  7. return items.removeLast()
  8. }
  9. }


この構造体は、スタック内に値を格納する items と言う Array プロパティを使用しています。Stack はスタックの上に値をプッシュする push と、スタックから値をポップする pop の2つのメソッドを提供します。これらのメソッドは構造体の items 配列を変更(または 変異)する必要があるため、mutating としてマークされています。


しかし、上に示した IntStack 型は、Int 型の値でしか使用できません。値の 全ての 型のスタックを管理することができる 汎用の Stack 構造体を定義することは、はるかに有用でしょう。


ここで、同じコードの汎用バージョンを挙げます:


  1. struct Stack<Element> {
  2. var items : [Element] = []
  3. mutating func push(_ item: Element) {
  4. items.append(item)
  5. }
  6. mutating func pop() -> Element {
  7. return items.removeLast()
  8. }
  9. }


Stack の汎用バージョンは本質的に汎用でないバージョンと同じであることに注目しましょう、しかし、Int の実際の型の代わりに、Element と言う型パラメータがあります。この型パラメータは、構造体の名前の直後に山括弧 (<Element>) のペアの中に書かれています。


Element は、後で提供される型のプレースホルダ名を定義しています。この将来の型は構造体の定義内のどこでも Element で参照できます。この場合は、Element は3つの場所でプレースホルダとして使用されています。


それは汎用型であるため、Stack は、ArrayDictionary と同様な方法で、Swift で有効な 全て の型のスタックを作成するのに使用できます。


山括弧の中のスタックに格納する型を書くことによって、新しい Stack インスタンスを作成して下さい。たとえば、文字列の新しいスタックを作成するには、Stack<String>() と書いて下さい。


  1. var stackOfStrings = Stack<String>()
  2. stackOfStrings.push("uno")
  3. stackOfStrings.push("dos")
  4. stackOfStrings.push("tres")
  5. stackOfStrings.push("cuatro")
  6. // the stack now contains 4 strings


ここで stackOfStrings がスタック上にこれらの4つの値をプッシュした後どのように見えるかを示します:





スタックから値をポップし、一番上の値、"cuatro" を削除し、戻ります。


  1. let fromTheTop = stackOfStrings.pop()
  2. // fromTheTop is equal to "cuatro", and the stack now contains 3 strings


ここで、スタックがその一番上の値をポップした後にどのように見えるかを示します:





汎用型の拡張


汎用型を拡張するときは、拡張機能の定義の一部として型パラメータリストを提供しないで下さい。代わりに、元の 型の定義から型パラメータリストが拡張機能の本体内で利用可能であり、元の型パラメータの名前は、元の定義から型パラメータを参照するために使用されます。


以下の例では、スタックからポップすることなく、スタック上の一番上の項目を返す、topItem と言う読み取り専用の計算されたプロパティを追加するために、汎用の Stack 型を拡張します。


  1. extension Stack {
  2. var topItem: Element? {
  3. return items.isEmpty ? nil : items[items.count - 1]
  4. }
  5. }


topItem プロパティは Element 型の optional の値を返します。スタックが空の場合、topItemnil を返します。スタックが空でない場合、topItemitems 配列内の最後の項目を返します。


この拡張機能は型パラメータリストを定義していないことに注意してください。代わりに、Stack 型の既存の型パラメータ名、Element が、topItem の計算されたプロパティの optional の型を示すために拡張機能内で使用されています。


topItem の計算されたプロパティは、どの Stack インスタンスでも使用できるようになり、それを削除する事なく、その先頭の項目にアクセスし、照会できるようになります。


  1. if let topItem = stackOfStrings.topItem {
  2. print("The top item on the stack is \(topItem).")
  3. }
  4. // prints "The top item on the stack is tres."


以下の 汎用の Where 句を含む拡張機能 で説明するように、汎用型の拡張機能には、新しい機能を取得するために拡張型のインスタンスが満たさなければならない要件も含まれます。


型制約


swapTwoValues(_:_:) 関数と Stack 型は全ての型を扱うことができます。しかし、汎用関数と汎用型で使用できる型に特定の 型制約 を強制すると便利な場合があります。型制約は、型パラメータが特定のクラスから継承しなければならず、特定のプロトコルまたはプロトコル構成に準拠することを指定します。


たとえば、Swift の Dictionary 型には辞書のキーとして使用できる型の制限があります。Dictionary で説明したように、辞書のキーの型は ハッシュ可能 (hashable) でなければなりません。つまり、それ自体を独自に表現可能にする方法を提供しなければなりません。Dictionary は、それが既に特定のキーの値を含んでいるかどうか確認できるようにそのキーがハッシュ可能である事を必要としています。この要件がなければ、Dictionary は、特定のキーの値を挿入または置き換える必要があるかどうかを見分けることができませんし、また辞書に既にある与えられたキーの値を見つけることもできないでしょう。


この要件は、Dictionary のキー型の、型制約により強制され、Swift 標準ライブラリで定義された特別なプロトコルの Hashable プロトコルに、そのキー型が準拠しなければならない事を指定します。(String、Int、Double、および bool のような) Swift の基本的なすべての型は、デフォルトでハッシュ可能です。独自のカスタム型を Hashable プロトコルに準拠させる方法については、Hashable プロトコルへの準拠 を参照してください。


カスタムの汎用型を作成する時、独自の型制約を定義することができ、そしてこれらの制約は、汎用プログラミングに多くの力を提供します。Hashable のような抽象的な概念は、それらの具象的な型よりも、それらの概念的な特性の点で型を特徴づけます。



型制約の構文


型パラメータリストの一部として、コロンで区切って、型パラメータの名前の後に、一つのクラスまたはプロトコル制約を配置することにより、型制約を書く事ができます。(構文は汎用型と同じですが) 汎用関数での型制約の基本的な構文を以下に示します。


  1. func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
  2. // function body goes here
  3. }


上記の仮の関数には、2つの型パラメータがあります。最初の型パラメータ T には、SomeClass のサブクラスであるように T を要求する型制約があります。第二の型パラメータ、U には、プロトコル SomeProtocol に準拠するよう U に求める型制約があります。



実際の型制約


ここで検索すべき与えられた String 値と、それをその中で見つけるべき String 値の配列を与えられた、findIndex(ofString:in:) と言う汎用でない関数を示します。findIndex(ofString:in:) 関数は、それが見つかった場合配列内で最初に一致した文字列のインデックスの、optional の Int 値を返し、または文字列が見つからない場合は nil を返します:


  1. func findIndex(ofString valueToFind: String, in array: [String]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }


findIndex(ofString:in:) 関数は、文字列の配列内の文字列値を見つけるために使用できます。


  1. let strings = ["cat", "dog", "llama", "parakeet", "terrapin"]
  2. if let foundIndex = findIndex(ofString: "llama", in: strings) {
  3. print("The index of llama is \(foundIndex)")
  4. }
  5. // prints "The index of llama is 2"


配列内の値のインデックスを見つける原理は、しかし、文字列のみのためだけに有用ではありません。代わりに何らかの型 T の値を使用して文字列の全ての言及を置き換える汎用関数として、同じ機能を書くことができます。


ここで、findIndex(of:in:) と言う findIndex(ofString:in:) の汎用バージョンを書けることを期待している方法を記します。この関数の戻り値の型は、配列からの optional の値ではなく、optional のインデックス番号を関数は返すため、まだ Int? であることに注意してください。しかし、注意して下さい、理由は例の後に説明しますが、この関数はコンパイルされません:


  1. func findIndex<T>(of valueToFind :T, in array: [T]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }


上述したように、この関数は、コンパイルされません。問題は、"if value == valueToFind" の等価性チェックの所です。Swift のすべての型が、等価演算子 (==) で比較できるわけではありません。たとえば、複雑なデータモデルを表現するために、独自のクラスまたは構造体を作成した場合、その後そのクラスまたは構造体の"等しい" という意味が Swift に推測できるわけではありません。このため、このコードは、すべての 可能な型 T で動作することを保証できず、コードをコンパイルしようとすると、適切なエラーが報告されます。


すべては、しかし、失われません。Swift 標準ライブラリは、Equatable と言うプロトコルを定義し、その型の全ての2つの値を比較するために等価演算子 (==) と非等価演算子 (!=) を実装するように、全ての準拠型に要求しています。Swift の標準的な型すべてが自動的に Equatable プロトコルをサポートしています。


Equatable である全ての型は、findIndex(of:in:) 関数で安全に使用でき、というのもそれは等価演算子をサポートすることを保証しているためです。関数を定義するときに、この事実を表現するには、型パラメータの定義の一部として Equatable の型制約を書いて下さい。


  1. func findIndex<T: Equatable>(of valueToFind: T, in array: [T]) -> Int? {
  2. for (index, value) in array.enumerated() {
  3. if value == valueToFind {
  4. return index
  5. }
  6. }
  7. return nil
  8. }


findIndex(of:in:) の一つの型パラメータは、T:Equatable と書かれ、これは "Equatable プロトコルに準拠する全ての型 T" を意味します。


findIndex(of:in:) 関数はうまくコンパイルするようになり、DoubleString のような Equatable である全ての型で使用できるようになります:


  1. let doubleIndex = findIndex(of:9.3, in: [3.14159, 0.1, 0.25])
  2. // doubleIndex is an optional Int with no value, because 9.3 is not in the array
  3. let stringIndex = findIndex(of:"Andrea", in: ["Mike", "Malcolm", "Andrea"])
  4. // stringIndex is an optional Int containing a value of 2


関連型


プロトコルを定義する際には、プロトコルの定義の一部として 1 つ以上の関連型を宣言すると便利な場合があります。関連型 はプロトコルの一部として使用される型へのプレースホルダ名を与えます。その関連型に使用する実際の型はプロトコルが採用されるまで、特定されません。関連型は associatedtype キーワードで指定されます。


実際の関連型


ここで Container と言うプロトコルの例をあげますが、これは Item と言う関連型を宣言します。


  1. protocol Container {
  2. associatedtype Item
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. }


Container プロトコルは、全てのコンテナが提供しなければならない3つの必要な機能を定義しています:


このプロトコルは、コンテナ内の項目がどのように格納されるべきか、またそれらがどのような型であるべきかを指定しません。プロトコルは、全ての型が Container と見なされるために提供しなければならない機能を3ビットだけ指定します。準拠型は、これら3つの要件を満たすように、追加の機能を提供できます。


Container プロトコルに準拠する全ての型は、格納する値の型を指定できなければなりません。具体的には、正しい型の項目だけがコンテナに追加されていることを確認しなければならず、そのサブスクリプトで返される項目の型が明確でなければなりません。


これらの要件を定義するには、Container プロトコルは、その型が特定のコンテナで何のためにあるのか知る事なしに、コンテナが保持する要素の型を参照するための方法が必要です。Containaer プロトコルは、 append(_:) メソッドに渡された全ての値がコンテナの要素型と同じ型を持たなければならないと指定する必要があり、コンテナのサブスクリプトで返された値は、コンテナの要素型と同じ型のものになることを指定します。


これを実現するために、Container プロトコルは associatedtype Item として書かれた Item と言う関連型を宣言します。プロトコルは、Item が何かを定義しませんー提供する全ての準拠型のためにその情報は残されています。それにもかかわらず、Item エイリアスは、Container 内の項目の型を参照する方法を提供し、 append(_:) メソッドとサブスクリプトで使用するための型を定義し、全ての Container の期待される動作が強制されているることを確認します。


ここに、上記の 汎用の型 からの汎用でない IntStack 型のバージョンで、Container プロトコルに準拠するようになっている例を挙げます。


  1. struct IntStack: Container {
  2. // original IntStack implementation
  3. var items : [Int] = []
  4. mutating func push(_ item: Int) {
  5. items.append(item)
  6. }
  7. mutating func pop() -> Int {
  8. return items.removeLast()
  9. }
  10. // conformance to the Container protocol
  11. typealias Item = Int
  12. mutating func append(_ item: Int) {
  13. self.push(item)
  14. }
  15. var count: Int {
  16. return items.count
  17. }
  18. subscript(i: Int) -> Int {
  19. return items[i]
  20. }
  21. }


IntStack 型は、Container プロトコルの要件の3つ全てを実装し、それぞれの場合に、これらの要件を満たすために IntStack 型の既存の機能の一部を包み込みます。


また、IntStack は、Container のこの実装のために、使用すべき適切な ItemInt 型であることを指定します。 typealias Item = Int の定義は、Container プロトコルのこの実施のため、Item の抽象型を Int の具体的な型に変えます。


Swift の型推論のおかげで、実際には IntStack の定義の一部として Int の具体的な Item を宣言する必要はありません。IntStackContainer プロトコル要件のすべてに準拠しているので、Swift は、単に append(_:) メソッドの item パラメータ型とサブスクリプトの戻り値の型を見ることで、使用すべき適切な Item を推測できます。実際、上記のコードから typealias Item = Int の行を削除した場合でも、Item のためどのような型を使用するべきか明らかであるため、すべては、まだ動作します。


また、汎用の Stack 型を Container プロトコルに準拠させることもできます。


  1. struct Stack<Element>: Container {
  2. // original Stack<Element> implementation
  3. var items : [Element] = []
  4. mutating func push(_ item: Element) {
  5. items.append(item)
  6. }
  7. mutating func pop() -> Element {
  8. return items.removeLast()
  9. }
  10. // conformance to the Container protocol
  11. mutating func append(_ item: Element) {
  12. self.push(item)
  13. }
  14. var count: Int {
  15. return items.count
  16. }
  17. subscript(i: Int) -> Element {
  18. return items[i]
  19. }
  20. }


今度は、型パラメータ Element は、append(_:) メソッドの item パラメータの型と、サブスクリプトの戻り値の型として使用されています。Swift は、したがって、その Element は、この特定のコンテナのため Item として使用するのに適切な型と推測できます。



既存の型を拡張して関連型を指定


拡張機能を持つプロトコル準拠の追加 で説明したように、プロトコルへの準拠を追加するために、既存の型を拡張できます。これは、関連型を持つプロトコルを含みます。


Swift の Array 型はすでに append(_:) メソッドを提供し、count プロパティ、およびその要素を取得するため Int インデックスでのサブスクリプトを提供しています。これら三つの機能は、Container プロトコルの要件に合致しています。これは、単にその Array がプロトコルに適合する事を宣言する事だけで Container プロトコルに準拠するために、Array を拡張できることを意味します。拡張機能を持つプロトコルの採用を宣言 で説明したように、空の拡張機能でこれを行います:


extension Array: Container {}



Array の既存の append(_:) メソッドとサブスクリプトにより、Swift は、上記の汎用の Stack 型とまさしく同じように、Item に使用する適切な型を推測できます。この拡張機能を定義した後は、任意の ArrayContainer として使用できます。



関連型に制約を追加


型制約をプロトコルの関連型に追加して、準拠する型がそれらの制約を満たすことを要求することができます。たとえば、以下のコードでは、コンテナ内の項目を equatable にする必要がある Container のバージョンを定義しています。


  1. protocol Container {
  2. associatedtype Item: Equatable
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. }


Container のこのバージョンに準拠するためには、コンテナの Item 型は Equatable プロトコルに準拠しなければなりません。



関連型の制約でプロトコルを使用


プロトコルはそれ自身の要件の一部として現れることができます。たとえば、Container プロトコルを改良し、suffix(_:) メソッドの要件を追加するプロトコルをここに挙げます。suffix(_:) メソッドはコンテナの最後から与えられた要素の数を返し、それらを Suffix 型のインスタンスに格納します。


  1. protocol SuffixableContainer: Container {
  2. associatedtype Suffix: SuffixableContainer where Suffix.Item == Item
  3. func suffix(_ size: Int) -> Suffix
  4. }


このプロトコルでは、Suffix は上記の Container の例の Item 型のように、関連型です。Suffix には 2 つの制約があります。SuffixableContainer プロトコル (現在定義されているプロトコル) に準拠していなければなりません。また、その Item 型はコンテナの Item 型と同じでなければなりません。Item の制約は汎用の where 句であり、これについては、以下の 関連型と汎用の Where 句 で説明します。


ここに、上記の 汎用の型 からの Stack 型の拡張機能で、SuffixableContainer プロトコルへの準拠を追加した例を挙げます。


  1. extension Stack: SuffixableContainer {
  2. func suffix(_ size: Int) -> Stack {
  3. var result = Stack()
  4. for index in (count-size)..<count {
  5. result.append(self[index])
  6. }
  7. return result
  8. }
  9. // Inferred that Suffix is Stack.
  10. }
  11. var stackOfInts = Stack<Int>()
  12. stackOfInts.append(10)
  13. stackOfInts.append(20)
  14. stackOfInts.append(30)
  15. let suffix = stackOfInts.suffix(2)
  16. // suffix contains 20 and 30


上記の例では、StackSuffix 関連型も Stack であるため、Stack の suffix 操作は別の Stack を返します。あるいは、SuffixableContainer に準拠する型は、それ自身とは異なる Suffix 型を持つことができます。つまり、suffix 操作は異なる型を返すことができます。例えば、以下は、SuffixableContainer 準拠を追加する、汎用でない IntStack 型への拡張機能で、IntStack の代わりに suffix 型として Stack<Int> を使用しています。


  1. extension IntStack: SuffixableContainer {
  2. func suffix(_ size: Int) -> Stack<Int> {
  3. var result = Stack<Int>()
  4. for index in (count-size)..<count {
  5. result.append(self[index])
  6. }
  7. return result
  8. }
  9. // Inferred that Suffix is Stack<Int>.
  10. }


汎用のWhere 句


型制約 で説明したように、型制約は、汎用関数、サブスクリプトまたは型に関連した型パラメータの要件を定義できます。


また、関連型の要件を定義することも有用です。汎用の where 句 を定義することによってこれを行って下さい。汎用の where 句は、関連型が特定のプロトコルに準拠していなければならないことを要求し、または特定の型パラメータと関連型が同じでなければならないことを要求します。汎用の where 句は、where キーワードで始まり、関連する型の制約、または型と関連する型の間の等価関係が続きます。型または関数の本体の開中括弧の直前に汎用の where 句を記述して下さい。


以下の例では、2つの Container インスタンスが同じ順序で同じ項目を含んでいるかをチェックする、allItemsMatch と言う汎用関数を定義しています。関数は、すべての項目が一致する場合は true のブール値を返し、一致しない場合は false の値を返します。


チェックすべき二つのコンテナは、コンテナの同じ型である必要はないが (同じであってもよい)、同じ型の項目を保持しなければなりません。この要件は、型制約と汎用 where 句の組み合わせによって表現されています。


  1. func allItemsMatch<C1: Container, C2: Container>
  2. (_ someContainer: C1, _ anotherContainer: C2) -> Bool
  3. where C1.Item == C2.Item, C1.Item: Equatable {
  4. // Check that both containers contain the same number of items.
  5. if someContainer.count != anotherContainer.count {
  6. return false
  7. }
  8. // Check each pair of items to see if they are equivalent.
  9. for i in 0..<someContainer.count {
  10. if someContainer[i] != anotherContainer[i] {
  11. return false
  12. }
  13. }
  14. // All items match, so return true.
  15. return true
  16. }


この関数は、someContaineranotherContainer と言う2つの引数を取ります。someContainer 引数は C1 型のものであり、anotherContainer 引数は C2 型のものです。C1C2 は両方とも、2つのコンテナ型の型パラメータで、関数が呼び出された際に決定されます。


以下の要件が関数の2つの型パラメータに配置されています:


第一および第二の要件は、関数の型パラメータリストに定義されており、第三および第四の要件は、関数の汎用 where 句で定義されています。


これらの要件は、以下を意味します:


第三及び第四の要件を合わせると、anotherContainer 内の項目 someContainer 内の項目と完全に同じ型であるため、!= 演算子で確認できることを意味します。


これらの要件は、それらが異なるコンテナ型であっても、allItemsMatch(_:_:) 関数で二つのコンテナを比較することを可能にします。


allItemsMatch(_:_:) 関数は、両方のコンテナが同じ数の項目を含んでいることをチェックすることによって始まります。それらが異なる数の項目を含んでいる場合、それらが一致できる方法はなく、関数は false を返します。


このチェックを行った後、関数は for-in ループと半開放範囲演算子 (..<) で someContainer 内のすべての項目にわたって反復処理します。各項目について、関数は someContainer からの項目が anotherContainer 内の対応する項目と同じではないかチェックします。2つの項目が等しくない場合、2つのコンテナは、一致しておらず、関数は false を返します。


ループが不一致を見つけることなく終了した場合、二つのコンテナは一致し、関数は true を返します。


ここで、allItemsMatch(_:_:) 関数が実際にどのように見えるかを示します:


  1. var stackOfStrings = Stack<String>()
  2. stackOfStrings.push("uno")
  3. stackOfStrings.push("dos")
  4. stackOfStrings.push("tres")
  5. var arrayOfStrings = ["uno", "dos", "tres"]
  6. if allItemsMatch(stackOfStrings, arrayOfStrings) {
  7. print("All items match.")
  8. } else {
  9. print("Not all items match.")
  10. }
  11. // prints "All items match."


上記の例では、String 値を格納する Stack インスタンスを作成し、スタック上に3つの文字列をプッシュします。この例ではまた、スタックと同じ3つの文字列を含む配列リテラルで初期化された Array インスタンスも作成します。スタックと配列が異なる型のものであっても、それら両者は、Container プロトコルに準拠しており、両者は同じ型の値を含んでいます。そのため、その引数として、これら二つのコンテナで allItemsMatch(_:_:) 関数を呼び出すことができます。上記の例では、allItemsMatch(_:_:) 関数は二つのコンテナ内の項目がすべて一致していることを正しく報告します。



汎用の Where 句を含む拡張機能


また、汎用の where 句を拡張機能の一部として使用することもできます。以下の例では、以前の例の汎用の Stack 構造体を拡張して isTop(_:) メソッドを追加しています。


  1. extension Stack where Element: Equatable {
  2. func isTop(_ item: Element) -> Bool {
  3. guard let topItem = items.last else {
  4. return false
  5. }
  6. return topItem == item
  7. }
  8. }


この新しい isTop(_:) メソッドは、最初にスタックが空でないことを確認して、それから与えられた項目をスタックの最上位の項目と比較します。汎用の where 句を使わずにこれを実行しようとすると、問題が発生します。isTop(_:) の実装では、== 演算子を使用しますが、Stack の定義ではその項目が equatable である必要はありません。そのため、== 演算子を使用するとコンパイル時エラーを起こします。汎用の where 句を使用すると、拡張機能に新しい要件を追加できるので、スタック内の項目が equatable である場合にのみ、拡張機能は isTop(_:) メソッドを追加します。


isTop(_:) メソッドが実際にどうなるかは以下のとおりです。


  1. if stackOfStrings.isTop("tres") {
  2. print("Top element is tres.")
  3. } else {
  4. print("Top element is something else.")
  5. }
  6. // Prints "Top element is tres."


その要素が equatable でないスタックで isTop(_:) メソッドを呼び出そうとすると、コンパイル時エラーが発生します。


  1. struct NotEquatable { }
  2. var notEquatableStack = Stack<NotEquatable>()
  3. let notEquatableValue = NotEquatable()
  4. notEquatableStack.push(notEquatableValue)
  5. notEquatableStack.isTop(notEquatableValue)     // Error


汎用の where 句をプロトコルへの拡張機能とともに使用することができます。以下の例では、以前の例の Container プロトコルを拡張して startsWith(_:) メソッドを追加しています。


  1. extension Container where Item: Equatable {
  2. func startsWith(_ item: Item) -> Bool {
  3. return count >= 1 && self[0] == item
  4. }
  5. }


startsWith(_:) メソッドは、コンテナに少なくとも 1 つの項目があることを最初に確認し、コンテナ内の最初の項目が与えられた項目と一致するかどうかをチェックします。この新しい startsWith(_:) メソッドは、コンテナの項目が equatable である限り、上記で使用されたスタックや配列を含め、Container プロトコルに準拠するすべての型で使用できます。


  1. if [9, 9, 9].startsWith(42) {
  2. print("Starts with 42.")
  3. } else {
  4. print("Starts with something else.")
  5. }
  6. // Prints "Starts with something else."


上記の例の汎用の where 句は、Item がプロトコルに準拠することを要求しますが、Item が特定の型である事を必要とする汎用の where 句を書くこともできます。例えば:


  1. extension Container where Item == Double {
  2. func average() -> Double {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += self[index]
  6. }
  7. return sum / Double(count)
  8. }
  9. }
  10. print([1260.0, 1200.0, 98.6, 37.0].average())
  11. // Prints "648.9"


この例では、その Item 型が Double であるコンテナに average() メソッドを追加しています。コンテナ内の項目を繰り返し処理して追加し、コンテナのカウントで除算して平均を計算します。浮動小数点除算を行う事ができるようになるために、Int から Double へカウントを明示的に変換します。


拡張機能の一部である汎用の where 句に複数の要件を含めることができ、これは、他の場所で記述する汎用の where 句と同様です。リストの各要件をコンマで区切ります。



文脈上の Where 句


ジェネリック型の文脈で既に作業している場合は、独自の汎用型制約を持たない宣言の一部として汎用の where 句を記述できます。たとえば、汎用型のサブスクリプトまたは汎用型への拡張機能のメソッドに汎用の where 句を記述できます。Container 構造体は汎用であり、以下の例の where 句は、これらの新しいメソッドをコンテナで使用できるようにするために満たさなければならない型制約を指定します。


  1. extension Container {
  2. func average() -> Double where Item == Int {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += Double(self[index])
  6. }
  7. return sum / Double(count)
  8. }
  9. func endsWith(_ item: Item) -> Bool where Item: Equatable {
  10. return count >= 1 && self[count-1] == item
  11. }
  12. }
  13. let numbers = [1260, 1200, 98, 37]
  14. print(numbers.average())
  15. // Prints "648.75"
  16. print(numbers.endsWith(37))
  17. // Prints "true"


この例では、Item (項目) が Int (整数) の場合は average() メソッドを Container に追加し、項目が equatable (同等) の場合は endsWith(_:) メソッドを追加します。どちらの関数にも、Container の元の宣言から汎用の Item の型パラメータに型制約を追加する汎用の where 句が含まれています。


文脈上の where 句を使用せずにこのコードを記述したい場合は、汎用の where 句ごとに 1 つずつ、合計 2 つの拡張機能を記述して下さい。上記の例と以下の例は同じ動作をします。


  1. extension Container where Item == Int {
  2. func average() -> Double {
  3. var sum = 0.0
  4. for index in 0..<count {
  5. sum += Double(self[index])
  6. }
  7. return sum / Double(count)
  8. }
  9. }
  10. extension Container where Item: Equatable {
  11. func endsWith(_ item: Item) -> Bool {
  12. return count >= 1 && self[count-1] == item
  13. }
  14. }


文脈上の where 句を使用するこの例のバージョンでは、average()endsWith(_:) の実装は両方とも同じ拡張機能にあります。これは、各メソッドの汎用の where 句が、そのメソッドを使用可能にするために満たすべき要件を示しているためです。これらの要件を拡張機能の汎用の where 句に移動すると、同じ状況でメソッドを使用できるようになりますが、要件ごとに 1 つの拡張機能が必要になります。



関連型と汎用の Where 句


関連型に汎用の where 句を含めることができます。たとえば、Sequence プロトコルが標準ライブラリで使用するような iterator(反復因子) を含むバージョンの Container を作成するとします。これを書く方法は以下のとおりです。


  1. protocol Container {
  2. associatedtype Item
  3. mutating func append(_ item: Item)
  4. var count: Int { get }
  5. subscript(i: Int) -> Item { get }
  6. associatedtype Iterator: IteratorProtocol where Iterator.Element == Item
  7. func makeIterator() -> Iterator
  8. }


Iterator 上の汎用の where 句では、反復因子の型にかかわらず、反復因子はコンテナの項目と同じ項目型の要素を横断しなければなりません。makeIterator() 関数は、コンテナの反復因子へのアクセスを提供します。


別のプロトコルから継承するプロトコルの場合、プロトコル宣言に汎用の where 句を含めることによって、継承された関連型に制約を追加して下さい。たとえば、以下のコードは、Item が Comparable に準拠していることを要求する ComparableContainer プロトコルを宣言しています。


protocol ComparableContainer: Container where Item: Comparable { }



汎用のサブスクリプト


サブスクリプトは汎用にする事ができ、汎用の where 句を含めることができます。subscript の後で角括弧の中にプレースホルダ型の名前を書いて、サブスクリプトの本体の開き中括弧の直前に汎用の where 句を書いて下さい。例えば:


  1. extension Container {
  2. subscript<Indices: Sequence>(indices: Indices) -> [Item]
  3. where Indices.Iterator.Element == Int {
  4. var result = [Item]()
  5. for index in indices {
  6. result.append(self[index])
  7. }
  8. return result
  9. }
  10. }


Container プロトコルへのこの拡張機能は、一連のインデックスを取り、与えられた各インデックスに項目を含む配列を返すサブスクリプトを追加します。この汎用のサブスクリプトは、以下のように制約されます。



まとめると、これらの制約は、indices パラメータに渡される値が整数のシーケンスであることを意味します。


前:プロトコル 次:不透明な型
















トップへ












トップへ












トップへ












トップへ
目次
Xcode の新機能

Swift について
Swift と Cocoa と Objective-C (obsolete)
Swift Blog より (obsolete)

SwiftLogo
  • Swift 5.8 全メニュー


  • Swift へようこそ
  • Swift について
  • Swift 言語のガイド
  • Swift の基本
  • 基本演算子
  • 文字列と文字
  • コレクション型
  • フロー制御
  • 関数
  • クロージャ
  • 列挙型
  • 構造体とクラス
  • プロパティ
  • メソッド
  • サブスクリプト
  • 継承
  • 初期化
  • デイニシャライザ
  • Optional の連鎖
  • エラー処理
  • 同時実行
  • 型キャスト
  • ネストした型
  • 拡張機能
  • プロトコル
  • ジェネリック(汎用)
  • 不透明な型
  • 自動参照カウント
  • メモリの安全性
  • アクセス制御
  • 高度な演算子

  • 言語リファレンス

  • マニュアルの変更履歴













  • トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ