くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

SwiftのGenericsについて

今回はジェネリクスについてまとめます

ジェネリクス(Generics)は指定したタイプで柔軟に動作する再利用可能な関数や型を指定できる機能です

ジェネリクスを使用するには、関数名のあとに<T>を指定します

<T>は型引数(型パラメータ)と呼ばれるもので、この関数ではTという型を使用することを意味しています

<T>を指定すると、関数が呼び出されたときにSwiftのシステムで自動で適切な型に置き換えて処理を実行してくれます

ジェネリクスの型は<T>でなくても構いせんが、慣例として<T>が使われています

ジェネリクスは関数やクラスで使用することができます

Generics関数

func isEqualCheck<T: Equatable>(x: T, y: T) -> Bool {
      return x == y
}

上記ではxとyが等しいかをチェックしています。 EquatableをつけているのはTがEquatableプロトコルに準拠している型というのを表しています。 StringやFloat、BoolなどはEquatableプロトコルに準拠しているので使用できます。 逆に型によっては使用できない可能性がある場合(今回の場合に比較や+-などのオペレータの場合)にはTがどのプロトコルに準拠しているかを明示する必要があります

Genericsクラス

ジェネリクスクラスは次のようにして使います

class GenericsClass<T> {
    
    var item: T
    
    init(item: T) {
        self.item = item
    }
    
}
 
let generics1 = GenericsClass(item: 100)
 
let generics2 = GenericsClass(item: "TEST")

指定方法はGenerics関数と同じで型引数(<T>)を指定するだけです

Genericsプロトコル

Swiftではプロトコルにもジェネリクスを使用することができます

例えば、次のように使います

protocol TestProtocol {
    associatedtype T
    var item:T { get }
    func testValue(item:T)
}

// 1つ目の継承クラス
class Test1:TestProtocol {
    var item:Int = 100
    
    func testvalue(item: Int) {
        self.item += 100
    }
}

// 2つ目の継承クラス
class Test2:TestProtocol {
    var item:String = "100"
    
    func fruitvalue(item: String) {
        self.item = item + "円です"
    }
}

プロトコルジェネリクスを使用するためには、定義内でassociatedtype Tのように任意の型を指定する必要があります

型制約

Generics関数の説明で、TがEquatableプロトコルに準拠している型というのを表しました。 このように準拠するプロトコルスーパークラスなどの型引数に制限を追加することがあります。 これを型制約といいます

型制約を追加することでジェネリクス関数やジェネリクス型を細かくコントロールすることができます

今回はwhereを使った制限を紹介します!

func sort<T: Collection>(numbers: T) -> [T.Iterator.Element] where T.Iterator.Element: Comparable {
      return numbers.sorted()
}

上記の例の場合には型引数TはCollectionプロトコルに準拠している上にwhere T.Iterator.Element: Comparableで定義されているようにComparableプロトコルに準拠している T.Iterator.Elementに制限されます

ちなみに、Elementは複数要素のそれぞれの型を表しています

このように細かい制限を加えることによって想定外の型が入る余地を省いています。ジェネリクスはどんな方も入る可能性があるため注意が必要です

参照

docs.swift.org