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-типу с дополнительными ограничениями. Это — паттерн "conditional conformance", когда тип получает функциональность только при определённых условиях. Сабскрипты также могут быть 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также