見出し画像

Swiftでプログラミング-Generics 1

ジェネリックコードを使用すると、定義した要件に応じて、任意の型で機能する柔軟で再利用可能な関数と型を記述できます。 重複を避け、その意図を明確にしながら抽象的な方法で表現するコードを書くことができます。

ジェネリックはSwiftの最も強力な機能の1つであり、Swift標準ライブラリの多くはジェネリックコードで構築されています。  たとえば、Swiftの配列型と辞書型はどちらもジェネリックコレクションです。 Int値を保持する配列、String値を保持する配列、または実際にSwiftで作成できる他の型の配列を作成できます。 同様に、指定したタイプの値を格納する辞書を作成できます。その型に制限はありません。

The Problem That Generics Solve Genericが解決する問題

これは、swapTwoInts(_:_ :)と呼ばれる標準の非汎用関数で、2つのInt値を交換します。

    func swapTwoInts(_ a: inout Int, _ b: inout Int) {
       let temporaryA = a
       a = b
       b = temporaryA
   }

この関数は、In-Outパラメーターで説明されているように、in-outパラメーターを使用してaとbの値を交換します。

swapTwoInts(_:_ :)関数は、bの元の値をaにスワップし、aの元の値をbにスワップします。 この関数を呼び出して、2つのInt変数の値を交換できます。

   var someInt = 3
   var anotherInt = 107
   swapTwoInts(&someInt, &anotherInt)
   print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
   // Prints "someInt is now 107, and anotherInt is now 3"

swapTwoInts(_:_ :)関数は便利ですが、Int値でのみ使用できます。 2つのString値または2つのDouble値を交換する場合は、以下に示すswapTwoStrings(_:_ :)関数やswapTwoDoubles(_:_ :)関数などの関数をさらに作成する必要があります。

   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
   }

swapTwoInts(_:_ :)、swapTwoStrings(_:_ :)、およびswapTwoDoubles(_:_ :)関数の本体が同一であることに気付いたかもしれません。 唯一の違いは、受け入れる値のタイプ(Int、String、およびDouble)です。

任意の型の2つの値を交換する単一の関数を作成する方が便利で、かなり柔軟性があります。 ジェネリックコードを使用すると、このような関数を記述できます。 (これらの関数の汎用バージョンを以下に定義します。)

3つの関数すべてで、aとbのタイプは同じである必要があります。 aとbが同じタイプでない場合、それらの値を交換することはできません。 Swiftはタイプセーフな言語であり、たとえば、String型の変数とDouble型の変数が値を相互に交換することを許可しいないのでコンパイル時エラーが発生します。

Generic Functions

ジェネリック関数はどのタイプでも機能します。 上記のswapTwoInts(_:_ :)関数の汎用バージョンは、swapTwoValues(_:_ :)と呼ばれます。

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

swapTwoValues(_:_ :)関数の本体は、swapTwoInts(_:_ :)関数の本体と同じです。 ただし、swapTwoValues(_:_ :)の最初の行は、swapTwoInts(_:_ :)とは少し異なります。 最初の行の比較は次のとおりです。

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

関数の汎用バージョンは、実際の型名(Int、String、Doubleなど)の代わりにプレースホルダー型名(この場合は"T"と呼ばれます:"T"がよく使われますがどんな文字でも使えます)を使用します。プレースホルダーの型名は、Tがどうあるべきかについては何も述べていませんが、Tが何を表していても、aとbの両方が同じ型Tでなければならないことを示しています。 Tの代わりに使用する実際の型は、swapTwoValues(_:_ :)関数が呼び出されるたびに決定されます。

総称関数と非総称関数のもう1つの違いは、総称関数の名前(swapTwoValues(_:_ :))の後に、山括弧(<T>)内のプレースホルダー型名(T)が続くことです。角かっこは、TがswapTwoValues(_:_ :)関数定義内のプレースホルダータイプ名であることをSwiftに通知します。 Tはプレースホルダーであるため、SwiftはTと呼ばれる実際のタイプを検索しません。

