2014-05-16 20 views
7

Perché l'esecuzione respondsToSelector su NSObject con il selettore "init" restituisce 1 anche se l'esecuzione di [NSObject init] dà un errore di runtime? So che init è un metodo di istanza e quindi funziona solo su istanze e non su classi. Perché questo restituisce un errore di runtime?Perché [NSObject respondsToSelector: @selector (init)] restituisce 1?

if([NSObject respondsToSelector: @selector(init)] == YES) 
    [NSObject performSelector: @selector(init)]; 

Inoltre, poiché respondsToSelector è un metodo di istanza, perché è anche possibile richiamare in primo luogo?

risposta

11

Risposta breve:

  • È possibile inviare qualsiasi NSObjectmetodo istanza (come respondsToSelector: o init) al NSObjectclasse, oa qualsiasi classe che eredita da NSObject.
  • [NSObject init] viene sovrascritto in CoreFoundation e genera un'eccezione di runtime (per i binari collegati su OS X 10.6 o successivo).

lungo Risposta:

Cominciamo con la tua ultima domanda:

Inoltre, dal momento che respondsToSelector è un metodo di istanza, perché è anche possibile chiamare in primo luogo?

respondsToSelector: è un metodo di istanza del protocollo NSObject, ai quali le NSObject conforme classe. Ora la classe NSObject (e ognuna delle sue sottoclassi) è un oggetto e un'istanza di una sottoclasse della classe radice NSObject.

Questo è spiegato ed illustrato nell'articolo di Greg Parker [objc explain]: Classes and metaclasses (corsivo):

Più importante è la superclasse di un metaclasse. La catena della superclasse di metaclass è parallela alla catena della superclasse della classe , quindi i metodi di classe vengono ereditati in parallelo con i metodi di istanza. E la superclasse della metaclass radice è la classe radice, , quindi ogni oggetto classe risponde ai metodi di istanza della classe radice. Alla fine, un oggetto classe è un'istanza di (una sottoclasse di) la classe radice, proprio come qualsiasi altro oggetto.

Questo spiega perché è possibile inviare respondsToSelector: alla classe NSObject affatto. Il valore restituito è YES se esiste un metodo di classe con il selettore specificato.

Ecco un altro esempio:

NSString *path = [NSString performSelector:@selector(pathWithComponents:) withObject:@[@"foo", @"bar"]]; 

performSelector:withObject: è un metodo esempio di NSObject, e si può inviare questo messaggio al NSStringdi classe. In questo caso, il risultato è lo stesso di

