views:

388

answers:

3

I was wanting to associate a set of rectangles with corresponding actions, so I tried to do

struct menuActions {
    CGRect rect;
    SEL action;
};

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

but I get the error "initializer element is not constant". Is there some reason that what I'm trying to do isn't allowed in general, or isn't allowed at global scope, or do I have some kind of minor punctuation mistake?

+3  A: 

How about:

struct menuActions {
   CGRect rect;
   const char *action;
};

struct menuActions someMenuRects[] = {
   { { { 0, 0 }, {320, 60 } }, "doSomething" },
   { { { 0, 60}, {320, 50 } }, "doSomethingElse" },
};

At runtime, register the selectors:

int numberOfActions = 2;
for (int i=0; i < numberOfActions; i++)
   NSLog (@"%s", sel_registerName(someMenuRects[i].action));

Output:

[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)

More about sel_registerName() at the Objective-C 2.0 Runtime Reference.

Alex Reynolds
+1 for cleverness
Dave DeLong
yes, I was aware of sel_registerName, but again in principle it seems wrong to do this step at runtime when it's really determined at compile-time. perhaps the answer to my question is really just "no, you can't", and I should ask another question like "what's the best way to build a function dispatch table in Objective-C"...
David Maymudes
I didn't mean to assume ignorance on your part. It's an interesting question, but I don't know the right answer.
Alex Reynolds
sorry if I sounded offended--I'm plenty ignorant on this platform!
David Maymudes
+11  A: 

This answer is to why "initializer element is not constant".

Given the following example:

SEL theSelector; // Global variable

void func(void) {
  theSelector = @selector(constantSelector:test:);
}

Compiles to something like this for the i386 architecture:

  .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

This part defines two local (in terms of assembly code) 'variables' (actually labels), L_OBJC_METH_VAR_NAME_4 and L_OBJC_SELECTOR_REFERENCES_5. The text .objc_meth_var_names and .objc_message_refs, just before the 'variable' labels, tells the assembler which section of the object file to put "the stuff that follows". The sections are meaningful to the linker. L_OBJC_SELECTOR_REFERENCES_5 is initially set to the address of L_OBJC_METH_VAR_NAME_4.

At execution load time, before the program begins executing, the linker does something approximately like this:

  • Iterates over each entry in the .objc_message_refs section.
  • Each entry is initially set to a pointer to a 0 terminated C string.
  • In our example, the pointer is initially set to the address of L_OBJC_METH_VAR_NAME_4, which contains the ASCII C string "constantSelector:test:".
  • It then performs sel_registerName("constantSelector:test:") and stores the returned value at L_OBJC_SELECTOR_REFERENCES_5. The linker, which knows private implementation details, may not call sel_registerName() literally.

Essentially the linker performs this at load time for our example:

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

This is why the "initializer element is not constant"- the initializer element must be constant at compile time. The value is not actually known until the program begins executing. Even then, your struct declarations are stored in a different linker section, the .data section. The linker only knows how to update SEL values in the .objc_message_refs section, and there is no way to 'copy' that run-time calculated SEL value from .objc_message_refs to some arbitrary location in .data.

The C source code...

theSelector = @selector(constantSelector:test:);

... becomes:

  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;

Since the linker does all its work before the program is executing, L_OBJC_SELECTOR_REFERENCES_5 contains the exact same value you would get if you were to call sel_registerName("constantSelector:test:"):

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

The difference is this is a function call, and the function needs to do the actual work of finding the selector if its already been registered, or go through the process of allocating a new SEL value to register the selector. This is considerably slower that just loading a constant value. Though this is 'slower', it does allow you to pass an arbitrary C string. This can be useful if:

  • The selector is not known at compile time.
  • The selector is not known until just before sel_registerName() is called.
  • You need to vary the selector dynamically at run time.

All selectors need to pass through sel_registerName(), which registers each SEL exactly once. This has the advantage of having exactly one value, everywhere, for any given selector. Though an implementation private detail, SEL is "usually" just a char * pointer to a copy of the selectors C string text.

Now you know. And knowing is half the battle!

johne
+1. Great answer.
Alex Reynolds
thanks for the details. I was assuming that the sel_registerName happened at compile/link time, rather than at load time, for a constant selector... so I guess sel_registerName is really only "slower" if you call it >1 time...
David Maymudes
A: 

Looks like you're reinventing NSCell here. If you want to implement a menu, why not use existing UI classes?

NSResponder
does the iPhone have NSCell?
David Maymudes