Senden einer Nachricht an nil in Objective-C

107

Als Java-Entwickler, der Apples Objective-C 2.0-Dokumentation liest: Ich frage mich, was " Senden einer Nachricht an Null " bedeutet - geschweige denn, wie nützlich es tatsächlich ist. Ein Auszug aus der Dokumentation:

Es gibt verschiedene Muster in Kakao, die diese Tatsache ausnutzen. Der von einer Nachricht an nil zurückgegebene Wert kann auch gültig sein:

  • Wenn die Methode ein Objekt, einen beliebigen Zeigertyp, einen ganzzahligen Skalar mit einer Größe kleiner oder gleich sizeof (void *), ein float, ein double, ein long double oder ein long long zurückgibt, gibt eine an nil gesendete Nachricht 0 zurück .
  • Wenn die Methode eine Struktur zurückgibt, die im Mac OS X ABI-Funktionsaufrufhandbuch definiert ist und in Registern zurückgegeben werden soll, gibt eine an nil gesendete Nachricht für jedes Feld in der Datenstruktur 0.0 zurück. Andere Strukturdatentypen werden nicht mit Nullen gefüllt.
  • Wenn die Methode etwas anderes als die oben genannten Werttypen zurückgibt, ist der Rückgabewert einer an nil gesendeten Nachricht undefiniert.

Hat Java mein Gehirn unfähig gemacht, die obige Erklärung zu verstehen? Oder fehlt mir etwas, das dies so klar wie Glas macht?

Ich habe die Idee von Nachrichten / Empfängern in Objective-C, ich bin einfach verwirrt über einen Empfänger, der zufällig ist nil.

Ryan Delucchi
quelle
2
Ich hatte auch einen Java-Hintergrund und war am Anfang von dieser netten Funktion erschrocken, aber jetzt finde ich sie absolut SCHÖN!;
Valentin Radu
1
Danke, das ist eine gute Frage. Haben Sie die Vorteile davon durchschaut? Es scheint mir eine "kein Bug, ein Feature" Sache zu sein. Ich bekomme immer wieder Fehler, bei denen Java mich mit einer Ausnahme nur schlagen würde, sodass ich wusste, wo das Problem lag. Ich bin nicht glücklich, die Nullzeiger-Ausnahme zu tauschen, um hier und da ein oder zwei Zeilen trivialen Codes zu speichern.
Maciej Trybiło

Antworten:

92

Nun, ich denke, es kann anhand eines sehr konstruierten Beispiels beschrieben werden. Angenommen, Sie haben eine Methode in Java, mit der alle Elemente in einer ArrayList ausgedruckt werden:

void foo(ArrayList list)
{
    for(int i = 0; i < list.size(); ++i){
        System.out.println(list.get(i).toString());
    }
}

Wenn Sie diese Methode nun so aufrufen: someObject.foo (NULL); Sie werden wahrscheinlich eine NullPointerException erhalten, wenn versucht wird, auf list zuzugreifen, in diesem Fall beim Aufruf von list.size (); Jetzt würden Sie wahrscheinlich niemals someObject.foo (NULL) mit einem solchen NULL-Wert aufrufen. Möglicherweise haben Sie Ihre ArrayList jedoch von einer Methode erhalten, die NULL zurückgibt, wenn beim Generieren der ArrayList ein Fehler auftritt, z. B. someObject.foo (otherObject.getArrayList ()).

Natürlich haben Sie auch Probleme, wenn Sie so etwas tun:

ArrayList list = NULL;
list.size();

In Objective-C haben wir nun die äquivalente Methode:

