Добавить объявление

Generics и Protocols: пишем код на Swift профессионально

Если вы пишете на 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 из просто языка в инструмент для создания надежных и масштабируемых систем.
23.09.2025 12 453