я могу поставить селекторы C в структуре? - PullRequest
7 голосов
/ 12 сентября 2009

Я хотел связать набор прямоугольников с соответствующими действиями, поэтому я попытался сделать

struct menuActions {
    CGRect rect;
    SEL action;
};

struct menuActions someMenuRects[] = {
    { { { 0, 0 }, {320, 60 } }, @selector(doSomething) },
    { { { 0, 60}, {320, 50 } }, @selector(doSomethingElse) },
};

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

Ответы [ 3 ]

23 голосов
/ 12 сентября 2009

Это ответ на вопрос, почему "initializer element is not constant".

Учитывая следующий пример:

SEL theSelector; // Global variable

void func(void) {
  theSelector = @selector(constantSelector:test:);
}

Компилируется в нечто подобное для архитектуры i386:

  .objc_meth_var_names
L_OBJC_METH_VAR_NAME_4:
  .ascii "constantSelector:test:\0"

  .objc_message_refs
  .align 2
L_OBJC_SELECTOR_REFERENCES_5:
  .long   L_OBJC_METH_VAR_NAME_4

Эта часть определяет две локальные (с точки зрения кода сборки) «переменные» (фактически метки), L_OBJC_METH_VAR_NAME_4 и L_OBJC_SELECTOR_REFERENCES_5. Текст .objc_meth_var_names и .objc_message_refs, непосредственно перед метками 'variable', говорит ассемблеру, в какой раздел объектного файла следует поместить «материал, который следует». Разделы имеют значение для компоновщика. L_OBJC_SELECTOR_REFERENCES_5 изначально установлен на адрес L_OBJC_METH_VAR_NAME_4.

Во время загрузки перед началом работы программы компоновщик делает что-то вроде этого:

  • Перебирает каждую запись в .objc_message_refs раздел.
  • Каждая запись изначально имеет указатель на 0 завершенную C строку.
  • В нашем примере указатель изначально установлен на адрес L_OBJC_METH_VAR_NAME_4, который содержит строку ASCII C "constantSelector:test:".
  • Затем он выполняет sel_registerName("constantSelector:test:") и сохраняет возвращенное значение в L_OBJC_SELECTOR_REFERENCES_5. Компоновщик, который знает подробности частной реализации, не могу назвать sel_registerName() буквально.

По сути, компоновщик выполняет это во время загрузки для нашего примера:

L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");

Вот почему "initializer element is not constant" - элемент инициализатора должен быть постоянным во время компиляции. Значение на самом деле не известно, пока программа не начнет выполняться. Даже тогда ваши объявления struct хранятся в другом разделе компоновщика, разделе .data. Компоновщик знает только, как обновить SEL значения в разделе .objc_message_refs, и нет способа «скопировать» это вычисленное во время выполнения значение SEL из .objc_message_refs в какое-либо произвольное место в .data.

Исходный код C ...

theSelector = @selector(constantSelector:test:);

... становится:

  movl    L_OBJC_SELECTOR_REFERENCES_5, %edx // The SEL value the linker placed there.
  movl    L_theSelector$non_lazy_ptr, %eax   // The address of theSelector.
  movl    %edx, (%eax)                       // theSelector = L_OBJC_SELECTOR_REFERENCES_5;

Поскольку компоновщик выполняет всю свою работу до выполнения программы, L_OBJC_SELECTOR_REFERENCES_5 содержит точно такое же значение, которое вы получили бы, если бы вы вызвали sel_registerName("constantSelector:test:"):

theSelector = sel_registerName("constantSelector:test:");

Разница в том, что это вызов функции, и функция должна выполнить фактическую работу по поиску селектора, если он уже зарегистрирован, или пройти процесс выделения нового значения SEL для регистрации селектора. Это значительно медленнее, чем просто загрузка постоянного значения. Хотя это «медленнее», оно позволяет вам передавать произвольную строку C. Это может быть полезно, если:

  • Селектор не известен во время компиляции.
  • Селектор не известен до вызова sel_registerName().
  • Вам необходимо динамически изменять селектор во время выполнения.

Все селекторы должны пройти через sel_registerName(), который регистрирует каждый SEL ровно один раз. Это имеет то преимущество, что для любого заданного селектора используется везде одно значение. Хотя частная деталь реализации SEL "обычно" является просто char * указателем на копию селектора C строкового текста.

Теперь вы знаете. И знание - это полдела!

4 голосов
/ 12 сентября 2009

Как насчет:

struct menuActions {
   CGRect rect;
   const char *action;
};

struct menuActions someMenuRects[] = {
   { { { 0, 0 }, {320, 60 } }, "doSomething" },
   { { { 0, 60}, {320, 50 } }, "doSomethingElse" },
};

Во время выполнения зарегистрируйте селекторы:

int numberOfActions = 2;
for (int i=0; i < numberOfActions; i++)
   NSLog (@"%s", sel_registerName(someMenuRects[i].action));

Выход:

[Session started at 2009-09-11 16:16:12 -0700.]
2009-09-11 16:16:14.527 TestApp[12800:207] @selector(doSomething)
2009-09-11 16:16:14.531 TestApp[12800:207] @selector(doSomethingElse)

Подробнее о sel_registerName() в справочной системе Objective-C 2.0 .

0 голосов
/ 12 сентября 2009

Похоже, вы заново изобретаете NSCell. Если вы хотите реализовать меню, почему бы не использовать существующие классы пользовательского интерфейса?

...