Sí, hay beneficios de usar instancetype
en todos los casos donde se aplica. Explicaré con más detalle, pero permítanme comenzar con esta declaración en negrita: useinstancetype
siempre que sea apropiado, que es cuando una clase devuelve una instancia de esa misma clase.
De hecho, esto es lo que Apple dice ahora sobre el tema:
En su código, reemplace las ocurrencias de id
como valor de retorno por instancetype
donde corresponda. Este suele ser el caso de los init
métodos y métodos de clase de fábrica. Aunque el compilador convierte automáticamente los métodos que comienzan con "alloc", "init" o "new" y tienen un tipo de id
retorno de return instancetype
, no convierte otros métodos. La convención Objective-C es escribir instancetype
explícitamente para todos los métodos.
Con eso fuera del camino, sigamos adelante y expliquemos por qué es una buena idea.
Primero, algunas definiciones:
@interface Foo:NSObject
- (id)initWithBar:(NSInteger)bar; // initializer
+ (id)fooWithBar:(NSInteger)bar; // class factory
@end
Para una fábrica de clase, siempre debe usar instancetype
. El compilador no se convierte automáticamente id
a instancetype
. Ese id
es un objeto genérico. Pero si lo haces uninstancetype
compilador, sabe qué tipo de objeto devuelve el método.
Este no es un problema académico. Por ejemplo, [[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData]
generará un error en Mac OS X ( solo ) Múltiples métodos llamados 'writeData:' encontrados con resultados no coincidentes, tipo de parámetro o atributos . La razón es que tanto NSFileHandle como NSURLHandle proporcionan a writeData:
. Como [NSFileHandle fileHandleWithStandardOutput]
devuelve un id
, el compilador no está seguro de qué clasewriteData:
se está llamando.
Necesita solucionar esto, usando:
[(NSFileHandle *)[NSFileHandle fileHandleWithStandardOutput] writeData:formattedData];
o:
NSFileHandle *fileHandle = [NSFileHandle fileHandleWithStandardOutput];
[fileHandle writeData:formattedData];
Por supuesto, la mejor solución es declarar fileHandleWithStandardOutput
que devuelve uninstancetype
. Entonces el reparto o la tarea no es necesaria.
(Tenga en cuenta que en iOS, este ejemplo no producirá un error, ya que solo NSFileHandle
proporciona un writeData:
allí. Existen otros ejemplos, como, por ejemplo length
, que devuelve un CGFloat
de UILayoutSupport
pero un NSUInteger
de NSString
).
Nota : desde que escribí esto, los encabezados de macOS se han modificado para devolver un en NSFileHandle
lugar de un id
.
Para los inicializadores, es más complicado. Cuando escribes esto:
- (id)initWithBar:(NSInteger)bar
... el compilador fingirá que escribiste esto en su lugar:
- (instancetype)initWithBar:(NSInteger)bar
Esto era necesario para ARC. Esto se describe en Extensiones de lenguaje Clang Tipos de resultados relacionados . Es por eso que la gente le dirá que no es necesario usarlo instancetype
, aunque yo afirmo que debería hacerlo. El resto de esta respuesta trata de esto.
Hay tres ventajas:
- Explícito. Su código está haciendo lo que dice, en lugar de otra cosa.
- Patrón. Estás creando buenos hábitos para los tiempos que importan, que existen.
- Consistencia. Ha establecido cierta consistencia en su código, lo que lo hace más legible.
Explícito
Es cierto que no hay ningún beneficio técnico al regresar instancetype
de un init
. Pero esto se debe a que el compilador convierte automáticamente el id
a instancetype
. Estás confiando en este capricho; mientras escribe que init
devuelve un id
, el compilador lo interpreta como si devolviera uninstancetype
.
Estos son equivalentes al compilador:
- (id)initWithBar:(NSInteger)bar;
- (instancetype)initWithBar:(NSInteger)bar;
Estos no son equivalentes a tus ojos. En el mejor de los casos, aprenderá a ignorar la diferencia y pasarla por alto. Esto no es algo que debas aprender a ignorar.
Patrón
Si bien no hay ninguna diferencia con init
otros métodos, no es una diferencia tan pronto como se define un generador de clases.
Estos dos no son equivalentes:
+ (id)fooWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Quieres la segunda forma. Si está acostumbrado a escribir instancetype
como el tipo de retorno de un constructor, siempre lo hará bien.
Consistencia
Finalmente, imagina si lo pones todo junto: quieres una init
función y también una fábrica de clase.
Si usas id
para init
, terminas con un código como este:
- (id)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Pero si usas instancetype
, obtienes esto:
- (instancetype)initWithBar:(NSInteger)bar;
+ (instancetype)fooWithBar:(NSInteger)bar;
Es más consistente y más legible. Vuelven lo mismo, y ahora eso es obvio.
Conclusión
A menos que esté escribiendo código intencionalmente para compiladores antiguos, debe usarlo instancetype
cuando sea apropiado.
Debes dudar antes de escribir un mensaje que regrese id
. Pregúntese: ¿Esto devuelve una instancia de esta clase? Si es así, es un instancetype
.
Ciertamente, hay casos en los que necesita regresar id
, pero probablemente lo usará con instancetype
mucha más frecuencia.