2009-05-24 5 views
44

Ce concept semble me troubler. Pourquoi un objet NSError nécessite-t-il que son pointeur soit transmis à une méthode qui modifie l'objet? Par exemple, ne ferait-on pas simplement passer une référence à l'erreur faire la même chose?Pourquoi NSError a-t-il besoin d'une double indirection? (Pointeur vers un pointeur)

NSError *anError; 
[myObjc doStuff:withAnotherObj error:error]; 

puis dans doStuff:

- (void)doStuff:(id)withAnotherObjc error:(NSError *)error 
{ 
    // something went bad! 
    [error doSomethingToTheObject]; 
} 

Pourquoi ne pas le travail ci-dessus comme la plupart des autres modèles de messagerie objet de travail? Pourquoi devons-nous utiliser l'erreur error: (NSError **)?

Répondre

75

Le modèle NSError** est utilisé lorsqu'une méthode renvoie normalement une valeur mais qu'elle peut avoir à retourner un objet d'erreur (de type NSError*) en cas d'échec. En Objective-C, une méthode ne peut retourner qu'un type d'objet, mais c'est un cas où vous voulez en retourner deux. Dans les langues C comme lorsque vous avez besoin de retourner une valeur supplémentaire que vous demandez un pointeur sur une valeur de ce type, pour ainsi retourner un NSError* dont vous avez besoin d'un paramètre NSError**. Un exemple plus réaliste serait:

// The method should return something, because otherwise it could just return 
// NSError* directly and the error argument wouldn't be necessary 
- (NSArray *)doStuffWithObject:(id)obj error:(NSError **)error 
{ 
    NSArray *result = ...; // Do some work that might fail 
    if (result != nil) { 
    return result; 
    } else { 
    // Something went bad! 
    // The caller might pass NULL for `error` if they don't care about 
    // the result, so check for NULL before dereferencing it 
    if (error != NULL) { 
     *error = [NSError errorWithDomain:...]; 
    } 
    return nil; // The caller knows to check error if I return nil 
    } 
} 

Si vous aviez seulement un paramètre NSError* au lieu d'un NSError** alors doStuff ne serait jamais en mesure de passer l'objet d'erreur à son appelant.

6

autre énoncé de ce n8gray dit:

Parce que vous ne recevez pas un objet pour envoyer des messages à; vous créez l'objet et le renvoyez. Vous avez généralement besoin de l'argument -variable pointer-vers-un-NSError * car vous ne pouvez utiliser que l'instruction return sur une chose à la fois, et vous l'utilisez déjà avec NO.

+0

Salut, Peter :-) J'espère que vous commencez bien la nouvelle année. En brossant le 'C' à propos de' ** 'j'ai atterri ici sur ce post. Excusez-moi, je ne comprends toujours pas: si nous passions seulement un '*' (un pointeur sur un objet), cela devrait suffire pour apporter des modifications à l'objet, n'est-ce pas? Pourquoi devrions-nous toujours passer un pointeur vers un pointeur vers un objet pour apporter des modifications à un objet? Merci d'avance. Cordialement. – Unheilig

+3

@Unheilig: "si nous passions seulement un' * '(un pointeur sur un objet), cela devrait suffire pour apporter des modifications à l'objet, n'est-ce pas?" Vous avez seulement besoin du pointeur sur l'objet pour envoyer des messages à cet objet. "Pourquoi aurions-nous encore besoin de passer un pointeur sur un pointeur vers un objet pour apporter des modifications à un objet?" Parce que vous n'apportez pas de modifications à un objet; vous créez un nouvel objet et le renvoyez à l'appelant. Vous faites cela en l'assignant à l'adresse que l'appelant vous a donnée - le pointeur vers la variable où vous placerez le pointeur sur l'objet. –

+0

+1. Merci d'avoir éclairci cela aussi. – Unheilig

89

Tout simplement:

si vous passez un pointeur vers un objet à votre fonction, la fonction ne peut modifier ce que le pointeur pointe.

