Перейти к содержимому

Что такое диспетчеризация в программировании

  • автор:

Учебники. Программирование для начинающих.

Programm.ws — это сайт, на котором вы можете почитать литературу по языкам программирования , а так-же посмотреть примеры работающих программ на С++, ассемблере, паскале и много другого..

Программирование — в обычном понимании, это процесс создания компьютерных программ.
В узком смысле (так называемое кодирование) под программированием понимается написание инструкций — программ — на конкретном языке программирования (часто по уже имеющемуся алгоритму — плану, методу решения поставленной задачи). Соответственно, люди, которые этим занимаются, называются программистами (на профессиональном жаргоне — кодерами), а те, кто разрабатывает алгоритмы — алгоритмистами, специалистами предметной области, математиками.
В более широком смысле под программированием понимают весь спектр деятельности, связанный с созданием и поддержанием в рабочем состоянии программ — программного обеспечения ЭВМ. Более точен современный термин — «программная инженерия» (также иначе «инженерия ПО»). Сюда входят анализ и постановка задачи, проектирование программы, построение алгоритмов, разработка структур данных, написание текстов программ, отладка и тестирование программы (испытания программы), документирование, настройка (конфигурирование), доработка и сопровождение.

Cамоучитель по Java

Глава 12. Обработка событий

Диспетчеризация событий

Если вам понадобится обработать просто действие мыши, не важно, нажатие это, перемещение или еще что-нибудь, то придется включать эту обработку во все семь методов двух классов-слушателей событий мыши.

Эту работу можно облегчить, выполнив обработку не в слушателе, а на более ранней стадии. Дело в том, что прежде чем событие дойдет до слушателя, оно обрабатывается несколькими методами.

Чтобы в компоненте произошло событие AWT, должно быть выполнено хотя бы одно из двух условий: к компоненту присоединен слушатель или в конструкторе компонента определена возможность появления события методом enableEvents (). В аргументе этого метода через операцию побитового сложения перечисляются константы класса AWTEvent, задающие события, которые могут произойти в компоненте, например:

AWTEvent.MOUSE_EVENT_MASK I AWTEvent.KEY_EVENT_MASK)

При появлении события создается объект соответствующего класса xxxEvent. Метод dispatchEvent () определяет, где появилось событие — в компоненте или одном из его подкомпонентов, — и передает объект-событие методу processEvent <) компонента-источника.

Метод processEvent о определяет тип события и передает его специализированному методу processxxxEvent о. Вот начало этого метода:

protected void processEvent(AWTEvent e)

if (e instanceof FocusEvent)

>else if (e instanceof MouseEvent)

break; > >else if (e instanceof KeyEvent)

Затем в дело вступает специализированный метод, например, processKeyEvent о. Он-то и передает объект-событие слушателю. Вот исходный текст этого метода:

protected void processKeyEvent(KeyEvent e)

KeyListener listener = keyListener;

if (listener != null)< int

case KeyEvent.KEYJTYPED: listener.keyTyped(e);

case KeyEvent.KEY_PRESSED: listener.keyPressed(e);

case KeyEvent.KEY_RELEASED: listener.keyReleased(e);

Из этого описания видно, что если вы хотите обработать любое событие типа AWTEvent, то вам надо переопределить метод processEvent (), а если более конкретное событие, например, событие клавиатуры, — переопределить более конкретный метод processKeyEvent о. Если вы не переопределяете весь метод целиком, то не забудьте в конце обратиться к методу суперкласса, например, super.processKeyEvent(e);

He забывайте обращаться к методу processXxxEvent() суперкласса.

В следующей главе мы применим такое переопределение в листинге 13.2 для вызова всплывающего меню.

Диспетчеризация методов в Swift: что это и как работает

Сегодня мы поговорим про механизм диспетчеризации методов. Он существует в разных языках, но мы сделаем акцент именно на Swift.

Обложка поста Диспетчеризация методов в Swift: что это и как работает

