代码之家  ›  专栏  ›  技术社区  ›  David Maymudes

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

  •  7
  • David Maymudes  · 技术社区  · 15 年前

    我想把一组矩形和相应的动作联系起来,所以我试着这样做。

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

    但我得到错误“初始值设定项元素不是常量”。我尝试做的事情一般来说是不允许的,或者在全球范围内是不允许的,还是我有一些小的标点错误?

    3 回复  |  直到 10 年前
        1
  •  23
  •   johne    15 年前

    这就是为什么 "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_选择器_references_5 最初设置为的地址 l_objc_meth_var_name_4号 .

    在执行加载时,在程序开始执行之前,链接器执行的操作大致如下:

    • 循环访问 .objc_消息参考 部分。
    • 每个条目最初都设置为指向 0 结束 C 字符串。
    • 在我们的示例中,指针最初设置为 地址 l_objc_meth_var_name_4号 哪一个 包含 ASCII C 一串 "constantSelector:test:" .
    • 然后执行 sel_registerName("constantSelector:test:") 并将返回值存储在 l_objc_选择器_references_5 . 链接器, 知道私有实施细节, 可能不呼叫 sel_registerName() 字面意思是。

    实际上,对于我们的示例,链接器在加载时执行此操作:

    L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");
    

    这就是为什么 “初始值设定项元素不是常量” -初始值设定项元素在编译时必须是常量。在程序开始执行之前,该值实际上是未知的。即使这样,你的 struct 声明存储在另一个链接器节中, .data 部分。链接器只知道如何更新 SEL 中的值 .objc_消息参考 部分,并且无法“复制”运行时计算的 塞尔 价值从 .objc_消息参考 到某个任意位置 数据 .

    这个 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_选择器_references_5 包含完全相同的值,如果要调用 sel_registername(“constantselector:测试:”) :

    theSelector = sel_registerName("constantSelector:test:");
    

    不同的是,这是一个函数调用,如果已经注册了选择器,那么函数需要实际完成查找选择器的工作,或者执行分配新的选择器的过程。 选择 用于注册选择器的值。这比加载一个常量要慢得多。虽然这是“慢”,但它允许您传递一个任意的 C 字符串。如果:

    • 选择器在编译时未知。
    • 选择器直到前面才知道 选择寄存器名称() 被称为。
    • 您需要在运行时动态地改变选择器。

    所有选择器都需要通过 选择寄存器名称() ,分别注册 塞尔 就一次。这样做的好处是,对于任何给定的选择器,在任何地方都只有一个值。尽管是实现的私有细节, 塞尔 “通常”只是 char * 指向选择器副本的指针 C 字符串文本。

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

        2
  •  4
  •   Alex Reynolds    15 年前

    怎么样:

    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 .

        3
  •  -1
  •   NSResponder    15 年前

    看起来你在重新设计nscell。如果要实现菜单,为什么不使用现有的UI类?