Swift

Swiftのジェネリックスについてのメモ

More than 5 years have passed since last update.

自分がジェネリックスという単語自体が初めて聞く言葉であり、

よく理解できてないのでまとめてみた

参照:The Swift Programming Language (iBooks)


Genericsとは

クラスや関数を使う側が扱う型を指定できるようにする仕組み

これにより、抽象的なコードを記述することを可能にする


関数でのGenerics

関数内で使用する任意の型を定義できる


1. 型制約なし

型の制約がないので、どんな型の場合でも使用することができる

任意の型は名前は自由(ただし、UpperCamelCaseであること)

func 関数名 <任意の型> () {

// 任意の型を使用した処理
}


例) ItemTypeという任意の型を引数と戻り値と内部で使用している

func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {

var result = ItemType[]()
for i in 0..times {
result += item
}
return result
}

// ItemTypeがStirngの場合
repeat("knock", 4) // ["knock", "knock", "knock", "knock"]

// ItemTyoeがIntの場合
repeat(10, 4) // [10, 10, 10, 10]

抜粋: Apple Inc. The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l


2. 型制約あり

func 関数名 <任意の型: クラス名 または プロトコル名> () {

// 任意の型を使用した処理
}


例) 使用できる型(T)がEquatableプロトコルに準拠した型だけに制限される

func findIndex<T: Equatable>(array: T[], valueToFind: T) -> Int? {

for (index, value) in enumerate(array) {
if value == valueToFind {
return index
}
}
return nil
}

抜粋: Apple Inc. The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l


構造体でのGenerics

クラス、構造体、列挙で使用する任意の型を定義することができる


例) 任意の型の値の要素を持つスタック

struct Stack<T> {

var items = T[]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
}

var stack = Stack<String>() // <String>と記述することで"String型"で指定する
stack.push("A")
stack.push("B")
stack.items // ["A", "B"]

stack.push(100) // String以外受け付けないのでエラー

抜粋: Apple Inc. The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l


プロトコルでのGenerics

プロトコル内で使用する任意の型を定義できる

このとき、typealiasに任意の型名を指定しておく


例) ItemTypeという任意の型を使用しているプロトコル

プロトコルの宣言

protocol Container {

typealias ItemType
mutating func append(item: ItemType)
var count: Int { get } // { get }はgetter処理の実装を必須とする
subscript(i: Int) -> ItemType { get }
}

抜粋: Apple Inc. The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l

Containerプロトコルに準拠した構造体

ここで任意の型をIntとして固定


// 任意の型をIntとして実装
struct IntStack: Container {
// original IntStack implementation
var items = Int[]()
mutating func push(item: Int) {
items.append(item)
}
mutating func pop() -> Int {
return items.removeLast()
}
// conformance to the Container protocol
typealias ItemType = Int
mutating func append(item: Int) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> Int {
return items[i]
}
}

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l

こちらはGenericsの構造体にContainerプロトコルを準拠させたパターン

こちらは、typealiasにセットはしていないが、

任意の型(T型)をitemとappendとsubscriptで使用していることから、

ItemTypeとしてT型を使用していることが推測できる

struct Stack<T>: Container {

// original Stack<T> implementation
var items = T[]()
mutating func push(item: T) {
items.append(item)
}
mutating func pop() -> T {
return items.removeLast()
}
// conformance to the Container protocol
mutating func append(item: T) {
self.push(item)
}
var count: Int {
return items.count
}
subscript(i: Int) -> T {
return items[i]
}
}

抜粋:: Apple Inc. “The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l


whereを使った型指定

型指定にプロトコルを指定するときに、

さらに、そのプロトコルの内容で細かく制限をかけることができる


例)

Containerプロトコルを実装したC1とC2という型で指定

それ以外に以下の指定を行う


  • C1のItemTypeにEquatableプロトコルの型が入っていること

  • C1のItemTypeとC2のItemTypeが同じ型であること

func allItemsMatch

<C1: Container, C2: Container
where C1.ItemType == C2.ItemType, C1.ItemType: Equatable>
(someContainer: C1, anotherContainer: C2) -> Bool {

// check that both containers contain the same number of items
if someContainer.count != anotherContainer.count {
return false
}

// check each pair of items to see if they are equivalent
for i in 0..someContainer.count {
if someContainer[i] != anotherContainer[i] {
return false
}
}

// all items match, so return true
return true

}

抜粋: Apple Inc. The Swift Programming Language”。 iBooks. https://itun.es/jp/jEUH0.l