【问题标题】:can I put an objective-C selector in a struct?我可以在结构中放置一个 Objective-C 选择器吗?
【发布时间】:2009-09-11 22:02:03
【问题描述】:

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

struct menuActions {
    CGRect rect;
    SEL action;
};

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

但我收到错误“初始化程序元素不是常量”。是否有某种原因导致我试图做的事情在一般情况下是不允许的,或者在全球范围内是不允许的,还是我有某种小的标点错误?

【问题讨论】:

  • 我不知道为什么@selector 不是常量,但如果你可以将初始化放入一个函数中,你就不必担心这个了。
  • 我不想写 someMenuRects[0].action = @doSomething 这样的代码,因为那样我还不如在运行时做同样的事情,即 if (CGRectContainsPoint(someMenuRects[0] , pt)) { [self doSomething] }
  • 选择器不是恒定的,因为该值直到运行时的非常早期才真正确定。因此,如果需要,您可以在其中插入一个字符串并在运行时进行查找。
  • 所以现在我使用的是 (char *)s 的静态表,并在选择时通过 sel_registerName 调用选择器,因为它是 UI 代码,额外的调用无关紧要。跨度>

标签: iphone objective-c selector


【解决方案1】:

这个答案就是为什么"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 部分。链接器只知道如何更新.objc_message_refs 部分中的SEL 值,并且无法将运行时计算的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 字符串文本的副本。

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

【讨论】:

  • 感谢您提供详细信息。我假设 sel_registerName 发生在编译/链接时,而不是加载时,对于一个常量选择器......所以我猜 sel_registerName 如果你调用它 >1 次,它真的只是“更慢”......
  • 哇...没听懂一半,但听起来你知道你在说什么)+1
  • “如果选择器已经注册,则该函数需要执行查找选择器的实际工作”:完全正确。例如。如果应用程序源文件使用@selector(constantSelector:test:),动态库(框架)也使用该选择器,并且如果某些动态运行时代码使用sel_registerName("constantSelector:test:"),Objective-C 确保它们都返回相同的SEL(AKA struct objc_selector * ) 运行时的指针。此功能使选择器有效地嵌入字符串,以便使选择器相等比较(因此,查找)非常快 - 只需要指针比较..
【解决方案2】:

怎么样:

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 Runtime Reference

【讨论】:

  • 是的,我知道 sel_registerName,但原则上,在运行时执行此步骤似乎是错误的,而实际上它是在编译时确定的。也许我的问题的答案真的只是“不,你不能”,我应该问另一个问题,比如“在 Objective-C 中构建函数调度表的最佳方法是什么”......
  • 我并不是要假设你是无知的。这是一个有趣的问题,但我不知道正确答案。
  • 对不起,如果我听起来被冒犯了——我对这个平台很无知!
【解决方案3】:

看起来你在这里重新发明了 NSCell。如果你想实现一个菜单,为什么不使用现有的 UI 类呢?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-09
    • 1970-01-01
    • 2011-03-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多