Сегодня мы поговорим про механизм диспетчеризации методов. Он существует в разных языках, но мы сделаем акцент именно на Swift. Разработчики часто имеют дело с данным механизмом. Даже не подозревая этого, каждый написанный нами метод ведёт к этому процессу. Данная статья подойдёт абсолютно всем уровням, здесь я не хочу закапываться в глубину байт-кода и смотреть что там происходит, но всё же хочу немного уйти под капот этого механизма. А также это очень частый вопрос на собеседованиях. Некоторые смелые компании спрашивают диспетчеризацию даже у Junior разработчиков.

Всем привет, меня зовут Макс Нечаев, я тех-лид и iOS разработчик в крупном фудтех стартапе на арабском рынке (Snoonu).

Что такое диспетчеризация простым языком? Диспетчеризация — это процесс, результатом которого является выбор имплементации метода. Хорошо, давайте попробуем проще. Диспетчеризация ловит вызов метода и выбирает его реализацию.

Например, ваш родственник просит сходить вас в магазин. Исходя из контекста разговора вы понимаете, что вам нужно сходить или в продуктовый за молоком, или в мясной за стейком, или в хозтовары за моющим средством. Процесс, в начале которого вы получили задачу сходить в “магазин” и до момента, когда вы поняли, в какой именно “магазин” нужно идти и будет являться диспетчеризацией.

С первого взгляда может показаться непонятным, где именно подобное может использоваться. Но стоит не забывать, что в Swift у нас есть наследование и полиморфизм. То есть методы могут переопределяться, могут определяться в протоколах через расширения и т.д.

Диспетчеризация методов в Swift: что это и как работает 1

Всего у нас три типа диспетчеризации

Первая: Static dispatch — статическая.

Вторая: Table dispatch — динамическая, которая делится на две:

Третья: Message dispatch

Далее предлагаю рассмотреть все три более подробно.

Static dispatch

Статическая диспетчеризация самая быстрая. Зачастую разработчики стараются привести именно к этому типу вызова методов. Почему так? Потому что уже на этапе комплиляции известно, какую имплементацию метода нужно использовать.

Статическая диспетчеризация используется в методах структур (value типов), final классов, а также в extension классов и протоколов. Во всех случаях метод не может быть переопределен никак, и выбор его имплементации всегда однозначен.

Примеры методов со Static dispatch:

final class SampleFinalClass < func sayHello() < print("Hello!") >> struct SampleStruct < func sayHello() < print("Hello!") >> extension SampleStruct < func someFunc() < print("Hello!") >> 

Преимущества: самая быстра диспетчеризация.

Недостатки: отсутствие полиморфизма и наследия.

Table dispatch

Табличная диспетчеризация (динамическая) работает на таблицах, как и следует из ее названия. Здесь стоит понимать, что на этапе компиляции формируются таблицы с адресами методов определенной сущности. Но уже именно в runtime происходит поиск нужного метода по адресам.

Так как это происходит именно в рантайме, думаю, вы понимаете, что здесь скорость диспетчеризации может повлиять на производительность. Но несмотря на это, есть и явные плюсы Table dispatch. Далее мы рассмотрим два подвида этой диспетчеризации.

Virtual Table

Если говорить про Virtual Table Dispatch, то скорее всего мы имеем ввиду работу с классами, с наследованием и переопределением методов.

Сейчас я попробую на простом примере рассказать как работает этот тип диспетчеризации. Сначала я сделаю это текстом, затем возьму в пример небольшую реализацию и пройдем по ней для закрепления.

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

Давайте создадим два класса, отец (Father) и ребенок (Child). Причем ребенок должен быть наследником класса отца. У отца будут методы: есть, гулять, спать, работать.

class Father < func eat() <>func walk() <> func sleep() <> func work() <> > class Child: Father < >let child = Child() child.eat() 

