@class vs. #import

Il est à mon sens que l’on devrait utiliser une déclaration de classe avant 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 sorte qu’un include ne se produit qu’une fois.

Ma question est la suivante: quand utilise-t-on #import et quand utilise @class t-on @class ? Parfois, si j’utilise une déclaration @class , je vois un avertissement du compilateur commun tel que les suivants:

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

Je serais ravi de comprendre cela, par opposition à la simple suppression de la déclaration en avant de la @class et au lancement d’une commande #import pour faire taire les avertissements que le compilateur me donne.

Si vous voyez cet avertissement:

avertissement: le récepteur ‘MyCoolClass’ est une classe forward et @interface correspondante peut ne pas exister

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 #import fichiers, il ne fait que déplacer l’exigence vers 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 d’autre chose que MyCoolClass est une classe valide, et il devrait réserver de la place pour un pointeur vers lui (vraiment, juste un pointeur). Ainsi, dans votre en-tête, @class suffit dans 90% des cas.

Cependant, si vous avez besoin de créer ou d’accéder à des membres de myObject , vous devrez indiquer au compilateur quelles sont ces méthodes. À ce stade (vraisemblablement dans votre fichier d’implémentation), vous devrez #import "MyCoolClass.h" , pour indiquer au compilateur des informations supplémentaires au-delà de “ceci est une classe”.

Trois règles simples:

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

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

Regardez la documentation Objective-C Programming Language sur ADC

Sous la section sur la définition d’une classe | L’interface de classe décrit pourquoi ceci est fait:

La directive @class minimise la quantité de code vue par le compilateur et l’éditeur de liens et constitue donc le moyen le plus simple de fournir une déclaration directe 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 typée statiquement d’une autre classe et que ses deux fichiers d’interface sont importés, aucune classe ne peut être compilée correctement.

J’espère que ça aide.

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

L’ exception à cette règle est que vous devez #import une classe ou un protocole formel dont vous héritez dans votre fichier d’en-tête (auquel cas vous n’aurez 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 super-classe) et #import dans les fichiers d’implémentation. Cela évitera les inclusions circulaires, et cela fonctionne.

Un autre avantage: compilation rapide

Si vous incluez un fichier d’en-tête, toute modification entraîne la compilation du fichier en cours, mais ce n’est pas le cas si le nom de la classe est inclus en tant que @class name . Bien sûr, vous devrez inclure l’en-tête dans le fichier source

Ma question est la suivante. Quand utilise-t-on #import et quand utilise-t-on @class?

Réponse simple: Vous #import ou #include quand il y a une dépendance physique. Sinon, vous utilisez des déclarations avancées ( @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 point CGPoint comme ivar ou 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 du compilateur commun, tel que: “warning: le récepteur ‘FooController’ est une classe forward et @interface correspondante peut ne pas exister.”

Le compilateur est vraiment très indulgent à cet égard. Il va laisser tomber des indices (comme celui ci-dessus), mais vous pouvez facilement éliminer votre stack si vous les ignorez et ne les #import pas correctement. Bien qu’il devrait (IMO), le compilateur n’applique pas cela. Dans ARC, le compilateur est plus ssortingct 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 lorsqu’il rencontre une méthode inconnue que vous appelez. Chaque valeur de retour et paramètre est supposé être id . Ainsi, vous devez é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 qui n’est pas déclarée. Avec C, les parameters sont supposés être int .

La raison pour laquelle vous préféreriez les déclarations anticipées est que vous pouvez réduire les temps de construction en fonction de facteurs, car la dépendance est minime. Avec les déclarations avancées, le compilateur voit qu’il y a un nom et peut parsingr et comstackr correctement le programme sans voir la déclaration de classe ou 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 se traduit rapidement par des temps de construction réduits (en supposant que votre projet n’est pas petit).

Si vous utilisez plutôt #import ou #include , le compilateur doit travailler beaucoup plus 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, ce qui nécessite beaucoup de mémoire, d’E / S disque et de processeur pour parsingr et comstackr les sources.

ObjC est assez proche de l’idéal pour un langage basé sur C en ce qui concerne la dépendance, car les types NSObject ne sont jamais des valeurs – les types NSObject sont toujours des pointeurs de référence. Ainsi, vous pouvez vous en sortir avec des temps de compilation incroyablement rapides si vous structurez les dépendances de votre programme de manière appropriée et rapide, car il y a très peu de dépendance physique requirejse. Vous pouvez également déclarer des propriétés dans les extensions de classe pour réduire davantage la dépendance. C’est un énorme avantage pour les grands systèmes – vous sauriez la différence si vous avez déjà développé une base de code C ++ volumineuse.

