Swift ジェネリック
Swift は、柔軟で再利用可能な関数や型を書くためのジェネリックを提供します。
Swift 標準ライブラリはジェネリックコードで構築されています。
Swift の配列型と辞書型はどちらもジェネリックコレクションです。
Int 配列を作成することも、String 配列を作成することも、あるいは他の任意の Swift 型のデータ配列を作成することもできます。
次の例は、2 つの Int 値を交換するための非ジェネリック関数 exchange です。
実例
上記プログラムの実行結果は次のとおりです。
交换前数据: 100 和 200 交换后数据: 200 和 100
上記の例は整数 Int 型の変数の交換にのみ適用できます。2 つの String 値または Double 値を交換したい場合は、対応する関数を書き直す必要があります。たとえば、swapTwoStrings(_:_:) と swapTwoDoubles(_:_:) です。以下に示します。
String と Double の値交換関数
上記のコードから分かるように、それらの機能コードは同じですが、型だけが異なります。このような場合、ジェネリックを使用することで、コードの重複を避けることができます。
ジェネリックは、実際の型名(Int、String、Double など)の代わりにプレースホルダ型名(ここでは文字 T で表します)を使用します。
func swapTwoValues<T>(_ a: inout T, _ b: inout T)
swapTwoValues の後にはプレースホルダ型名(T)が続き、山括弧で囲みます(<T>)。この山括弧は、T が swapTwoValues(_:_:) 関数定義内のプレースホルダ型名であることを Swift に伝えます。したがって、Swift は T という名前の実際の型を探しに行きません。
次の例は、2 つの Int または String の値を交換するためのジェネリック関数 exchange です。
実例
上記プログラムの実行結果は次のとおりです。
交换前数据: 100 和 200 交换后数据: 200 和 100 交换前数据: A 和 B 交换后数据: B 和 A
ジェネリック型
Swift では、独自のジェネリック型を定義できます。
独自のクラス、構造体、列挙型は、Array や Dictionary と同じように、あらゆる型に対して機能します。
次に、Stack(スタック)というジェネリックコレクション型を作成します。スタックは、コレクションの末尾にのみ新しい要素を追加でき(プッシュと呼びます)、末尾からのみ要素を削除することもできます(ポップと呼びます)。

