【Swift】ジェネリクスの意味と使い方!Comparableプロトコルとは?

この記事からわかること
- Swiftのジェネリクスとは?
- 意味や使い方、メリット
- 関数や構造体への組み込み方
- Comparableプロトコルとは?
index
[open]
\ アプリをリリースしました /
ジェネリクスとは?
ジェネリクスとはSwift言語の仕様の1つで、特定のデータの型に依存しない関数などを定義することができます。Swift内部でもジェネリクスは使用されており、タプルや配列などが異なるデータ型を保持できるのもこのジェネリクスを使用しているからのようです。
ジェネリクスのような仕様は他言語でも実装されている仕様でジェネリックプログラミングとも呼ばれています。generic(:ジェネリック)は日本語で「一般的な」を意味する英単語です。
ジェネリクスの構文と使い方
ジェネリクスは型を定義する様に使用したり、関数名の後に<任意の型名>
を使って使用します。型名は任意の名称を付けることができますが、慣習的にT
が使われることが多いようです。また任意の際の命名規則はキャメルケース(CamelCase)を用いることが推奨されています。
例えば下記のような引数のデータ型を出力してnil
を返す関数を定義するとします。関数名の後にジェネリクスを表す<T>
を、引数には2つとも様々なデータ型でも許容できるように同様にT
を指定します。今回は返り値にnil
を許容できるようにオプショナル型にします。型の制限を設けないジェネリクスですがオプショナル型は許容していないので明示的に?
を使ってオプショナル型を許容します。
func judgeType<T> (one: T, two: T) -> T? {
print("oneのデータ型は\(type(of: one))")
print("twoのデータ型は\(type(of: two))")
return nil
}
上記の実行結果
judgeType(one: 6, two: 4000) // oneのデータ型はInt // twoのデータ型はInt judgeType(one: 0.7, two: 4000) // oneのデータ型はDouble // twoのデータ型はDouble judgeType(one: "Swift", two:"UI") // oneのデータ型はString // twoのデータ型はString judgeType(one: "Swift", two: 4000) // パースエラー
expression failed to parse: error: conflicting arguments to generic parameter 'T' ('Int' vs. 'String') judgeType(one: "Swift", two:5)
judgeType
関数は1つしか定義していませんが、異なるデータ型を引数で受け取ってもエラーになることなく実行することができます。しかし今回のように引数2つともにジェネリクスT
を指定した場合、両者のデータ型は同じでなければなりません。
またこのようにジェネリクスを用いた関数のことをジェネリクス関数と呼びます。
ジェネリクスのポイント
- データ型の制限をなくす
- ジェネリクスは「T」もしくはキャメルケースで命名する(慣習的に)
- nil(オプショナル型)は許容できない
- ジェネリクスを指定した複数の引数は同じデータ型でないとエラーになる
- 関数内で比較をしたい場合はComparableプロトコルを指定
Comparableプロトコルでジェネリクス関数に比較式を許容する
ジェネリクスを引数に用いている場合に関数の中で比較( = や > など)をしてしまうとエラーになってしまいます。
func equalType<T> (one: T, two: T) -> Bool {
if one == two {
return true
}else{
return false
}
}
比較を用いた場合のパースエラー
expression failed to parse:
error: binary operator '==' cannot be applied to two 'T' operands
// エラー:二項演算子'=='は2つの'T'オペランドに適用できません
これを防ぐにはT
の後にComparableプロトコル
を指定します。
func equalType<T: Comparable> (one: T, two: T) -> Bool {
if one == two {
return true
}else{
return false
}
}
equalType(one: 6, two: 6) // true
equalType(one: "SWIFT", two: "swift") // false
エラーになってしまう理由はどんなデータ型でも許容できる代わりに、比較できないデータ型を渡される可能性もあるためです。
Comparable
プロトコルは値の比較が可能なデータ型であることをルールとしているプロトコルです。
Comparableプロトコルの定義
public protocol Comparable : Equatable {
static func < (lhs: Self, rhs: Self) -> Bool
static func <= (lhs: Self, rhs: Self) -> Bool
static func >= (lhs: Self, rhs: Self) -> Bool
static func > (lhs: Self, rhs: Self) -> Bool
}
受ける変数の型によってメソッド内の型を変更する
ジェネリクスを使用して以下のようにメソッド自体には引数などで受け取らず、受け取る変数の型に応じてメソッド内の処理を切り替えることも可能です。
func test<T>() -> T {
if T.self == Int.self {
return 10 as! T
} else if T.self == String.self {
return "Hello" as! T
} else {
fatalError("Unsupported type")
}
}
let test2:Int = test() // test2はInt型
print(test2) // 10
let test3:String = test() // test3はString型
print(test3) // Hello
構造体にジェネリクスを指定する
構造体にジェネリクスを指定する場合も関数と同様に<T>
のように定義します。
struct Box<T>{
var area1 : T
var area2 : T
var key : String
}
またプロパティ(変数)にもBox<T>
部分で指定した型名でジェネリクスとして指定することができるようになります。
プロトコルへのジェネリックプログラミング
Swiftのプロトコルに対してジェネリックプログラミングな実装をするために用意されているassociatedtype
キーワードが用意されています。
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。