In Objective-C perché dovrei verificare se self = [super init] non è zero?


165

Ho una domanda generale sulla scrittura di metodi init in Objective-C.

Vedo ovunque (codice Apple, libri, codice open source, ecc.) Che un metodo init dovrebbe verificare se self = [super init] non è zero prima di continuare con l'inizializzazione.

Il modello Apple predefinito per un metodo init è:

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

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

    return self;
}

Perché?

Voglio dire, quando mai mai init restituirà zero? Se ho chiamato init su NSObject e ho ottenuto zero, allora qualcosa deve essere davvero fregato, giusto? E in quel caso, potresti anche non scrivere nemmeno un programma ...

È davvero così comune che un metodo init di una classe possa restituire zero? In tal caso, in quale caso e perché?


1
Wil Shipley ha pubblicato un articolo relativo a questo qualche tempo fa. [self = [stupid init];] ( wilshipley.com/blog/2005/07/self-stupid-init.html ) Leggi anche i commenti, alcune cose utili.
Ryan Townshend,

6
Potresti chiedere a Wil Shipley o Mike Ash o Matt Gallagher . Ad ogni modo, è qualcosa di un argomento dibattuto. Ma di solito è bene attenersi ai modi di dire di Apple ... sono i loro quadri, dopo tutto.
Jbrennan,

1
Sembra che Wil stia sollevando una causa in più per non riassegnare ciecamente sé durante init, sapendo che [super init] potrebbe non restituire il ricevitore.
Jasarien,

3
Wil ha cambiato idea da quando quel post è stato originariamente scritto.
bbum,

5
Avevo visto questa domanda qualche tempo fa e l'ho trovata di nuovo. Perfetto. +1
Dan Rosenstark,

Risposte:


53

Per esempio:

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

