@class vs. #import

709

Tengo entendido que uno debe usar una declaración de clase directa en el caso de que ClassA necesite incluir un encabezado de ClassB, y ClassB debe incluir un encabezado de ClassA para evitar inclusiones circulares. También entiendo que an #importes simple, ifndefpor lo que una inclusión solo ocurre una vez.

Mi pregunta es esta: ¿cuándo se usa #importy cuándo se usa @class? A veces, si uso una @classdeclaración, veo una advertencia común del compilador como la siguiente:

warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.

Realmente me encantaría entender esto, en lugar de simplemente eliminar la @classdeclaración de avance y lanzar un #importsilencio para silenciar las advertencias que me está dando el compilador.

Coocoo4Cocoa
fuente
10
La declaración de avance solo le dice al compilador: "Oye, sé que estoy declarando cosas que no reconoces, pero cuando digo @MyClass, prometo que lo # importaré en la implementación".
JoeCortopassi

Respuestas:

754

Si ves esta advertencia:

advertencia: el receptor 'MyCoolClass' es una clase de reenvío y es posible que @interface correspondiente no exista

necesita #importel archivo, pero puede hacerlo en su archivo de implementación (.m) y usar la @classdeclaración en su archivo de encabezado.

@classno elimina (por lo general) la necesidad de #importarchivos, simplemente mueve el requisito más cerca de donde la información es útil.

Por ejemplo

Si dices @class MyCoolClass, el compilador sabe que puede ver algo como:

MyCoolClass *myObject;

No tiene que preocuparse por otra cosa que no MyCoolClasssea ​​una clase válida, y debe reservar espacio para un puntero (en realidad, solo un puntero). Por lo tanto, en su encabezado, es @classsuficiente el 90% del tiempo.

Sin embargo, si alguna vez necesita crear o acceder a myObjectlos miembros, deberá informar al compilador cuáles son esos métodos. En este punto (presumiblemente en su archivo de implementación), tendrá #import "MyCoolClass.h"que decirle al compilador información adicional más allá de "esta es una clase".

Ben Gottlieb
fuente
55
Gran respuesta, gracias. Para futuras referencias: esto también se ocupa de situaciones en las que @classalgo en su .harchivo, pero se olvidan de #importque en el .m, tratar de acceder a un método en el @classobjeto ed, y obtener advertencias como: warning: no -X method found.
Tim
24
Un caso en el que necesitaría #importar en lugar de @class es si el archivo .h incluye tipos de datos u otras definiciones necesarias para la interfaz de su clase.
Ken Aspeslagh
2
Otra gran ventaja que no se menciona aquí es la compilación rápida. Consulte la respuesta de Venkateshwar
MartinMoizard
@BenGottlieb ¿No debería ser esa 'm' en "myCoolClass" mayúscula? Como en "MyCoolClass"?
Basil Bourque
182

Tres reglas simples:

  • Solo #importla superclase y los protocolos adoptados en los archivos de encabezado ( .harchivos).
  • #importtodas las clases y protocolos a los que envía mensajes en la implementación ( .marchivos).
  • Reenviar declaraciones para todo lo demás.

Si reenvía la declaración en los archivos de implementación, entonces probablemente haga algo mal.

PeyloW
fuente
22
En los archivos de encabezado, también puede tener que # importar cualquier cosa que defina un protocolo que adopte su clase.
Tyler
¿Hay alguna diferencia en declarar #import en el archivo de interfaz h o el archivo de implementación m?
Samuel G
Y # import si usa variables de instancia de la clase
user151019
1
@Mark: cubierto por la regla n. ° 1, solo accede a los ivars desde tu superclase, incluso si es así.
PeyloW
@Tyler, ¿por qué no reenviar la declaración del protocolo?
JoeCortopassi
110

Mire la documentación del lenguaje de programación Objective-C en ADC

En la sección sobre Definición de una clase | La interfaz de clase describe por qué se hace esto:

La directiva @class minimiza la cantidad de código visto por el compilador y el enlazador, y es, por lo tanto, la forma más sencilla de dar una declaración directa de un nombre de clase. Siendo simple, evita problemas potenciales que pueden venir con la importación de archivos que todavía importan otros archivos. Por ejemplo, si una clase declara una variable de instancia estáticamente tipada de otra clase, y sus dos archivos de interfaz se importan entre sí, ninguna clase puede compilarse correctamente.

