Warum sollte ich in Objective-C prüfen, ob self = [super init] nicht null ist?

165

Ich habe eine allgemeine Frage zum Schreiben von Init-Methoden in Objective-C.

Ich sehe überall (Apples Code, Bücher, Open Source Code usw.), dass eine Init-Methode prüfen sollte, ob self = [super init] nicht null ist, bevor sie mit der Initialisierung fortfährt.

Die Standardvorlage von Apple für eine Init-Methode lautet:

- (id) init
{
    self = [super init];

    if (self != nil)
    {
        // your code here
    }

    return self;
}

Warum?

Ich meine, wann wird init jemals null zurückgeben? Wenn ich init auf NSObject angerufen habe und nichts zurückbekommen habe, muss etwas wirklich durcheinander gebracht werden, oder? Und in diesem Fall könnten Sie genauso gut nicht einmal ein Programm schreiben ...

Ist es wirklich so üblich, dass die Init-Methode einer Klasse null zurückgibt? Wenn ja, in welchem ​​Fall und warum?

Jasarien
quelle
1
Wil Shipley hat vor einiger Zeit einen Artikel dazu veröffentlicht. [self = [dummer Init];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Lesen Sie auch die Kommentare durch, einige gute Sachen.
Ryan Townshend
6
Sie könnten Wil Shipley oder Mike Ash oder Matt Gallagher fragen . In jedem Fall ist es ein umstrittenes Thema. Aber normalerweise ist es gut, sich an Apples Redewendungen zu halten ... es sind schließlich ihre Frameworks.
Jbrennan
1
Es scheint, dass Wil mehr dafür plädierte, sich während der Init nicht blind neu zuzuweisen, da er wusste, dass [Super-Init] den Empfänger möglicherweise nicht zurückgibt.
Jasarien
3
Wil hat seine Gedanken geändert, seit dieser Beitrag ursprünglich gemacht wurde.
bbum
5
Ich hatte diese Frage vor einiger Zeit gesehen und sie gerade wiedergefunden. Perfekt. +1
Dan Rosenstark

Antworten:

53

Beispielsweise:

[[NSData alloc] initWithContentsOfFile:@"this/path/doesn't/exist/"];
[[NSImage alloc] initWithContentsOfFile:@"unsupportedFormat.sjt"];
[NSImage imageNamed:@"AnImageThatIsntInTheImageCache"];

... und so weiter. (Hinweis: NSData löst möglicherweise eine Ausnahme aus, wenn die Datei nicht vorhanden ist.) Es gibt einige Bereiche, in denen die Rückgabe von Null das erwartete Verhalten ist, wenn ein Problem auftritt, und aus diesem Grund ist es üblich, aus Gründen der Konsistenz fast immer auf Null zu prüfen.

iKenndac
quelle
10
Ja, aber dies ist nicht IN der Init-Methode der jeweiligen Klasse. NSData erbt von NSObject. Prüft NSData, ob [super init] null zurückgibt? Das frage ich hier. Entschuldigung, wenn ich nicht klar war ...
Jasarien
9
Wenn Sie diese Klassen unterklassifizieren würden, bestünde eine ziemlich reale Chance, dass [super init] null zurückgibt. Nicht alle Klassen sind direkte Unterklassen von NSObject. Es gibt nie eine Garantie dafür, dass es nicht null zurückgibt. Es ist nur eine defensive Codierungspraxis, die allgemein empfohlen wird.
Chuck
7
Ich bezweifle, dass NSObject init auf jeden Fall jemals null zurückgeben kann. Wenn Sie nicht genügend Speicher haben, schlägt die Zuweisung fehl, aber wenn dies erfolgreich ist, bezweifle ich, dass init fehlschlagen könnte - NSObject hat nicht einmal Instanzvariablen außer Class. In GNUStep wird es nur als "return self" implementiert, und das Zerlegen auf dem Mac sieht genauso aus. All dies ist natürlich irrelevant - folgen Sie einfach der Standardsprache und Sie müssen sich keine Sorgen machen, ob es kann oder nicht.
Peter N Lewis
9
Ich bin nicht darauf aus, Best Practices nicht zu befolgen. Ich würde jedoch gerne wissen, warum es sich überhaupt um Best Practices handelt. Es ist, als würde man sagen, man solle von einem Turm springen. Sie machen es nicht einfach, es sei denn, Sie wissen warum. Gibt es ein großes, weiches Kissen, auf dem man unten landen kann, mit einer massiven Geldbelohnung? Wenn ich wüsste, dass ich springen würde. Wenn nicht, würde ich nicht. Ich möchte einer Übung nicht einfach blind folgen, ohne zu wissen, warum ich ihr
folge
4
Wenn alloc nil zurückgibt, wird init an nil gesendet, was immer zu nil führt, was dazu führt, dass self nil ist.
T.
50

Diese spezielle Redewendung ist Standard, da sie in allen Fällen funktioniert.

Es ist zwar ungewöhnlich, aber es wird Fälle geben, in denen ...

[super init];

... gibt eine andere Instanz zurück und erfordert daher die Zuweisung zu self.

In einigen Fällen wird nil zurückgegeben, sodass die Prüfung nil erforderlich ist, damit Ihr Code nicht versucht, einen nicht mehr vorhandenen Instanzvariablen-Slot zu initialisieren.

Die Quintessenz ist, dass es das dokumentierte korrekte Muster ist, das verwendet werden soll, und wenn Sie es nicht verwenden, machen Sie es falsch.

bbum
quelle
3
Trifft dies angesichts der Nullabilitätsspezifizierer immer noch zu? Wenn der Initialisierer meiner Oberklasse nicht null ist, lohnt es sich dann wirklich noch, die zusätzliche Unordnung zu überprüfen? (Obwohl NSObject selbst keine zu haben -initscheint ...)
Natevw
Gibt es jemals einen Fall, in dem [super init]Null zurückgegeben wird, wenn die direkte Oberklasse ist NSObject? Ist das nicht ein Fall, in dem "alles kaputt ist"?
Dan Rosenstark
1
@DanRosenstark Nicht wenn NSObjectes sich um die direkte Oberklasse handelt. Aber ... selbst wenn Sie NSObjectals direkte Superklasse deklarieren , könnte zur Laufzeit etwas geändert worden sein, so dass NSObjectdie Implementierung von initnicht das ist, was eigentlich genannt wird.
bbum
1
Vielen Dank @bbum, dies hat mir wirklich geholfen, mich an einigen Fehlerbehebungen zu orientieren. Gut, einige Dinge auszuschließen!
Dan Rosenstark vor
26

Ich denke, in den meisten Klassen funktioniert Ihre App im Grunde immer noch nicht richtig, wenn der Rückgabewert von [super init] Null ist und Sie ihn überprüfen, wie von den Standardpraktiken empfohlen, und dann vorzeitig zurückgeben, wenn Null, dass Ihre App im Grunde immer noch nicht richtig funktioniert. Wenn man darüber nachdenkt, obwohl , dass , wenn (selbst! = Nil) Überprüfung ist es, für den ordnungsgemäßen Betrieb der Klasse, 99,99% der Zeit , die Sie tatsächlich tun müssen selbst Nicht-Null zu sein. Nehmen wir nun an, aus welchem ​​Grund auch immer, [super init] hat nil zurückgegeben. Im Grunde geht Ihr Scheck gegen nil im Grunde genommen an den Anrufer Ihrer Klasse, wo er wahrscheinlich sowieso fehlschlagen würde, da er natürlich davon ausgeht, dass der Anruf war erfolgreich.

Grundsätzlich geht es mir darum, dass das if (self! = Nil) in 99,99% der Fälle nichts im Hinblick auf eine größere Robustheit kauft, da Sie nur das Geld an Ihren Anrufer weitergeben. Um dies wirklich robust handhaben zu können, müssten Sie tatsächlich Überprüfungen in Ihrer gesamten aufrufenden Hierarchie vornehmen. Und selbst dann würde es Ihnen nur kaufen, wenn Ihre App etwas sauberer / robuster ausfällt. Aber es würde immer noch scheitern.

Wenn eine Bibliotheksklasse aufgrund eines [Super-Init] willkürlich beschlossen hat, Null zurückzugeben, sind Sie sowieso ziemlich verärgert, und dies ist eher ein Hinweis darauf, dass der Verfasser der Bibliotheksklasse einen Fehler bei der Implementierung gemacht hat.

Ich denke, dies ist eher ein Legacy-Codierungsvorschlag, wenn Apps in einem viel begrenzteren Speicher ausgeführt wurden.

Aber für Code auf C-Ebene würde ich normalerweise immer noch den Rückgabewert von malloc () gegen einen NULL-Zeiger prüfen. Während ich für Objective-C, bis ich Beweise für das Gegenteil finde, im Allgemeinen die if (self! = Nil) -Prüfungen überspringe. Warum die Diskrepanz?

Denn auf der C- und Malloc-Ebene können Sie sich in einigen Fällen tatsächlich teilweise erholen. Während ich in Objective-C denke, dass in 99,99% der Fälle, wenn [super init] null zurückgibt, Sie im Grunde genommen gefickt sind, selbst wenn Sie versuchen, damit umzugehen. Sie können die App auch einfach abstürzen lassen und sich mit den Folgen befassen.

Gino
quelle
6
Gut gesprochen. Ich stimme dem zu.
Roger CS Wernersson
3
+1 Ich stimme vollkommen zu. Nur eine kleine Anmerkung: Ich glaube nicht, dass das Muster ein Ergebnis von Zeiten ist, in denen die Zuordnung häufiger fehlgeschlagen ist. Die Zuweisung erfolgt normalerweise bereits zum Zeitpunkt des Aufrufs von init. Wenn die Zuordnung fehlschlägt, wird init nicht einmal aufgerufen.
Nikolai Ruhe
8

Dies ist eine Art Zusammenfassung der obigen Kommentare.

Nehmen wir an, die Oberklasse kehrt zurück nil. Was ist passiert?

Wenn Sie die Konventionen nicht befolgen

Ihr Code wird mitten in Ihrer initMethode abstürzen . (es sei denn init, nichts von Bedeutung)

Wenn Sie die Konventionen befolgen und nicht wissen, dass die Oberklasse möglicherweise Null zurückgibt (die meisten Leute landen hier)

Ihr Code wird wahrscheinlich irgendwann später abstürzen, weil Sie in Ihrer Instanz niletwas anderes erwartet haben. Oder Ihr Programm verhält sich unerwartet, ohne abzustürzen. Ach je! Willst du das? Ich weiß es nicht...

Wenn Sie die Konventionen befolgen, lassen Sie Ihre Unterklasse bereitwillig Null zurückgeben

In Ihrer Codedokumentation (!) Sollte eindeutig angegeben sein: "return ... oder nil", und der Rest Ihres Codes muss darauf vorbereitet sein. Jetzt macht es Sinn.

user123444555621
quelle
5
Der interessante Punkt hier ist meiner Meinung nach, dass Option 1 Option 2 eindeutig vorzuziehen ist . Wenn es wirklich Umstände gibt, unter denen Sie möchten, dass der Init Ihrer Unterklasse Null zurückgibt, ist # 3 vorzuziehen. Wenn der einzige Grund, der jemals passieren würde, ein Fehler in Ihrem Code ist, verwenden Sie # 1. Wenn Sie Option 2 verwenden, wird die Explosion Ihrer App nur bis zu einem späteren Zeitpunkt verzögert, wodurch Ihre Arbeit beim Debuggen des Fehlers erheblich erschwert wird. Es ist, als würde man stillschweigend Ausnahmen fangen und weitermachen, ohne sie zu behandeln.
Mark Amery
Oder Sie wechseln zu schnell und verwenden nur Optionals
AndrewSB
7

Wenn Ihre Klasse direkt von abgeleitet ist NSObject, müssen Sie dies normalerweise nicht tun. Es ist jedoch eine gute Angewohnheit, sich darauf einzulassen, als ob Ihre Klasse von anderen Klassen abgeleitet wäre, deren Initialisierer möglicherweise zurückkehren nil. Wenn dies der Fall ist, kann Ihr Initialisierer dies erfassen und sich korrekt verhalten.

Und ja, fürs Protokoll, ich folge den Best Practices und schreibe sie in alle meine Klassen, auch in diejenigen, die direkt von ihnen stammen NSObject.

John Rudy
quelle
1
In diesem Sinne wäre es empfehlenswert, nach dem Initialisieren einer Variablen und vor dem Aufrufen von Funktionen auf Null zu prüfen. zBFoo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software
Das Erben von NSObjectgarantiert nicht, dass -inites Ihnen NSObjectauch gibt , wenn Sie exotische Laufzeiten wie ältere Versionen von GNUstep in (die zurückkehren GSObject) zählen, egal was passiert , eine Prüfung und eine Zuweisung.
Maxthon Chan
3

Sie haben Recht, Sie könnten oft nur schreiben [super init], aber das würde für eine Unterklasse von einfach nichts funktionieren. Die Leute bevorzugen es, sich nur eine Standard-Codezeile zu merken und sie die ganze Zeit zu verwenden, auch wenn dies nur manchmal notwendig ist, und so erhalten wir den Standard if (self = [super init]), der sowohl die Möglichkeit der Rückgabe von Null als auch die Möglichkeit eines anderen Objekts als selfder Rückgabe berücksichtigt berücksichtigen.

andyvn22
quelle
3

Ein häufiger Fehler ist das Schreiben

self = [[super alloc] init];

Dies gibt eine Instanz der Oberklasse zurück, die NICHT das ist, was Sie in einem Unterklassenkonstruktor / init wollen. Sie erhalten ein Objekt zurück, das nicht auf die Unterklassenmethoden reagiert, was verwirrend sein kann, und verwirrende Fehler, wenn nicht auf nicht gefundene Methoden oder Bezeichner usw. geantwortet wird.

self = [super init]; 

wird benötigt, wenn die Superklasse Mitglieder (Variablen oder andere Objekte) hat, die zuerst initialisiert werden müssen, bevor die Mitglieder der Unterklassen eingerichtet werden. Andernfalls initialisiert die objc-Laufzeit sie alle auf 0 oder null . (Im Gegensatz zu ANSI C, das häufig Speicherblöcke zuweist, ohne sie zu löschen )

Und ja, die Initialisierung der Basisklasse kann aufgrund von Fehlern aufgrund von Speichermangel, fehlenden Komponenten, Fehlern bei der Ressourcenerfassung usw. fehlschlagen. Eine Überprüfung auf Null ist daher sinnvoll und dauert weniger als einige Millisekunden.

Chris Reid
quelle
2

Hiermit wird überprüft, ob die Intialazation funktioniert hat. Die if-Anweisung gibt true zurück, wenn die init-Methode nicht nil zurückgegeben hat. Auf diese Weise können Sie überprüfen, ob die Erstellung des Objekts ordnungsgemäß funktioniert hat. Nur wenige Gründe, warum ich mir vorstellen kann, dass dieser Init fehlschlägt, sind möglicherweise eine überschriebene Init-Methode, die die Superklasse nicht kennt, oder etwas Ähnliches. Ich würde jedoch nicht denken, dass dies so häufig vorkommt. Aber wenn es passiert, ist es besser, dass nichts passiert, als ein Absturz, den ich vermute, also wird es immer überprüft ...

Daniel
quelle
es ist, aber htey werden zusammengerufen, was passiert, wenn Probleme zugeteilt werden?
Daniel
Ich stelle mir vor, wenn die Zuweisung fehlschlägt, wird init an nil gesendet und nicht an eine Instanz der Klasse, für die Sie init aufrufen. In diesem Fall passiert nichts und es wird kein Code ausgeführt, um zu testen, ob [super init] null zurückgegeben hat oder nicht.
Jasarien
2
Die Speicherzuweisung erfolgt nicht immer in + alloc. Betrachten Sie den Fall von Klassenclustern. Ein NSString weiß erst, welche bestimmte Unterklasse verwendet werden soll, wenn der Initialisierer aufgerufen wird.
bbum
1

In OS X ist es aus -[NSObject init]Speichergründen nicht so wahrscheinlich , dass ein Fehler auftritt. Das gilt nicht für iOS.

Es ist auch eine gute Vorgehensweise zum Schreiben, wenn Sie eine Klasse unterordnen, die nilaus irgendeinem Grund zurückkehren kann.

MaddTheSane
quelle
2
Sowohl unter iOS als auch unter Mac OS -[NSObject init]ist es sehr unwahrscheinlich, dass aus Speichergründen ein Fehler auftritt, da kein Speicher zugewiesen wird.
Nikolai Ruhe
Ich denke, er meinte Zuteilung, nicht Init :)
Thomas Tempelmann