2009-12-01 3 views
11

Chaque fois que j'implémente une méthode dans mon propre code qui peut accepter ou retourner des objets de plus d'une classe, j'essaie toujours d'utiliser la superclasse la plus spécifique disponible. Par exemple, si j'allais implémenter une méthode qui pourrait retourner un NSArray * ou un NSDictionary * en fonction de son entrée, je donnerais à cette méthode un type de retour de NSObject *, puisque c'est la superclasse la plus directe. Voici un exemple:Pourquoi utiliser (id) dans une signature de méthode quand (NSObject *) serait plus précis?

@interface MyParser() 
- (BOOL)stringExpressesKeyValuePairs:(NSString *)string; 
- (BOOL)stringExpressesAListOfEntities:(NSString *)string; 
- (NSArray *)parseArrayFromString:(NSString *)string; 
- (NSDictionary *)parseDictionaryFromString:(NSString *)string; 
@end 

@implementation MyParser 
- (NSObject *)parseString:(NSString *)string { 
    if ([self stringExpressesKeyValuePairs:string]) { 
     return [self parseDictionaryFromString:string]; 
    } 
    else if ([self stringExpressesAListOfEntities:string]) { 
     return [self parseArrayFromString:string]; 
    } 
} 
// etc... 
@end 

J'ai remarqué de nombreux cas dans Foundation et d'autres API où les utilisations d'Apple (id) dans certaines signatures de méthode quand (NSObject *) seraient plus précis. Par exemple, voici une méthode de NSPropertyListSerialization:

+ (id)propertyListFromData:(NSData *)data 
      mutabilityOption:(NSPropertyListMutabilityOptions)opt 
        format:(NSPropertyListFormat *)format 
      errorDescription:(NSString **)errorString 

Les types de retour possibles de cette méthode sont NSData, NSString, NSArray, NSDictionary, NSDate et NSNumber. Il me semble qu'un type de retour de (NSObject *) serait un meilleur choix que (id), puisque l'appelant serait alors capable d'appeler des méthodes NSObject comme retain sans cast.

En général, j'essaie d'imiter les idiomes établis par les cadres officiels, mais j'aime aussi comprendre ce qui les motive. Je suis sûr que Apple a une raison valable d'utiliser (id) dans des cas comme celui-ci, mais je ne le vois tout simplement pas. Qu'est-ce que je rate?

+0

NSProxy n'est pas un objet NSObject. NSObject n'est pas la seule classe racine, donc ce n'est pas une supposition sûre que tout sera une sous-classe. –

Répondre

8

L'utilisation de id indique au compilateur qu'il s'agit d'un objet de type inconnu. À l'aide de NSObject, le compilateur s'attendrait à ce que vous utilisiez uniquement les messages disponibles pour NSObject. Donc ... Si vous savez qu'un tableau a été retourné et qu'il est converti en id, vous pouvez appeler objectAtIndex: sans les avertissements du compilateur. En revenant avec une distribution de NSObject, vous obtiendrez des avertissements.

1

Vous pouvez déjà appeler -retain sur des pointeurs de type id sans conversion. Si vous utilisez un type de superclasse spécifique, vous devrez lancer le pointeur chaque fois que vous appelez la méthode d'une sous-classe afin d'éviter les avertissements du compilateur. Utilisez id pour que le compilateur ne vous avertisse pas et pour mieux indiquer votre intention.

19

La raison pour laquelle (id) est utilisé dans les déclarations de méthode est double:

(1) Le procédé peut prendre ou remettre tout type. NSArray contient n'importe quel objet aléatoire et, par conséquent, objectAtIndex: retournera un objet de n'importe quel type aléatoire. Le lancer à NSObject* ou id <NSObject> serait incorrect pour deux raisons; Premièrement, un tableau peut contenir des sous-classes non NSObject tant qu'elles implémentent un petit ensemble de méthodes et, deuxièmement, un type de retour spécifique nécessiterait un cast.

(2) Objective-C ne supporte pas les déclarations covariantes. Prendre en compte:

@interface NSArray:NSObject 
+ (id) array; 
@end 

Maintenant, vous pouvez appeler +array sur les deux NSArray et NSMutableArray. Le premier renvoie un tableau immuable et le second un tableau mutable. En raison de l'absence de prise en charge des déclarations covariantes par Objective-C, si les éléments ci-dessus ont été déclarés comme renvoyant (NSArray*), les clients de la méthode des sous-classes doivent effectuer un cast en `(NSMutableArray *). Laide, fragile et sujette aux erreurs. Ainsi, l'utilisation du type générique est généralement la solution la plus simple. Donc ... si vous déclarez une méthode qui renvoie une instance d'une classe spécifique, transtypez-la explicitement. Si vous déclarez une méthode qui sera substituée et que cette substitution peut renvoyer une sous-classe et le fait qu'elle renvoie une sous-classe sera exposé aux clients, puis utilisez (id).

Pas besoin de déposer un bug - il y en a déjà plusieurs.


Notez que ObjC a limité le soutien de co-variance par le mot-clé instancetype.

I.e. méthode de tableau de NSArray + pourrait maintenant être déclaré:

+ (instancetype) array; 

Et le compilateur traiterait [NSMutableArray array] comme un retour NSMutableArray* alors [NSArray array] serait considéré comme revenant NSArray*.

1

(id) est également souvent retourné afin de permettre aux objets d'être sous-classés plus facilement. Par exemple, dans les méthodes d'initialisation et de commodité, return (id) signifie que n'importe quelle sous-classe n'a pas à redéfinir les méthodes de la super-classe, sauf s'il y a une raison spécifique de le faire.

Questions connexes