Sim, há benefícios em usar instancetype
em todos os casos em que se aplica. Explicarei com mais detalhes, mas deixe-me começar com esta declaração em negrito: Use instancetype
sempre que apropriado, ou seja, sempre que uma classe retornar uma instância dessa mesma classe.
De fato, eis o que a Apple diz agora sobre o assunto:
No seu código, substitua as ocorrências id
como um valor de retorno por instancetype
onde apropriado. Normalmente, esse é o caso dos init
métodos e métodos de fábrica de classe. Mesmo que o compilador converta automaticamente métodos que começam com "alocar", "init" ou "novo" e têm um tipo de id
retorno a ser retornado instancetype
, ele não converte outros métodos. A convenção Objective-C é escrever instancetype
explicitamente para todos os métodos.
Com isso fora do caminho, vamos seguir em frente e explicar por que é uma boa ideia.
Primeiro, algumas definições:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Para uma fábrica de classe, você deve sempre usar instancetype
. O compilador não converte automaticamente id
para instancetype
. Esse id
é um objeto genérico. Mas se você o fizer, instancetype
o compilador saberá que tipo de objeto o método retornará.
Este não é um problema acadêmico. Por exemplo, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
gerará um erro no Mac OS X ( apenas ) Vários métodos denominados 'writeData:' encontrados com resultado, tipo de parâmetro ou atributos incompatíveis . O motivo é que NSFileHandle e NSURLHandle fornecem um writeData:
. Como [NSFileHandle fileHandleWithStandardOutput]
retorna um id
, o compilador não tem certeza de qual classe writeData:
está sendo chamada.
Você precisa contornar isso, usando:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
ou:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Obviamente, a melhor solução é declarar fileHandleWithStandardOutput
como retornando um instancetype
. Então o elenco ou a tarefa não são necessários.
(Observe que no iOS, este exemplo não produzirá um erro, pois apenas NSFileHandle
fornece um writeData:
lá. Existem outros exemplos, como length
, que retorna um CGFloat
de UILayoutSupport
mas um NSUInteger
de NSString
.)
Nota : Desde que escrevi isso, os cabeçalhos do macOS foram modificados para retornar um em NSFileHandle
vez de um id
.
Para inicializadores, é mais complicado. Quando você digita isso:
- (id)initWithBar:(NSInteger)bar
… O compilador fingirá que você digitou isso:
- (instancetype)initWithBar:(NSInteger)bar
Isso foi necessário para o ARC. Isso é descrito em Tipos de resultados relacionados ao Clang Language Extensions . É por isso que as pessoas lhe dirão que não é necessário usá-lo instancetype
, embora eu afirme que você deveria. O restante desta resposta lida com isso.
Há três vantagens:
- Explícito. Seu código está fazendo o que diz, em vez de outra coisa.
- Padronizar. Você está construindo bons hábitos para tempos que importam e que existem.
- Consistência. Você estabeleceu alguma consistência no seu código, o que o torna mais legível.
Explícito
É verdade que não há benefício técnico em retornar instancetype
de um init
. Mas isso ocorre porque o compilador converte automaticamente o id
para instancetype
. Você está confiando nessa peculiaridade; enquanto você escreve que init
retorna um id
, o compilador o interpreta como se retornasse um instancetype
.
Eles são equivalentes ao compilador:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Estes não são equivalentes aos seus olhos. Na melhor das hipóteses, você aprenderá a ignorar a diferença e examiná-la. Isso não é algo que você deve aprender a ignorar.
padronizar
Enquanto não há nenhuma diferença com init
e outros métodos, não é uma diferença assim que você definir uma fábrica de classe.
Estes dois não são equivalentes:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Você quer o segundo formulário. Se você está acostumado a digitar instancetype
como o tipo de retorno de um construtor, sempre acertará.
Consistência
Por fim, imagine se você juntar tudo: deseja uma init
função e também uma fábrica de classes.
Se você usar id
para init
, você acaba com um código como este:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Mas se você usar instancetype
, você obtém o seguinte:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
É mais consistente e mais legível. Eles retornam a mesma coisa, e agora isso é óbvio.
Conclusão
A menos que você esteja intencionalmente escrevendo código para compiladores antigos, use-o instancetype
quando apropriado.
Você deve hesitar antes de escrever uma mensagem que retorne id
. Pergunte a si mesmo: isso está retornando uma instância desta classe? Se sim, é um instancetype
.
Certamente há casos em que você precisa retornar id
, mas provavelmente usará com instancetype
muito mais frequência.