Clang agrega una palabra clave instancetypeque, por lo que puedo ver, reemplaza idcomo un tipo de retorno en -allocy init.
¿Hay algún beneficio en usar en instancetypelugar de id?
fuente
Clang agrega una palabra clave instancetypeque, por lo que puedo ver, reemplaza idcomo un tipo de retorno en -allocy init.
¿Hay algún beneficio en usar en instancetypelugar de id?
Definitivamente hay un beneficio. Cuando usas 'id', básicamente no obtienes ninguna verificación de tipo. Con el tipo de instancia, el compilador y el IDE saben qué tipo de cosas se devuelven, y pueden verificar su código mejor y autocompletar mejor.
Úselo solo donde tenga sentido, por supuesto (es decir, un método que devuelve una instancia de esa clase); Identificación sigue siendo útil.
alloc, init, Etc., están promovidos automáticamente instancetypepor el compilador. Eso no quiere decir que no haya beneficio; hay, pero no es esto.
Sí, hay beneficios de usar instancetypeen 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
idcomo valor de retorno porinstancetypedonde corresponda. Este suele ser el caso de losinitmé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 deidretorno de returninstancetype, no convierte otros métodos. La convención Objective-C es escribirinstancetypeexplí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 ida instancetype. Ese ides 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 fileHandleWithStandardOutputque 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 NSFileHandleproporciona un writeData:allí. Existen otros ejemplos, como, por ejemplo length, que devuelve un CGFloatde UILayoutSupportpero un NSUIntegerde NSString).
Nota : desde que escribí esto, los encabezados de macOS se han modificado para devolver un en NSFileHandlelugar 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:
Es cierto que no hay ningún beneficio técnico al regresar instancetypede un init. Pero esto se debe a que el compilador convierte automáticamente el ida instancetype. Estás confiando en este capricho; mientras escribe que initdevuelve 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.
Si bien no hay ninguna diferencia con initotros 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 instancetypecomo el tipo de retorno de un constructor, siempre lo hará bien.
Finalmente, imagina si lo pones todo junto: quieres una initfunción y también una fábrica de clase.
Si usas idpara 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.
A menos que esté escribiendo código intencionalmente para compiladores antiguos, debe usarlo instancetypecuando 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 instancetypemucha más frecuencia.
instancetypevs idrealmente no es una decisión de estilo. Los cambios recientes instancetyperealmente dejan en claro que debemos usar instancetypeen lugares como -initdonde queremos decir 'una instancia de mi clase'
Las respuestas anteriores son más que suficientes para explicar esta pregunta. Solo me gustaría agregar un ejemplo para que los lectores lo entiendan en términos de codificación.
Clase A
@interface ClassA : NSObject
- (id)methodA;
- (instancetype)methodB;
@end
Clase B
@interface ClassB : NSObject
- (id)methodX;
@end
TestViewController.m
#import "ClassA.h"
#import "ClassB.h"
- (void)viewDidLoad {
[[[[ClassA alloc] init] methodA] methodX]; //This will NOT generate a compiler warning or error because the return type for methodA is id. Eventually this will generate exception at runtime
[[[[ClassA alloc] init] methodB] methodX]; //This will generate a compiler error saying "No visible @interface ClassA declares selector methodX" because the methodB returns instanceType i.e. the type of the receiver
}
También puede obtener detalles en The Designated Initializer
** **
** Esta palabra clave solo se puede usar para el tipo de retorno, que coincide con el tipo de retorno del receptor. El método init siempre declara devolver el tipo de instancia. ¿Por qué no hacer que el tipo de devolución Party para party, por ejemplo? Eso causaría un problema si la clase Party alguna vez se subclasificara. La subclase heredaría todos los métodos de Party, incluido el inicializador y su tipo de retorno. Si a una instancia de la subclase se le envió este mensaje inicializador, ¿eso sería devuelto? No es un puntero a una instancia de Party, sino un puntero a una instancia de subclase. Puede pensar que no hay problema, anularé el inicializador en la subclase para cambiar el tipo de retorno. Pero en Objective-C, no puede tener dos métodos con el mismo selector y diferentes tipos de retorno (o argumentos). Al especificar que un método de inicialización devuelve "
** Antes de que se introdujera el tipo de instancia en Objective-C, los inicializadores devuelven id (eye-dee). Este tipo se define como "un puntero a cualquier objeto". (id es muy parecido a void * en C.) Al momento de escribir esto, las plantillas de clase XCode todavía usan id como el tipo de retorno de inicializadores agregados en el código repetitivo. A diferencia del tipo de instancia, id puede usarse como algo más que un tipo de retorno. Puede declarar variables o parámetros de método de identificación de tipo cuando no está seguro de a qué tipo de objeto la variable terminará apuntando. Puede usar id cuando usa la enumeración rápida para iterar sobre una matriz de tipos de objetos múltiples o desconocidos. Tenga en cuenta que debido a que la identificación no está definida como "un puntero a ningún objeto", no incluye un * al declarar una variable o parámetro de objeto de este tipo.
idse han reemplazado muchos métodos que solían regresarinstancetype, inclusoinitdesdeNSObject. Si desea que su código sea compatible con Swift, debe usarloinstancetype