2014-05-16 5 views
7

Warum reagiert running_ToSelector auf NSObject mit dem Selektor "init" 1, obwohl das Ausführen von [NSObject init] einen Laufzeitfehler gibt? Ich weiß, dass init eine Instanzmethode ist und daher nur auf Instanzen und nicht auf Klassen ausgeführt wird. Warum gibt das einen Laufzeitfehler zurück?Warum gibt [NSObject antwortetToSelector: @selector (init)] 1 zurück?

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

Da ferner respondsToSelector eine Instanzmethode ist, warum ist es sogar möglich, es in erster Linie zu nennen?

Antwort

11

Kurze Antwort:

  • Sie können (wie respondsToSelector: oder init) an die NSObjectKlasse, oder auf jede Klasse jede NSObjectInstanzmethode senden, die von NSObject erbt.
  • [NSObject init] ist in CoreFoundation überschrieben und löst eine Laufzeitausnahme (für Binärdateien auf OS X 10.6 oder höher verknüpft).

Lange Antwort:

die mit Ihrer letzten Frage Beginnen wir:

Da darüber hinaus respondsToSelector ist eine Instanzmethode, warum ist es sogar möglich, es in erster Linie zu nennen?

respondsToSelector: ist eine Instanzmethode des NSObject Protokolls, zu der die NSObject Klasse entspricht. Nun ist die NSObject Klasse (und jede ihrer Unterklassen) ein Objekt und eine Instanz einer Unterklasse der Stammklasse NSObject.

Dies wird erläutert und dargestellt in Greg Parker Artikel [objc explain]: Classes and metaclasses (Hervorhebung hinzugefügt):

Wichtiger ist die übergeordnete Klasse einer Metaklasse. Die Superklassenkette der Metaklasse entspricht der Oberklassenkette der Klasse, sodass Klassenmethoden parallel mit Instanzmethoden vererbt werden. Und die Oberklasse der Wurzelmetaklasse ist die Stammklasse , so dass jedes Klassenobjekt auf die Instanzmethoden der Stammklasse antwortet. Am Ende ist ein Klassenobjekt eine Instanz von (einer Unterklasse von) der Wurzelklasse, genau wie jedes andere Objekt.

Dies erklärt, warum Sie überhaupt respondsToSelector: zum NSObject Klasse senden. Der Rückgabewert ist YES, wenn es eine Klassenmethode mit dem angegebenen Selektor gibt.

Hier ist ein weiteres Beispiel:

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

performSelector:withObject: ist eine Instanz-Methode von NSObject, und Sie können diese Mitteilung an die NSStringKlasse senden. In diesem Fall ist das Ergebnis das gleiche wie

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

Nun zu Ihrer ersten Frage:

Warum respondsToSelector auf NSObject mit dem Selektor „init“, obwohl läuft zurückkehren 1 selbst dann nicht läuft [NSObject init] gibt einen Laufzeitfehler?

Mit der gleichen Argumentation wie oben, muss es möglich sein, die init Nachricht an all Klasse zu senden, die von NSObject abgeleitet ist.

Jetzt NSObjecthat eine Klassenmethode init, die dokumentiert wird, um eine Ausnahme zur Laufzeit zu werfen, sehen http://opensource.apple.com/source/objc4/objc4-532.2/runtime/NSObject.mm:

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

Dies erklärt, warum

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

Der Kommentar in NSObject .mm gibt an, dass +init in CoreFoundation und in der Tatüberschrieben wirdwenn die Ausnahme ausgelöst wird, der Stapel Backtrace ist

(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 

so dass diese Methode für Corefoundation Ausnahme zuständig ist.


Ich weiß nicht, warum die init Methode in Corefoundation außer Kraft gesetzt wird (statt eine Ausnahme direkt in NSObject werfen) und die ersetzt Verfahren Teil der Open Source-Repository zu sein scheinen nicht http://opensource.apple.com/source/CF/CF-855.14/ zu sein (Zumindest konnte ich es dort nicht finden).

Aber ein interessanter Punkt kann sein, dass das beobachtete Verhalten zwischen OS-Versionen geändert hat. Wenn

id x = [NSObject init]; 

auf OS X 10.5 kompiliert wird, dann funktioniert es und gibt ein NSObject Klassenobjekt. Dies funktioniert auch, wenn die Binärdatei unter OS X 10.5 kompiliert und verknüpft wurde und auf einer späteren Version wie als OS X 10.9 ausgeführt wird.

Dies kann auch aus dem Assembler-Code von

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() (von http://www.opensource.apple.com/source/CF/CF-476.14/CFPriv.h) prüft, ob die Binärdatei auf OS zu sehen X 10.6 oder höher in Verbindung gebracht wurden, und löst eine Ausnahme in diesem Fall.

So init auf das Klassenobjekt aufrufen müssen in früheren OS Versionen erlaubt worden, und Corefoundation erlaubt es immer noch in „alten Binärdateien“ für die Abwärtskompatibilität.

+0

Sehr nette Antwort! –

1

AntwortToSelector: ist auch eine Klassenmethode von NSProxy, und diese Methode nimmt eine Klasse als Empfänger. Aus der NSProxy docs,

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

Die respondsToSelector der NSObject intern implementiert als:

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

Dies zeigt nur, wenn Instanzen von NSObject auf die erwähnten Wähler reagieren. Verschiedene Klassen überschreiben diese Methode, um ihre eigene Implementierung zu haben. Zum Beispiel überschreibt NSProxy diese Methode, um eine eigene Implementierung zu erhalten.

+0

aber was ruft die Klassenmethode? ein Blick auf den NSObject-Header zeigt "- (BOOL) anantwortenToSelector: (SEL) aSelector;" Dies ist eine Instanzmethode. –

+0

Die Klassenmethode ist in NSObject nicht dokumentiert. –

+0

Wenn es keine Klassenmethode readstToSelector gab, hätte es beim Versuch, es in einer Klasse aufzurufen, eine Ausnahme ausgelöst. Da es eine undokumentierte Klassenmethode mit demselben Namen gibt, wird sie erfolgreich ausgeführt –

Verwandte Themen