Смотрите, это очень интересный момент. У каждого класса своя виртуальная таблица. Но при это когда у child мы вызываем метод eat(), вызывается имплементация из класса Father. Почему так? Потому что адреса методов в виртуальной таблице ребенка ведут на те же адреса, что и в виртуальной таблице класса отца.

Вопрос на подумать. Как же сделать так, чтобы у виртуальной таблицы класса Child были свои уникальные адреса на имплементацию этих методов?

class Child: Father < override func eat() <>override func walk() <> override func sleep() <> override func work() <> > 

Мы переопределяем методы, теперь Virtual Table Dispatch у класса Child будет вызывать именно его имплементацию методов, а не класса Father.

Witness Table

Когда мы говорим про Witness Table Dispatch, то здесь скорее всего мы имеем ввиду работу с протоколами. На уровне теории, для класса, который реализует протокол, а именно для методов этого протокола, создается отдельная таблица. Если класс реализует несколько протоколов, то создается несколько Witness таблиц. Основное отличие от Virtual table, что здесь нет наследования.

Давайте попробуем разобраться на примере. Создадим два класса: брат и сестра (Brother / Sister). Также создадим протокол GameProtocol, который обязует класс реализовать метод func buyGame().

Для каждого класса создается своя таблица со своим адресом реализации метода. Здесь не может быть одного адреса, даже не смотря на то, что метод один и протокол один. Так как каждый класс может реализовать его по-своему.

protocol GameProtocol < func buyGame() >class Brother: GameProtocol < func buyGame() < print("AssassinsCreed") >> class Sister: GameProtocol < func buyGame() < print("Barbie") >> 

Каждый класс имеет свою Witness Table и сам решает, как ему реализовать протокол.

Подведем небольшой итог Table dispatch.

Преимущества: наличие полиморфизма (гибкости), возможность наследования

Недостатки: медленнее, чем статическая диспетчеризация

Message dispatch

Динамичный вид диспетчеризации, самый медленный и связан с Objective-C. Он полностью работает в runtime. Для работы с Message Dispatch методам добавляется модификатор @objc dynamic. Как он работает под капотом? В отличии от табличной диспетчеризации, Message Dispatch ищет реализацию метода в дочернем классе, затем по ссылке переходит в таблицу родительского класса и ищет там, если и там нет реализации, переходит на уровень ниже. Таким образом создается длинная цепочка поиска реализации метода от самого верхнего класса, до самого нижнего класса, именно это и делает данный тип диспатча самым медленным.

Message Dispatch лежит в основе KVO и KVC. Так как Message Dispatch работает в runtime, появляется возможность подменить реализацию методов — это называется Method Swizzling. Method Swizzling позволяет подменить метод другим в runtime, при этом оригинальная имплементация остается доступной.

Преимущества: KVO и KVC, Method Swizzling

Недостатки: самая медленная диспетчеризация

Итог

В конце концов хочется сказать, что данная тема не самая ключевая в работе iOS разработчика, но при этом рекомендуется к пониманию. Более того, на собеседованиях вас часто будут спрашивать и гонять по диспетчеризации. Данных знаний вам должно хватить, чтобы ответить на 95% всех вопросов по теме. Более того, прилагаю вам такую схемку, которую можно держать перед глазами, она помогает очень просто и быстро понять, какая и где диспетчеризация.

Диспетчеризация по ключу (данные) — JS: Полиморфизм

В связке с полиморфизмом постоянно возникает выражение «динамическая диспетчеризация». С самим понятием мы познакомимся позже, а сейчас поговорим про диспетчеризацию в принципе.

Диспетчеризация (от английского dispatch — отсылать, отправлять) — процесс координации каких-либо действий. Например, диспетчер в аэропорту разводит самолеты, диспетчер в службе такси связывает доступного водителя и клиента.

Рассмотрим условный код, в котором выбор ветки исполнения идет по конкретному значению переменной:

