这个答案是为什么"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
and .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
字符串文本副本的指针。
现在你知道了。知道是成功的一半!