2009-09-11 25 views
7

我是想一组矩形与相应的行动联系起来,所以我试图做我可以在结构中放置一个Objective-C选择器吗?

struct menuActions { 
    CGRect rect; 
    SEL action; 
}; 

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

,但我得到的错误“初始元素不是常量”。是否有某种原因,我试图做的事情一般是不允许的,或者在全球范围内是不允许的,或者我有某种小标点符号错误?

+2

我不知道为什么'@ selector'不是常量,但是如果你可以把初始化放到一个函数中,你就不必担心这个。 – Amok 2009-09-11 22:10:15

+0

我不想编写像someMenuRects [0] .action = @doSomething这样的代码,因为那么我可能只是在运行时做同样的事情,即如果(CGRectContainsPoint(someMenuRects [0],pt)){[self doSomething]} – 2009-09-11 22:35:32

+2

选择器不是常量,因为该值在运行时非常早才确定。因此,如果需要,可以在该处粘贴一个字符串并在运行时进行查找。 – bbum 2009-09-12 06:22:25

回答

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,其 包含ASCIIC"constantSelector:test:"
  • 然后执行 sel_registerName("constantSelector:test:") 并将返回值存储在 L_OBJC_SELECTOR_REFERENCES_5。链接器, 知道私有实现细节, 可能不会字面上调用。

本质上的连接器在加载时间为我们的例子中执行此:

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。这对于任何给定的选择器都具有恰到好处的一个值的优点。虽然实现私人细节,SEL是“通常”,只是一个char *指向选择器C字符串文本副本的指针。

现在你知道了。知道是战斗的一半!

+1

+1。很好的答案。 – 2009-09-12 05:17:01

+0

感谢您的详细信息。我假设sel_registerName发生在编译/链接时间,而不是在加载时间,对于一个常量选择器...所以我猜sel_registerName真的只是“更慢”,如果你把它称为> 1次... – 2009-09-12 08:28:39

+0

哇.. 。不明白这一半,但听起来你知道你在说什么)+1 – 2013-10-01 20:47:43

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 Reference

+0

巧妙的+1 – 2009-09-11 23:33:29

+0

是的,我意识到了sel_registerName,但原则上在编译时确定在运行时执行此步骤似乎是错误的。也许我的问题的答案实际上只是“不,你不能”,我应该问另一个问题,例如“在Objective-C中构建函数调度表的最佳方式是什么......” – 2009-09-11 23:35:35

+1

我并不是指假设你无知。这是一个有趣的问题,但我不知道正确的答案。 – 2009-09-11 23:44:33

-1

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

+1

iPhone有NSCell吗? – 2009-09-12 15:39:51

相关问题