let databaseConfiguration; if (env === 'development')  databaseConfiguration =  adapter: 'sqlite', >; > else if (env === 'production')  databaseConfiguration =  adapter: 'postgresql', >; > 

Во всех проектах существует понятие «среда». Это то окружение, в котором происходит запуск проекта. Во время разработки код запускают в среде разработчика, ее традиционно называют development. Среда, в которой приложение работает по-настоящему, называется production. В зависимости от среды приложения по-разному стартуют, конфигурируются и даже работают, например, в разных средах могут использоваться разные базы данных.

Код выше как раз типичный пример того, как меняется конфигурация в зависимости от среды. Процесс выбора можно назвать процессом диспетчеризации.

Статичная условная конструкция, в которой диспетчеризация идет по строкам, легко заменяется на свитч. Так код становится понятнее и проще:

let databaseConfiguration; switch (env)  case 'development': databaseConfiguration =  adapter: 'sqlite', >; break; case 'production': databaseConfiguration =  adapter: 'postgresql', >; break; > 

Хотя кода и стало чуть больше, switch сам по себе более явно описывает процесс диспетчеризации. Но можно пойти еще дальше и сделать диспетчеризацию по ключу ассоциативного массива:

const databaseSettingsByEnv =  development:  adapter: 'sqlite', >, production:  adapter: 'postgresql', >, >; const databaseConfiguration = databaseSettingsByEnv[env]; 

Значение по умолчанию легко добавить через оператор ?? :

const databaseConfiguration = databaseSettingsByEnv[env] ??  adapter: 'memory' >; // Либо через _.get // const databaseConfiguration = _.get(databaseSettingsByEnv, env, < adapter: 'memory' >); 

Этот вариант лучше предыдущих двух сразу по нескольким причинам. Во-первых, он короче, во-вторых, он гибче. Условные конструкции — это статичный код, который нельзя поменять без переписывания самой программы в отличие от последнего варианта. А вот данные — это совсем другое дело. Для удобства их часто кладут в файлы конфигурации, которые используют формат JSON или YAML, например:

--- development: adapter: sqlite production: adapter: postgresql 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

Диспетчеризация в Swift: погружение в теорию и практику

Все iOS-разработчики так или иначе сталкиваются с диспетчеризацией (Method Dispatch), но далеко не каждый понимает, как это работает. Зная, как проходит процесс диспетчеризации под капотом программы, вы можете повысить производительность своего кода.

В этой статье мы разберем типы диспетчеризации, их плюсы и минусы, а также затронем один из распространённых багов.

Материал будет полезен для всех iOS-разработчиков, которые хотят улучшить производительность своего кода. Кроме того, этот материал поможет при подготовке к собеседованию, где вопросы о Method Dispatch встречаются достаточно часто.

Что такое диспетчеризация в Swift?

Это процесс, при котором программа выбирает, какие инструкции выполнить при вызове метода. Диспетчеризация происходит каждый раз, когда вызывается метод. Многим кажется, что это происходит так:

Рис 1. Неправильное понимание отработки метода.

Это заблуждение, потому что есть промежуточный пункт — это диспетчеризация.

Рис 2. Правильное понимание отработки метода.

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

Типы диспетчеризации

Перейдем к типам диспетчеризации на языке Swift. Существует три вида: Direct Dispatch (статическая), Table Dispatch (динамическая, в свою очередь, делится на Virtual Table и Witness Table) и Message Dispatch (самая динамическая диспетчеризация).

Рис 3. Типы диспетчеризации.

1. Direct Dispatch

Direct Dispatch — это самый быстрый способ отправки метода, часто называют статической диспетчеризацией. Однако прямая отправка наиболее ограничивающая с точки зрения программирования и недостаточно динамична для ООП. У всех value-объектов (например, структуры) используется Direct Dispatch.

Рис 4. Плюсы и минусы Direct Dispatch.

Рассмотрим практические примеры Direct Dispatch:

Final Class