swapTwoValues(_:_ :)関数は、swapTwoIntsと同じ方法で呼び出すことができるようになりました。ただし、両方の値が互いに同じタイプである限り、任意のタイプの2つの値を渡すことができます。 swapTwoValues(_:_ :)が呼び出されるたびに、Tに使用するタイプは、関数に渡される値のタイプから推測されます。

以下の2つの例では、TはそれぞれIntとStringであると推測されます。

   var someInt = 3
   var anotherInt = 107
   swapTwoValues(&someInt, &anotherInt)
   // someInt is now 107, and anotherInt is now 3
   
   var someString = "hello"
   var anotherString = "world"
   swapTwoValues(&someString, &anotherString)
   // someString is now "world", and anotherString is now "hello"

上で定義されたswapTwoValues(_:_ :)関数は、Swift標準ライブラリの一部であるswapと呼ばれる汎用関数に触発されており、アプリで使用できるように自動的に提供されます。 独自のコードでswapTwoValues(_:_ :)関数の動作が必要な場合は、独自の実装を提供するのではなく、Swiftの既存のswap(_:_ :)関数を使用できます。

Type Parameters

上記のswapTwoValues(_:_ :)の例では、プレースホルダータイプTはタイプパラメーターの例です。 型パラメーターは、プレースホルダータイプを指定して名前を付け、関数名の直後に、一致する角度ブラケットのペア(<T>など)の間に書き込まれます。

タイプパラメータを指定すると、それを使用して、関数のパラメータのタイプ(swapTwoValues(_:_ :)関数のaおよびbパラメータなど)を定義したり、関数の戻り型として、またはタイプとして定義したりできます。 関数本体内の注釈。 いずれの場合も、関数が呼び出されるたびに、型パラメーターは実際の型に置き換えられます。 (上記のswapTwoValues(_:_ :)の例では、関数が最初に呼び出されたときにTがIntに置き換えられ、2回目に呼び出されたときにStringに置き換えられました。)

角かっこ内にコンマで区切って複数の型パラメーター名を書き込むことにより、複数の型パラメーターを指定できます。

Naming Type Parameters

ほとんどの場合、型パラメーターには、Dictionary <Key、Value>のKey and ValueやArray <Element>のElementなどのわかりやすい名前が付いています。これにより、型パラメーターと、それが使用される一般的な型または関数との関係がわかります。 ただし、それらの間に意味のある関係がない場合は、上記のswapTwoValues(_:_ :)関数のTなど、T、U、Vなどの1文字を使用して名前を付けるのが伝統的です。

タイプパラメータには、値ではなくタイプのプレースホルダーであることを示すために、常に大文字のキャメルケース名(TやMyTypeParameterなど)を付けてください。

Generic Types

ジェネリック関数に加えて、Swiftでは独自のジェネリック型を定義できます。これらは、配列や辞書と同様に、任意の型で機能するカスタムクラス、構造、および列挙型です。

このセクションでは、Stackと呼ばれるジェネリックコレクション型を作成する方法を示します。スタックは、配列に似た順序付けられた値のセットですが、Swiftの配列型よりも操作のセットが制限されています。配列を使用すると、配列内の任意の場所で新しいアイテムを挿入および削除できます。ただし、スタックでは、コレクションの最後にのみ新しいアイテムを追加できます(新しい値をスタックにプッシュすることと呼ばれます)。同様に、スタックを使用すると、コレクションの最後からのみアイテムを削除できます(スタックから値をポップすることとして知られています)。

スタックの概念は、ナビゲーション階層でビューコントローラーをモデル化するためにUINavigationControllerクラスによって使用されます。 UINavigationControllerクラスpushViewController(_:animated :)メソッドを呼び出してビューコントローラーをナビゲーションスタックに追加(またはプッシュ)し、そのpopViewControllerAnimated(_ :)メソッドを呼び出してビューコントローラーをナビゲーションスタックから削除(またはポップ)します。スタックは、コレクションを管理するための厳密な「後入れ先出し」アプローチが必要な場合に役立つコレクションモデルです。