図中では左から右に次のように説明されます。
- 3 つの値がスタックにあります。
- 4 つ目の値がスタックの一番上にプッシュされます。
- 現在、スタックには 4 つの値があり、最後にプッシュされた値が一番上にあります。
- スタックの一番上の値が削除されます。これをポップと呼びます。
- 1 つの値を削除した後、スタックには再び 3 つの値だけが残ります。
次の例は、非ジェネリック版のスタックで、Int 型のスタックを例にしています。
Int 型のスタック
この構造体は、スタック内の値を格納するために items という名前の Array プロパティを使用します。Stack は、値をスタックにプッシュし、スタックから値を削除するための 2 つのメソッド push(_:) と pop() を提供します。これらのメソッドは構造体の items 配列を変更する必要があるため、mutating としてマークされています。
上記の IntStack 構造体は Int 型にしか使用できません。ただし、ジェネリックな Stack 構造体を定義すれば、任意の型の値を処理できます。
以下は、同じコードのジェネリックバージョンです。
ジェネリックなスタック
実行結果は次のとおりです。
字符串元素入栈: ["google", "サンプル"] 出栈元素: サンプル 整数元素入栈: [1, 2]
Stack は基本的に IntStack と同じですが、実際の Int 型の代わりにプレースホルダ型パラメータ Element を使用しています。
上記の例では、Element は次の 3 つの場所でプレースホルダとして使用されています。
- 作成itemsプロパティを使用Element型の空配列で初期化します。
- 指定push(_:)メソッドの唯一のパラメータitemの型はElement型でなければなりません。
- 指定pop()メソッドの戻り値の型はElement型でなければなりません。
ジェネリック型の拡張
ジェネリック型を拡張するとき(extension キーワードを使用)、拡張の定義で型パラメータリストを提供する必要はありません。さらに便利なことに、元の型定義で宣言された型パラメータリストは拡張内で使用でき、元の型からのこれらのパラメータ名は、元の定義の型パラメータへの参照として使用されます。
次の例では、ジェネリック型 Stack を拡張し、topItem という名前の読み取り専用の計算型プロパティを追加します。これは、現在のスタックの最上位の要素を返しますが、スタックからは削除しません。ジェネリック
この例では、topItem プロパティは Element 型のオプショナル値を返します。スタックが空のとき、topItem は nil を返します。スタックが空でないとき、topItem は items 配列の最後の要素を返します。
上記プログラムの実行結果は次のとおりです。
字符串元素入栈: 栈中的顶部元素是:サンプル. ["google", "サンプル"]
既存の型を拡張することによって関連型を指定することもできます。
たとえば、Swift の Array 型はすでに append(_:) メソッド、count プロパティ、および Int 型のインデックス値を受け取って要素を取得する添え字を提供しています。これらの 3 つの機能はすべて Container プロトコルの要件を満たしているため、Array がこのプロトコルを採用していると宣言するだけで Array を拡張できます。
次の例では、空の拡張を作成するだけで済みます。
extension Array: Container {}
型制約
型制約は、指定されたクラスを継承するか、特定のプロトコルまたはプロトコル構成に準拠する必要がある型パラメータを指定します。
型制約構文
型パラメータ名の後に型制約をコロンで区切って記述し、型パラメータチェーンの一部にすることができます。ジェネリック関数に適用される型制約の基本構文は次のとおりです(ジェネリック型の構文と同じ)。
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
上記の関数には 2 つの型パラメータがあります。最初の型パラメータ T には、T が SomeClass のサブクラスでなければならないという型制約があります。2 番目の型パラメータ U には、U が SomeProtocol プロトコルに準拠しなければならないという型制約があります。
実例
ジェネリック
インデックスは 0 から始まります。
上記プログラムの実行結果は次のとおりです。
サンプル 的索引为 3
関連型
Swift では、associatedtype キーワードを使用して関連型を定義します。
次の例では、関連型 ItemType を定義する Container プロトコルを定義しています。
Container プロトコルは、Container プロトコルに準拠するすべての型が提供しなければならない 3 つの機能だけを指定しています。プロトコルに準拠する型は、これらの 3 つの条件を満たしていれば、他の追加機能を提供することもできます。
// Container 协议
protocol Container {
associatedtype ItemType
// 添加一个新元素到容器里
mutating func append(_ item: ItemType)
// 获取容器中元素的数
var count: Int { get }
// 通过索引值类型为 Int 的下标检索到容器中的每一个元素
subscript(i: Int) -> ItemType { get }
}
// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
// Stack<Element> 的原始实现部分
var items = [Element]()
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element {
return items.removeLast()
}
// Container 协议的实现部分
mutating func append(_ item: Element) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Element {
return items[i]
}
}
var tos = Stack<String>()
tos.push("google")
tos.push("サンプル")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)
上記プログラムの実行結果は次のとおりです。
["google", "サンプル", "taobao"] 3
Where 文
型制約により、型がジェネリック関数またはクラスの定義の制約を満たしていることを確認できます。
パラメータリスト内で where 文を使用してパラメータの制約を定義できます。
where 文は型パラメータリストの直後に記述します。where 文の後には、関連型に対する 1 つ以上の制約、および(または)型と関連型の間の 1 つ以上の等価(equality)関係を続けます。
実例
次の例では、allItemsMatch という名前のジェネリック関数を定義しています。これは、2 つの Container インスタンスが同じ順序で同じ要素を含むかどうかをチェックします。
すべての要素が一致する場合は true を返し、そうでない場合は false を返します。
ジェネリック
上記のプログラムを実行した出力結果は次のとおりです。
すべての要素にマッチするその他の拡張