- (void)foo:(NSArray*)anArray
{
    int i;
    for(i = 0; i < [anArray count]; ++i){
        NSLog(@"%@", [[anArray objectAtIndex:i] stringValue];
    }
}

Nun, wenn wir den folgenden Code haben:

[someObject foo:nil];

Wir haben die gleiche Situation, in der Java eine NullPointerException erzeugt. Auf das nil-Objekt wird zuerst mit [anArray count] zugegriffen. Anstatt jedoch eine NullPointerException auszulösen, gibt Objective-C gemäß den obigen Regeln einfach 0 zurück, sodass die Schleife nicht ausgeführt wird. Wenn wir die Schleife jedoch so einstellen, dass sie eine festgelegte Anzahl von Malen ausgeführt wird, senden wir zuerst eine Nachricht an anArray unter [anArray objectAtIndex: i]; Dies gibt auch 0 zurück, aber da objectAtIndex: einen Zeiger zurückgibt und ein Zeiger auf 0 nil / NULL ist, wird NSLog jedes Mal nil durch die Schleife übergeben. (Obwohl NSLog eine Funktion und keine Methode ist, wird es ausgegeben (null), wenn kein NSString übergeben wird.

In einigen Fällen ist es besser, eine NullPointerException zu haben, da Sie sofort feststellen können, dass etwas mit dem Programm nicht stimmt. Wenn Sie jedoch die Ausnahme nicht abfangen, stürzt das Programm ab. (In C führt der Versuch, NULL auf diese Weise zu dereferenzieren, zum Absturz des Programms.) In Objective-C verursacht es stattdessen möglicherweise nur ein falsches Laufzeitverhalten. Wenn Sie jedoch eine Methode haben, die nicht kaputt geht, wenn sie 0 / nil / NULL / eine auf Null gesetzte Struktur zurückgibt, müssen Sie nicht überprüfen, ob das Objekt oder die Parameter Null sind.

Michael Buckley
quelle
33
Es ist wahrscheinlich erwähnenswert, dass dieses Verhalten in den letzten Jahrzehnten in der Objective-C-Community viel diskutiert wurde. Der Kompromiss zwischen "Sicherheit" und "Bequemlichkeit" wird von verschiedenen Personen unterschiedlich bewertet.
Mark Bessey
3
In der Praxis besteht eine große Symmetrie zwischen der Übergabe von Nachrichten an nil und der Funktionsweise von Objective-C, insbesondere bei der neuen Funktion für schwache Zeiger in ARC. Schwache Zeiger werden automatisch auf Null gesetzt. Entwerfen Sie
Ihre
1
Ich denke, wenn Sie dies tun myObject->iVar, wird es abstürzen, unabhängig davon, ob es C mit oder ohne Objekte ist. (Entschuldigung zu
gravedig
3
@ 11684 Das ist richtig, aber ->keine Objective-C-Operation mehr, sondern ein generischer C-Ismus.
bbum
1
Die aktuelle OSX-Root-Exploit- / Hidden-Backdoor-API ist für alle Benutzer (nicht nur für Administratoren) aufgrund der Nil-Nachrichten von obj-c zugänglich.
dcow
51

Eine Nachricht an die niltut nichts und kehrt nil, Nil, NULL, 0, oder 0.0.

Peter Hosey
quelle
41

Alle anderen Beiträge sind korrekt, aber vielleicht ist es das Konzept, das hier wichtig ist.

In Objective-C-Methodenaufrufen ist jede Objektreferenz, die einen Selektor akzeptieren kann, ein gültiges Ziel für diesen Selektor.

Dies spart eine Menge von "Ist das Zielobjekt vom Typ X?" Code - Solange das empfangende Objekt den Selektor implementiert, spielt es absolut keine Rolle, um welche Klasse es sich handelt! nilist ein NSObject, das jeden Selektor akzeptiert - es macht einfach nichts. Dadurch entfällt auch viel Code "Auf Null prüfen, Nachricht nicht senden, wenn wahr". (Das Konzept "Wenn es es akzeptiert, implementiert es es" ermöglicht es Ihnen auch, Protokolle zu erstellen , die Java-Schnittstellen ähneln: eine Erklärung, dass eine Klasse, die die angegebenen Methoden implementiert, dem Protokoll entspricht.)

Der Grund dafür ist, Affencode zu eliminieren, der nichts anderes tut, als den Compiler bei Laune zu halten. Ja, Sie erhalten den Overhead eines weiteren Methodenaufrufs, sparen jedoch Programmiererzeit , was eine weitaus teurere Ressource als die CPU-Zeit ist. Darüber hinaus eliminieren Sie mehr Code und mehr bedingte Komplexität aus Ihrer Anwendung.

Klarstellung für Downvoter: Sie denken vielleicht, dass dies kein guter Weg ist, aber es ist, wie die Sprache implementiert wird, und es ist die empfohlene Programmiersprache in Objective-C (siehe die Stanford iPhone-Programmiervorträge).

Joe McMahon
quelle
17

Dies bedeutet, dass die Laufzeit keinen Fehler erzeugt, wenn objc_msgSend für den Nullzeiger aufgerufen wird. Stattdessen wird ein (oft nützlicher) Wert zurückgegeben. Nachrichten, die einen Nebeneffekt haben könnten, bewirken nichts.

Dies ist nützlich, da die meisten Standardwerte besser geeignet sind als ein Fehler. Beispielsweise:

[someNullNSArrayReference count] => 0

Dh nil scheint das leere Array zu sein. Das Ausblenden einer Null-NSView-Referenz führt zu nichts. Praktisch, was?

Reich
quelle
12

Im Zitat aus der Dokumentation gibt es zwei getrennte Konzepte - vielleicht ist es besser, wenn die Dokumentation dies klarer macht:

Es gibt verschiedene Muster in Kakao, die diese Tatsache ausnutzen.

Der von einer Nachricht an nil zurückgegebene Wert kann auch gültig sein:

Ersteres ist hier wahrscheinlich relevanter: Wenn Sie normalerweise Nachrichten an senden können, nilwird der Code einfacher - Sie müssen nicht überall nach Nullwerten suchen. Das kanonische Beispiel ist wahrscheinlich die Zugriffsmethode:

- (void)setValue:(MyClass *)newValue {
    if (value != newValue) { 
        [value release];
        value = [newValue retain];
    }
}

Wenn das Senden von Nachrichten an nilnicht gültig war, wäre diese Methode komplexer - Sie würden zwei zusätzliche Kontrollen haben müssen , um sicherzustellen , valueund newValuesind nicht nilvor ihnen Nachrichten zu senden.

Der letztere Punkt (die von einer Nachricht an zurückgegebenen Werte nilsind normalerweise ebenfalls gültig) fügt dem ersteren jedoch einen Multiplikatoreffekt hinzu. Beispielsweise:

if ([myArray count] > 0) {
    // do something...
}

Dieser Code erfordert wiederum keine Überprüfung auf nilWerte und fließt natürlich ...

Die zusätzliche Flexibilität, an die Nachrichten gesendet werden können, ist niljedoch mit einigen Kosten verbunden. Es besteht die Möglichkeit, dass Sie irgendwann Code schreiben, der auf besondere Weise fehlschlägt, weil Sie die Möglichkeit eines Werts nicht berücksichtigt haben nil.

mmalc
quelle
12

Von Greg Parker ‚s - Website :

Wenn Sie LLVM Compiler 3.0 (Xcode 4.2) oder höher ausführen

Nachrichten an Null mit Rückgabetyp | Rückkehr
Ganzzahlen bis zu 64 Bit | 0
Gleitkomma bis zum langen Doppel | 0.0
Zeiger | Null
Strukturen | {0}
Beliebiger _Komplextyp | {0, 0}
Heidegrenzen
quelle
9

Es bedeutet oft, dass Sie aus Sicherheitsgründen oft nicht überall nach null Objekten suchen müssen - insbesondere:

[someVariable release];

oder, wie bereits erwähnt, geben verschiedene Zähl- und Längenmethoden alle 0 zurück, wenn Sie einen Nullwert haben, sodass Sie nicht überall zusätzliche Überprüfungen für Null hinzufügen müssen:

if ( [myString length] > 0 )

oder dieses:

return [myArray count]; // say for number of rows in a table
Kendall Helmstetter Gelner
quelle
Denken Sie daran, dass die andere Seite der Medaille das Potenzial für Fehler wie "if ([myString length] == 1)"
hatfinch
Wie ist das ein Fehler? [myString length] gibt null (null) zurück, wenn myString null ist ... eine Sache, von der ich denke, dass sie ein Problem darstellt, ist [myView frame], von der ich denke, dass sie Ihnen etwas Verrücktes geben kann, wenn myView null ist.
Kendall Helmstetter Gelner
Wenn Sie Ihre Klassen und Methoden nach dem Konzept gestalten, dass Standardwerte (0, Null, NEIN) "nicht nützlich" bedeuten, ist dies ein leistungsstarkes Werkzeug. Ich muss meine Zeichenfolgen nie auf Null überprüfen, bevor ich die Länge überprüfe. Für mich ist eine leere Zeichenfolge genauso nutzlos und eine Nullzeichenfolge, wenn ich Text verarbeite. Ich bin auch ein Java-Entwickler und ich weiß, dass Java-Puristen dies meiden werden, aber es spart viel Codierung.
Jason Fuerstenberg
6

Denken Sie nicht an "der Empfänger ist Null"; Ich stimme zu, das ist ziemlich komisch. Wenn Sie eine Nachricht an nil senden, gibt es keinen Empfänger. Sie senden nur eine Nachricht an nichts.

Wie man damit umgeht, ist ein philosophischer Unterschied zwischen Java und Objective-C: In Java ist das ein Fehler; in Objective-C ist es ein No-Op.

Benzado
quelle
Es gibt eine Ausnahme zu diesem Verhalten in Java. Wenn Sie eine statische Funktion für eine Null aufrufen, entspricht dies dem Aufruf der Funktion für die Kompilierungszeitklasse der Variablen (unabhängig davon, ob sie null ist).
Roman A. Taycher
6

ObjC-Nachrichten, die an nil gesendet werden und deren Rückgabewerte größer als sizeof (void *) sind, erzeugen auf PowerPC-Prozessoren undefinierte Werte. Darüber hinaus führen diese Nachrichten dazu, dass undefinierte Werte in Feldern von Strukturen zurückgegeben werden, deren Größe auch auf Intel-Prozessoren größer als 8 Byte ist. Vincent Gable hat dies in seinem Blog-Beitrag gut beschrieben

Nikita Zhuk
quelle
6

Ich glaube, keine der anderen Antworten hat dies klar erwähnt: Wenn Sie an Java gewöhnt sind, sollten Sie bedenken, dass Objective-C unter Mac OS X zwar Unterstützung für die Ausnahmebehandlung bietet, dies jedoch eine optionale Sprachfunktion sein kann Ein- / Ausschalten mit einem Compiler-Flag. Ich vermute, dass dieses Design des "Sendens von Nachrichten an nilist sicher" vor der Aufnahme der Ausnahmebehandlungsunterstützung in die Sprache erfolgte und mit einem ähnlichen Ziel durchgeführt wurde: Methoden können zurückkehren nil, um Fehler anzuzeigen, und da das Senden einer Nachricht an nilnormalerweise zurückkehrtnilAuf diese Weise kann sich die Fehleranzeige über Ihren Code ausbreiten, sodass Sie nicht bei jeder einzelnen Nachricht danach suchen müssen. Sie müssen nur an Stellen nachsehen, an denen es darauf ankommt. Ich persönlich denke, dass die Weitergabe und Behandlung von Ausnahmen ein besserer Weg ist, um dieses Ziel zu erreichen, aber möglicherweise sind nicht alle damit einverstanden. (Andererseits gefällt mir zum Beispiel nicht, dass Java deklarieren muss, welche Ausnahmen eine Methode auslösen darf, was Sie häufig dazu zwingt, Ausnahmedeklarationen syntaktisch im gesamten Code zu verbreiten. Dies ist jedoch eine andere Diskussion.)

Ich habe eine ähnliche, aber längere Antwort auf die verwandte Frage "Ist es in Ziel C notwendig, zu behaupten, dass jede Objekterstellung erfolgreich war?" wenn Sie mehr Details wünschen.

Rinzwind
quelle
Ich habe nie so darüber nachgedacht. Dies scheint eine sehr praktische Funktion zu sein.
mk12
2
Gute Vermutung, aber historisch ungenau, warum die Entscheidung getroffen wurde. Die Ausnahmebehandlung war in der Sprache von Anfang an vorhanden, obwohl die ursprünglichen Ausnahmebehandlungsroutinen im Vergleich zur modernen Sprache recht primitiv waren. Nil-eats-message war eine bewusste Designentscheidung, die aus dem optionalen Verhalten des Nil-Objekts in Smalltalk abgeleitet wurde. Bei der Entwicklung der ursprünglichen NeXTSTEP-APIs war die Verkettung von Methoden weit verbreitet, und eine nilRückkehr wurde häufig verwendet, um eine Kette in einen NO-Op kurzzuschließen.
bbum
2

C steht für 0 als primitiv für primitive Werte und NULL für Zeiger (was in einem Zeigerkontext 0 entspricht).

Objective-C baut auf Cs Darstellung von Nichts auf, indem es Null hinzufügt. nil ist ein Objektzeiger auf nichts. Obwohl sie sich semantisch von NULL unterscheiden, sind sie technisch äquivalent zueinander.

Neu zugewiesene NSObjects beginnen ihr Leben mit einem auf 0 gesetzten Inhalt. Dies bedeutet, dass alle Zeiger, die das Objekt auf andere Objekte hat, mit Null beginnen, sodass es beispielsweise nicht erforderlich ist, in init-Methoden self. (Association) = nil zu setzen.

Das bemerkenswerteste Verhalten von nil ist jedoch, dass Nachrichten an ihn gesendet werden können.

In anderen Sprachen wie C ++ (oder Java) würde dies Ihr Programm zum Absturz bringen. In Objective-C gibt das Aufrufen einer Methode für nil jedoch einen Wert von Null zurück. Dies vereinfacht Ausdrücke erheblich, da nicht mehr nach Null gesucht werden muss, bevor etwas unternommen wird:

// For example, this expression...
if (name != nil && [name isEqualToString:@"Steve"]) { ... }

// ...can be simplified to:
if ([name isEqualToString:@"Steve"]) { ... }

Wenn Sie wissen, wie Null in Objective-C funktioniert, ist dieser Komfort eine Funktion und kein lauernder Fehler in Ihrer Anwendung. Stellen Sie sicher, dass Sie sich vor Fällen schützen, in denen keine Werte unerwünscht sind, indem Sie entweder überprüfen und frühzeitig zurückkehren, um stillschweigend fehlzuschlagen, oder einen NSParameterAssert hinzufügen, um eine Ausnahme auszulösen.

Quelle: http://nshipster.com/nil/ https://developer.apple.com/library/ios/#documentation/cocoa/conceptual/objectivec/Chapters/ocObjectsClasses.html (Nachricht an nil senden).

Zee
quelle