... e così via. (Nota: NSData potrebbe generare un'eccezione se il file non esiste). Ci sono alcune aree in cui la restituzione di zero è il comportamento previsto quando si verifica un problema, e per questo motivo è prassi normale verificare quasi sempre lo zero, per motivi di coerenza.


10
Sì, ma questo non è INSIDE del rispettivo metodo init della classe. NSData eredita da NSObject. NSData verifica se [superinizializzazione] restituisce zero? Questo è quello che chiedo qui. Scusa se non sono stato chiaro ...
Jasarien,

9
Se dovessi sottoclassare quelle classi, ci sarebbe una reale possibilità che [super init] restituisca zero. Non tutte le classi sono sottoclassi dirette di NSObject. Non c'è mai alcuna garanzia che non restituirà nulla. È solo una pratica di codifica difensiva generalmente incoraggiata.
Chuck,

7
Dubito che NSObject init possa restituire zero in ogni caso, mai. Se hai esaurito la memoria, l'allocazione fallirà, ma se riesce, dubito che init potrebbe fallire - NSObject non ha nemmeno variabili d'istanza tranne Class. In GNUStep, è implementato come "return self" e lo smontaggio su Mac sembra lo stesso. Tutto questo è, ovviamente, irrilevante - basta seguire il linguaggio standard e non dovrai preoccuparti se può o no.
Peter N Lewis,

9
Non sono intenzionato a non seguire le migliori pratiche. Tuttavia, vorrei sapere perché sono le migliori pratiche in primo luogo. È come se gli fosse stato detto di saltare da una torre. Non basta andare avanti e farlo a meno che non si sappia perché. C'è un cuscino grande e morbido su cui atterrare sul fondo, con un enorme premio in denaro? Se sapessi che salterei. Altrimenti, non lo farei. Non voglio seguire ciecamente una pratica senza sapere perché la sto seguendo ...
Jasarien,

4
Se alloc restituisce zero, init viene inviato a zero, il che si tradurrà sempre in zero, finendo con l'essere zero.
T.

50

Questo particolare linguaggio è standard perché funziona in tutti i casi.

Mentre non comune, ci saranno casi in cui ...

[super init];

... restituisce un'istanza diversa, quindi richiede l'assegnazione a sé.

E ci saranno casi in cui restituirà zero, richiedendo quindi il controllo zero in modo che il codice non tenti di inizializzare uno slot variabile di istanza che non esiste più.

La linea di fondo è che è il modello corretto documentato da usare e, se non lo stai usando, lo stai facendo in modo sbagliato.


3
È ancora vero alla luce degli specificatori di nullability? Se l'inizializzatore della mia superclasse non è null, vale davvero la pena controllare il disordine in più? (Anche se NSObject non sembra avere nulla per il suo -initafaict ...)
natevw,

C'è mai un caso in cui [super init]restituisce zero quando la superclasse diretta è NSObject? Non è questo un caso in cui "tutto è rotto?"
Dan Rosenstark,

1
@DanRosenstark Non se NSObjectè la superclasse diretta. Ma ... anche se dichiarassi NSObjectla superclasse diretta, qualcosa potrebbe essere stato modificato in fase di esecuzione in modo tale che NSObjectl'implementazione di initnon sia quella che viene effettivamente chiamata.
bbum

1
Grazie mille @bbum, questo mi ha davvero aiutato a orientarmi nella correzione di alcuni bug. Buono a escludere alcune cose!
Dan Rosenstark,

26

Penso che, nella maggior parte delle classi, se il valore restituito da [super init] è nullo e lo controlli, come raccomandato dalle pratiche standard, e quindi restituisci prematuramente se zero, in pratica la tua app non funzionerà correttamente. Se ci pensate, anche se che se (auto! = Nil) controllo è lì, per il corretto funzionamento della classe, il 99,99% del tempo che effettivamente fate bisogno di sé di essere non-zero. Supponiamo ora che, per qualsiasi motivo, [super init] abbia restituito zero, in pratica il tuo controllo contro zero sta sostanzialmente passando il dollaro al chiamante della tua classe, dove probabilmente fallirebbe comunque, dal momento che presumerà naturalmente che la chiamata fosse riuscito.

Fondamentalmente, quello che sto ottenendo è che il 99,99% delle volte, il if (self! = Zero) non ti compra nulla in termini di maggiore robustezza, dal momento che stai solo passando il dollaro al tuo invocatore. Per essere davvero in grado di gestirlo in modo efficace, in realtà dovresti mettere dei controlli nell'intera gerarchia chiamante. E anche in quel caso, l'unica cosa che ti comprerebbe è che la tua app fallirebbe in modo un po 'più pulito / robusto. Ma fallirebbe comunque.

Se una classe di biblioteca ha deciso arbitrariamente di restituire zero come risultato di un [superiniziale], sei comunque praticamente fottuto, e questo è più un'indicazione che lo scrittore della classe di biblioteca ha fatto un errore di implementazione.

Penso che questo sia più un suggerimento di codifica legacy, quando le app venivano eseguite in una memoria molto più limitata.

Ma per il codice di livello C, in genere verificherei comunque il valore di ritorno di malloc () rispetto a un puntatore NULL. Considerando che, per Objective-C, fino a quando non troverò prove contrarie, penso che in genere salterò i controlli if (self! = Zero). Perché la discrepanza?

Perché, a livello C e malloc, in alcuni casi è possibile recuperare parzialmente. Mentre penso in Objective-C, nel 99,99% dei casi, se [superiniziale] restituisce zero, in pratica sei fottuto, anche se provi a gestirlo. Potresti anche lasciare che l'app si blocchi e gestire le conseguenze.


6
Ben detto. Sono d'accordo con questo.
Roger CS Wernersson l'

3
+1 Sono totalmente d'accordo. Solo una piccola nota: non credo che il modello sia il risultato di tempi in cui l'allocazione più spesso falliva. L'allocazione di solito viene già eseguita al momento della chiamata di init. Se alloc non fosse riuscito, init non sarebbe nemmeno chiamato.
Nikolai Ruhe,

8

Questo è una specie di riassunto dei commenti sopra.

Diciamo che la superclasse ritorna nil. Che succederà?

Se non segui le convenzioni

Il tuo codice andrà in crash nel mezzo del tuo initmetodo. (a meno initche non faccia nulla di significativo)

Se segui le convenzioni, non sapendo che la superclasse potrebbe restituire zero (la maggior parte delle persone finisce qui)

Il tuo codice è destinato a bloccarsi ad un certo punto in seguito, perché la tua istanza è nil, dove ti aspettavi qualcosa di diverso. Oppure il tuo programma si comporterà inaspettatamente senza crash. Oh caro! Vuoi questo? Non lo so...

Se segui le convenzioni, consenti volentieri alla tua sottoclasse di restituire zero

La documentazione del codice (!) Dovrebbe indicare chiaramente: "restituisce ... o zero" e il resto del codice deve essere preparato per gestirlo. Adesso ha senso.


5
Il punto interessante qui, penso, è che l'opzione n. 1 è chiaramente preferibile all'opzione n. 2. Se ci sono davvero circostanze in cui potresti volere che init della sottoclasse restituisca zero, allora è preferibile il n. 3. Se l'unica ragione che potrebbe mai accadere è a causa di un bug nel tuo codice, usa il numero 1. L'utilizzo dell'opzione n. 2 sta solo ritardando l'esplosione dell'app fino a un momento successivo, e quindi rendendo il tuo lavoro quando arrivi a eseguire il debug dell'errore molto più difficile. È come catturare silenziosamente le eccezioni e continuare senza gestirle.
Mark Amery,

Oppure si passa a rapido e basta usare gli opzionali
AndrewSB

7

In genere, se la tua classe deriva direttamente da NSObject, non sarà necessario. Tuttavia, è una buona abitudine entrare, come se la tua classe derivi da altre classi, i loro inizializzatori potrebbero tornare nile, in tal caso, il tuo inizializzatore può quindi catturarlo e comportarsi correttamente.

E sì, per la cronaca, seguo le migliori pratiche e le scrivo su tutte le mie lezioni, anche su quelle derivanti direttamente NSObject.


1
Con questo in mente, sarebbe buona norma controllare lo zero dopo aver inizializzato una variabile e prima di chiamare funzioni su di essa? es.Foo *bar = [[Foo alloc] init]; if (bar) {[bar doStuff];}
dev_does_software

Ereditare da NSObjectnon garantisce che -initti dia NSObjectanche se contando i runtime esotici come le versioni precedenti di GNUstep in (che ritorna GSObject), quindi, indipendentemente da cosa, un controllo e un'assegnazione.
Maxthon Chan,

3

Hai ragione, spesso potresti semplicemente scrivere [super init], ma non funzionerebbe per una sottoclasse di qualsiasi cosa. Le persone preferiscono semplicemente memorizzare una riga di codice standard e usarla sempre, anche quando è solo a volte necessario, e quindi otteniamo lo standard if (self = [super init]), che prende sia la possibilità di restituire zero sia la possibilità di un oggetto diverso dalla selfrestituzione in considerazione.


3

Un errore comune è scrivere

self = [[super alloc] init];

che restituisce un'istanza della superclasse, che NON è ciò che si desidera in un costruttore / init di una sottoclasse. Si ottiene indietro un oggetto che non risponde ai metodi della sottoclasse, che può essere fonte di confusione, e generare errori di confusione sulla mancata risposta a metodi o identificatori non trovati, ecc.

self = [super init]; 

è necessaria se la classe super-ha membri (variabili o altri oggetti) per inizializzare prima prima di impostare i membri delle sottoclassi. Altrimenti il ​​runtime objc li inizializza tutti su 0 o su zero . (a differenza dell'ANSI C, che spesso alloca blocchi di memoria senza cancellarli affatto )

E sì, l'inizializzazione della classe base può fallire a causa di errori di memoria insufficiente, componenti mancanti, errori di acquisizione delle risorse, ecc. Quindi un controllo per zero è saggio e richiede meno di pochi millisecondi.


2

Questo per verificare che l'intializzazione abbia funzionato, l'istruzione if restituisce vero se il metodo init non ha restituito zero, quindi è un modo per verificare che la creazione dell'oggetto abbia funzionato correttamente. Poche ragioni a cui riesco a pensare che init potrebbe fallire, forse è un metodo init inverso che la superclasse non conosce o qualcosa del genere, non penso che sia così comune. Ma se succede, è meglio che non accada un incidente che suppongo, quindi è sempre controllato ...


lo è, ma gli htey sono chiamati insieme, cosa succede se alloc f ails?
Daniel,

Immagino che se l'allocazione fallisce, allora init verrà inviato a zero anziché a un'istanza della classe su cui stai chiamando init. In tal caso, non accadrà nulla e nessun codice verrà eseguito nemmeno per testare se [superiniziale] abbia restituito zero.
Jasarien,

2
L'allocazione della memoria non viene sempre eseguita in + alloc. Considera il caso dei cluster di classe; una NSString non sa quale sottoclasse specifica utilizzare fino a quando non viene invocato l'inizializzatore.
bbum

1

In OS X, non è altrettanto probabile -[NSObject init]che fallisca a causa di motivi di memoria. Lo stesso non si può dire per iOS.

Inoltre, è buona norma scrivere quando si esegue una sottoclasse di una classe che potrebbe tornare nilper qualsiasi motivo.


2
Su entrambi iOS e Mac OS -[NSObject init]è molto improbabile che fallire a causa di motivi di memoria in quanto non alloca alcuna memoria.
Nikolai Ruhe,

Immagino che intendesse alloc, non init :)
Thomas Tempelmann,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.