Risposta breve:
- È possibile inviare qualsiasi
NSObject
metodo istanza (come respondsToSelector:
o init
) al NSObject
classe, 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 NSString
di 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 NSObject
ha 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.
Risposta molto bella! –