Por que usar (id) em uma assinatura de método quando (NSObject *) seria mais preciso?
-
11-09-2019 - |
Pergunta
Sempre que eu implementar um método no meu próprio código que pode aceitar ou objetos de retorno de mais de uma classe, eu sempre tentar usar o mais específico superclasse disponível. Por exemplo, se eu estivesse indo para implementar um método que pode retornar um NSArray * ou um NSDictionary * dependendo de sua entrada, eu daria esse método um tipo de retorno de NSObject *, já que é a superclasse comum mais direta. Aqui está um exemplo:
@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
Eu observei muitos casos em Fundação e outras APIs quando as utilizações da Apple (ID) em determinadas assinaturas de método quando (NSObject *) seria mais preciso. Por exemplo, aqui está um método de NSPropertyListSerialization:
+ (id)propertyListFromData:(NSData *)data
mutabilityOption:(NSPropertyListMutabilityOptions)opt
format:(NSPropertyListFormat *)format
errorDescription:(NSString **)errorString
Os possíveis tipos de retorno deste método são NSData, NSString, NSArray, NSDictionary, NSDate e NSNumber. Parece-me que um tipo de retorno (NSObject *) seria uma escolha melhor do que (id), uma vez que o autor da chamada, então, seria capaz de chamar métodos NSObject como manter sem um tipo de-cast.
Eu geralmente tentam imitar as expressões idiomáticas estabelecidos pelas estruturas oficiais, mas também gosto de entender o que os motiva. Tenho certeza de que a Apple tem alguma razão válida para usar (id) em casos como este, mas eu não estou vendo isso. O que eu estou ausente?
Solução
Usando id informa ao compilador que vai ser um objeto de tipo desconhecido. Usando NSObject o compilador, então, espero que você esteja usando apenas mensagens disponíveis para NSObject. Então ... Se você conhece um array foi devolvido e está escalado como id, você pode chamar objectAtIndex: sem avisos do compilador. Considerando retornar com um elenco de NSObject, você vai ter avisos.
Outras dicas
A razão pela qual (ID) é utilizado em declarações de método é dupla:
(1) O método pode pegar ou devolver qualquer tipo. NSArray contém qualquer objeto aleatória e, deste modo, objectAtIndex:
retornará um objecto de qualquer tipo aleatório. Lançando-a NSObject*
ou id <NSObject>
seria incorreto por duas razões; em primeiro lugar, uma matriz pode conter subclasses não NSObject enquanto eles implementar um certo pequeno conjunto de métodos e, por outro, um tipo de retorno específica exigiria casting.
(2) Objective-C não suporta declarações covariantes. Considere o seguinte:
@interface NSArray:NSObject
+ (id) array;
@end
Agora, você pode chamar +array
em ambos NSArray
e NSMutableArray
. Os antigos retorna uma matriz imutável e a uma matriz mutável último. Por causa da falta de apoio declaração covariante de Objective-C, se o acima foram declaradas como retornando (NSArray*)
, os clientes do método subclasses teria de elenco para `(NSMutableArray *). Feio, frágil e propenso a erros. Assim, usando o tipo genérico é, em geral, a solução mais simples.
Então ... se você está declarando um método que retorna uma instância de uma classe específica, typecast explicitamente. Se você está declarando um método que será substituído e que a substituição pode retornar uma subclasse e o fato de que ele retorna uma subclasse será exposto aos clientes, em seguida, usar (id)
.
Não há necessidade de registrar um bug - existem vários já
Note que ObjC agora tem suporte co-variância limitada através da palavra-chave instancetype
.
i. Método + variedade de NSArray
poderia agora ser declarado como:
+ (instancetype) array;
E o compilador iria tratar [NSMutableArray array]
como retornando um NSMutableArray*
enquanto [NSArray array]
seria considerado como retornando NSArray*
.
Você já pode chamar -retain em ponteiros do tipo id sem lançar. Se você usar o tipo de uma específica superclasse, você vai ter que converter o ponteiro toda vez que você chamar o método de uma subclasse, a fim de evitar os avisos do compilador. Use id para que o compilador não irá avisá-lo e para melhor significar sua intenção.
(id) também é muitas vezes voltou a fim de permitir objetos para ser uma subclasse com mais facilidade. Por exemplo, em métodos inicializador e conveniência, retornando meios (id) que qualquer subclasse não tem que substituir os métodos da superclasse a menos que haja uma razão específica para fazê-lo.