web-dev-qa-db-fra.com

Dans Objective-C pourquoi devrais-je vérifier si self = [super init] n'est pas nul?

J'ai une question générale sur l'écriture des méthodes init dans Objective-C.

Je vois partout (code d'Apple, livres, code open source, etc.) qu'une méthode init doit vérifier si self = [super init] n'est pas nul avant de poursuivre l'initialisation.

Le modèle par défaut Apple pour une méthode init est:

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

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

    return self;
}

Pourquoi?

Je veux dire quand est-ce que init va retourner nul? Si j'ai appelé init sur NSObject et que je suis revenu à zéro, alors quelque chose doit être vraiment foutu, non? Et dans ce cas, vous pourriez tout aussi bien ne pas écrire de programme ...

Est-il vraiment si courant qu'une méthode init d'une classe retourne nil? Si oui, dans quel cas et pourquoi?

162
Jasarien

Par exemple:

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

... etc. (Remarque: NSData peut lever une exception si le fichier n'existe pas). Il y a un certain nombre de domaines où le retour de zéro est le comportement attendu lorsqu'un problème se produit, et à cause de cela, il est courant de vérifier à peu près tout le temps pour des raisons de cohérence.

52
iKenndac

Cet idiome particulier est standard car il fonctionne dans tous les cas.

Bien que cela soit rare, il y aura des cas où ...

[super init];

... renvoie une instance différente, nécessitant ainsi l'affectation à soi-même.

Et il y aura des cas où il retournera nil, nécessitant ainsi la vérification de nil pour que votre code n'essaie pas d'initialiser un emplacement de variable d'instance qui n'existe plus.

L'essentiel est que c'est le modèle correct documenté à utiliser et, si vous ne l'utilisez pas, vous le faites mal.

50
bbum

Je pense que dans la plupart des classes, si la valeur de retour de [super init] est nulle et que vous la vérifiez, comme recommandé par les pratiques standard, puis que vous la retournez prématurément si elle est nulle, fondamentalement, votre application ne fonctionnera toujours pas correctement. Si vous y réfléchissez, même si si (self! = Nil) la vérification est là, pour le bon fonctionnement de votre classe, 99,99% du temps, vous do avez besoin que self soit non nul. Maintenant, supposons que, pour quelque raison que ce soit, [super init] did renvoie nil, fondamentalement, votre contrôle contre nil revient essentiellement à l'appelant de votre classe, où il échouerait probablement de toute façon, car il supposera naturellement que l'appel a réussi.

Fondamentalement, ce que je veux dire, c'est que 99,99% du temps, le if (self! = Nil) ne vous offre rien en termes de plus grande robustesse, car vous ne faites que renvoyer la balle à votre invocateur. Pour vraiment être en mesure de gérer cela de manière robuste, vous devez en fait effectuer des vérifications dans l'ensemble de votre hiérarchie d'appels. Et même dans ce cas, la seule chose que cela vous apporterait, c'est que votre application échouerait un peu plus proprement/plus solidement. Mais cela échouerait toujours.

Si une classe de bibliothèque décide arbitrairement de retourner nil à la suite d'un [super init], vous êtes à peu près tout foutu de toute façon, et c'est plus une indication que l'auteur de la classe de bibliothèque a fait une erreur d'implémentation.

Je pense qu'il s'agit davantage d'une suggestion de codage héritée, lorsque les applications s'exécutaient dans une mémoire beaucoup plus limitée.

Mais pour le code de niveau C, je vérifierais généralement la valeur de retour de malloc () par rapport à un pointeur NULL. Alors que, pour Objective-C, jusqu'à ce que je trouve des preuves du contraire, je pense que je vais généralement sauter les vérifications if (self! = Nil). Pourquoi cet écart?

Parce que, aux niveaux C et malloc, dans certains cas, vous pouvez réellement récupérer partiellement. Alors que je pense que dans Objective-C, dans 99,99% des cas, si [super init] retourne zéro, vous êtes fondamentalement f *** ed, même si vous essayez de le gérer. Vous pourriez tout aussi bien laisser l'application se bloquer et gérer les conséquences.

25
Gino

Ceci est une sorte de résumé des commentaires ci-dessus.

Supposons que la superclasse renvoie nil. Qu'est-ce qui va arriver?

Si vous ne respectez pas les conventions

Votre code va planter au milieu de votre méthode init. (à moins que init ne fasse rien de significatif)

Si vous suivez les conventions, ne sachant pas que la superclasse pourrait retourner nulle (la plupart des gens se retrouvent ici)

Votre code va probablement se bloquer plus tard, car votre instance est nil, où vous vous attendiez à quelque chose de différent. Ou votre programme se comportera de manière inattendue sans se bloquer. Oh cher! Tu veux ça? Je ne sais pas...

Si vous suivez les conventions, autorisez volontiers votre sous-classe à retourner nil

La documentation de votre code (!) Doit indiquer clairement: "renvoie ... ou nil", et le reste de votre code doit être préparé pour gérer cela. Maintenant, c'est logique.

8
user123444555621

En règle générale, si votre classe dérive directement de NSObject, vous n'en aurez pas besoin. Cependant, c'est une bonne habitude à prendre, comme si votre classe dérive d'autres classes, leurs initialiseurs peuvent retourner nil, et si c'est le cas, votre initialiseur peut alors capturer cela et se comporter correctement.

Et oui, pour mémoire, je suis la meilleure pratique et l'écris sur toutes mes classes, même celles dérivant directement de NSObject.

7
John Rudy

Une erreur courante est d'écrire

self = [[super alloc] init];

qui retourne une instance de la superclasse, ce qui n'est PAS ce que vous voulez dans un constructeur de sous-classe/init. Vous récupérez un objet qui ne répond pas aux méthodes de la sous-classe, ce qui peut être déroutant, et générez des erreurs déroutantes sur le fait de ne pas répondre à des méthodes ou des identifiants non trouvés, etc.

self = [super init]; 

est nécessaire si la super classe a des membres (variables ou autres objets) pour initialiser premier avant de configurer les membres des sous-classes. Sinon, le runtime objc les initialise tous à ou à nil. (contrairement à ANSI C, qui alloue souvent des morceaux de mémoire sans les effacer du tout)

Et oui, l'initialisation de la classe de base peut échouer en raison d'erreurs de mémoire insuffisante, de composants manquants, d'échecs d'acquisition de ressources, etc.

3
Chris Reid

Vous avez raison, vous pouvez souvent simplement écrire [super init], Mais cela ne fonctionnerait pas pour une sous-classe de n'importe quoi. Les gens préfèrent simplement mémoriser une ligne de code standard et l'utiliser tout le temps, même lorsque cela n'est que parfois nécessaire, et nous obtenons ainsi la norme if (self = [super init]), qui prend à la fois la possibilité de retourner nil et la possibilité d'un objet autre que self retourné en compte.

3
andyvn22

C'est pour vérifier que l'intialazation a fonctionné, l'instruction if renvoie true si la méthode init n'a pas retourné nil, donc c'est un moyen de vérifier que la création de l'objet a fonctionné correctement. Peu de raisons pour lesquelles je peux penser que l'init pourrait échouer, c'est peut-être une méthode d'initialisation ignorée que la super classe ne connaît pas ou quelque chose du genre, je ne pense pas que ce soit si commun. Mais si cela se produit, il vaut mieux que rien ne se produise qu'un crash je suppose donc c'est toujours vérifié ...

2
Daniel

Sous OS X, ce n'est pas aussi probable pour -[NSObject init] pour échouer pour des raisons de mémoire. On ne peut pas en dire autant pour iOS.

En outre, il est recommandé d'écrire lors du sous-classement d'une classe susceptible de renvoyer nil pour une raison quelconque.

1
MaddTheSane