Par conséquent, je vous recommande d’utiliser les transfères si possible, puis d’ #import là où il existe une dépendance physique. Si vous voyez l’avertissement ou un autre qui implique une dépendance physique, corrigez-les tous. Le correctif consiste à #import dans votre fichier d’implémentation.

Au fur et à mesure que vous construisez des bibliothèques, vous allez probablement classer certaines interfaces en tant que groupe, auquel cas vous #import bibliothèque où la dépendance physique est introduite (par exemple, #import ). Cela peut entraîner une dépendance, mais les responsables de la bibliothèque peuvent souvent gérer les dépendances physiques en fonction de vos besoins. S’ils introduisent une fonctionnalité, ils peuvent minimiser l’impact sur vos générations.

Je vois beaucoup de “Do it this way” mais je ne vois aucune réponse à “Pourquoi?”

Alors: 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’inheritance. Dans ce cas, vous serez # importé plusieurs fois pour une seule classe @class. Ensuite, vous devez vous rappeler de supprimer de plusieurs fichiers différents si vous décidez soudain que vous n’avez plus besoin d’accéder à une déclaration.

L’importation du même fichier plusieurs fois n’est pas un problème en raison de la nature de #import. Comstackr les performances n’est pas vraiment un problème non plus. Si c’était le cas, nous ne serions pas #importing Cocoa / Cocoa.h ou similaire dans presque tous les fichiers d’en-tête que nous avons.

si on fait ça

 @interface Class_B : Class_A 

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 nous faisons cela

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

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

pour plus d’informations sur les dépendances de fichiers & #import & @class, consultez:

http://qualitycoding.org/file-dependencies/ C’est un bon article

résumé de l’article

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

  • # importez la superclasse dont vous héritez et les protocoles que vous implémentez.
  • Transmettre tout le rest (sauf s’il provient d’un framework avec un en-tête maître).
  • Essayez d’éliminer tous les autres #imports.
  • Déclarez les protocoles dans leurs propres en-têtes pour réduire les dépendances.
  • Trop de déclarations anticipées? Vous avez une grande classe.

les importations dans les fichiers d’implémentation:

  • Éliminez les #imports # qui ne sont pas utilisés.
  • Si une méthode délègue à un autre object et retourne ce qu’il récupère, essayez de déclarer cet object au lieu de l’importer.
  • Si inclure un module vous oblige à inclure le niveau après le niveau des dépendances successives, vous pouvez avoir un ensemble de classes qui veulent devenir une bibliothèque. Construisez-le comme une bibliothèque séparée avec un en-tête principal, de sorte que tout puisse être intégré sous la forme d’un seul morceau pré-construit.
  • Trop de #imports? Vous avez une grande classe.

Lorsque je me développe, je n’ai en tête que trois choses qui ne me causent aucun problème.

  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 une classe avancée.

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

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

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

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

Je sais que tu ne connais pas cette classe, mais ça existe. Il va être importé ou implémenté ailleurs

Il demande essentiellement au compilateur de se taire et de comstackr, même s’il n’est pas certain que cette classe soit implémentée.

Vous utiliserez généralement #import dans les fichiers .m et @class dans les fichiers .h .

Renvoyer la déclaration juste pour empêcher le compilateur de montrer une erreur.

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

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

Ex:

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

Il ne se plaindra pas si vous allez juste l’utiliser comme un pointeur. Bien sûr, vous devrez l’importer dans le fichier d’implémentation (si vous instanciez un object de cette classe) car il doit connaître le contenu de la classe pour instancier un object.

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

Essayez ceci, importez Ah en Bh et Bh en Ah. Il n’y aura pas de problèmes ou de plaintes et ça ira 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 ce cours. 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 sur A, mais encore une fois il ne sera peut-être pas nécessaire 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 Ah, la progression de la bibliothèque 2 est bloquée.

Pensez à @class pour dire 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 vient à l’esprit est la réduction du temps de compilation.

Notez que lorsque vous héritez d’une classe, vous ne pouvez pas simplement utiliser une déclaration de transfert. Vous devez importer le fichier pour que la classe que vous déclarez sache comment elle est définie.

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

Considérez si vous souhaitez créer un protocole dans le fichier d’en-tête, qui a un paramètre avec le type de données de la même classe, alors vous pouvez utiliser @class. S’il vous plaît rappelez-vous que vous pouvez également déclarer des protocoles séparément, ce n’est qu’un exemple.

 // DroneSearchField.h #import  @class DroneSearchField; @protocol DroneSearchFieldDelegate @optional - (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField; @end @interface DroneSearchField : UITextField @end