Generic (generické programování) — je mechanismus Swift, který umožňuje psát typy a funkce s parametrizovaným typem
Hlavní body
Generic (generický typ) — je syntaktická konstrukce Swiftu, která umožňuje psát flexibilní, znovupoužitelný kód s parametrizací podle typu. Místo konkrétního typu se píše placeholder v úhlových závorkách — obvykle
Bez generik by se musely psát oddělené funkce pro každý typ: swapInts, swapStrings, swapDates. Generická funkce swap nahrazuje všechny tři varianty jedinou deklarací. To snižuje duplicitu, zvyšuje čitelnost a snižuje pravděpodobnost chyb při kopírování kódu.
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)
Kompilátor Swift generuje specializovaný kód pro každé konkrétní použití generik. To znamená, že generická funkce swapValues
Generické funkce — jsou funkce, které přijímají jeden nebo více parametrů-typů. Parametry-typy se uvádějí za názvem funkce v úhlových závorkách:
Funkce může mít několik generických parametrů. Například funkce 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
}
Omezení K: Hashable a where V: Equatable zaručují, že findKey lze zavolat pouze se slovníkem, jehož klíče jsou hašovatelné a hodnoty porovnatelné. Taková omezení nejsou byrokracie, ale nutnost: bez Hashable nelze indexovat vyhledávání podle klíče, bez Equatable nelze porovnat value s dictValue.
where — je klíčové slovo Swiftu, které ukládá dodatečná omezení na generické parametry. Na rozdíl od jednoduchého uvedení protokolu v úhlových závorkách (
Klauzule where se píše za signaturou funkce nebo typu, před otevírací složenou závorkou. V where lze uvést, že T: Comparable & Hashable (současně dva protokoly), nebo že T.U == Int (konkretizace associated type).
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
}
}
Rozšíření s where přidává metody pouze pro ty typy, které splňují podmínku. Container
Associated Types (asociované typy) — je způsob, jak učinit protokol generickým bez uvedení konkrétního typu ve fázi deklarace. Místo
Pomocí where lze vytvořit vazby mezi associated types různých protokolů. Lze například požadovat, aby Item jednoho protokolu odpovídal Iterator.Element druhého. To zaručuje kompatibilitu typů na úrovni kompilace.
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 konkretizuje Element jako Int pomocí typealias. Kompilátor kontroluje, zda jsou všechny požadavky protokolu s tímto typem splněny. Bez associated types by se musel psát StackProtocol
Generická rozšíření umožňují přidávat metody ke generickému typu s dodatkovými omezeními. To je vzor „podmíněné shody”, kdy typ získává funkcionalitu pouze za určitých podmínek. Subskripty mohou být také generické.
Subskript může být generický: přijímá generické parametry v úhlových závorkách. To je užitečné pro bezpečný přístup ke kolekcím s libovolným indexem, když index může být různých typů.
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] }
}
}
Taková konstrukce poskytuje mocnou kompozici: metoda sum se objevuje pouze u numerických polí, subskript s libovolnými indexy pracuje s jakoukoli sekvencí celočíselných indexů. Kompilátor sám řídí viditelnost těchto členů na základě podmínek where.
Chyby při použití generik jsou často spojeny s nesprávnými omezeními nebo pokusem použít generika tam, kde je potřeba konkrétní typ. Podívejme se na tři časté scénáře, se kterými se vývojáři setkávají.
Přidávání generických parametrů tam, kde stačí konkrétní typ — je antipattern. Pokud funkce vždy pracuje s String, není třeba ji dělat
Volání metody na generickém parametru bez určení, že metoda existuje — je chyba.
// ❌ 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 }
Chyba je pochopitelná: generické
Často kladené otázky
Generic — je způsob psaní kódu, který pracuje s libovolným typem, bez duplikování stejné logiky pro Int, String a další typy. Místo konkrétního typu se používá placeholder
Generic uchovává informaci o konkrétním typu ve fázi kompilace, což umožňuje kompilátoru kontrolovat typy a generovat optimalizovaný kód. Any — je mazání typu: jakákoliv hodnota může být předána, ale typ se ztrácí a je vyžadován převod (as?).
Použijte syntaxi T: SomeProtocol v úhlových závorkách nebo samostatnou klauzuli where T: SomeProtocol. Omezení zaručují, že generický parametr podporuje určité metody a vlastnosti.
Associated Type (associatedtype) — je placeholder pro typ uvnitř protokolu. Protokol neurčuje konkrétní typ, ale deklaruje associatedtype, který je konkretizován v implementaci: Array má Element, Dictionary — Key a Value.
Swift kompiluje generický kód do specializovaných verzí pro každý konkrétní typ. V praxi není generická funkce pro Int pomalejší než ručně napsaná funkce pro Int. Neexistuje žádná režie za abstrakci.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také