Ответ
Generics позволяют писать типобезопасный и переиспользуемый код, избегая дублирования.
1. Функции с Generics:
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 5, y = 10
swapValues(&x, &y) // x = 10, y = 5
2. Кастомные типы с Generics:
struct Stack<Element> {
private var elements = [Element]()
mutating func push(_ element: Element) {
elements.append(element)
}
mutating func pop() -> Element? {
return elements.popLast()
}
}
var intStack = Stack<Int>()
intStack.push(42)
3. Ограничения типов (Type Constraints):
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
return array.firstIndex { $0 == value }
}
4. Протоколы с Associated Types:
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
Преимущества:
- Типобезопасность — компилятор проверяет типы
- Производительность — нет накладных расходов как с
Any - Переиспользование кода — одна реализация для разных типов
Использование в стандартной библиотеке:
Array<Element>Dictionary<Key, Value>Optional<Wrapped>
Ответ 18+ 🔞
А, слушай, смотри, вот эта вся хуйня с дженериками — это вообще огонь, если понять, нахуя она нужна. Без них пиздец как неудобно, честно.
Ну, типа, представь: пишешь ты функцию, чтобы два значения поменять местами. Без дженериков пришлось бы для каждого типа свою функцию городить, блядь. А так — раз, и на все случаи жизни:
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 5, y = 10
swapValues(&x, &y) // x = 10, y = 5
Видишь этот <T>? Это типа "эй, компилятор, я тут с каким-то типом работаю, назову его T, а ты сам потом догадаешься, с каким именно, когда я буду вызывать эту функцию". И он реально догадывается, ёпта! Подставит Int, если числа, String, если строки. Умная жопа.
А вот ещё прикол — можно свои типы с дженериками делать. Смотри, стек, например:
struct Stack<Element> {
private var elements = [Element]()
mutating func push(_ element: Element) {
elements.append(element)
}
mutating func pop() -> Element? {
return elements.popLast()
}
}
var intStack = Stack<Int>()
intStack.push(42)
Создал Stack<Int> — и всё, внутри только целые числа. Создал Stack<String> — и там уже строки болтаются. Одна структура, а работает с чем угодно. Красота, блядь.
Но иногда надо, чтобы этот твой абстрактный тип T не совсем уж левым был. Ну, например, чтобы его можно было сравнивать. И тут на сцену выходят ограничения, type constraints:
func findIndex<T: Equatable>(of value: T, in array: [T]) -> Int? {
return array.firstIndex { $0 == value }
}
Вот смотри: <T: Equatable>. Это как приказ: "ладно, T — любой тип, но, сука, он должен уметь сравниваться через ==". Иначе компилятор тебе в ебальник такой ошибкой швырнёт, что мало не покажется. Зато внутри функции можно спокойно $0 == value писать, и всё будет тип-топ.
А ещё есть в протоколах такая штука — associatedtype. Это, блядь, как дженерик, но для протоколов. Объявляешь какой-то абстрактный внутренний тип, а уже при реализации каждый сам решает, чем его заполнить.
protocol Container {
associatedtype Item
mutating func append(_ item: Item)
var count: Int { get }
}
Сказал — и пошёл реализовывать. В одной структуре Item будет Int, в другой — String. Гибко, ёпта.
И в чём, собственно, профит, спросишь ты? А профит, блядь, налицо:
- Типобезопасность. Компилятор следит, чтобы ты не сунул кота в массив собак. Никаких сюрпризов в рантайме.
- Производительность. Всё проверяется на этапе компиляции, никаких накладных расходов, как если бы ты через
Anyвсё писал. Быстро, чётко. - Переиспользование кода. Написал алгоритм один раз — и он работает с любым типом. Не надо копипастить одно и то же, меняя только
IntнаString.
Да ты посмотри вокруг — вся стандартная библиотека на этом стоит! Array<Element>, Dictionary<Key, Value>, даже этот ёбанный Optional<Wrapped> — всё дженерики, блядь. Так что не игнорируй эту тему, а то так и будешь копипастить функции, как последний распиздяй.