1  スタックには3つの値があります。
2  4番目の値がスタックの一番上にプッシュされます。
3  スタックは4つの値を保持し、最新の値が一番上になります。
4  スタックの一番上のアイテムがポップされます。
5  値をポップした後、スタックは再び3つの値を保持します。

スタックの非汎用バージョン(この場合はInt値のスタック)を作成する方法は次のとおりです。

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

この構造体は、itemsというArrayプロパティを使用して、値をスタックに格納します。 スタックには、値をスタックにプッシュおよびスタックからポップするためのプッシュとポップの2つの方法があります。 これらのメソッドは、構造体のitems配列を変更(または変更)する必要があるため、変更としてマークされています。

ただし、上記のInt Stackタイプは、Int値でのみ使用できます。 任意のタイプの値のスタックを管理できる汎用Stackクラスを定義する方がはるかに便利です。

同じコードの一般的なバージョンは次のとおりです。

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

Stackの汎用バージョンは基本的に非汎用バージョンと同じですが、実際のIntの型ではなくElementと呼ばれる型パラメーターを使用していることに注意してください。この型パラメータは、構造体の名前の直後にある1組の山かっこ(<Element>)内に記述されます。

要素は、後で提供される型の代用名を定義します。この特徴的な型は、構造体の定義内のどこでも要素と呼ぶことができます。この場合、Elementは次の3つの場合に代用名として使用されます。

・Element型の値の空の配列で初期化されるitemsというプロパティを作成
・push(_ :)メソッドにitemという単一のパラメーターを指定するにはElement型であることが必要
・pop()メソッドの戻り値でElement型の値を指定

ジェネリック型であるため、Stackを使用して、ArrayやDictionaryと同様に、Swiftで任意の有効な型のスタックを作成できます。

スタックに格納するタイプを山かっこで囲んで書き込むことにより、新しいStackインスタンスを作成します。たとえば、文字列の新しいスタックを作成するには、Stack <String>()と記述します。

   var stackOfStrings = Stack<String>()
   
   stackOfStrings.push("uno")
   stackOfStrings.push("dos")
   stackOfStrings.push("tres")
   stackOfStrings.push("cuatro")
   // the stack now contains 4 strings

スタックから値をポップすると、最上位の値「cuatro」が削除されて返されます。

    let fromTheTop = stackOfStrings.pop()
   // fromTheTop is equal to "cuatro", and the stack now contains 3 strings

Extending a Generic Type

ジェネリック型を拡張する場合、拡張の定義の一部として型パラメーターリストを提供しません。 代わりに、元の型定義の型パラメーターリストがextensionの内で使用可能であり、元の型パラメーター名は、元の定義の型パラメーターを参照するために使用されます。

次の例では、汎用Stackタイプを拡張して、topItemという読み取り専用の計算プロパティを追加します。これにより、スタックからポップせずにスタックの最上位アイテムが返されます。

    extension Stack {
       var topItem: Element? {
           return items.isEmpty ? nil : items[items.count - 1]
       }
   }

topItemプロパティは、Element型のオプションの値を返します。 スタックが空の場合、topItemはnilを返します。 スタックが空でない場合、topItemはitems配列の最後のアイテムを返します。

このextension は型パラメータリストを定義しないことに注意してください。 代わりに、スタックタイプの既存のタイプパラメータ名であるElementがextension 内で使用され、topItem計算プロパティのoptionalの型を示します。

topItem計算プロパティを任意のStackインスタンスで使用して、最上位のitemを削除せずにアクセスできるようになりました。

    if let topItem = stackOfStrings.topItem {
       print("The top item on the stack is \(topItem).")
   }
   // Prints "The top item on the stack is tres."

ジェネリック型のextensionには、以下のジェネリックWhere句を使用したextensionがあるように、新しい機能を取得するために拡張型のインスタンスが満たさなければならない要件を含めることもできます。


















この記事が気に入ったらサポートをしてみませんか?