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