Ответ
Методы, объявленные в extension, используют статическую диспетчеризацию (static dispatch). Компилятор определяет конкретную реализацию для вызова на этапе компиляции.
Причина: extensions не могут переопределять существующие методы в исходном типе — они только добавляют новую функциональность. Поэтому для них не требуется динамическая диспетчеризация (v-table).
Пример, демонстрирующий разницу:
class BaseClass {
func original() { print("Base: original") }
func extensible() { print("Base: extensible") }
}
extension BaseClass {
func added() { print("Extension: added") }
func extensible() { print("Extension: extensible") } // НЕ переопределение!
}
let instance: BaseClass = BaseClass()
instance.original() // Динамическая диспетчеризация
instance.added() // Статическая диспетчеризация
instance.extensible() // Вызовется метод из класса, а не из extension
Исключения и нюансы:
- Для
@objcметодов в extension классов диспетчеризация становится динамической через Objective-C runtime. - Протоколы с реализацией по умолчанию в extension также используют статическую диспетчеризацию для этих реализаций.
Ответ 18+ 🔞
Да ты посмотри, какая история интересная вырисовывается! Сидит себе компилятор Swift, такой весь из себя умный, и думает: "Ну что, дружок-пирожок, extension написали? Щас я тебе покажу, как диспетчеризация работает".
Вот смотри. Объявляешь ты методы прямо в теле класса — всё, пиши пропало, это динамическая диспетчеризация. Компилятор строит для них виртуальную табличку (v-table), и во время работы программы процессор бегает по ней, как угорелый, ища, какую же реализацию вызвать. Особенно если там наследники и полиморфизм.
А теперь — барабанная дробь — ты пишешь extension. И компилятор такой: "Ага, ну это же просто новая функциональность, а не переопределение!". И на этапе компиляции, ебать мои старые костыли, он уже точно знает, какой кусок кода выполнить. Это и есть статическая диспетчеризация. Быстро, эффективно, никаких танцев с бубном в рантайме.
Почему? Да потому что extension, сука, не может переопределить существующий метод! Он может только добавить. Ну, или сделать вид, что переопределяет, но это самообман, как мы сейчас увидим.
class BaseClass {
func original() { print("Base: original") }
func extensible() { print("Base: extensible") } // Главный метод, так сказать
}
extension BaseClass {
func added() { print("Extension: added") } // Чистое добавление
func extensible() { print("Extension: extensible") } // А вот это — ПИЗДЁЖ! Не переопределение, а новый метод.
}
let instance: BaseClass = BaseClass()
instance.original() // Динамика: "Base: original"
instance.added() // Статика: компилятор сразу знает куда идти — "Extension: added"
instance.extensible() // И тут динамика! Вызовется метод ИЗ КЛАССА, а не из extension. "Base: extensible"
Видишь? Написал в extension метод с таким же именем — и думаешь, обманул систему? Хуй там! Компилятор смотрит на v-table класса, видит там extensible, и вызывает его. Твой extension-метод — это просто другой, независимый метод, который ты даже не вызвал. Вообще пиздец, как легко обмануться.
Но, как всегда, есть свои "а вот если":
-
Магия
@objc. Если твой класс наследник отNSObjectи ты пометишь метод в extension как@objc— всё, поезд ушёл. Диспетчеризация станет динамической через Objective-C runtime. Тут уже компилятор руки разводит — разбирайтесь сами там, в рантайме. -
Протоколы с default implementation. Тоже любимая тема для подставы. Реализуешь метод по умолчанию в extension протокола — и он тоже будет вызываться статически. Если класс переопределит этот метод — вызовется его. Но если положить экземпляр в переменную типа протокола и вызвать метод... ой, тут уже начинается головная боль, но суть в том, что для реализации из extension компилятор опять же решит всё за тебя на этапе компиляции.
Короче, вывод простой: extension — это не про переопределение, а про добавление. И компилятор это знает, поэтому и оптимизирует вызовы, не тратя время на поиск в рантайме. Хитро, да?