Generic (汎用プログラミング) — Swiftの機構で、パラメータ化された型
ポイント
Generic (汎用型) — Swiftの構文構造で、型をパラメータ化して柔軟かつ再利用可能なコードを書くことができます。具体的な型の代わりに、角括弧にプレースホルダーを書きます—通常は
汎用型がなければ、型ごとに別々の関数を書かなければなりません: swapInts、swapStrings、swapDates。汎用のswap関数はこれら3つのバリエーションを1つの宣言で置き換えます。これにより、コードの重複が減り、可読性が向上し、コードのコピー時のエラーが減ります。
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y)
Swiftコンパイラは汎用型の各具体的な使用例に対して専用コードを生成します。すなわち、汎用関数swapValues
汎用関数は、1つ以上の型パラメータを受け取る関数です。型パラメータは関数名の後ろに角括弧で指定します:
関数は複数の汎用パラメータを持つことができます。例えば、findKey
func findKey<K: Hashable, V>(
for value: V,
in dictionary: [K: V]
) -> K? where V: Equatable {
for (key, dictValue) in dictionary {
if dictValue == value {
return key
}
}
return nil
}
制約K: Hashableとwhere V: Equatableにより、findKeyはキーがハッシュ可能で値が比較可能な辞書でのみ呼べるようになります。こうした制約は形式的なものではなく必須です: Hashableがなければキーの検索をインデックスできず、EquatableがなければvalueとdictValueを比較できません。
where — Swiftのキーワードで、汎用パラメータに追加の制約を課します。単に角括弧 (
where句は関数または型のシグネチャの後、開き括弧の前に書きます。whereではT: Comparable & Hashable(同時に2つのプロトコルに適合)やT.U == Int(関連型の明確化)を指定できます。
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
extension Container where Item: Comparable {
func isSorted() -> Bool {
// Implementation available only if Item: Comparable
return true
}
}
where付きの拡張は、条件を満たす型だけにメソッドを追加します。Container
Associated Types (関連型) — 宣言時に具体的な型を指定せずに、プロトコルを汎用型にする方法です。
whereを使って、異なるプロトコルのassociated typesの間に関係を設定できます。例えば、あるプロトコルのItemが別のプロトコルのIterator.Elementと一致することを要求できます。これにより、コンパイルレベルで型の互換性が保証されます。
protocol StackProtocol {
associatedtype Element
mutating func push(_ item: Element)
mutating func pop() -> Element?
}
struct IntStack: StackProtocol {
typealias Element = Int
private var items: [Int] = []
mutating func push(_ item: Int) { items.append(item) }
mutating func pop() -> Int? { items.popLast() }
}
IntStackはtypealiasを使ってElementをIntとして具体化します。コンパイラはこの型でプロトコルのすべての要件を満たしているかを確認します。associated typesがなければ、汎用プロトコルを使ってStackProtocol
汎用拡張により、追加の制約を付けて汎用型にメソッドを追加できます。これは「条件付き適合」パターンで、型が特定の条件でのみ機能を得るものです。サブスクリプトも汎用型にできます。
サブスクリプトは汎用型にできます: 角括弧で汎用パラメータを受け取ります。これは、インデックスが異なる型になり得る場合に、任意のインデックスを使った配列への安全なアクセスに役立ちます。
extension Array where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}
extension Array {
subscript<Indices: Sequence>(indices: Indices) -> [Element]
where Indices.Element == Int {
return indices.map { self[$0] }
}
}
この構造は強力な組み合わせを提供します: sumメソッドは数値配列にのみ表示され、任意のインデックスを使ったサブスクリプトは整数インデックスのあらゆるシーケンスで動作します。コンパイラはwhere条件に基づいてこれらのメンバーの可視性を管理します。
誤りは、汎用型の使用において、間違った制約や、具体的な型が必要な場所で汎用型を使おうとすることによって生じます。開発者がよく直面する3つのシナリオを見てみましょう。
具体的な型で十分な場所で汎用パラメータを追加するのはアンチパターンです。ある関数が常にStringを扱う場合、それを
メソッドが存在することを指定せずに、汎用パラメータでメソッドを呼ぶのはエラーです。
// ❌ Error: value has no count method
func countElements<T>>(value: T) -> Int { value.count }
// ✅ Works: T: Collection guarantees count exists
func countElements<T: Collection>(value: T) -> Int { value.count }
エラーは明確です: 汎用
よくある質問
Genericとは、Int、String、その他の型について同じ論理を繰り返すことなく、あらゆる型で動作するコードを書く方法です。具体的な型の代わりにプレースホルダー
Genericはコンパイル時に具体的な型の情報を保持し、コンパイラが型をチェックし、最適化されたコードを生成できます。Anyは型消しです: どんな値でも渡せますが、型が失われ、キャスト(as?)が必要です。
角括弧でT: SomeProtocol構文か、別途のwhere句where T: SomeProtocolを使用します。制約により、汎用パラメータが特定のメソッドやプロパティをサポートすることが保証されます。
Associated Type (associatedtype)はプロトコル内の型のプレースホルダーです。プロトコルは具体的な型を指定せずにassociatedtypeを宣言し、実装で具体化されます: ArrayはElement、DictionaryはKeyとValueを持ちます。
Swiftは汎用コードを各具体的な型に対して専用バージョンにコンパイルします。実際には、Int用の汎用関数はInt用に手書きされた関数より遅くはありません。抽象化によるオーバーヘッドはありません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。