web-dev-qa-db-fra.com

@class vs. #import

Si je comprends bien, il faut utiliser une déclaration de classe de transfert dans le cas où ClassA doit inclure un en-tête ClassB et ClassB doit inclure un en-tête ClassA pour éviter toute inclusion circulaire. Je comprends aussi qu'un #import est un simple ifndef de sorte qu'un include ne se produit qu'une fois.

Ma question est la suivante: quand utilise-t-on #import et quand on utilise @class? Parfois, si j'utilise une déclaration @class, un avertissement du compilateur commun, tel que celui-ci, s'affiche:

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

J'aimerais vraiment comprendre ceci, par rapport à la suppression de la @class déclaration en aval et au lancement d'un #import pour faire taire les avertissements que le compilateur me donne.

706
Coocoo4Cocoa

Si vous voyez cet avertissement:

avertissement: le destinataire 'MyCoolClass' est une classe de transfert et il est possible que @interface correspondante n'existe pas

vous devez #import le fichier, mais vous pouvez le faire dans votre fichier d'implémentation (.m) et utiliser la déclaration @class dans votre fichier d'en-tête.

@class ne supprime pas (généralement) le besoin de fichiers #import, il déplace simplement l'exigence plus près de l'endroit où l'information est utile.

Par exemple

Si vous dites @class MyCoolClass, le compilateur sait qu'il peut voir quelque chose comme:

MyCoolClass *myObject;

Il n'a pas à s'inquiéter de rien d'autre que MyCoolClass est une classe valide, et il devrait réserver de la place pour un pointeur dessus (en réalité, juste un pointeur). Ainsi, dans votre en-tête, @class suffit 90% du temps.

Toutefois, si vous avez besoin de créer ou d'accéder à des membres de myObject, vous devez indiquer au compilateur la nature de ces méthodes. À ce stade (vraisemblablement dans votre fichier d'implémentation), vous aurez besoin de #import "MyCoolClass.h", pour indiquer au compilateur des informations supplémentaires au-delà de "c'est une classe".

750
Ben Gottlieb

Trois règles simples:

  • Seulement #import la super classe et les protocoles adoptés dans les fichiers d'en-tête (fichiers .h).
  • #import toutes les classes et tous les protocoles auxquels vous envoyez des messages dans l'implémentation (fichiers .m).
  • Déclarations en aval pour tout le reste.

Si vous transmettez une déclaration dans les fichiers d'implémentation, vous ferez probablement quelque chose de mal.

183
PeyloW

Consultez la documentation du langage de programmation Objective-C sur ADC

Sous la section Définir une classe | Interface de classe, il explique pourquoi cela est fait:

La directive @class minimise la quantité de code vue par le compilateur et l'éditeur de liens. Elle constitue donc le moyen le plus simple de donner une déclaration en aval d'un nom de classe. Étant simple, il évite les problèmes potentiels liés à l’importation de fichiers qui importent encore d’autres fichiers. Par exemple, si une classe déclare une variable d'instance de type statique d'une autre classe et que leurs deux fichiers d'interface s'importent, aucune des classes ne peut être compilée correctement.

J'espère que ça aide.

110
Abizern

Utilisez une déclaration forward dans le fichier d'en-tête si nécessaire et #import les fichiers d'en-tête de toutes les classes que vous utilisez dans l'implémentation. En d'autres termes, vous toujours #import les fichiers que vous utilisez dans votre implémentation, et si vous avez besoin de référencer une classe dans votre fichier d'en-tête, utilisez également une déclaration forward.

La exception à ceci est que vous devez #import une classe ou un protocole formel dont vous héritez dans votre fichier d'en-tête (dans ce cas, vous n'avez pas besoin de l'importer dans l'implémentation) .

47
Marc Charbonneau

La pratique courante consiste à utiliser @class dans les fichiers d'en-tête (mais vous devez toujours # importer la superclasse) et #import dans les fichiers d'implémentation. Cela évitera les inclusions circulaires et cela fonctionne.

24
Steph Thirion

Autre avantage: compilation rapide

Si vous incluez un fichier d'en-tête, toute modification apportée à celui-ci entraîne la compilation du fichier actuel, mais ce n'est pas le cas si le nom de la classe est inclus sous la forme @class name. Bien sûr, vous aurez besoin d'inclure l'en-tête dans le fichier source

24
vent

Mon enquête est la suivante. Quand utilise-t-on # import et quand utilise-t-on @class?

Réponse simple: vous #import ou #include lorsqu'il existe une dépendance physique. Sinon, vous utilisez des déclarations en aval (@class MONClass, struct MONStruct, @protocol MONProtocol).

Voici quelques exemples courants de dépendance physique:

  • Toute valeur C ou C++ (un pointeur ou une référence n'est pas une dépendance physique). Si vous avez un CGPoint sous la forme d'un ivar ou d'une propriété, le compilateur devra voir la déclaration de CGPoint.
  • Votre super-classe.
  • Une méthode que vous utilisez.

Parfois, si j'utilise une déclaration @class, je vois un avertissement courant du compilateur, tel que: "avertissement: le destinataire 'FooController' est une classe de transfert et @interface correspondante peut ne pas exister."

Le compilateur est en réalité très indulgent à cet égard. Il laissera tomber des allusions (comme celle ci-dessus), mais vous pouvez facilement jeter votre pile si vous les ignorez et ne #import pas correctement. Bien que ce soit le cas (IMO), le compilateur ne l’impose pas. Dans ARC, le compilateur est plus strict car il est responsable du comptage des références. Qu'est-ce qui se passe est que le compilateur retombe sur un défaut quand il rencontre une méthode inconnue que vous appelez. Chaque valeur et paramètre de retour est supposé être id. Ainsi, vous devriez éliminer tous les avertissements de vos bases de code car cela devrait être considéré comme une dépendance physique. Ceci est analogue à l'appel d'une fonction C non déclarée. Avec C, les paramètres sont supposés être int.

Si vous préférez les déclarations en aval, vous pouvez réduire votre temps de génération par facteurs, car la dépendance est minimale. Avec les déclarations en aval, le compilateur voit qu'il y a un nom et peut analyser et compiler correctement le programme sans voir la déclaration de classe ni toutes ses dépendances lorsqu'il n'y a pas de dépendance physique. Les constructions propres prennent moins de temps. Les constructions incrémentielles prennent moins de temps. Bien sûr, vous finirez par passer un peu plus de temps à vous assurer que toutes les en-têtes dont vous avez besoin sont visibles pour chaque traduction, mais cela vous rapportera moins de temps de construction (en supposant que votre projet ne soit pas minuscule).

Si vous utilisez plutôt #import ou #include, le compilateur effectuera beaucoup plus de travail que nécessaire. Vous introduisez également des dépendances d'en-tête complexes. Vous pouvez comparer cela à un algorithme de force brute. Lorsque vous #import, vous faites glisser des tonnes d'informations inutiles, qui nécessitent beaucoup de mémoire, d'E/S de disque et de CPU pour analyser et compiler les sources.

ObjC est assez proche de l’idéal pour une langue basée sur C en ce qui concerne la dépendance parce que NSObject les types ne sont jamais des valeurs - NSObject les types sont toujours des pointeurs comptés en référence. Ainsi, vous pouvez vous échapper avec des temps de compilation incroyablement rapides si vous structurez correctement les dépendances de votre programme et que vous les transmettez dans la mesure du possible, car la dépendance physique est minime. Vous pouvez également déclarer des propriétés dans les extensions de classe pour réduire davantage la dépendance. C'est un bonus énorme pour les grands systèmes - vous sauriez la différence si vous avez déjà développé une grande base de code C++.

Par conséquent, ma recommandation est d'utiliser des renvois si possible, puis de #import là où il y a une dépendance physique. Si vous voyez un avertissement ou un autre qui implique une dépendance physique, corrigez-les tous. Le correctif consiste à #import dans votre fichier d'implémentation.

Lors de la création des bibliothèques, vous classerez probablement certaines interfaces en tant que groupe. Dans ce cas, vous devrez #import cette bibliothèque où la dépendance physique est introduite (par exemple #import <AppKit/AppKit.h>). Cela peut introduire une dépendance, mais les responsables de la bibliothèque peuvent souvent gérer les dépendances physiques pour vous si nécessaire - s'ils introduisent une fonctionnalité, ils peuvent en minimiser l'impact sur vos générations.

18
justin

Je vois beaucoup de "Faites comme ça" mais je ne vois aucune réponse à "Pourquoi?"

Donc: Pourquoi devriez-vous @class dans votre en-tête et #import uniquement dans votre implémentation? Vous doublez votre travail en ayant à @class et #import tout le temps. Sauf si vous utilisez l'héritage. Dans ce cas, vous importerez plusieurs fois pour une seule classe. Ensuite, vous devez vous rappeler de supprimer de plusieurs fichiers si vous décidez soudainement que vous n'avez plus besoin d'accéder à une déclaration.

Importer le même fichier plusieurs fois ne pose pas de problème en raison de la nature de #import. Compiler les performances n'est pas vraiment un problème non plus. Si c'était le cas, nous n'importerions pas Cocoa/Cocoa.h ou similaires dans à peu près tous les fichiers d'en-tête que nous avons.

11
Bruce Goodwin

si on fait ça

@interface Class_B : Class_A

cela signifie que nous héritons de Class_A dans Class_B, dans Class_B, nous pouvons accéder à toutes les variables de class_A.

si on fait ça

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

ici, nous disons que nous utilisons Class_A dans notre programme, mais si nous voulons utiliser les variables Class_A dans Class_B, nous devons importer le fichier Class_A dans le fichier .m (créer un objet et utiliser sa fonction et ses variables).

7
Anshuman Mishra

pour des informations supplémentaires sur les dépendances de fichiers & #import & @class, vérifiez ceci:

http://qualitycoding.org/file-dependencies/ c'est un bon article

résumé de l'article

importations dans les fichiers d'en-tête:

  • #import la super-classe dont vous héritez et les protocoles que vous implémentez.
  • Forward-déclarer tout le reste (sauf si cela provient d'un framework avec un en-tête principal).
  • Essayez d'éliminer toutes les autres importations.
  • Déclarez les protocoles dans leurs propres en-têtes afin de réduire les dépendances.
  • Trop de déclarations en aval? Vous avez une grande classe.

importations dans les fichiers d'implémentation:

  • Éliminez les imbrications inutiles qui ne sont pas utilisées.
  • Si une méthode délègue à un autre objet et retourne ce qu'elle récupère, essayez de déclarer cet objet par anticipation au lieu de # l'importer.
  • Si l'inclusion d'un module vous oblige à inclure niveau après niveau des dépendances successives, vous pouvez avoir un ensemble de classes souhaitant devenir une bibliothèque. Construisez-la en tant que bibliothèque séparée avec un en-tête principal, de sorte que tout puisse être importé comme un seul bloc prédéfini.
  • Trop d’importations? Vous avez une grande classe.
5
Homam

Si vous essayez de déclarer une variable ou une propriété de votre fichier d'en-tête, que vous n'avez pas encore importée, vous obtiendrez une erreur indiquant que le compilateur ne connaît pas cette classe.

Votre première pensée est probablement #import it.
Cela peut causer des problèmes dans certains cas.

Par exemple, si vous implémentez un ensemble de méthodes C dans le fichier d’en-tête, ou dans des structures, ou quelque chose de similaire, car elles ne doivent pas être importées plusieurs fois.

Par conséquent, vous pouvez indiquer au compilateur avec @class:

Je sais que vous ne connaissez pas cette classe, mais elle existe. Il va être importé ou mis en œuvre ailleurs

En gros, il dit au compilateur de se taire et de compiler, même s'il n'est pas sûr que cette classe soit implémentée.

Vous utiliserez généralement #import dans les fichiers . M et @class dans les fichiers . H.

3
NSAddict

Lorsque je me développe, je n'ai que trois choses en tête qui ne me causent jamais de problèmes.

  1. Importer des super classes
  2. Importer des classes de parents (lorsque vous avez des enfants et des parents)
  3. Importer des classes en dehors de votre projet (comme dans les frameworks et les bibliothèques)

Pour toutes les autres classes (sous-classes et classes enfants dans mon projet), je les déclare via forward-class.

3

Ceci est un exemple de scénario où nous avons besoin de @class.

Si vous souhaitez créer un protocole dans le fichier d’en-tête, qui comporte un paramètre avec un type de données de la même classe, vous pouvez utiliser @class. N'oubliez pas que vous pouvez également déclarer des protocoles séparément, ceci n'est qu'un exemple.

// DroneSearchField.h

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

Le compilateur ne se plaindra que si vous allez utiliser cette classe de manière à ce qu'il ait besoin de connaître son implémentation.

Ex:

  1. Cela pourrait être comme si vous allez en tirer votre classe ou
  2. Si vous allez avoir un objet de cette classe en tant que variable membre (bien que rare).

Vous ne vous plaindrez pas si vous allez simplement l'utiliser comme pointeur. Bien sûr, vous devrez # l'importer dans le fichier d'implémentation (si vous instanciez un objet de cette classe) car il doit connaître le contenu de la classe pour instancier un objet.

REMARQUE: #import n'est pas identique à #include. Cela signifie qu'il n'y a rien appelé importation circulaire. import est une sorte de demande pour que le compilateur cherche des informations dans un fichier particulier. Si cette information est déjà disponible, le compilateur l'ignore.

Essayez ceci, importez A.h dans B.h et B.h dans A.h. Il n'y aura pas de problèmes ou de plaintes et cela fonctionnera bien aussi.

Quand utiliser @class

Vous utilisez @class uniquement si vous ne souhaitez même pas importer un en-tête dans votre en-tête. Cela pourrait être un cas où vous ne vous souciez même pas de savoir ce que sera cette classe. Cas où vous n'avez peut-être même pas encore d'en-tête pour cette classe.

Un exemple de ceci pourrait être que vous écrivez deux bibliothèques. Une classe, appelons-la A, existe dans une bibliothèque. Cette bibliothèque comprend un en-tête de la deuxième bibliothèque. Cet en-tête peut avoir un pointeur de A mais encore une fois peut ne pas avoir besoin de l'utiliser. Si la bibliothèque 1 n'est pas encore disponible, la bibliothèque B ne sera pas bloquée si vous utilisez @class. Mais si vous souhaitez importer A.h, la progression de la bibliothèque 2 est bloquée.

0
Deepak G M

Pensez à @class comme disant au compilateur "croyez-moi, cela existe".

Pensez à #import en tant que copier-coller.

Vous voulez minimiser le nombre d'importations que vous avez pour plusieurs raisons. Sans aucune recherche, la première chose qui me vient à l’esprit est que cela réduit le temps de compilation.

Notez que lorsque vous héritez d'une classe, vous ne pouvez pas simplement utiliser une déclaration forward. Vous devez importer le fichier pour que la classe que vous déclarez sache comment il est défini.

0
Brandon M

Transmettre la déclaration juste au compilateur afin d'éviter l'affichage d'erreur.

le compilateur saura qu'il existe une classe avec le nom que vous avez utilisé dans votre fichier d'en-tête à déclarer.

0
karthick