Generic (генеричко програмирање) — је механизам Swift-а који омогућава писање типова и функција са параметризованим типом
Главно
Generic (генерички тип) — је синтаксичка конструкција Swift-а која омогућава писање флексибилног, поново употребљивог кода са параметризацијом по типу. Уместо конкретног типа пише се плејсхолдер у угластим заградама — обично
Без generic-а би се морале писати одвојене функције за сваки тип: swapInts, swapStrings, swapDates. Generic функција swap замењује све три варијанте једном декларацијом. Ово смањује дуплирање, повећава читљивост и смањује вероватноћу грешака при копирању кода.
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 компајлер генерише специјализовани код за сваку конкретну употребу generic-а. То значи да generic функција swapValues
Generic функције — су функције које прихватају један или више параметара-типова. Параметри-типови се наводе после имена функције у угластим заградама:
Функција може имати више generic параметара. На пример, функција 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-а која намеће додатна ограничења на generic параметре. За разлику од једноставног навођења протокола у угластим заградама (
Клаузула where се пише после потписа функције или типа, пре отварајуће витичасте заграде. У where се може навести да T: Comparable & Hashable (истовремено два протокола), или да T.U == Int (конкретизација 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
}
}
Проширење са 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 конкретизује Element као Int, користећи typealias. Компајлер проверава да ли су сви захтеви протокола испуњени са овим типом. Без associated types би се морао писати StackProtocol
Generic проширења омогућавају додавање метода generic типу са додатним ограничењима. Ово је образац „условне усклађености”, када тип добија функционалност само под одређеним условима. Субскрипти такође могу бити generic.
Субскрипт може бити генерички: прихвата generic параметре у угластим заградама. Ово је корисно за безбедан приступ колекцијама са произвољним индексом, када индекс може бити различитих типова.
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 услова.
Грешке при коришћењу generic-а су често повезане са нетачним ограничењима или покушајем коришћења generic-а тамо где је потребан конкретан тип. Размотримо три честа сценарија са којима се сусрећу програмери.
Додавање generic параметара тамо где је довољан конкретан тип — антиобразац. Ако функција увек ради са String-ом, не треба је чинити
Позивање метода на generic параметру без навођења да метод постоји — грешка.
// ❌ 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
Често постављана питања
Generic — је начин писања кода који ради са било којим типом, без дуплирања исте логике за Int, String и друге типове. Уместо конкретног типа користи се плејсхолдер
Generic чува информацију о конкретном типу у фази компајлирања, што омогућава компајлеру да проверава типове и генерише оптимизовани код. Any — је брисање типа: било која вредност може бити прослеђена, али се тип губи и потребна је конверзија (as?).
Користите синтаксу T: SomeProtocol у угластим заградама или одвојену клаузулу where T: SomeProtocol. Ограничења гарантују да generic параметар подржава одређене методе и својства.
Associated Type (associatedtype) — је плејсхолдер за тип унутар протокола. Протокол не наводи конкретан тип, већ декларише associatedtype, који се конкретизује у имплементацији: Array има Element, Dictionary — Key и Value.
Swift компајлира generic код у специјализоване верзије за сваки конкретан тип. У пракси, generic функција за Int није спорија од ручно написане функције за Int. Нема додатних трошкова за апстракцију.
Резиме
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође