Как модификатор `final` ускоряет выполнение кода в Swift?

Ответ

Ключевое слово final отключает динамическую диспетчеризацию для класса, его методов или свойств, позволяя компилятору применять оптимизации.

Как это работает:

  1. Статическая диспетчеризация: Компилятор точно знает, какая реализация метода будет вызвана (так как переопределение невозможно). Это убирает необходимость поиска в таблице виртуальных методов (vtable) во время выполнения.
  2. Инлайнинг (Inlining): Компилятор может встроить тело final-метода прямо в место вызова, устраняя накладные расходы на вызов функции.

Пример:

final class NetworkManager {
    // Компилятор знает, что этот метод не будет переопределен
    func sendRequest() {
        // ... код запроса
    }
}

let manager = NetworkManager()
manager.sendRequest() // Вызов может быть оптимизирован или инлайнен

Когда использовать:

  • Для классов, которые заведомо не предназначены для наследования (например, утилитарные или фасадные классы).
  • В performance-critical коде, где важна максимальная скорость.

Осторожно: Необоснованное использование final ограничивает расширяемость и тестируемость кода.

Ответ 18+ 🔞

Да ты послушай, что за магия такая, этот твой final! Ну, типа, берёшь ты класс, метод или свойство, и навешиваешь на него этот самый final. И всё, приехали. Это как поставить на код огромный амбарный замок и сказать: "Всё, ребята, наследование закончилось, дальше — ни-ни".

А че там под капотом-то происходит?

  1. Статическая диспетчеризация, ёпта! Компилятор, этот хитрожопый гений, смотрит на final и такой: "Ага, сука, этот метод уже никуда не денется, его никто не переопределит". И он выкидывает нахуй всю эту возню с таблицами виртуальных методов (vtable), где нужно во время выполнения искать, какую же реализацию вызывать. Он сразу знает, куда стучать. Быстро, чётко, без лишних телодвижений.
  2. Инлайнинг, мать его! А ещё он может взять и просто впендюрить код этого final-метода прямо туда, где его вызывают. Представь: вместо команды "пойди туда, сделай то" — он сразу вставляет туда все инструкции. Ну, как будто ты не функцию вызвал, а код её прямо на этом месте написал. Нахуй накладные расходы на вызов!

Смотри, как это выглядит в деле:

final class NetworkManager { // Всё, этот класс — священная корова. Наследоваться от него? Не, не слышал.
    // Компилятор уже выпил пива и расслабился. Он ТОЧНО знает, что этот метод — последняя инстанция.
    func sendRequest() {
        // ... тут какая-то магия с запросами
    }
}

let manager = NetworkManager()
manager.sendRequest() // БАЦ! И вызов может превратиться просто в набор инструкций прямо здесь. Волшебство, блядь!

Так когда же этим пользоваться, чтобы не быть мудаком?

  • Для классов-одиночек, утилиток или фасадов. Ну, тех, которые по задумке должны быть в одном экземпляре и точка. Зачем их наследовать? Чтобы всё разъебать?
  • В коде, где скорость — всё. Performance-critical, как говорят умные дяди. Когда каждая наносекунда на счету, и тебе не до полиморфизма.

Но предупреждаю, как старший товарищ: не начинай всё подряд помечать final! Это как взять и заварить наглухо все двери в доме. Код станет жёстким, как сухая палка. Его будет сложно тестировать (мокать-то нечего), расширять — вообще писец. Используй с умом, а то получишь архитектурный говногоршок, в который потом сам же и вляпаешься.