7 Stimmen

Kann ich einen Objective-C-Selektor in eine Struktur einfügen?

Ich wollte eine Reihe von Rechtecken mit entsprechenden Aktionen verknüpfen, also habe ich versucht, Folgendes zu tun

struct menuActions {
    CGRect rect;
    SEL action;
};

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

aber ich erhalte die Fehlermeldung "initializer element is not constant". Gibt es irgendeinen Grund, dass das, was ich zu tun versuche, im Allgemeinen nicht erlaubt ist, oder ist nicht im globalen Bereich erlaubt, oder habe ich eine Art von kleinen Interpunktionsfehler?

23voto

johne Punkte 6750

Diese Antwort lautet: warum "initializer element is not constant" .

Das folgende Beispiel:

SEL theSelector; // Global variable

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

Kompiliert zu etwas wie diesem für die i386 Architektur:

  .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

In diesem Teil werden zwei lokale (im Sinne des Assembler-Codes) "Variablen" (eigentlich Bezeichnungen) definiert, L_OBJC_METH_VAR_NAME_4 y L_OBJC_SELECTOR_REFERENCES_5 . Der Text .objc_meth_var_names y .objc_message_refs kurz vor den "Variablen"-Labels, teilt dem Assembler mit, in welchen Abschnitt der Objektdatei "das Folgende" eingefügt werden soll. Die Abschnitte sind für den Linker von Bedeutung. L_OBJC_SELECTOR_REFERENCES_5 wird zunächst auf die Adresse von L_OBJC_METH_VAR_NAME_4 .

Zum Zeitpunkt des Ladens der Ausführung, bevor das Programm mit der Ausführung beginnt, macht der Linker ungefähr Folgendes:

  • Iteriert über jeden Eintrag in der Datei .objc_message_refs Abschnitt.
  • Jeder Eintrag wird zunächst auf einen Zeiger auf eine 0 gekündigt C String.
  • In unserem Beispiel wird der Zeiger zunächst auf den Adresse von L_OBJC_METH_VAR_NAME_4 die enthält die ASCII C String "constantSelector:test:" .
  • Sie führt dann sel_registerName("constantSelector:test:") und speichert den zurückgegebenen Wert unter L_OBJC_SELECTOR_REFERENCES_5 . Der Linker, der private Implementierungsdetails kennt, darf nicht aufrufen sel_registerName() buchstäblich.

In unserem Beispiel führt der Linker dies im Wesentlichen zur Ladezeit durch:

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

Aus diesem Grund ist die "initializer element is not constant" - das Initialisierungselement muss zur Kompilierzeit konstant sein. Der Wert ist erst dann bekannt, wenn das Programm ausgeführt wird. Selbst dann muss Ihr struct Deklarationen in einem anderen Linker-Abschnitt gespeichert sind, wird die .data Abschnitt. Der Linker weiß nur, wie er die SEL Werte in der .objc_message_refs Abschnitt, und es gibt keine Möglichkeit, diese zur Laufzeit berechnete SEL Wert von .objc_message_refs an eine beliebige Stelle in .data .

Les C Quellcode...

theSelector = @selector(constantSelector:test:);

... wird:

  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;

Da der Linker seine gesamte Arbeit erledigt, bevor das Programm ausgeführt wird, L_OBJC_SELECTOR_REFERENCES_5 enthält genau denselben Wert, den Sie erhalten würden, wenn Sie sel_registerName("constantSelector:test:") :

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

Der Unterschied besteht darin, dass es sich um einen Funktionsaufruf handelt und die Funktion die eigentliche Arbeit erledigen muss, um den Selektor zu finden, wenn er bereits registriert wurde, oder den Prozess der Zuweisung eines neuen SEL Wert, um den Selektor zu registrieren. Dies ist wesentlich langsamer als das Laden eines konstanten Wertes. Obwohl dies 'langsamer' ist, erlaubt es Ihnen, eine beliebige C String. Dies kann nützlich sein, wenn:

  • Der Selektor ist zur Kompilierzeit nicht bekannt.
  • Der Selektor ist erst kurz vor der sel_registerName() genannt wird.
  • Sie müssen den Selektor während der Laufzeit dynamisch ändern.

Alle Selektoren müssen durch sel_registerName() die jedes SEL genau einmal. Dies hat den Vorteil, dass für jeden beliebigen Selektor überall genau ein Wert vorhanden ist. Dies ist jedoch ein privates Detail der Implementierung, SEL ist "normalerweise" nur ein char * Zeiger auf eine Kopie der Selektoren C String-Text.

Jetzt wissen Sie es. Und Wissen ist die halbe Miete!

4voto

Alex Reynolds Punkte 93906

Wie wäre es damit:

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

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

Registrieren Sie zur Laufzeit die Selektoren:

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

Sortie :

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

Mehr über sel_registerName() am Objective-C 2.0 Laufzeitreferenz .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X