Kann ich in einer Struktur einen Objective-C-Selektor?
-
06-07-2019 - |
Frage
Ich will eine Reihe von Rechtecken assoziieren mit Aktionen entsprechen, also versuchte ich,
zu tunstruct 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 ist nicht konstant“. Gibt es einen Grund, dass das, was ich versuche nicht, in der Regel erlaubt zu tun ist, oder überhaupt nicht globalen Bereich erlaubt, oder muss ich eine Art von kleineren Zeichensetzung Fehler haben?
Lösung
Diese Antwort ist, warum "initializer element is not constant"
.
Da das folgende Beispiel:
SEL theSelector; // Global variable
void func(void) {
theSelector = @selector(constantSelector:test:);
}
Kompiliert zu so etwas wie dies 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
Dieser Teil definiert zwei lokal (in Bezug auf dem Assembler-Code) 'Variablen' (eigentlich Etikett), L_OBJC_METH_VAR_NAME_4
und L_OBJC_SELECTOR_REFERENCES_5
. Der Text .objc_meth_var_names
und .objc_message_refs
, kurz vor den ‚Variable‘ Etikett, erzählen die Assembler, den Abschnitt der Objektdatei „das Zeug, das folgt“ zu setzen. Die Abschnitte sind sinnvoll, um den Linker. L_OBJC_SELECTOR_REFERENCES_5
wird zunächst an die Adresse von L_OBJC_METH_VAR_NAME_4
eingestellt.
Bei der Ausführung der Ladezeit, bevor das Programm die Ausführung beginnt, hat der Linker etwas ungefähr wie folgt aus:
- Iteriert über jeden Eintrag in der
.objc_message_refs
Abschnitt. - Jeder Eintrag anfänglich auf einen Zeiger auf einen
0
beendetC
Zeichenfolge festgelegt. - In unserem Beispiel wird der Zeiger zunächst auf die eingestellte
Adresse des
L_OBJC_METH_VAR_NAME_4
, die enthält dieASCII
C
Zeichenfolge"constantSelector:test:"
. - Es führt dann
sel_registerName("constantSelector:test:")
und speichert den zurückgegebenen Wert anL_OBJC_SELECTOR_REFERENCES_5
. Der Linker, was weiß private Implementierungsdetails, kann nicht wörtlich nennensel_registerName()
.
Im Wesentlichen führt der Linker dieses zur Ladezeit für unser Beispiel:
L_OBJC_SELECTOR_REFERENCES_5 = sel_registerName("constantSelector:test:");
Aus diesem Grunde ist das "initializer element is not constant"
- das initializer Element bei der Kompilierung konstant sein muss. Der Wert wird nicht wirklich bekannt, bis das Programm die Ausführung beginnt. Selbst dann sind Ihre struct
Erklärungen in einem anderen Linkerabschnitt gespeichert, der .data
Abschnitt. Der Linker nur weiß, wie SEL
Werte im .objc_message_refs
Abschnitt zu aktualisieren, und es gibt keine Möglichkeit, ‚Kopie‘ diese Laufzeit berechnet SEL
Wert von .objc_message_refs
an eine beliebige Stelle in .data
.
Die 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 tut all seine Arbeit, bevor das Programm ausgeführt wird, L_OBJC_SELECTOR_REFERENCES_5
enthält den exakt gleichen Wert, den Sie erhalten würden, wenn Sie sel_registerName("constantSelector:test:")
nennen sind:
theSelector = sel_registerName("constantSelector:test:");
Der Unterschied ist dies ein Funktionsaufruf ist, und die Funktion muss die eigentliche Arbeit des Findens der Wähler tun, wenn seine bereits registriert, oder der Prozess durchlaufen einen neuen SEL
Wert der Verteilung der Wähler zu registrieren. Das ist deutlich langsamer, die nur einen konstanten Wert zu laden. Obwohl diese ‚langsamer‘ ist, tut es erlauben Sie einen beliebigen C
Zeichenfolge zu übergeben. Dies kann nützlich sein, wenn:
- Der Wähler wird bei der Kompilierung nicht bekannt.
- Der Wähler ist bekannt, erst kurz vor
sel_registerName()
genannt wird. - Sie müssen die Wähler zur Laufzeit dynamisch verändern.
müssen alle Selektoren durch sel_registerName()
passieren, die jede SEL
genau einmal registriert. Dies hat den Vorteil, genau einen Wert, überall, für jeden gegebenen Wähler. Obwohl eine Implementierung privaten Detail ist SEL
„in der Regel“ nur ein char *
Zeiger auf eine Kopie der Selektoren C
Zeichenfolge Text.
Jetzt wissen Sie. Und zu wissen, ist das halbe Leben!
Andere Tipps
Wie wäre:
struct menuActions {
CGRect rect;
const char *action;
};
struct menuActions someMenuRects[] = {
{ { { 0, 0 }, {320, 60 } }, "doSomething" },
{ { { 0, 60}, {320, 50 } }, "doSomethingElse" },
};
Zur Laufzeit registrieren die Wähler:
int numberOfActions = 2;
for (int i=0; i < numberOfActions; i++)
NSLog (@"%s", sel_registerName(someMenuRects[i].action));
Ausgabe:
[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()
an der Objective-C 2.0 Runtime Referenz .
Sieht aus wie Sie NSCell sind neu zu erfinden hier. Wenn Sie ein Menü implementieren möchten, warum nicht UI-Klassen verwenden bestehende?