Если вы пишете на
Swift, но не используете
Generics и
Protocols на полную, то вы теряете главные преимущества языка. Эти инструменты превращают код из простого набора инструкций в гибкую и безопасную систему.
Generics - пишем код один раз, используем везде:
Дженерики - это параметрический полиморфизм. Проще говоря, мы пишем функцию или тип один раз, а работаем с разными типами данных.
Простой пример:
func swapValues(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}
var x = 10
var y = 20
swapValues(&x, &y) // Работает с Int
var s1 = "Hello"
var s2 = "World"
swapValues(&s1, &s2) // Работает с String
Ограничения generic-ов:
// Проблема: нельзя складывать любой тип данных
func sum(_ a: T, _ b: T) -> T {
return a + b // Ошибка: Binary operator '+' cannot be applied
}
// Решение: сообщаем компилятору что T должен быть числом
func sum(_ a: T, _ b: T) -> T {
return a + b // Работает с любыми числами
}
Пример с протоколом:
protocol Identifiable {
var id: Int { get }
}
struct User: Identifiable {
let id: Int
let name: String
}
class DataService {
private var storage: [Int: T] = [:]
func save(_ item: T) {
storage[item.id] = item
}
}
Использование where для сложных ограничений:
func process(_ value: T) where T: Identifiable, T: Equatable {
// value имеет и id, и может сравниваться
}
Protocols - контракты для ваших типов:
Протоколы определяют требования, которым должны соответствовать типы. Это как чертеж для класса или структуры.
Простой пример:
protocol Drawable {
func draw()
}
struct Circle: Drawable {
func draw() {
print("Рисуем круг")
}
}
struct Square: Drawable {
func draw() {
print("Рисуем квадрат")
}
}
let shapes: [Drawable] = [Circle(), Square()]
shapes.forEach { $0.draw() }
Расширения протоколов:
extension Drawable {
// Теперь можно использовать реализацию по умолчанию
func draw() {
print("Рисуем что-то")
}
}
Associated Types - протоколы как дженерики:
Иногда нужно, чтобы протокол работал с разными типами данных, но мы заранее не знаем, с какими именно. Для этого используют
associatedtype - это как заглушка для типа, которую заполнят при реализации.
Простой пример:
protocol Storage {
associatedtype Item
var items: [Item] { get set }
func add(_ item: Item)
}
class StringStorage: Storage {
typealias Item = String // Задаем тип
var items: [String] = []
func add(_ item: String) {
items.append(item)
}
}
class IntStorage: Storage {
var items: [Int] = [] // Компилятор сам определит тип
func add(_ item: Int) {
items.append(item)
}
}
Пример с ограничениями:
protocol ComparableStorage {
associatedtype Item: Comparable // Только сравниваемые типы
func isFirstItemGreater() -> Bool
}
class NumberStorage: ComparableStorage {
var items: [Int] = [5, 2, 8]
func isFirstItemGreater() -> Bool {
return items[0] > items[1] // Можем сравнивать
}
}
Что дает associatedtype:
- Гибкость: один протокол для разных типов.
- Безопасность: компилятор следит за типами.
- Четкость: ясно, какие данные ожидаются.
Главное:
associatedtype делает протоколы по-настоящему универсальными, позволяя им работать с любыми типами данных, сохраняя при этом полную типобезопасность.
Как начать использовать Generics и Protocols эффективно:
- Найдите повторяющийся код: где вы копируете логику для разных типов.
- Определите общее поведение: что общего у ваших объектов.
- Выносите в протоколы: создайте контракты для похожих объектов.
- Добавляйте generics: делайте код универсальным.
Вывод:
Generics и
Protocols это не просто синтаксис, а способ мышления. Они превращают
Swift из просто языка в инструмент для создания надежных и масштабируемых систем.