NSString *path = [NSString pathWithComponents:@[@"foo", @"bar"]; 

Ora alla tua domanda iniziale:

Perché in esecuzione respondsToSelector sul NSObject con il selettore "init" return 1 anche se in esecuzione [NSObject init] dà un errore di runtime?

Con lo stesso ragionamento di cui sopra, deve essere possibile inviare il messaggio a qualsiasi init classe che deriva da NSObject.

Ora NSObjectha un metodo classe init, che è documentata per generare un'eccezione in fase di esecuzione, vedere http://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm:

// Replaced by CF (throws an NSException) 
+ (id)init { 
    return (id)self; 
} 

Questo spiega perché

[NSObject respondsToSelector:@selector(init)] == YES 

Il commento in NSObject .mm afferma che +init viene sovrascritto in CoreFoundation, e infatti, quando viene generata l'eccezione, la traccia dello stack è

(lldb) bt 
* thread #1: tid = 0x6eda, 0x01f69952 libsystem_kernel.dylib`__pthread_kill + 10, queue = 'com.apple.main-thread', stop reason = signal SIGABRT 
    .... 
    frame #8: 0x0156b9fc libobjc.A.dylib`objc_exception_throw + 323 
    frame #9: 0x01889931 CoreFoundation`+[NSObject(NSObject) init] + 241 
    * frame #10: 0x00002a51 foo`main(argc=1, argv=0xbfffee30) + 257 at main.m:25 

quindi questo metodo CoreFoundation è responsabile per l'eccezione.


Non so perché il metodo init viene sovrascritto in CoreFoundation (invece di lanciare un'eccezione direttamente NSObject), e il metodo di sostituzione non sembra essere far parte del repository Open Source http://opensource.apple.com/source/CF/CF-855.14/ (almeno non ho potuto trovarlo lì).

Ma un punto interessante potrebbe essere che il comportamento osservato è cambiato tra le versioni del sistema operativo. Se

id x = [NSObject init]; 

è compilato su OS X 10.5, allora funziona e restituisce un oggetto di classe NSObject. Funziona anche se il file binario è stato compilato e collegato su OS X 10.5 e viene eseguito in una versione successiva come come OS X 10.9.

Questo può essere visto anche dal codice assembly di

CoreFoundation`+[NSObject(NSObject) init]: 
0x1019d8ad0: pushq %rbp 
0x1019d8ad1: movq %rsp, %rbp 
0x1019d8ad4: pushq %rbx 
0x1019d8ad5: pushq %rax 
0x1019d8ad6: movq %rdi, %rbx 
0x1019d8ad9: movl $0x6, %edi 
0x1019d8ade: callq 0x1018dfcc0    ; _CFExecutableLinkedOnOrAfter 
0x1019d8ae3: testb %al, %al 
0x1019d8ae5: jne 0x1019d8af1    ; +[NSObject(NSObject) init] + 33 
0x1019d8ae7: movq %rbx, %rax 
0x1019d8aea: addq $0x8, %rsp 
0x1019d8aee: popq %rbx 
0x1019d8aef: popq %rbp 
0x1019d8af0: ret  
0x1019d8af1: movq %rbx, %rdi 
0x1019d8af4: callq 0x101a19cee    ; symbol stub for: class_getName 
0x1019d8af9: leaq 0x9fe80(%rip), %rcx  ; kCFAllocatorSystemDefault 
0x1019d8b00: movq (%rcx), %rdi 
0x1019d8b03: leaq 0xc5a66(%rip), %rdx  ; @"*** +[%s<%p> init]: cannot init a class object." 
... 
0x1019d8b72: callq *0x9e658(%rip)   ; (void *)0x00000001016b9fc0: objc_msgSend 
0x1019d8b78: movq %rax, %rdi 
0x1019d8b7b: callq 0x101a19d4e    ; symbol stub for: objc_exception_throw 

_CFExecutableLinkedOnOrAfter() (da http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h) controlla se il binario è stato collegato su OS X 10.6 o versioni successive, e genera un'eccezione in quel caso.

Quindi chiamando init sul oggetto di classe deve essere stato concesso in versioni precedenti del sistema operativo, e CoreFoundation permette ancora in "vecchi binari" per la compatibilità a ritroso.

+0

Risposta molto bella! –

1

respondsToSelector: è anche un metodo di classe di NSProxy e tale metodo accetta una classe come ricevitore. Dalla documentazione NSProxy,

respondsToSelector: 
Returns a Boolean value that indicates whether the receiving class responds to a given selector. 

+ (BOOL)respondsToSelector:(SEL)aSelector 
Parameters 
aSelector 
A selector. 
Return Value 
YES if the receiving class responds to aSelector messages, otherwise NO. 
1

del NSObject respondsToSelector è implementato internamente come:

+ (BOOL)respondsToSelector:(SEL)aSelector { 
    return class_respondsToSelector([self class], aSelector); 
} 

Ciò indica solo se istanze di NSObject rispondono al selettore menzionato. Varie classi sovrascrivono questo metodo per avere la propria implementazione. Ad esempio, NSProxy sovrascrive questo metodo per fornire la propria implementazione.

+0

ma cosa sta chiamando il metodo di quella classe? uno sguardo all'intestazione di NSObject mostra "- (BOOL) rispondeSelettore: (SEL) aSelector;" che è un metodo di istanza –

+0

Il metodo di classe non è documentato in NSObject. –

+0

Se non esisteva un metodo di classe respondsToSelector, avrebbe generato un'eccezione quando si tenta di richiamarlo su una classe. Poiché esiste un metodo di classe non documentato con lo stesso nome, viene eseguito correttamente –

Problemi correlati