@class vs #import

709

À ma connaissance, il faut utiliser une déclaration de classe directe 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 également que an #importest simple, de ifndefsorte qu'une inclusion ne se produit qu'une seule fois.

Ma question est la suivante: quand utilise-t-on #importet quand utilise -t-on @class? Parfois, si j'utilise une @classdéclaration, je vois un avertissement de compilateur commun tel que le suivant:

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

J'adorerais vraiment comprendre cela, par rapport à la simple suppression de la @classdéclaration directe et à la suppression #importdes avertissements que le compilateur me donne.

Coocoo4Cocoa
la source
10
La déclaration directe indique simplement au compilateur: "Hé, je sais que je déclare des choses que vous ne reconnaissez pas, mais quand je dis @MyClass, je promets de les #importer dans l'implémentation".
JoeCortopassi

Réponses:

754

Si vous voyez cet avertissement:

avertissement: le récepteur 'MyCoolClass' est une classe directe et @interface correspondante peut ne pas exister

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

@classne supprime pas (généralement) le besoin de #importfichiers, il déplace simplement l'exigence plus près de l'endroit où les informations sont utiles.

Par exemple

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

MyCoolClass *myObject;

Il n'a pas à se soucier d'autre chose qu'une MyCoolClassclasse valide, et il devrait réserver de la place pour un pointeur sur elle (vraiment, juste un pointeur). Ainsi, dans votre en-tête, cela @classsuffit 90% du temps.

Cependant, si vous avez besoin de créer ou d'accéder à myObjectdes membres, vous devrez informer le compilateur de ces méthodes. À ce stade (probablement 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".

Ben Gottlieb
la source
5
Excellente réponse, merci. Pour référence future: il traite également des situations où vous @classquelque chose dans votre .hfichier, mais oubliez #importdans le .m, essayez d'accéder à une méthode sur l' @classobjet ed et obtenir des avertissements comme: warning: no -X method found.
Tim
24
Un cas où vous auriez besoin de #import au lieu de @class est si le fichier .h inclut des types de données ou d'autres définitions nécessaires pour l'interface de votre classe.
Ken Aspeslagh
2
Un autre grand avantage non mentionné ici est la compilation rapide. Veuillez vous référer à la réponse de Venkateshwar
MartinMoizard
@BenGottlieb Le "m" de "myCoolClass" ne devrait-il pas être en majuscules? Comme dans, "MyCoolClass"?
Basil Bourque
182

Trois règles simples:

  • Uniquement #importla super classe et les protocoles adoptés dans les fichiers d'en-tête ( .hfichiers).
  • #importtoutes les classes et protocoles auxquels vous envoyez des messages dans l'implémentation ( .mfichiers).
  • Transmet des déclarations pour tout le reste.

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

PeyloW
la source
22
Dans les fichiers d'en-tête, vous devrez peut-être #importer tout ce qui définit un protocole adopté par votre classe.
Tyler
Y a-t-il une différence dans la déclaration de #import dans le fichier d'interface h ou le fichier d'implémentation m?
Samuel G
Et #import si vous utilisez des variables d'instance de la classe
user151019
1
@Mark - Couvert par la règle n ° 1, accédez uniquement aux ivars depuis votre superclasse, le cas échéant.
PeyloW
@Tyler pourquoi ne pas transmettre la déclaration du protocole?
JoeCortopassi
110

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

Sous la section sur la définition d'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, et est donc le moyen le plus simple de donner une déclaration directe d'un nom de classe. Étant simple, il évite les problèmes potentiels qui peuvent survenir lors de 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 compiler correctement.

J'espère que ça aide.

Abizern
la source
48

Utilisez une déclaration directe dans le fichier d'en-tête si nécessaire, et #importles fichiers d'en-tête pour toutes les classes que vous utilisez dans l'implémentation. En d'autres termes, vous #importutilisez 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 directe.