Espero que esto ayude.

Abizern
fuente
48

Utilice una declaración de reenvío en el archivo de encabezado si es necesario, y #importlos archivos de encabezado para cualquier clase que esté utilizando en la implementación. En otras palabras, siempre #importutiliza los archivos que está utilizando en su implementación, y si necesita hacer referencia a una clase en su archivo de encabezado, utilice también una declaración directa.

La excepción a esto es que debería tener #importuna clase o protocolo formal del que está heredando en su archivo de encabezado (en cuyo caso no necesitaría importarlo en la implementación).

Marc Charbonneau
fuente
24

La práctica común es usar @class en archivos de encabezado (pero aún necesita # importar la superclase) e # importar en archivos de implementación. Esto evitará cualquier inclusión circular, y simplemente funciona.

Steph Thirion
fuente
2
Pensé que #import era mejor que #Incluir porque solo importa una instancia.
Matthew Schinckel
2
Cierto. No sé si se trata de inclusiones circulares o de pedidos incorrectos, pero me alejé de esa regla (con una importación en un encabezado, las importaciones ya no eran necesarias en la implementación de subclasse), y pronto se volvió realmente desordenado. En pocas palabras, siga esa regla, y el compilador estará feliz.
Steph Thirion
1
Los documentos actuales dicen que #import"es como la directiva #include de C, excepto que se asegura de que el mismo archivo nunca se incluya más de una vez". Entonces, según esto #importse ocupa de las inclusiones circulares, las @classdirectivas no ayudan particularmente con eso.
Eric
24

Otra ventaja: compilación rápida

Si incluye un archivo de encabezado, cualquier cambio en él hace que el archivo actual también se compile, pero este no es el caso si el nombre de la clase se incluye como @class name. Por supuesto, deberá incluir el encabezado en el archivo fuente

respiradero
fuente
18

Mi consulta es esta. ¿Cuándo se usa #import y cuándo se usa @class?

Respuesta simple: usted #importo #includecuando hay una dependencia física. De lo contrario, se utiliza declaraciones adelantadas ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Aquí hay algunos ejemplos comunes de dependencia física:

  • Cualquier valor de C o C ++ (un puntero o referencia no es una dependencia física). Si tiene un CGPointcomo ivar o propiedad, el compilador deberá ver la declaración de CGPoint.
  • Tu superclase.
  • Un método que usas.

A veces, si uso una declaración @class, veo una advertencia común del compilador como la siguiente: "advertencia: el receptor 'FooController' es una clase de reenvío y la interfaz @ correspondiente puede no existir".

El compilador es realmente muy indulgente a este respecto. Caerá pistas (como la anterior), pero puede tirar basura a la pila fácilmente si las ignora y no lo hace #importcorrectamente. Aunque debería (IMO), el compilador no aplica esto. En ARC, el compilador es más estricto porque es responsable del recuento de referencias. Lo que sucede es que el compilador recurre a un valor predeterminado cuando encuentra un método desconocido al que llama. Se supone que cada valor de retorno y parámetro es id. Por lo tanto, debe erradicar todas las advertencias de sus bases de código porque esto debe considerarse dependencia física. Esto es análogo a llamar a una función C que no se declara. Con C, se supone que los parámetros son int.

La razón por la que preferiría las declaraciones adelantadas es que puede reducir sus tiempos de compilación por factores porque hay una dependencia mínima. Con las declaraciones directas, el compilador ve que hay un nombre y puede analizar y compilar correctamente el programa sin ver la declaración de clase o todas sus dependencias cuando no hay dependencia física. Las construcciones limpias toman menos tiempo. Las construcciones incrementales toman menos tiempo. Claro, terminará pasando un poco más de tiempo asegurándose de que todos los encabezados que necesita sean visibles para cada traducción como consecuencia, pero esto se amortiza en tiempos de construcción reducidos rápidamente (suponiendo que su proyecto no sea pequeño).

Si usa #importo #include, en cambio, está lanzando mucho más trabajo en el compilador de lo necesario. También está introduciendo dependencias complejas de encabezado. Puede comparar esto con un algoritmo de fuerza bruta. Cuando usted #import, está arrastrando toneladas de información innecesaria, que requiere mucha memoria, E / S de disco y CPU para analizar y compilar las fuentes.

ObjC es bastante ideal para un lenguaje basado en C con respecto a la dependencia porque los NSObjecttipos nunca son valores, los NSObjecttipos son siempre punteros contados por referencia. Por lo tanto, puede escapar con tiempos de compilación increíblemente rápidos si estructura las dependencias de su programa de manera adecuada y reenvía donde sea posible porque se requiere muy poca dependencia física. También puede declarar propiedades en las extensiones de clase para minimizar aún más la dependencia. Esa es una gran ventaja para sistemas grandes: sabría la diferencia que hace si alguna vez ha desarrollado una gran base de código C ++.

Por lo tanto, mi recomendación es usar reenvíos siempre que sea posible, y luego a #importdonde haya dependencia física. Si ve la advertencia u otra que implique dependencia física, corríjalas todas. La solución está #importen su archivo de implementación.

A medida que construye bibliotecas, es probable que clasifique algunas interfaces como un grupo, en cuyo caso elegiría #importla biblioteca donde se introduce la dependencia física (por ejemplo #import <AppKit/AppKit.h>). Esto puede introducir dependencia, pero los mantenedores de la biblioteca a menudo pueden manejar las dependencias físicas por usted según sea necesario; si presentan una característica, pueden minimizar el impacto que tiene en sus compilaciones.

justin
fuente
Por cierto buen esfuerzo para explicar las cosas. . pero parecen ser bastante complejas.
Ajay Sharma
NSObject types are never values -- NSObject types are always reference counted pointers.No del todo cierto. Los bloques arrojan un vacío en su respuesta, solo diciendo.
Richard J. Ross III
@ RichardJ.RossIII ... y GCC le permite a uno declarar y usar valores, mientras que clang lo prohíbe. y, por supuesto, debe haber un valor detrás del puntero.
justin
11

Veo mucho "Hazlo de esta manera" pero no veo ninguna respuesta a "¿Por qué?"

Entonces: ¿ Por qué debería @class en su encabezado e #importar solo en su implementación? Estás duplicando tu trabajo al tener que @clase y #importar todo el tiempo. A menos que hagas uso de la herencia. En cuyo caso, #importará varias veces para una sola @clase. Luego debe recordar eliminar de varios archivos diferentes si de repente decide que ya no necesita acceder a una declaración.

Importar el mismo archivo varias veces no es un problema debido a la naturaleza de #import. Compilar el rendimiento tampoco es realmente un problema. Si lo fuera, no estaríamos #importando Cocoa / Cocoa.h o similares en casi todos los archivos de encabezado que tenemos.

Bruce Goodwin
fuente
1
vea la respuesta de Abizem arriba para ver un ejemplo de la documentación de por qué debería hacer esto. Es una programación defensiva para cuando tienes dos encabezados de clase que se importan entre sí con variables de instancia de la otra clase.
Jackslash
7

si hacemos esto

@interface Class_B : Class_A

significa que estamos heredando Class_A en Class_B, en Class_B podemos acceder a todas las variables de class_A.

si estamos haciendo esto

#import ....
@class Class_A
@interface Class_B

aquí decimos que estamos usando Class_A en nuestro programa, pero si queremos usar las variables Class_A en Class_B tenemos que # importar Class_A en el archivo .m (hacer un objeto y usar sus funciones y variables).

Anshuman Mishra
fuente
5

para obtener información adicional sobre las dependencias de archivos y #importación y @clase, consulte esto:

http://qualitycoding.org/file-dependencies/ es un buen artículo

resumen del artículo

importaciones en archivos de encabezado:

  • # importa la superclase que estás heredando y los protocolos que estás implementando.
  • Reenviar todo lo demás (a menos que provenga de un marco con un encabezado maestro).
  • Intenta eliminar todos los demás #imports.
  • Declarar protocolos en sus propios encabezados para reducir las dependencias.
  • ¿Demasiadas declaraciones a futuro? Tienes una clase grande.

importaciones en archivos de implementación:

  • Elimina las # importaciones cruft que no se usan.
  • Si un método delega a otro objeto y devuelve lo que obtiene, intente declarar ese objeto hacia adelante en lugar de # importarlo.
  • Si incluir un módulo lo obliga a incluir nivel tras nivel de dependencias sucesivas, es posible que tenga un conjunto de clases que desee convertirse en una biblioteca. Constrúyalo como una biblioteca separada con un encabezado maestro, para que todo se pueda incorporar como un solo fragmento preconstruido.
  • ¿Demasiadas #importaciones? Tienes una clase grande.
Homam
fuente
3

Cuando me desarrollo, solo tengo tres cosas en mente que nunca me causan ningún problema.

  1. Importar superclases
  2. Importar clases para padres (cuando tiene hijos y padres)
  3. Importe clases fuera de su proyecto (como en frameworks y bibliotecas)

Para todas las demás clases (subclases y clases secundarias en mi propio proyecto), las declaro a través de la clase forward.

Constantino Tsarouhas
fuente
3

Si intenta declarar una variable o una propiedad en su archivo de encabezado, que aún no importó, obtendrá un error que indica que el compilador no conoce esta clase.

Tu primer pensamiento es probablemente #importeso.
Esto puede causar problemas en algunos casos.

Por ejemplo, si implementa un montón de métodos C en el archivo de encabezado, o estructuras, o algo similar, porque no deberían importarse varias veces.

Por lo tanto, puede decirle al compilador con @class:

Sé que no conoces esa clase, pero existe. Será importado o implementado en otro lugar

Básicamente le dice al compilador que se calle y compile, aunque no está seguro si esta clase se implementará alguna vez.

Se suele utilizar #importen el .m y @classen los .h archivos.

IluTov
fuente
0

Reenvíe la declaración solo para evitar que el compilador muestre un error.

el compilador sabrá que hay una clase con el nombre que ha utilizado en su archivo de encabezado para declarar.

karthick
fuente
¿Podrías ser un poco más específico?
Sam Spencer
0

El compilador se quejará solo si va a usar esa clase de tal manera que el compilador necesite saber su implementación.

Ex:

  1. Esto podría ser como si va a derivar su clase de ella o
  2. Si va a tener un objeto de esa clase como una variable miembro (aunque raro).

No se quejará si solo lo va a usar como puntero. Por supuesto, tendrá que # importarlo en el archivo de implementación (si está instanciando un objeto de esa clase) ya que necesita conocer el contenido de la clase para crear una instancia de un objeto.

NOTA: #import no es lo mismo que #include. Esto significa que no hay nada llamado importación circular. importar es una especie de solicitud para que el compilador busque información en un archivo en particular. Si esa información ya está disponible, el compilador la ignora.

Simplemente intente esto, importe Ah en Bh y Bh en Ah No habrá problemas ni quejas y también funcionará bien.

Cuando usar @class

Utiliza @class solo si ni siquiera desea importar un encabezado en su encabezado. Este podría ser un caso en el que ni siquiera te importa saber cuál será esa clase. Casos en los que es posible que ni siquiera tenga un encabezado para esa clase todavía.

Un ejemplo de esto podría ser que está escribiendo dos bibliotecas. Una clase, llamémosla A, existe en una biblioteca. Esta biblioteca incluye un encabezado de la segunda biblioteca. Ese encabezado podría tener un puntero de A, pero nuevamente podría no necesitar usarlo. Si la biblioteca 1 aún no está disponible, la biblioteca B no se bloqueará si usa @class. Pero si está buscando importar Ah, entonces el progreso de la biblioteca 2 está bloqueado.

Deepak GM
fuente
0

Piense en @class como diciéndole al compilador "confía en mí, esto existe".

Piense en #import como copiar y pegar.

Desea minimizar la cantidad de importaciones que tiene por varias razones. Sin ninguna investigación, lo primero que viene a la mente es que reduce el tiempo de compilación.

Tenga en cuenta que cuando hereda de una clase, no puede simplemente usar una declaración directa. Debe importar el archivo para que la clase que declara sepa cómo se define.

Brandon M
fuente
0

Este es un escenario de ejemplo, donde necesitamos @class.

Considere si desea crear un protocolo dentro del archivo de encabezado, que tiene un parámetro con el tipo de datos de la misma clase, entonces puede usar @class. Recuerde que también puede declarar protocolos por separado, esto es solo un ejemplo.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Sujananth
fuente