Что такое extension в Swift и каковы его ограничения?

Ответ

Extension (расширение) в Swift позволяет добавлять новую функциональность к существующему классу, структуре, перечислению или протоколу без изменения их исходного кода и без использования наследования.

Что можно добавлять в extension:

  • Вычисляемые свойства (computed properties).
  • Методы (экземпляра и типа).
  • Новые инициализаторы (convenience init).
  • Сабскрипты.
  • Вложенные типы.
  • Реализацию протоколов.

Пример:

extension String {
    // Добавляем метод экземпляра
    var isPalindrome: Bool {
        let cleaned = self.lowercased().filter { $0.isLetter }
        return cleaned == String(cleaned.reversed())
    }

    // Добавляем метод типа (static)
    static func random(length: Int) -> String {
        let letters = "abcdefghijklmnopqrstuvwxyz"
        return String((0..<length).map{ _ in letters.randomElement()! })
    }
}

print("radar".isPalindrome) // true
print(String.random(length: 5)) // "kqdjp"

Строгие ограничения extensions:

  1. Нельзя добавлять хранимые свойства (stored properties).
  2. Нельзя добавлять property observers к существующим свойствам.
  3. Нельзя переопределять существующую функциональность (для этого нужно использовать наследование в классах).
  4. Нельзя добавлять designated инициализаторы в классах, только convenience.

Практическое применение: Организация кода, разделение реализации протоколов, добавление вспомогательных методов к системным типам.

Ответ 18+ 🔞

Слушай, а вот эти ваши расширения в Swift — это же просто пиздец какой удобный инструмент, ей-богу! Представь: есть у тебя какой-нибудь стандартный тип, как String или Int, и тебе вдруг позарез понадобилось, чтобы он умел что-то, чего он отродясь не умел. Ну, например, проверять, является ли строка палиндромом. Так вот, блядь, не надо городить наследника, не надо лезть в исходники — просто накидываешь на него extension, и всё, готово! Как будто он так с рождения и был.

extension String {
    var isPalindrome: Bool {
        let cleaned = self.lowercased().filter { $0.isLetter }
        return cleaned == String(cleaned.reversed())
    }
}

Вот и вся магия, ёпта! Теперь любая строка в твоём коде может отчитаться, палиндром она или нет. Красота, да и только.

Но, конечно, не всё так просто, как кажется. Есть тут свои подводные, блядь, камни. Расширение — это не волшебная палочка, которая всё может. Есть жёсткие правила, и их надо знать, а то охуеешь потом, когда компилятор начнёт материться.

Во-первых, самое главное: хранимые свойства добавлять нихуя нельзя. Вообще. Никаких var myProperty: Int = 10. Только вычисляемые. Это типа как обещание: "функционал добавим, но состояние — ни-ни". Так уж язык задуман, терпи.

Во-вторых, наблюдателей за свойствами (willSet, didSet) к уже существующим полям тоже не прикрутишь. Хотел следить, когда меняется какое-нибудь старое свойство? Не судьба. Придёшь с такой идеей — получишь в ответ лишь холодный взгляд компилятора.

В-третьих, переопределять существующие методы — это уже не к расширениям. Это удел наследования, для классов. В расширении ты можешь только добавить новое, но не поменять старое. Иначе был бы пиздец и анархия, все бы друг другу реализации ломали.

Ну и в-четвёртых, с инициализаторами тоже не всё гладко. В классах через расширение можно добавить только convenience init. А designated — ни в какую. Это чтобы иерархию не сломать случайно.

А так-то, блядь, инструмент — огонь! Идеально, когда нужно:

  • Размазать реализацию протокола по отдельным файлам для чистоты кода.
  • Научить Int считать факториал, а Date — показывать "два дня назад".
  • Сгруппировать кучу вспомогательных методов в одном месте, чтобы не засирать основной класс.

В общем, расширения — это как суперспособность для аккуратного разработчика. Главное — не пытаться ими делать то, что они не умеют, а то будет не "ёперный театр", а настоящий цирк с конями.