Обмен сообщениями

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

Функция objc_msgSend

В Objective C сообщения не связываются с реализациями метода до времени выполнения. Компилятор преобразовывает выражение сообщения,

[receiver message]

в запрос к обменивающейся сообщениями функции, objc_msgSend. Эта функция берет получатель и имя метода, упомянутого в сообщении — т.е. селектор метода — как его два основных параметра:

objc_msgSend(receiver, selector)

Любым параметрам, переданным в сообщении, также вручают objc_msgSend:

objc_msgSend(receiver, selector, arg1, arg2, ...)

Обменивающаяся сообщениями функция делает все необходимое для динамического связывания:

Ключ к обмену сообщениями находится в структурах, которые компилятор создает для каждого класса и объекта. Каждая структура класса включает эти два существенных элемента:

Когда новый объект создается, память для него выделяется, и ее переменные экземпляра инициализируются. Сначала среди переменных объекта указатель на его структуру класса. Этот указатель, вызванный isa, предоставляет доступ к объекту к его классу и, через класс, ко всем классам, от которых это наследовалось.

Эти элементы класса и структуры объекта проиллюстрированы на рисунке 3-1.

  Платформа обмена сообщениями рисунка 3-1

Когда сообщение отправляется в объект, обменивающаяся сообщениями функция следует за объектом isa указатель на структуру класса, где это ищет селектор метода в таблице отгрузки. Если это не может найти селектор там, objc_msgSend следует за указателем на суперкласс и пытается найти селектор в его таблице отгрузки. Последовательная причина отказов objc_msgSend подняться на иерархию классов, пока это не достигает NSObject класс. Как только это определяет местоположение селектора, вызовы функции метод, вводимый в таблицу, и передает его структура данных принимающего объекта.

Это - способ, которым реализации метода выбраны во время выполнения — или на жаргоне объектно-ориентированного программирования, что методы динамично связываются с сообщениями.

Для ускорения процесса обмена сообщениями система во время выполнения кэширует селекторы и адреса методов, поскольку они используются. Существует отдельный кэш для каждого класса, и это может содержать селекторы для унаследованных методов, а также для методов, определенных в классе. Прежде, чем искать таблицы отгрузки, обменивающаяся сообщениями подпрограмма сначала проверяет кэш класса принимающего объекта (на теории, что метод, использовавшийся один раз, может, вероятно, использоваться снова). Если селектор метода находится в кэше, обмен сообщениями только немного медленнее, чем вызов функции. Как только программа работала достаточно долго для «нагревания» ее кэшей, почти все сообщения, которые она отправляет, находят кэшируемый метод. Кэши растут динамично для размещения новых сообщений, когда работает программа.

Используя скрытые параметры

Когда objc_msgSend находит процедуру, реализующую метод, она вызывает процедуру и передает все это параметры в сообщении. Это также передает процедуру два скрытых параметра:

Эти параметры дают каждой реализации метода явную информацию о двух половинах выражения сообщения, вызвавшего его. Они, как говорят, «скрыты», потому что они не объявляются в исходном коде, определяющем метод. Когда код компилируется, они вставляются в реализацию.

Несмотря на то, что эти параметры явно не объявляются, исходный код может все еще относиться к ним (как он может относиться к переменным экземпляра принимающего объекта). Метод относится к принимающему объекту как self, и к его собственному селектору как _cmd. В примере ниже, _cmd относится к селектору для strange метод и self к объекту, получающему a strange сообщение.

- strange
{
    id  target = getTheReceiver();
    SEL method = getTheMethod();
 
    if ( target == self || method == _cmd )
        return nil;
    return [target performSelector:method];
}

self более полезные из этих двух параметров. Это - фактически, способ, которым переменные экземпляра принимающего объекта сделаны доступными для определения метода.

Получение адреса метода

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

С методом, определенным в NSObject класс, methodForSelector:, можно попросить подсказку по процедуре, реализующей метод, затем используйте указатель для вызова процедуры. Указатель это methodForSelector: возвраты должны быть тщательно брошены к надлежащему функциональному типу. Оба возврата и типы аргумента должны быть включены в бросок.

Пример ниже шоу, как процедура, реализующая setFilled: метод можно было бы вызвать:

void (*setter)(id, SEL, BOOL);
int i;
 
setter = (void (*)(id, SEL, BOOL))[target
    methodForSelector:@selector(setFilled:)];
for ( i = 0 ; i < 1000 ; i++ )
    setter(targetList[i], @selector(setFilled:), YES);

Первыми двумя параметрами, переданными процедуре, является принимающий объект (self) и селектор метода (_cmd). Когда метод вызывают как функция, эти аргументы скрыты в синтаксисе метода, но должны быть приведены явные.

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

Обратите внимание на то, что methodForSelector: предоставлен системой времени выполнения Какао; это не функция самого языка Objective C.