Как наложить ограничения на generic-параметры в Swift?

«Как наложить ограничения на generic-параметры в Swift?» — вопрос из категории Swift Core, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Ограничения (constraints) для generic-параметров указывают, какими свойствами должен обладать конкретный тип. Это делается с помощью протоколов, классов или требований равенства типов.

1. Базовый синтаксис (после двоеточия :) Указывает, что тип T должен соответствовать одному протоколу или быть подклассом определенного класса.

// T должен соответствовать протоколу Comparable
func findMax<T: Comparable>(_ a: T, _ b: T) -> T {
    return a > b ? a : b
}

// T должен быть подклассом UIView
func configureView<T: UIView>(_ view: T) where T: Configurable {
    view.configure()
}

2. Уточнение с помощью where-предложения Позволяет задавать более сложные условия, особенно когда ограничений несколько или они касаются нескольких параметров.

// T должен соответствовать и Numeric, и Comparable
func processNumbers<T>(_ numbers: [T]) -> T where T: Numeric, T: Comparable {
    return numbers.sorted().last!
}

// Два параметра, Item которых должен быть одного и того же Equatable типа
func areItemsEqual<C1: Container, C2: Container>(_ c1: C1, _ c2: C2) -> Bool 
    where C1.Item == C2.Item, C1.Item: Equatable {
    guard let item1 = c1.items.first, let item2 = c2.items.first else { return false }
    return item1 == item2
}

3. Ограничения на ассоциированные типы в протоколах

protocol Container {
    associatedtype Item
    var items: [Item] { get set }
}

// Расширение добавляет метод только для Container, у которых Item — Equatable
extension Container where Item: Equatable {
    func contains(_ item: Item) -> Bool {
        return items.contains(item)
    }
}

// Расширение для Container, у которых Item — конкретный тип (например, String)
extension Container where Item == String {
    func concatenated() -> String {
        return items.joined()
    }
}

Основные виды ограничений:

  • Соответствие протоколу: T: SomeProtocol
  • Наследование класса: T: SomeClass
  • Равенство типов: T == SomeSpecificType или T.Item == U.Item
  • Составные условия: Через where T: P1, T: P2 или where T: P1, U: P2.

Ограничения делают generics типобезопасными, позволяя компилятору гарантировать, что с типами можно выполнять только допустимые операции.