L' exception à cela est que vous devez #importune classe ou un protocole formel dont vous héritez dans votre fichier d'en-tête (auquel cas vous n'auriez pas besoin de l'importer dans l'implémentation).

Marc Charbonneau
la source
24

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 toutes les inclusions circulaires, et cela fonctionne simplement.

Steph Thirion
la source
2
Je pensais que #import était meilleur que #Include en ce sens qu'il n'importait qu'une seule instance?
Matthew Schinckel
2
Vrai. Je ne sais pas s'il s'agit d'inclusions circulaires ou d'un ordre incorrect, mais je me suis éloigné de cette règle (avec une importation dans un en-tête, les importations n'étaient plus nécessaires dans la mise en œuvre de la sous-classe), et bientôt c'est devenu vraiment compliqué. En bout de ligne, suivez cette règle, et le compilateur sera heureux.
Steph Thirion
1
Les documents actuels disent que #import"est comme la directive #include de C, sauf qu'il s'assure que le même fichier n'est jamais inclus plus d'une fois". Donc, selon cela, il #importprend en charge les inclusions circulaires, les @classdirectives n'y aident pas particulièrement.
Eric
24

Autre avantage: compilation rapide

Si vous incluez un fichier d'en-tête, toute modification de celui-ci entraîne également la compilation du fichier actuel, 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

évent
la source
18

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

Réponse simple: vous #importou en #includecas de dépendance physique. Dans le cas contraire, vous utilisez des déclarations avant ( @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 CGPointcomme un ivar ou une propriété, le compilateur devra voir la déclaration de CGPoint.
  • Votre superclasse.
  • Une méthode que vous utilisez.

Parfois, si j'utilise une déclaration @class, je vois un avertissement de compilateur commun tel que le suivant: "avertissement: le récepteur 'FooController' est une classe directe et @interface correspondante peut ne pas exister."

Le compilateur est en fait très indulgent à cet égard. Il laissera tomber des indices (comme celui ci-dessus), mais vous pouvez facilement supprimer votre pile si vous les ignorez et ne le faites pas #importcorrectement. Bien que cela doive (IMO), le compilateur n'applique pas cela. Dans ARC, le compilateur est plus strict car il est responsable du comptage des références. Ce qui se passe, c'est que le compilateur retombe sur une valeur par défaut lorsqu'il rencontre une méthode inconnue que vous appelez. Chaque valeur de retour et paramètre est supposé l'être id. Ainsi, vous devez supprimer tous les avertissements de vos bases de code car cela doit ê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 paramètres sont supposés être int.

La raison pour laquelle vous préférez les déclarations avancées est que vous pouvez réduire vos temps de génération par des facteurs car il y a une dépendance minimale. Avec les déclarations avancées, le compilateur voit qu'il y a un nom et peut correctement analyser et compiler 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 versions incrémentielles prennent moins de temps. Bien sûr, vous finirez par passer un peu plus de temps à vous assurer que tous les en-têtes dont vous avez besoin sont visibles pour chaque traduction, mais cela se traduit par des temps de construction réduits rapidement (en supposant que votre projet n'est pas minuscule).

Si vous utilisez #importou à la #includeplace, vous lancez beaucoup plus de travail sur le compilateur 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 glissez des tonnes d'informations inutiles, ce qui nécessite beaucoup de mémoire, d'E / S disque et de CPU pour analyser et compiler 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 NSObjecttypes ne sont jamais des valeurs - les NSObjecttypes sont toujours des pointeurs comptés par référence. Ainsi, vous pouvez vous en tirer avec des temps de compilation incroyablement rapides si vous structurez les dépendances de votre programme de manière appropriée et si possible, car il y a très peu de dépendance physique requise. Vous pouvez également déclarer des propriétés dans les extensions de classe pour minimiser davantage la dépendance. C'est un énorme bonus pour les grands systèmes - vous sauriez la différence que cela fait si vous avez déjà développé une grande base de code C ++.

Par conséquent, ma recommandation est d'utiliser les transferts vers l'avant dans la mesure du possible, puis de #import où il y a une dépendance physique. Si vous voyez l'avertissement ou un autre qui implique une dépendance physique - corrigez-les tous. Le correctif se trouve #importdans votre fichier d'implémentation.

Au fur et à mesure que vous construisez des bibliothèques, vous classerez probablement certaines interfaces en tant que groupe, auquel cas vous utiliserez #importcette 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 au besoin - s'ils introduisent une fonctionnalité, ils peuvent minimiser l'impact qu'elle a sur vos builds.

Justin
la source
BTW bel effort pour expliquer les choses. .mais ils semblent assez complexes.
Ajay Sharma
NSObject types are never values -- NSObject types are always reference counted pointers.pas entièrement vrai. Les blocs jettent une faille dans votre réponse, juste en disant.
Richard J. Ross III
@ RichardJ.RossIII… et GCC permet de déclarer et d'utiliser des valeurs, tandis que clang l'interdit. et bien sûr, il doit y avoir une valeur derrière le pointeur.
juste le
11

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 devant @class et #import tout le temps. A moins que vous n'utilisiez l'héritage. Dans ce cas, vous importerez plusieurs fois pour une seule @classe. Ensuite, vous devez vous rappeler de supprimer de plusieurs fichiers différents si vous décidez soudainement 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. La compilation des performances n'est pas vraiment un problème non plus. Si c'était le cas, nous n'importerions pas # Cocoa / Cocoa.h ou similaire dans à peu près tous les fichiers d'en-tête que nous avons.

Bruce Goodwin
la source
1
voir la réponse d'Abizem ci-dessus pour un exemple de la documentation expliquant pourquoi vous devriez le faire. Sa programmation défensive lorsque vous avez deux en-têtes de classe qui s'importent avec les variables d'instance de l'autre classe.
jackslash
7

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 Class_A dans notre programme, mais si nous voulons utiliser les variables Class_A dans Class_B, nous devons #importer Class_A dans le fichier .m (créer un objet et utiliser sa fonction et ses variables).

Anshuman Mishra
la source
5

pour plus d'informations sur les dépendances de fichiers et #import & @class, vérifiez ceci:

http://qualitycoding.org/file-dependencies/ bon article

résumé de l'article

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

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

importations dans les fichiers d'implémentation:

  • Éliminez les # importations importunes qui ne sont pas utilisées.
  • Si une méthode délègue à un autre objet et renvoie ce qu'elle récupère, essayez de déclarer en avant cet objet 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 qui souhaite devenir une bibliothèque. Construisez-le en tant que bibliothèque séparée avec un en-tête principal, de sorte que tout puisse être importé sous la forme d'un seul bloc prédéfini.
  • Trop de # importations? Vous avez une grande classe.
Homam
la source
3

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

  1. Importer des super classes
  2. Importer des classes de parents (lorsque vous avez des enfants et des parents)
  3. Importez 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.

Constantino Tsarouhas
la source
3

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ée, vous obtiendrez une erreur indiquant que le compilateur ne connaît pas cette classe.

Votre première pensée est probablement #importcelle-ci.
Cela peut entraîner 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 importées plusieurs fois.

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

Je sais que vous ne connaissez pas cette classe, mais elle existe. Ça va être importé ou implémenté ailleurs

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

Vous utiliserez généralement #importdans le .m et @classdans les .h fichiers.

IluTov
la source
0

Transférer la déclaration uniquement au compilateur empêchant d'afficher une erreur.

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

karthick
la source
Pourriez-vous être un peu plus précis?
Sam Spencer
0

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

Ex:

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

Il ne se plaindra 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 a besoin de 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 examine un fichier particulier pour obtenir des informations. Si ces informations sont déjà disponibles, le compilateur les ignore.

Essayez simplement cela, importez Ah en Bh et Bh en Ah Il n'y aura pas de problèmes ou de plaintes et cela fonctionnera aussi très bien.

Quand utiliser @class

Vous n'utilisez @class que si vous ne voulez 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 quelle sera cette classe. Les 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 peut-être pas besoin de l'utiliser à nouveau. 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 cherchez à importer Ah, la progression de la bibliothèque 2 est bloquée.

Deepak GM
la source
0

Considérez @class comme disant au compilateur "croyez-moi, cela existe".

Considérez #import comme un copier-coller.

Vous souhaitez minimiser le nombre d'importations dont vous disposez pour plusieurs raisons. Sans aucune recherche, la première chose qui vient à l'esprit est qu'elle réduit le temps de compilation.

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

Brandon M
la source
0

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 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, ce 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
Sujananth
la source