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?
Structs are a C construct. The compiler is telling you, in very unambiguous terms, that you can't have Objective-C objects inside a struct, not that structs are illegal.
In the code above, SEL type in objective-c is a pointer to struct objc_selector .
A selector is an identifier which represents the name of a method. It is not related to any specific class or method, and can be used to describe a method of any class, whether it is a class or instance method. Simply, a selector is like a key in a dictionary.
Use Selectors to Arrange Calls to Objective-C Methods In Objective-C, a selector is a type that refers to the name of an Objective-C method. In Swift, Objective-C selectors are represented by the Selector structure, and you create them using the #selector expression.
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:
.objc_message_refs
section.0
terminated C
string.L_OBJC_METH_VAR_NAME_4
, which
contains the ASCII
C
string
"constantSelector:test:"
.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:
sel_registerName()
is called.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!
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With