Swiftのプロトコルにおけるカスタマイズ可能なジェネリックパラメーターは、最初のSwiftリリースからassociatedtypeというキーワードを使って導入され、行動の抽象化における型付けの制御を強化しました。
プロトコルが特定の型(例えば、コンテナ内の要素の型)を定義する必要がある要求を宣言すると、associatedtypeなしでは抽象化されたプロトコルを使用することができません:型は厳密に固定される必要があり、汎用的なコードを記述することができません。
associatedtypeはプロトコル内で、プロトコルの具体的な実装によって決定される関連型を宣言することを可能にします。これにより、コンパイル時に具体的な型を知らなくても、プロトコルやジェネリック関数を書くことができます。
associatedtypeを持つプロトコルの例:
protocol MyContainer { associatedtype Item var count: Int { get } mutating func append(_ item: Item) subscript(i: Int) -> Item { get } } struct IntStack: MyContainer { var items = [Int]() mutating func append(_ item: Int) { items.append(item) } var count: Int { items.count } subscript(i: Int) -> Int { items[i] } // Swiftは自動的にItem = Intであると推論します }
主な特徴:
let x: MyContainer // エラー)、Swiftにジェネリックが既にあるのに、なぜassociatedtypeが必要なのですか?
回答: ジェネリックとassociatedtypeは、類似のものでありながら異なる問題を解決します。ジェネリックパラメーターは、タイプや関数に適用され、汎用的な構造やメソッドを作成することを可能にします。一方、associatedtypeはプロトコル内の抽象化であり、実装される型が自分の型を定義するための要件を設定します。汎用的なアルゴリズムやコンテナにはジェネリックを使用し、プロトコルを通じての動作にはassociatedtypeを使用します。
associatedtypeを持つプロトコルを変数の型として使用できますか?
いいえ、Swiftはそのようなプロトコルを型として直接使用することを許可しません。associatedtypeに関する情報が失われるためです。抽象化のために、タイプエラージやジェネリック制約を使用できます:
func printElements<C: MyContainer>(container: C) { for i in 0..<container.count { print(container[i]) } }
associatedtypeを持つプロトコルを "型" にするにはどうすればよいですか(type erasure)?
そのためには、タイプエラージパターンを使用して、closureやボックスを介して内部実装を保存するラッパーを作成します。
struct AnyContainer<T>: MyContainer { private let _append: (T) -> Void private let _count: () -> Int private let _subscript: (Int) -> T ... // クロージャを介した初期化 }
開発者はデータモデルのために多数のassociatedtypeを持つプロトコルを作成し、次に配列let boxes: [MyContainer]を作成しようとしましたが、コンパイラがエラーを出しました。その結果、余計なラッパーを導入し、型安全性を失うことになりました。
利点:
欠点:
プロジェクトでは、associatedtypeを持つプロトコルがコレクションの実装に使用され、それらと働くための抽象化のために、個別のタイプエラージラッパーAnyCollectionが作成されました。これにより、ビジネスロジック層の型安全性を失うことなく、コレクションの具体的な実装から抽象化されました。
利点:
欠点: