2009-12-15 23 views
7

no tengo idea de por qué, pero en ocasiones me las he arreglado para corregir algunos errores de compilación, más notablementeCacao: ¿Cuál es la diferencia entre importar en el encabezado e importar en el archivo principal?

error expected specifier-qualifier-list before 'someClass' 

moviendo #import "someClass.h" desde el archivo .h en el archivo .m. Esto también ha funcionado con un par de otros problemas que he encontrado que han sido (misteriosamente desde mi punto de vista) relacionados con los encabezados.

Algunas palabras rápidas han encontrado la respuesta "nunca importar encabezados en el archivo de encabezado" y ahí es donde se detiene el consejo.

O bien lo he inventado por completo, o he adquirido el hábito de alguna parte, pero pensé que el encabezado era donde se suponía que se iban a importar los encabezados. Claramente no, pero ¿alguien puede explicarme por qué es así y cuál es la forma preferida de importar encabezados?

Respuesta

10

A menos que seas heredero de la clase que estás incluyendo, no deberías incluir encabezados en los encabezados. Si necesita incluir un objeto como una variable de interfaz, debe usar la directiva @class; eso le dirá al compilador que el identificador se refiere a una clase.

En su lugar, importe los encabezados solo en los archivos de implementación. El compilador sabrá que las variables de su instancia son punteros a los objetos, pero no conoce los detalles del objeto al analizar el encabezado. Todo lo que necesita saber es que es una clase. El compilador puede ver los métodos de la clase al analizar su archivo de implementación; en ese punto, sí necesita la clase, para verificar que responda a los mensajes que está enviando.


Actualización: que iba a actualizar mi respuesta para responder a algunas preguntas posteriores, pero Rob Napier has a good follow-up.

+1

¿Qué pasa con 'typedef's y' protocols'? – Joost

+0

¿No sabría usted de un tutorial, o una muestra de código que usa esto? – gargantuan

13

John da buenos consejos, pero aquí hay un poco más de antecedentes sobre los por qué, dónde y las excepciones.

Existen dos objetivos detrás de evitar la importación de encabezados en los encabezados: mejores tiempos de compilación incrementales y la evitación de dependencias circulares. Si importa A.h en B.h e importar B.h en C.h, entonces cada vez que cambie nada en A.h, usted tiene que volver a compilar C.m, incluso si C.m no hace uso de ninguna de las cosas definidas en A.h. Esto puede llevar a un abandono de la construcción realmente terrible e innecesario, especialmente si tiene encabezados que cambian con frecuencia (como es común en las primeras etapas de desarrollo).

El primer objetivo es loable, pero para proyectos pequeños, ¿a quién le importa? Tienes un Pro de cuatro núcleos y una compilación completa toma un par de minutos, ¿verdad? Pero aún debe preocuparse por el segundo problema: dependencias circulares. A.h referencias clase B y B.h referencias clase A. En realidad, esto puede suceder con bastante frecuencia y puede introducirse en un sistema de forma bastante inocente. Un objeto de colección podría hacer referencia al tipo de objetos que contiene, y los objetos podrían hacer referencia al tipo del objeto de colección. Todo lo que se necesita es una sola referencia debido a algún método que toma o devuelve ese tipo. Si tiene encabezados que importan otros encabezados, la probabilidad de que esto ocurra rápidamente se aproxima a la unidad. Terminas con importaciones recursivas, y los errores en tiempo de compilación pueden ser alucinantes. "I saber que typdef está definido!¡Está justo ahí! Se importa!" Sin embargo, no se ha analizado aún cuando se ha importado esta cabecera. Esto es lo que está causando su error anterior.

Por esta razón, incluso si no se preocupan mucho acerca de los tiempos de construcción (aunque usted debe), evitar la importación de cabeceras en las cabeceras ... excepto ....

Algunos encabezados que tiene importar. Su superclase, por supuesto. Los archivos que definen un @protocol implementar o typedef que utilice. Así que sí, tiene que incluir esos.

¿Y qué hay de los encabezados del sistema? Bueno, nunca van a c Ause mantequera, y obviamente no van a causar importaciones recursivas, entonces están bien. No recomiendo a las personas que usen el @class declaraciones anticipadas para cosas en los encabezados del sistema. Crea trabajo adicional en el usuario de su encabezado sin ningún valor. Para una buena higiene del cabezal, recuerde encerrar los encabezados del sistema en < escuadras angulares > y sus encabezados entre comillas.

Así que no es una pregunta trivial, pero la regla simple es: evitar importar encabezados de usuario en otros encabezados de usuario cada vez que el compilador lo permita.

+0

muchas gracias por eso. – gargantuan

0

No solo incluirá su encabezado en su archivo de implementación sino también en otros archivos que usen esa clase. Si incluye todas las dependencias en el encabezado, todos los demás archivos, incluido el encabezado solo para usar la clase específica que define, también incluirán todas las dependencias.

Esto no solo es ruidoso sino que ocasiona problemas cuando las clases se referencian a sí mismas. Cada clase tendría que ser importada antes que la otra, lo que lleva a que una sea importada primero y luego no pueda encontrar la otra clase. Esto da como resultado el mensaje de error críptico que publicó, lo que básicamente significa que el compilador no puede encontrar el tipo someClass.

Al mover sus importaciones a su archivo de implementación y predecir clases y tipos en su encabezado (usando @class, @protocol, etc.) puede evitar estos problemas.

Cuestiones relacionadas