// MARK: Example 1 - Final Class final class ClassExample1 < // MARK: Direct Dispatch func doSomething() < print("Example 1 - Final Class") >>

При добавлении final становится недоступным наследование этого класса, соответственно, метод тоже нельзя оверрайднуть. Возможно, вы часто видели такое и не понимали, для чего это делается. А это один из плюсов — изменение диспетчеризации на Direct Dispatch и, соответственно, улучшение производительности кода. Ну и защита от наследования, когда оно не нужно, например, при создании класса ViewController, единственного в приложении.

Protocol Extension

// MARK: Example 2 - Protocol Extension extension SomeProtocol < // MARK: Direct Dispatch func doSomething() < print("Example 2 - Protocol Extension") >> class ClassExample2: SomeProtocol <> let classExample2 = ClassExample2() classExample2.doSomething()

При реализации дефолтного метода протокола с помощью его расширения, диспетчеризация c Witness Table (об этом больше информации представлено ниже) меняется на Direct Dispatch.

Class Extension

// MARK: Example 3 - Class Extension class ClassExample3 <> extension ClassExample3 < // MARK: Direct Dispatch func doSomething() < print("Example 3 - Class Extension") >> let classExample3 = ClassExample3() classExample3.doSomething()

При написании метода класса в его расширении данный метод нельзя будет оверрайднуть, и он меняет свою диспетчеризацию на Direct Dispatch.

Access Control

// MARK: Example 4 - Access Control class ClassExample4 < func doSomething() < doSomethingPrivate() >// MARK: Direct Dispatch private func doSomethingPrivate() < print("Example 4 - Access Control") >>

К приватному методу нет доступа, чтобы его как-то переписать у наследника (сабкласса), и соответственно он меняет свою диспетчеризацию на Direct Dispatch.

Данные практические примеры Direct Dispatch используются при разработке приложений для улучшения производительности кода, а также для защиты участков кода от переписывания сабклассами. Они помогут повысить скорость вашего кода и сделать его более профессиональным, старайтесь не забывать об этом.

2. Table Dispatch

Virtual Table

Virtual Table используется в наследовании. Для каждого класса и его наследника (сабкласса) создается виртуальная таблица (пример приведён ниже), по которой центральный процессор понимает, где искать нужную ссылку на метод для его выполнения. Главный минус динамической диспетчеризации в том, что ее скорость существенно ниже, чем у статической.

Рис 5. Плюсы и минусы Virtual Table.

Рассмотрим Virtual Table на практическом примере:

// MARK: Example 5 - Virtual Table class ClassExample5 < func doSomething() < print("Example 5 - Virtual Table") >> class SubclassExample5: ClassExample5 < override func doSomething() < print("Override for subclass") >func doSomething2() < print("Method of subclass") >>

Создается класс и его сабкласс, для каждого из этих классов создается отдельная виртуальная таблица.

Рис 6. Virtual Table для данного кода.

Witness Table

Witness Table используется для реализации протоколов и создается для каждого класса, реализовавшего протокол. По этой таблице центральный процессор понимает, где искать нужную ссылку на метод для его выполнения. Главный минус Witness Table такой же, как и у Virtual Table — скорость существенно ниже, чем у Direct Dispatch.

Рис 7. Плюсы и минусы Witness Table.

Рассмотрим Witness Table на практическом примере:

// MARK: Example 6 - Witness Table protocol ProtocolExample6 < func doSomething() >class ClassExample6: ProtocolExample6 < func doSomething() < print("Example 6 - Witness Table") >> class AnotherClassExample6: ProtocolExample6 < func doSomething() < print("Hello World") >>

Создается протокол и два класса, которые реализуют этот протокол, для каждого из этих классов создается Witness Table.

Рис 8. Witness Table для данного кода.

3. Message Dispatch

