【Swift】ジェネリクスの使い方。メソッドで使うデータの型に幅を持たせる。(Swift 2.1、XCode 7.2)

2020年6月16日

ジェネリクスとは

ジェネリクスとは、プロパティやメソッドで使用される型が実際に使う段階で決定される機能である。これにより、型の制限の幅を広くできる。

ジェネリクスを利用してコードを書くことをジェネリックプログラミングという。ジェネリクス(generics)とジェネリック(generic)、読み方が微妙に違うが同じことをいっている。

以下のコードはInt型の引数を1つ受け取って、それをキーと値にして辞書に格納して返す普通の関数の例である。この関数をStringの引数にも対応させたいときどうするか。

 

普通に考えると、以下のコードのようにIntをStringに置換したもう1つの関数を作る。そして、Double型やChar型のものも必要になると似たようなコードが沢山出来上がってしまう。そんなときに利用できるのがジェネリクスである。

 

ジェネリックメソッドの定義方法

ジェネリックなメソッドは以下のように定義する。メソッド名の横に記述されている<T>が型パラメータと言われるもので、「このメソッドでTという型宣言を使います。」ということを意思表示したことになる。

そして、メソッド内にTという型宣言があると、メソッドが呼び出されるときにシステムが自動で型を判断してTを適切な型に置き換えて処理してくれる。なお、Tはすべて同一の型を意味しているので、型を混在させることは出来ない。

なお、Tが広く使われているためにTと記述しているだけで、規約に沿って好きな文字列に変更してもいい。

 

先ほどの辞書を作る関数をジェネリックな関数にすると以下のコードになる。「T型の変数を受け取り、T型のキーと値を持つ辞書を返すメソッド」であることを表している。例えば、このメソッドにInt型の変数を与えると、「Int型の変数を受け取り、Int型のキーと値を持つ辞書を返すメソッド」に様変わりするというイメージだ。

このメソッドにInt、String、Double、Bool、どんな型を引数に与えても動作する。

型パラメーターにプロトコルを指定する

上記のコードはどのような型でも引数に与えることができたが、そうできないコードがある。

例えば、以下のコードは2つの引数を比較し、「小さい方をキー、大きい方を値」で辞書に格納して返す関数であるがコンパイルエラーが発生する。

なぜなら、Int型やDouble型など大きさを比較できる型が引数として渡ってきた場合は動くが、もし、比較できない型が渡ってきた場合は動きようがないためである。

 

そんなときは<T:プロトコル名>のように定義すれば、型に適用されているプロトコルを指定することができる。

比較可能を表すプロトコルといえばComparable。以下のコードは型パラメータに<T:Comparable>を指定したものである。「T型はComparableプロトコルを適用したものでなければならない。」ことを表している。

型パラメーターの複数定義

型パラメータは何種類でも定義できる。以下のコードはTとUの2種類の型パラメータを定義した例。第1引数と第2引数は大きさを比較できる値(Comparable)、第3引数はDoubleやFloatなどの浮動小数の値(FloatingPointType)を渡す関数である。

 

型パラメーターにクラスを指定する

<T:クラス名>のように定義すれば、指定したクラス、または、そのサブクラスに限定できる。以下のコードは商品クラス、または、商品クラスのサブクラスのインスタンスを受け取るジェネリックメソッドの例。

 

クラス、プロトコルを作るときにジェネリクスを利用する

クラスやプロトコルを作るときにもジェネリクスを利用できる。

以下のコードは商品クラスの内部変数codeを型パラメータで扱うようにした例。第1引数にどんな型を指定しても商品クラスのインスタンスを生成することができる。便利だ。

 

以下のコードはジェネリクスを利用したプロトコル定義の例。プロトコル名の横に<T>を記述するのでは無く、定義内部で「typealias 型パラメータ名」のように任意の型名を宣言するのが特徴だ。

例では、商品プロトコルはT型の商品コードと、T型を引数に商品コードを変更するメソッドを宣言している。そして、Int型、または、String型を採用した2つの商品クラスを実装している。

 

型パラメータの宣言箇所でwhere句を用いると型に関する複雑な条件を指定することができる。

例えば、以下のコードは商品プロトコルが適用されている2つのインスタンスの商品コードを比較して、大きい方を返すメソッドである。where句の条件式で、「2つのインスタンスの商品コードの型が同じ(S1.T == S2.T)」、「商品コードは比較可能(S1.T:Comparable)」が指定されている。

 

上記のwhere句によって、先ほど作成したInt型バージョンとString型バージョンのインスタンスを引数に比較メソッドを呼び出すことはできない。