Многие из вас, должно быть, сталкивались с вопросом диспетчеризации методов в интервью или сами задавались им когда те или иные методы работали или не отрабатывали так как вы этого сами ожидали. Поэтому дайте разбираться в том что это такое и какие виды диспетчеризации бывают.
Любой разработчик должен быть внимателен к тому как именно написать ту или иную функцию, чтобы бы она была быстрой, простой и одновременно с этим также быстро вызывалась. Нельзя просто полагаться на то что Swift методы быстрее чем в obj-c так что эта не такая уж и большая проблема, рассмотрим все варианты.
Начнем с определения, Method Dispatch - это алгоритм, который решает, какой метод должен вызываться в ответ на вызов. Его цель заключается в том, чтобы проинформировать процессор о том, где он может найти код для вызова метода в памяти
Виды диспетчеризаций
Язык Swift предлагает пользоваться всеми видами диспетчеризаций которые только доступны:
Direct Dispatch (она же статическая)
Table Dispatch (она же динамическая) табличная диспетчеризация бывает Virtual Table и Witness Table
Message Dispatch (самая динамическая диспетчеризация)
Direct Dispatch
Direct Dispatch - самый быстрый способ отправки метода. Прямая отправка не смотря на свою скорость может иметь свои минусы с точки зрения программирования, например у нас не получится воспользоваться наследованием для использования метода в другом классе и переопределением метода в соответствии с этим же пунктом.
пример 1:
Если мы указываем что class является final, то его нельзя будет наследовать, соответственно его методы не могут быть использованы в другом классе и уж тем более метод нельзя переопределять override, вместе с этим диспетчеризация меняется на Direct Dispatch
пример 2:
Если наш класс подписан на протокол у которого есть расширение с методом, а сам класс такой метод не реализует в своем теле - то мы имеем Direct Dispatch
пример 3:
В случае если мы поместим наш метод в extension класса, то такой метод уже нельзя будет переопределять и поэтому это будет Direct Dispatch
пример 4:
В тех случаях когда метод является private, то такой метод вы также не сможете переопределять и доступа к нему у вашего сабкласа не будет, поэтому это снова Direct Dispatch
пример 5 и 6:
В этих примерах мы разметили методы в объектах типа Value, как вы понимаете такие объекты нельзя унаследовать, а соответственно и получить эти методы где-то кроме самих value не возможно и по этой причине мы опять получаем Direct Dispatch
пример 7:
В последнем примере мы используем ключевое слово static, такой метод также не возможно будет переопределись и к такому методу не будет доступа даже если унаследовать этот класс, а соответственно мы вновь получаем Direct Dispatch
Table Dispatch
Virtual Table
Virtual table - используется чаще всего, так как вы можете увидеть его в обычных классах и его наследниках, у этого типа диспетчеризации уже присутствует динамичность так как наши методы могут переопределяться, однако здесь есть и очевидный минус, за счет такого поведения мы получаем более медленное исполнение.
пример:
Для каждого из классов создается своя собственная таблица, в нашем случае у нас есть два класса Sample и SampleSubclass, в первом классе есть методы one и two, во втором же у нас переопределен метод two и есть свой метод three, как будет выглядеть таблица в этом случае.
Sample - 0x00BA
one - 0x110
two - 0x111
а теперь смотрим на наследника:
SampleSubclass - 0X00BD своя таблица
one - 0x110 (мы никак не переопределяли метод поэтому ссылка на метод такая же как и у родителя)
two - 0x112 (переопределили и получили новую ссылку на метод)
three - 0x113 (просто новый метод соответственно и новая ссылка)
Также у каждого метода в таблице есть свой индекс по которому и определяется его ссылка, грубо говоря это такой способ смещения ссылки, обратите внимание на последнюю цифру в ссылках методов которые я привел выше, метод two имел в первой таблице последнюю цифру 1, после переопределения она стала равна 2.
Witness Table
Witness Table используется для реализации протоколов и создается для каждого класса, реализовавшего протокол. По этой таблице центральный процессор понимает, где искать нужную ссылку на метод для его выполнения. Главный минус Witness Table такой же, как и у Virtual Table — скорость существенно ниже, чем у Direct Dispatch.
пример:
Message Dispatch
На самом деле, именно Objective-C предоставляет этот механизм (иногда он называется передачей сообщений), а код Swift просто использует библиотеку среды выполнения Objective-C. Каждый раз, когда вызывается метод Objective-C, вызов передается в objc_msgSend, который обрабатывает поиск. Технически процесс начинается с данного класса и повторяет иерархию классов, чтобы вытащить реализацию.
В отличие от диспетчеризации таблиц, словарь передачи сообщений может быть изменен во время выполнения, что позволяет нам корректировать поведение программы во время запуска. Это называется Swizzling
Отправка сообщений является самой динамичной из трех. Однако вместе с тем и самой медленной.
Для реализации Message Dispatch требуется префикс "@objc dynamic", или можно добавить @objcMembers перед классом, тогда все его методы станут с префиксом @objc по дефолту.
Пример:
И напоследок пример того как делать Swizzling:
В целом мы разобрали все три типа диспетчеризации, но для того чтобы все окончательно уложилось в голове с пониманием того когда, где и какая диспетчеризация будет применена, предлагаю следующую таблицу:
Вполне вероятно что зачастую вы будете стараться использовать Direct Dispatch в своем коде, но знать и другие типы необходимо чтобы понимать как поднять перфоманс ваших проектов или иметь не стандартные решения вроде того же swizzilng.
Если вы дочитали статью до конца:
- поставьте лайк
- подпишитесь на канал
Это мотивирует писать новые статьи, а вы в свою очередь не упустите обновления!
В комментариях также можно оставить вопросы по статье или запросы на другие темы. Спасибо за прочтение!