Message Dispatch — это самый динамичный вызов метода с помощью Objective-C. Message Dispatch работает в рантайме и показывает, какой метод вызывать, то есть проверяет это в реальном времени. Message Dispatch лежит в основе KVO (и соответственно в реактивном программировании), UIAppearance, CoreData. Так как Message Dispatch работает в рантайме, соответственно, можно подменять реализацию методов — это называется Method Swizzling. Method Swizzling позволяет подменить метод вашим в рантайме, оставляя оригинальную имплементацию доступной. А также в рантайме можно менять экземпляры класса.

Message Dispatch часто используется для тестирования кода. Редко его можно встретить в проде, так как это не очень безопасный вызов и относится к самой медленной диспетчеризации. Но иногда он встречается и в проде, когда существующая библиотека не может обеспечить нам нужный результат и разработчик делает замену метода на свой в рантайме с помощью Method Swizzling.

Рис 9. Плюсы и минусы Message Dispatch.

Для реализации Message Dispatch требуется префикс «@objc dynamic», или можно добавить @objcMembers перед классом, тогда все его методы станут с префиксом @objc по дефолту.

Рассмотрим пример из того, что разобрали выше:

// MARK: Example 7 - All types of dispatching protocol ProtocolExample7 < func doSomethingWithWitnessTable() >class ClassExample7: NSObject < @objc dynamic func doSomething() < print("Example 7 - Message Dispatch") >> class SubclassExample7: ClassExample7, ProtocolExample7 < private func doSomethingWithDirectDispatch() < print("Direct Dispatch") >func doSomethingWithVirtualTable() < print("Virtual Table") >func doSomethingWithWitnessTable() < print("Witness Table") >@objc override dynamic func doSomething() < print("Override with Message Dispatch") >>

Тут присутствуют все типы диспетчеризации. Так как остальные типы мы уже разобрали сверху, разберем только Message Dispatch. В рантайме при вызове метода doSomething сначала будет выполняться поиск в данном сабклассе, если имплементация метода не найдется, то будет поиск по родительскому классу. Если и там его не окажется, то поиск переходит в класс NSObject. Если метод не найдется, а такое может произойти при указании метода через функцию performSelector, то случится краш с ошибкой: «NSInvalidArgumentException: unrecognized selector sent to instance».

Баг с диспетчеризацией

Вместе с тем в Swift существуют различные баги с диспетчеризацией от Apple. Многие их них были пофикшены с выходом новых версий языка. Ниже разберем баг с протоколом, который до сих пор присутствует при разработке, и приведём пример, как его пофиксить.

// MARK: Example 8 - First Bug protocol ProtocolExample8 <> extension ProtocolExample8 < func doSomething() < print("Default Implementation") >> class ClassExample8: ProtocolExample8 < func doSomething() < print("Required Implementation") >> let first = ClassExample8() let second: ProtocolExample8 = ClassExample8() first.doSomething() second.doSomething()

По логике, два раза в консоль должна выводиться строчка «Required Implementation», но реальный результат таков:

Это происходит, потому что во втором случае система выбирает дефолтную реализацию протокола и это Direct Dispatch, а мы осуществляем в нашем классе свою реализацию, и под капотом этот метод переходит в Witness Table.

Для того, чтобы программа поняла, что в нашем случае мы используем Witness Table, нам нужно добавить этот метод в протокол.

protocol ProtocolExample8

После добавления результат становится правильным:

Итоги

Итак, мы разобрали, как работает диспетчеризация. Это обширная тема, изучив которую, вы поймете, как методы реализуются под капотом на самом деле. Например, сможете смело заменять многие методы на Direct Dispatch, где ранее использовалась другая диспетчеризация по умолчанию. От этого ваш код станет лучше и быстрее.

Спасибо за внимание! Хорошего кодинга 🙂

Полезные материалы для разработчиков мы также публикуем в наших соцсетях – ВК и Telegram.

  • ios development
  • swift
  • method dispatch
  • objective-c
  • Блог компании SimbirSoft
  • Программирование
  • Разработка под iOS
  • Разработка мобильных приложений
  • Swift

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *