Swift ジェネリック

Swift は、柔軟で再利用可能な関数や型を書くためのジェネリックを提供します。

Swift 標準ライブラリはジェネリックコードで構築されています。

Swift の配列型と辞書型はどちらもジェネリックコレクションです。

Int 配列を作成することも、String 配列を作成することも、あるいは他の任意の Swift 型のデータ配列を作成することもできます。

次の例は、2 つの Int 値を交換するための非ジェネリック関数 exchange です。

実例

// 2つの変数を交換する関数を定義する func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("交換前のデータ:\(numb1) と\(numb2)") swapTwoInts(&numb1, &numb2) print("交換後のデータ:\(numb1) と\(numb2)")

上記プログラムの実行結果は次のとおりです。

交换前数据: 100 和 200
交换后数据: 200 和 100

上記の例は整数 Int 型の変数の交換にのみ適用できます。2 つの String 値または Double 値を交換したい場合は、対応する関数を書き直す必要があります。たとえば、swapTwoStrings(_:_:) と swapTwoDoubles(_:_:) です。以下に示します。

String と Double の値交換関数

func swapTwoStrings(_ a: inout String, _ b: inout String) { let temporaryA = a a = b b = temporaryA } func swapTwoDoubles(_ a: inout Double, _ b: inout Double) { let temporaryA = a a = b b = temporaryA }

上記のコードから分かるように、それらの機能コードは同じですが、型だけが異なります。このような場合、ジェネリックを使用することで、コードの重複を避けることができます。

ジェネリックは、実際の型名(Int、String、Double など)の代わりにプレースホルダ型名(ここでは文字 T で表します)を使用します。

func swapTwoValues<T>(_ a: inout T, _ b: inout T)

swapTwoValues の後にはプレースホルダ型名(T)が続き、山括弧で囲みます(<T>)。この山括弧は、T が swapTwoValues(_:_:) 関数定義内のプレースホルダ型名であることを Swift に伝えます。したがって、Swift は T という名前の実際の型を探しに行きません。

次の例は、2 つの Int または String の値を交換するためのジェネリック関数 exchange です。

実例

// 2つの変数を交換する関数を定義する func swapTwoValues<T>(_ a: inout T, _ b: inout T) { let temporaryA = a a = b b = temporaryA } var numb1 = 100 var numb2 = 200 print("交換前のデータ:\(numb1) と\(numb2)") swapTwoValues(&numb1, &numb2) print("交換後のデータ:\(numb1) と\(numb2)") var str1 = "A" var str2 = "B" print("交換前のデータ:\(str1) と\(str2)") swapTwoValues(&str1, &str2) print("交換後のデータ:\(str1) と\(str2)")

上記プログラムの実行結果は次のとおりです。

交换前数据:  100 和 200
交换后数据: 200 和 100
交换前数据:  A 和 B
交换后数据: B 和 A

ジェネリック型

Swift では、独自のジェネリック型を定義できます。

独自のクラス、構造体、列挙型は、Array や Dictionary と同じように、あらゆる型に対して機能します。

次に、Stack(スタック)というジェネリックコレクション型を作成します。スタックは、コレクションの末尾にのみ新しい要素を追加でき(プッシュと呼びます)、末尾からのみ要素を削除することもできます(ポップと呼びます)。

図中では左から右に次のように説明されます。

  • 3 つの値がスタックにあります。
  • 4 つ目の値がスタックの一番上にプッシュされます。
  • 現在、スタックには 4 つの値があり、最後にプッシュされた値が一番上にあります。
  • スタックの一番上の値が削除されます。これをポップと呼びます。
  • 1 つの値を削除した後、スタックには再び 3 つの値だけが残ります。

次の例は、非ジェネリック版のスタックで、Int 型のスタックを例にしています。

Int 型のスタック

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

この構造体は、スタック内の値を格納するために items という名前の Array プロパティを使用します。Stack は、値をスタックにプッシュし、スタックから値を削除するための 2 つのメソッド push(_:) と pop() を提供します。これらのメソッドは構造体の items 配列を変更する必要があるため、mutating としてマークされています。

上記の IntStack 構造体は Int 型にしか使用できません。ただし、ジェネリックな Stack 構造体を定義すれば、任意の型の値を処理できます。

以下は、同じコードのジェネリックバージョンです。

ジェネリックなスタック

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } var stackOfStrings = Stack<String>() print("文字列の要素をプッシュ:") stackOfStrings.push("google") stackOfStrings.push("サンプル") print(stackOfStrings.items); let deletetos = stackOfStrings.pop() print("ポップされた要素:" + deletetos) var stackOfInts = Stack<Int>() print("整数の要素をプッシュ:") stackOfInts.push(1) stackOfInts.push(2) print(stackOfInts.items);

実行結果は次のとおりです。

字符串元素入栈: 
["google", "サンプル"]
出栈元素: サンプル
整数元素入栈: 
[1, 2]

Stack は基本的に IntStack と同じですが、実際の Int 型の代わりにプレースホルダ型パラメータ Element を使用しています。

上記の例では、Element は次の 3 つの場所でプレースホルダとして使用されています。

  • 作成itemsプロパティを使用Element型の空配列で初期化します。
  • 指定push(_:)メソッドの唯一のパラメータitemの型はElement型でなければなりません。
  • 指定pop()メソッドの戻り値の型はElement型でなければなりません。

ジェネリック型の拡張

ジェネリック型を拡張するとき(extension キーワードを使用)、拡張の定義で型パラメータリストを提供する必要はありません。さらに便利なことに、元の型定義で宣言された型パラメータリストは拡張内で使用でき、元の型からのこれらのパラメータ名は、元の定義の型パラメータへの参照として使用されます。

次の例では、ジェネリック型 Stack を拡張し、topItem という名前の読み取り専用の計算型プロパティを追加します。これは、現在のスタックの最上位の要素を返しますが、スタックからは削除しません。

ジェネリック

struct Stack<Element> { var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } } extension Stack { var topItem: Element? { return items.isEmpty ? nil : items[items.count - 1] } } var stackOfStrings = Stack<String>() print("文字列の要素をプッシュ:") stackOfStrings.push("google") stackOfStrings.push("サンプル") if let topItem = stackOfStrings.topItem { print("スタックの最上位の要素は次のとおりです。\(topItem).") } print(stackOfStrings.items)

この例では、topItem プロパティは Element 型のオプショナル値を返します。スタックが空のとき、topItem は nil を返します。スタックが空でないとき、topItem は items 配列の最後の要素を返します。

上記プログラムの実行結果は次のとおりです。

字符串元素入栈: 
栈中的顶部元素是:サンプル.
["google", "サンプル"]

既存の型を拡張することによって関連型を指定することもできます。

たとえば、Swift の Array 型はすでに append(_:) メソッド、count プロパティ、および Int 型のインデックス値を受け取って要素を取得する添え字を提供しています。これらの 3 つの機能はすべて Container プロトコルの要件を満たしているため、Array がこのプロトコルを採用していると宣言するだけで Array を拡張できます。

次の例では、空の拡張を作成するだけで済みます。

extension Array: Container {}

型制約

型制約は、指定されたクラスを継承するか、特定のプロトコルまたはプロトコル構成に準拠する必要がある型パラメータを指定します。

型制約構文

型パラメータ名の後に型制約をコロンで区切って記述し、型パラメータチェーンの一部にすることができます。ジェネリック関数に適用される型制約の基本構文は次のとおりです(ジェネリック型の構文と同じ)。

func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
    // 这里是泛型函数的函数体部分
}

上記の関数には 2 つの型パラメータがあります。最初の型パラメータ T には、T が SomeClass のサブクラスでなければならないという型制約があります。2 番目の型パラメータ U には、U が SomeProtocol プロトコルに準拠しなければならないという型制約があります。

実例

ジェネリック

// 非ジェネリック関数。指定された文字列が配列内のどのインデックスにあるかを検索します func findIndex(ofString valueToFind: String, in array: [String]) -> Int? { for (index, value) in array.enumerated() { if value == valueToFind { // 見つかった場合はインデックス値を返します return index } } return nil } let strings = ["google", "weibo", "taobao", "サンプル", "facebook"] if let foundIndex = findIndex(ofString: "サンプル", in: strings) { print("サンプル のインデックスは\(foundIndex)") }

インデックスは 0 から始まります。

上記プログラムの実行結果は次のとおりです。

サンプル 的索引为 3

関連型

Swift では、associatedtype キーワードを使用して関連型を定義します。

次の例では、関連型 ItemType を定義する Container プロトコルを定義しています。

Container プロトコルは、Container プロトコルに準拠するすべての型が提供しなければならない 3 つの機能だけを指定しています。プロトコルに準拠する型は、これらの 3 つの条件を満たしていれば、他の追加機能を提供することもできます。

// Container 协议
protocol Container {
    associatedtype ItemType
    // 添加一个新元素到容器里
    mutating func append(_ item: ItemType)
    // 获取容器中元素的数
    var count: Int { get }
    // 通过索引值类型为 Int 的下标检索到容器中的每一个元素
    subscript(i: Int) -> ItemType { get }
}

// Stack 结构体遵从 Container 协议
struct Stack<Element>: Container {
    // Stack<Element> 的原始实现部分
    var items = [Element]()
    mutating func push(_ item: Element) {
        items.append(item)
    }
    mutating func pop() -> Element {
        return items.removeLast()
    }
    // Container 协议的实现部分
    mutating func append(_ item: Element) {
        self.push(item)
    }
    var count: Int {
        return items.count
    }
    subscript(i: Int) -> Element {
        return items[i]
    }
}

var tos = Stack<String>()
tos.push("google")
tos.push("サンプル")
tos.push("taobao")
// 元素列表
print(tos.items)
// 元素个数
print( tos.count)

上記プログラムの実行結果は次のとおりです。

["google", "サンプル", "taobao"]
3

Where 文

型制約により、型がジェネリック関数またはクラスの定義の制約を満たしていることを確認できます。

パラメータリスト内で where 文を使用してパラメータの制約を定義できます。

where 文は型パラメータリストの直後に記述します。where 文の後には、関連型に対する 1 つ以上の制約、および(または)型と関連型の間の 1 つ以上の等価(equality)関係を続けます。

実例

次の例では、allItemsMatch という名前のジェネリック関数を定義しています。これは、2 つの Container インスタンスが同じ順序で同じ要素を含むかどうかをチェックします。

すべての要素が一致する場合は true を返し、そうでない場合は false を返します。

ジェネリック

// Container プロトコル protocol Container { associatedtype ItemType // コンテナに新しい要素を追加する mutating func append(_ item: ItemType) // コンテナ内の要素の数を取得する var count: Int { get } // インデックス値の型が Int の添字を使って、コンテナ内の各要素を取得する subscript(i: Int) -> ItemType { get } } // // Container プロトコルに準拠するジェネリックの TOS 型 struct Stack<Element>: Container { // Stack<Element> の元の実装部分 var items = [Element]() mutating func push(_ item: Element) { items.append(item) } mutating func pop() -> Element { return items.removeLast() } // Container プロトコルの実装部分 mutating func append(_ item: Element) { self.push(item) } var count: Int { return items.count } subscript(i: Int) -> Element { return items[i] } } // Array を Container として使用する拡張 extension Array: Container {} func allItemsMatch<C1: Container, C2: Container> (_ someContainer: C1, _ anotherContainer: C2) -> Bool where C1.ItemType == C2.ItemType, C1.ItemType: Equatable { // 2つのコンテナが同じ数の要素を含むか確認する if someContainer.count != anotherContainer.count { return false } // 各要素のペアが等しいか確認する for i in 0..<someContainer.count { if someContainer[i] != anotherContainer[i] { return false } } // すべての要素が一致する場合、true を返す return true } var tos = Stack<String>() tos.push("google") tos.push("サンプル") tos.push("taobao") var aos = ["google", "サンプル", "taobao"] if allItemsMatch(tos, aos) { print("すべての要素が一致") } else { print("要素が一致しません") }

上記のプログラムを実行した出力結果は次のとおりです。

すべての要素にマッチする
その他の拡張