我可以在结构中放置一个Objective-C选择器吗?

时间:2009-09-11 22:02:04

标签: iphone objective-c selector

我想要将一组矩形与相应的动作相关联,所以我试着做

struct menuActions {
    CGRect rect;
    SEL action;
};

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

但我收到错误“初始化元素不是常数”。是否有一些理由说我想要做的事情一般不被允许,或者在全球范围内是不允许的,或者我是否有某种轻微的标点错误?

3 个答案:

答案 0 :(得分:23)

这个答案是为什么"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_4L_OBJC_SELECTOR_REFERENCES_5。文本.objc_meth_var_names.objc_message_refs,就在'变量'标签之前,告诉汇编器目标文件的哪个部分放置“后面的东西”。这些部分对链接器有意义。 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指针。

现在你知道了。知道是成功的一半!

答案 1 :(得分:4)

怎么样:

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)

有关Objective-C 2.0 Runtime Referencesel_registerName()的更多信息。

答案 2 :(得分:-1)

看起来你在这里重塑NSCell。如果要实现菜单,为什么不使用现有的UI类?