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.
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".
Trois règles simples:
#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
).Si vous transmettez une déclaration dans les fichiers d'implémentation, vous ferez probablement quelque chose de mal.
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.
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) .
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.
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
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:
CGPoint
sous la forme d'un ivar ou d'une propriété, le compilateur devra voir la déclaration de CGPoint
.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.
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.
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).
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.
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.
Lorsque je me développe, je n'ai que trois choses en tête qui ne me causent jamais de problèmes.
Pour toutes les autres classes (sous-classes et classes enfants dans mon projet), je les déclare via forward-class.
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
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:
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.
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.
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.