si vous passez un pointeur vers un pointeur vers un objet, la fonction peut modifier le pointeur pour pointer vers un autre objet.

Dans le cas de NSError, la fonction peut vouloir créer un nouvel objet NSError et vous passez en arrière un pointeur vers cet objet NSError. Ainsi, vous avez besoin d'une double indirection pour que le pointeur puisse être modifié.

+7

Cela m'aide vraiment à comprendre. J'ai l'habitude de penser aux pointeurs dans le contexte des langages qui distinguent les arguments "byref" et "byval". Les arguments Byref (ie les variables référencées par des pointeurs) peuvent être changés par la fonction appelée et l'appelant peut voir le changement, donc je n'ai pas compris la réponse de @ n8gray: "si vous aviez seulement un' NSError * '...' doStuff' ne serait jamais capable de renvoyer l'objet d'erreur à son appelant ". Votre réponse "la fonction peut modifier le pointeur pour pointer vers un autre objet" rend clair pourquoi vous voudriez le '**' si l'appelant peut déjà voir les changements avec juste un pointeur. – stifin

6

Une vieille question, mais je pense qu'il est utile de mettre ce ici -

Le véritable coupable est NSError. Si vous regardez sa référence de classe, il n'y a pas de méthodes setter pour aucun de ses attributs, c'est-à-dire, domain, code ou userInfo. Donc, il n'y a aucun moyen, vous pouvez simplement allouer et initialiser une erreur NSError, la passer à la méthode, puis remplir les informations sur l'objet NSError passé. (S'il y avait une méthode setter, nous aurions pu passer un NSError * et faire quelque chose comme error.code = 1 dans la méthode.)

Donc, en cas d'erreur, vous devez générer un nouvel objet NSError dans la méthode et si vous faites la seule façon de le transmettre à l'appelant est d'avoir un argument NSError **. (Pour la raison expliqué dans les réponses ci-dessus.)

0

Je ne ai toujours pas eu l'image complète en lisant toutes les réponses ci-dessus. L'exercice de profane que j'ai fait ci-dessous, m'a finalement aidé à comprendre ce qui se passe.Juste le mettre là-bas au cas où il aide d'autres débutants.

Supposons que vous avez ci-dessous

@interface Class X 
-(void) methodX:(NSMutableArray *)array; 
@end 

Dans une autre partie du code que vous avez la séquence suivante

ClassX *objectX = [[ClassX alloc] init]; 
NSMutableArray *arrayXX = [@[@(1), @(2)] mutableCopy]; 
//What is stored in arrayXX is the address in the heap at which the NSMutableArray object starts, lets call this address ZZZ 
//array starting at address ZZZ in the heap now contains NSNUmbers @1,@2 
[objectX methodX:array] 

Lorsque vous appelez [objectX methodX:array], ce qui est reçu par la méthode est une copie de array. Puisque le tableau contient une adresse (c'est-à-dire un pointeur), la copie est spéciale en ce que ce qui est reçu est une autre variable avec l'adresse ZZZ dedans. Donc, si methodX fait [array removeObjectAtIndex:0], alors l'objet commençant à l'adresse ZZZ est affecté (il ne contient plus qu'un seul NSNUmber @ (2)). Ainsi, lorsque la méthode revient, le tableau d'origine est également affecté. Supposons à la place que methodX fasse array = [@[@(2)] mutableCopy]; alors le tableau original n'est pas affecté. C'est parce que vous n'êtes pas allé dans l'adresse ZZZ et changer quelque chose. Au lieu de cela, vous avez écrasé le ZZZ dans le copie reçu par la méthode à une adresse différente YYY. L'adresse YYY est le début d'un objet NSMUtableArray avec un élément NSNUmber @ (2). L'adresse ZZZ d'origine contient toujours un NSMUtableArray avec deux éléments. @ (1) et @ (2). Ainsi, lorsque la méthode retourne, le tableau d'origine n'est pas affecté.

Questions connexes