【Swift】ジェネリクスを簡単に理解する

Swift

ジェネリクスとは

ジェネリクスとは型をパラメータとして受け取ることで汎用的なプログラムを記述するための機能です。Swiftではジェネリクス関数とジェネリクス型として提供されており、これらを活用すれば関数や型を汎用的かつ安全に記述できるという特徴があります。

ジェネリクスの基本

定義方法

ジェネリクス関数やジェネリクス型を定義するには、通常の定義に型引数を追加します。型引数は<>で囲み、複数ある場合は、区切りで<T, U>のように定義します。以下ジェネリクス関数の例です。

//ジェネリック関数
func 関数<型引数>(引数名: 型引数) -> 戻り値の型{
	関数呼び出し時に実行される文
}

型引数として宣言された型、ジェネリクス関数やジェネリクス型の内部で通所の型と同様に扱えます。また、ジェネリクス関数の戻り値としても利用できます。

特殊化方法

実際にジェネリクス関数を呼び出したりジェネリクス型をインスタンス化したりするときには、型引数に具体的な型をする必要がある。具体的な型引数を与えて型を確定させることを特殊化といいます。方法は2つあり、一つは具体的な型を指定する方法もう一つは型推論によって型引数を推論する方法です。
以下がサンプルコードです。

struct Container<Content>{
	let content: Content
}

let stringContainer = Container<String>(content: "abc")//Content<String>

let intContainer = Container(content: 1)//Content<Int>
コード説明
struct Container<Content>{
	let content: Content
}

この構造体の宣言でジェネリクス型の宣言を行っています。型引数はContainer<Content>の部分になります。

let stringContainer = Container<String>(content: "abc")//Content<String>

明示的に型を宣言し特殊化する方法です。上記の部分で、ジェネリクス型として定義したContentにStringを当てはめています。この場合はContainer<String>とすることで明示的に型を宣言しています。

let intContainer = Container(content: 1)//Content<Int>

型推論を用いて特殊化する方法です。記の部分で、ジェネリクス型として定義したContentにInt型の値を入れることにより、型推論を用いてInt型としています。

ジェネリクスの利用法

ジェネリクス関数

ジェネリクス関数とは、型引数を持つ関数。サンプルコードは以下のような形となります。

func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

var x = 5
var y = 10
swapValues(&x, &y)
print("x: \(x), y: \(y)")  // x: 10, y: 5

var str1 = "Hello"
var str2 = "World"
swapValues(&str1, &str2)
print("str1: \(str1), str2: \(str2)")  // str1: World, str2: Hello
コード説明
func swapValues<T>(_ a: inout T, _ b: inout T) {
    let temp = a
    a = b
    b = temp
}

この部分がジェネリクス関数となってます。今回はTとして型引数を定義しています。関数の処理は、引数の値を入れ替えるだけの処理です。

var x = 5
var y = 10
swapValues(&x, &y)
print("x: \(x), y: \(y)")  // x: 10, y: 5

上記の部分では、Int型の値を入れています。swapValuesはInt型を引数としてとる関数となります。初期値としては、xは5、yは10として定義していますが、swapValuesの実行後はxが10、yが10となっています。

var str1 = "Hello"
var str2 = "World"
swapValues(&str1, &str2)
print("str1: \(str1), str2: \(str2)")  // str1: World, str2: Hello

上記の部分では、String型の値を入れています。swapValuesはString型を引数としてとる関数となります。初期値としては、str1は”Hello”、str2は”World”として定義していますが、swapValuesの実行後はstr1が”World”、str2が”Hello”となっています。

ジェネリクス型

ジェネリクス型とは型引数を持つクラス、構造体、列挙型のこと。以下のような記載方法をする。

//構造体
struct 構造体名<型引数>{
	構造体の定義
}

//クラス
class クラス型<型引数>{
	クラスの定義
}

//列挙型
enum 列挙型<型引数>{
	列挙型の定数
}
構造体

 ジェネリクスを利用し構造体を定義する場合は以下のように定義をします。

struct Box<T> {
    var value: T
}

let intBox = Box(value: 10)
let stringBox = Box(value: "Hello")


print(intBox.value)  // 10
print(stringBox.value)  // Hello
コード説明
struct Box<T> {
    var value: T
}

上記がジェネリクス利用して、定義した構造体。今回は値があるだけの形として定義します。

let intBox = Box(value: 10)
let stringBox = Box(value: "Hello")

上記で、BoxにString型とInt型の値を入れる。これによりジェネリクスとして定義していたBoxがintBoxではInt型、stringBoxではString型となります。

クラス

ジェネリクス型を利用しクラスを定義する場合は以下のように定義します。

class genericsClass<T>{
    var same: T
    
    init(same: T){
        self.same = same
    }
    
    func get() -> T{
        return same
    }
}

let genericsSample1 = genericsClass(same: 1)
let genericsSample2 = genericsClass(same: "sample")

print(genericsSample1.get()) //1
print(genericsSample2.get()) //sample
コード説明
class genericsClass<T>{
    var same: T
    
    init(same: T){
        self.same = same
    }
    
    func get() -> T{
        return same
    }
}

上記でジェネリクスを利用しクラスを定義、変数sameとイニシャライザ、getメソッドがあるクラスです。

let genericsSample1 = genericsClass(same: 1)
let genericsSample2 = genericsClass(same: "somple")

変数genericsSample1をInt型、変数genericsSample2をString型として利用しています。

列挙型

ジェネリクスを利用して、列挙型を定義する場合は以下のようになります。

enum Box<T> {
    case value(T)
    
    func getValue() -> T {
        switch self {
        case .value(let value):
            return value
        }
    }
}

// 使用例
let intBox = Box.value(10)
let stringBox = Box.value("Hello")

print(intBox.getValue())   // 10
print(stringBox.getValue()) // Hello
コード説明
enum Box<T> {
    case value(T)
    
    func getValue() -> T {
        switch self {
        case .value(let value):
            return value
        }
    }
}

上記がジェネリクスを利用した列挙型です。valueとその値を返すメソッドがある列挙型です。

let intBox = Box.value(10)
let stringBox = Box.value("Hello")

上記で、BoxにString型とInt型の値を入れる。これによりジェネリクスとして定義していたBoxがintBoxではInt型、stringBoxではString型となります。

ジェネリクスを利用する理由

これまでサンプルコードを見てきましたが、それではなぜジェネリクスを利用するのでしょうか。ジェネリクスを利用する理由は大きく3つあると思います。

1.型の安全性

ジェネリクス関数は単なる汎用化ではなく、静的型付けによる型の安全性を保ったうえでの汎用化です。型引数として与えられた型は通常の型と同等の安全性を持っています。

2.コードの重複の減少

複数の型に対して同じ操作をする必要がある場合に、引数と戻り値だけが違うコードを複数書かずに済みます。ジェネリクスを書くことによって、同じ処理を複数個所に書かずに済むことになります。これによりコードの重複が減少します。

3.メンテナンスを簡素化

汎用的で再利用可能なコンポーネントの作成をすることで、コードの重複を削減し、メンテナンスを容易にできるようにします。

最後に

簡単にジェネリクスについてまとめてみました。自分も完璧に理解できているわけではないので、拙い点もあるりますが、最後までよんでいただいてありがとうございました。これからもIT技術について発信していくので、よかったらまた来てください。

参考資料

Generics in Swift, Part 2 · Episteme and Techne
Amazon.co.jp

プロフィール

SIer勤めのエンジニア
アプリケーションエンジニアとして、WebやiOSなどのアプリ開発をメインにしてます

tomaをフォローする
Swift