Generic (обобщено програмиране) — е механизъм на Swift, който позволява писане на типове и функции с параметризиран тип
Основни точки
Generic (обобщен тип) — е синтактична конструкция на Swift, която позволява писане на гъвкав, многократно използваем код с параметризация по тип. Вместо конкретен тип се пише placeholder в ъглови скоби — обикновено
Без generic ще трябва да се пишат отделни функции за всеки тип: swapInts, swapStrings, swapDates. Обобщената функция 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. Това означава, че обобщената функция swapValues
Обобщени функции — са функции, които приемат един или повече параметри-типове. Параметрите-типове се посочват след името на функцията в ъглови скоби:
Една функция може да има няколко 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 тип с допълнителни ограничения. Това е моделът „условно съответствие”, когато типът получава функционалност само при определени условия. Subscript също могат да бъдат generic.
Subscript може да бъде обобщен: приема 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 се появява само при числови масиви, а subscript с произволни индекси работи с всяка последователност от целочислени индекси. Компилаторът сам управлява видимостта на тези членове въз основа на 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 и други типове. Вместо конкретен тип се използва placeholder
Generic запазва информация за конкретния тип във фазата на компилиране, което позволява на компилатора да проверява типове и да генерира оптимизиран код. Any — е изтриване на тип: всяка стойност може да бъде предадена, но типът се губи и е необходимо преобразуване (as?).
Използвайте синтаксиса T: SomeProtocol в ъглови скоби или отделна клауза where T: SomeProtocol. Ограниченията гарантират, че generic параметърът поддържа определени методи и свойства.
Associated Type (associatedtype) — е placeholder за тип вътре в протокола. Протоколът не посочва конкретен тип, а декларира associatedtype, който се конкретизира в имплементацията: Array има Element, Dictionary — Key и Value.
Swift компилира generic код в специализирани версии за всеки конкретен тип. На практика generic функция за Int не е по-бавна от ръчно написана функция за Int. Няма допълнителни разходи за абстракция.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също