2009-09-11 14 views
7

Mi mancava di associare una serie di rettangoli con le azioni corrispondenti, così ho cercato di fareposso inserire un selettore C obiettivo in una struct?

struct menuActions { 
    CGRect rect; 
    SEL action; 
}; 

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

ma ottengo l'errore "elemento inizializzatore non è costante". C'è qualche ragione per cui ciò che sto cercando di fare non è permesso in generale, o non è consentito a livello globale, o ho qualche tipo di errore di punteggiatura minore?

+2

Non so perché '@ selector' non è costante, ma se si può mettere l'inizializzazione in una funzione non ci si deve preoccupare di ciò. – Amok

+0

Non volevo scrivere codice come alcuniMenuRects [0] .action = @doSomething, perché allora potrei anche fare la stessa cosa in fase di runtime, cioè se (CGRectContainsPoint (someMenuRects [0], pt)) {[self doSomething]} –

+2

Un selettore non è costante perché il valore non è realmente determinato fino a molto all'inizio del runtime. Pertanto, puoi inserire una stringa e fare una ricerca in fase di esecuzione, se lo desideri. – bbum

risposta

23

Questa risposta è il motivo per cui "initializer element is not constant".

Dato il seguente esempio:

SEL theSelector; // Global variable 

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

compila a qualcosa di simile per la i386 architettura:

.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 

questo componente definisce due 'variabili' locali (in termini di codice assembly) (in realtà etichette), L_OBJC_METH_VAR_NAME_4 e L_OBJC_SELECTOR_REFERENCES_5. Il testo .objc_meth_var_names e .objc_message_refs, appena prima delle etichette 'variabile', dice all'assemblatore quale sezione del file oggetto inserisce "le cose che seguono". Le sezioni sono significative per il linker. L_OBJC_SELECTOR_REFERENCES_5 viene inizialmente impostato all'indirizzo L_OBJC_METH_VAR_NAME_4.

Al momento del caricamento di esecuzione, prima che il programma inizia l'esecuzione, il linker fa qualcosa circa come questo:

  • iterazioni sulle ogni voce nella sezione .objc_message_refs .
  • Ogni voce è inizialmente impostata su un puntatore a una stringa 0 terminata C.
  • Nel nostro esempio, il puntatore viene inizialmente impostato l'indirizzo di L_OBJC_METH_VAR_NAME_4, che contiene la stringa "constantSelector:test:"ASCIIC.
  • Quindi esegue sel_registerName("constantSelector:test:") e memorizza il valore restituito a L_OBJC_SELECTOR_REFERENCES_5. Il linker che conosce i dettagli di implementazione privata, non può chiamare letteralmente .

Essenzialmente il linker esegue questo al tempo di caricamento per il nostro esempio:

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

Ecco perché la "initializer element is not constant" - elemento dell'inizializzatore deve essere costante in fase di compilazione. Il valore non è noto fino a quando il programma non inizia l'esecuzione. Anche in questo caso, le tue dichiarazioni struct vengono memorizzate in una sezione diversa del linker, la sezione .data. Il linker sa solo come aggiornare i valori SEL nella sezione .objc_message_refs e non è possibile "copiare" il valore di runtime calcolato SEL da .objc_message_refs in una posizione arbitraria in .data.

Il codice sorgente di C ...

theSelector = @selector(constantSelector:test:); 

... diventa:

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; 

Dal momento che il linker fa tutto il suo lavoro prima che il programma è in esecuzione, L_OBJC_SELECTOR_REFERENCES_5 contiene lo stesso valore esatto otterrebbe se si dovesse chiamare sel_registerName("constantSelector:test:"):

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

La differenza è che si tratta di una chiamata di funzione e che la funzione deve eseguire il lavoro effettivo per trovare il selettore se è già stato registrato oppure passare attraverso il processo di allocazione di un nuovo valore SEL per registrare il selettore. Questo è molto più lento che basta caricare un valore costante. Sebbene questo sia 'più lento', ti consente di passare una stringa arbitraria C. Questo può essere utile se:

  • Il selettore non è noto al momento della compilazione.
  • Il selettore non è noto fino a poco prima che venga chiamato .
  • È necessario variare il selettore in modo dinamico in fase di esecuzione.

Tutti i selettori devono passare attraverso , che registra ogni SEL esattamente una volta. Questo ha il vantaggio di avere esattamente un valore, ovunque, per ogni dato selettore. Sebbene sia un dettaglio privato dell'implementazione, SEL è "di solito" solo un puntatore char * a una copia dei selettori C testo stringa.

Ora lo sai. E sapere è metà della battaglia!

+1

+1. Bella risposta. –

+0

grazie per i dettagli. Stavo partendo dal presupposto che sel_registerName fosse successo al momento della compilazione/collegamento, piuttosto che al momento del caricamento, per un selettore costante ... quindi suppongo che sel_registerName sia davvero "più lento" se lo chiami> 1 volta ... –

+0

Wow .. non ho capito la metà, ma sembra che tu sappia di cosa stai parlando) +1 –

4

ne dite:

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

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

In fase di esecuzione, registrare i selettori:

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

uscita:

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

più su al Objective-C 2.0 Runtime Reference.

+0

+1 per intelligenza –

+0

sì, ero a conoscenza di sel_registerName, ma di nuovo in linea di principio sembra sbagliato fare questo passo in fase di esecuzione quando è veramente determinato in fase di compilazione. forse la risposta alla mia domanda è in realtà solo "no, non puoi", e dovrei fare un'altra domanda come "qual è il modo migliore per costruire una tabella di distribuzione delle funzioni in Objective-C" ... –

+1

Non intendevo per assumere l'ignoranza da parte vostra. È una domanda interessante, ma non conosco la risposta giusta. –

-1

Sembra che tu stia reinventando NSCell qui. Se vuoi implementare un menu, perché non utilizzare le classi dell'interfaccia utente esistenti?

+1

l'iPhone ha NSCell? –