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

Dav*_*des 7 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) },
};
Run Code Online (Sandbox Code Playgroud)

但我得到错误"初始化元素不是常数".是否有某些原因使我想要做的事情一般不被允许,或者在全球范围内不被允许,或者我是否有某种轻微的标点错误?

joh*_*hne 23

这个答案就是为什么"initializer element is not constant".

给出以下示例:

SEL theSelector; // Global variable

void func(void) {
  theSelector = @selector(constantSelector:test:);
}
Run Code Online (Sandbox Code Playgroud)

为这个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
Run Code Online (Sandbox Code Playgroud)

这部分定义了两个局部(就汇编代码而言)'变量'(实际上是标签),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:");
Run Code Online (Sandbox Code Playgroud)

这就是"initializer element is not constant"- 初始化元素在编译时必须是常量的原因.在程序开始执行之前,该值实际上是不可知的.即便如此,您的struct声明也存储在不同的链接器.data部分中.链接器只知道如何更新SEL.objc_message_refs部分中的值,并且无法将该运行时计算的SEL值"复制" .objc_message_refs到其中的某个任意位置.data.

C源代码...

theSelector = @selector(constantSelector:test:);
Run Code Online (Sandbox Code Playgroud)

......变成:

  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;
Run Code Online (Sandbox Code Playgroud)

由于链接器在程序执行之前完成所有工作,因此L_OBJC_SELECTOR_REFERENCES_5包含与调用时完全相同的值sel_registerName("constantSelector:test:"):

theSelector = sel_registerName("constantSelector:test:");
Run Code Online (Sandbox Code Playgroud)

区别在于这是一个函数调用,如果已经注册了选择器,函数需要执行查找选择器的实际工作,或者完成分配新SEL值以注册选择器的过程.只需加载一个常量值,速度就慢得多.虽然这是"慢",但它确实允许您传递任意C字符串.这在以下情况下非常有用:

  • 选择器在编译时是未知的.
  • 直到sel_registerName()调用之前,才知道选择器.
  • 您需要在运行时动态更改选择器.

所有选择器都需要通过sel_registerName(),每次只能注册SEL一次.对于任何给定的选择器,这具有在任何地方具有恰好一个值的优点.虽然是一个实现私有细节,但SEL"通常"只是一个char *指向选择器C字符串文本副本的指针.

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