@class vs. #import


709

Nach meinem Verständnis sollte eine Forward-Class-Deklaration verwendet werden, wenn ClassA einen ClassB-Header und ClassB einen ClassA-Header enthalten muss, um kreisförmige Einschlüsse zu vermeiden. Ich verstehe auch, dass an #importein einfaches ist, ifndefso dass ein Include nur einmal vorkommt.

Meine Anfrage lautet: Wann benutzt man #importund wann benutzt man @class? Wenn ich eine @classDeklaration verwende, wird manchmal eine allgemeine Compiler-Warnung wie die folgende angezeigt:

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

Würde das wirklich gerne verstehen, anstatt nur die @classForward-Deklaration zu entfernen und #importdie Warnungen, die der Compiler mir gibt, zum Schweigen zu bringen.


10
Die Weiterleitungsdeklaration teilt dem Compiler lediglich mit: "Hey, ich weiß, dass ich Dinge deklariere, die Sie nicht erkennen, aber wenn ich @MyClass sage, verspreche ich, dass ich sie in die Implementierung importieren werde."
JoeCortopassi

Antworten:


754

Wenn Sie diese Warnung sehen:

Warnung: Empfänger 'MyCoolClass' ist eine Forward-Klasse und die entsprechende @ -Interface ist möglicherweise nicht vorhanden

Sie müssen #importdie Datei, aber Sie können dies in Ihrer Implementierungsdatei (.m) tun und die @classDeklaration in Ihrer Header-Datei verwenden.

@classEntfernt (normalerweise) nicht die Notwendigkeit von #importDateien, sondern rückt die Anforderung nur näher an den Ort, an dem die Informationen nützlich sind.

Zum Beispiel

Wenn Sie sagen @class MyCoolClass, weiß der Compiler, dass er möglicherweise Folgendes sieht:

MyCoolClass *myObject;

Es muss sich um nichts anderes kümmern als MyCoolClassum eine gültige Klasse, und es sollte Platz für einen Zeiger darauf reservieren (wirklich nur einen Zeiger). Somit reicht in Ihrem Header @class90% der Zeit aus.

Wenn Sie jedoch jemals myObjectMitglieder erstellen oder darauf zugreifen müssen, müssen Sie den Compiler über diese Methoden informieren. An diesem Punkt (vermutlich in Ihrer Implementierungsdatei) müssen Sie #import "MyCoolClass.h"dem Compiler zusätzliche Informationen mitteilen, die über "Dies ist eine Klasse" hinausgehen.


5
Tolle Antwort, danke. Zum späteren Nachschlagen: Dies betrifft auch Situationen, in denen Sie @classetwas in Ihrer .hDatei haben, es aber #importin der .m vergessen , versuchen, auf eine Methode für das @classed-Objekt zuzugreifen , und Warnungen erhalten wie : warning: no -X method found.
Tim

24
Ein Fall, in dem Sie #import anstelle von @class benötigen, ist, wenn die .h-Datei Datentypen oder andere Definitionen enthält, die für die Schnittstelle Ihrer Klasse erforderlich sind.
Ken Aspeslagh

2
Ein weiterer großer Vorteil, der hier nicht erwähnt wird, ist die schnelle Kompilierung. Bitte beziehen Sie sich auf die Antwort von Venkateshwar
MartinMoizard

@BenGottlieb Sollte das nicht in "myCoolClass" in Großbuchstaben geschrieben werden? Wie in "MyCoolClass"?
Basil Bourque

182

Drei einfache Regeln:

  • Nur #importdie Superklasse und übernommene Protokolle in Header-Dateien ( .hDateien).
  • #importAlle Klassen und Protokolle, an die Sie in der Implementierung Nachrichten senden ( .mDateien).
  • Weiterleitungserklärungen für alles andere.

Wenn Sie eine Deklaration in den Implementierungsdateien weiterleiten, machen Sie wahrscheinlich etwas falsch.


22
In Header-Dateien müssen Sie möglicherweise auch alles importieren, was ein Protokoll definiert, das Ihre Klasse übernimmt.
Tyler

Gibt es einen Unterschied bei der Deklaration von #import in der h-Schnittstellendatei oder der m-Implementierungsdatei?
Samuel G

Und #import, wenn Sie Instanzvariablen aus der Klasse verwenden
user151019

1
@Mark - Unter Regel 1 fällt nur dann auf Ivars aus Ihrer Oberklasse, wenn dies der Fall ist.
PeyloW

@ Tyler warum nicht Deklaration des Protokolls weiterleiten?
JoeCortopassi

110

Lesen Sie die Dokumentation zur Objective-C-Programmiersprache in ADC

Unter dem Abschnitt Definieren einer Klasse | Klassenschnittstelle beschreibt, warum dies getan wird:

Die @ class-Direktive minimiert die Menge an Code, die vom Compiler und Linker gesehen wird, und ist daher die einfachste Möglichkeit, eine Vorwärtsdeklaration eines Klassennamens zu geben. Es ist einfach und vermeidet potenzielle Probleme beim Importieren von Dateien, die noch andere Dateien importieren. Wenn beispielsweise eine Klasse eine statisch typisierte Instanzvariable einer anderen Klasse deklariert und ihre beiden Schnittstellendateien sich gegenseitig importieren, kann keine Klasse korrekt kompiliert werden.

Ich hoffe das hilft.


48

Verwenden Sie bei Bedarf eine Vorwärtsdeklaration in der Header-Datei und #importdie Header-Dateien für alle Klassen, die Sie in der Implementierung verwenden. Mit anderen Worten, Sie haben immer #importdie Dateien, die Sie in Ihrer Implementierung verwenden, und wenn Sie auf eine Klasse in Ihrer Header-Datei verweisen müssen, verwenden Sie auch eine Forward-Deklaration.

Die Ausnahme ist, dass Sie #importeine Klasse oder ein formales Protokoll, von dem Sie erben, in Ihrer Header-Datei erben sollten (in diesem Fall müssten Sie es nicht in die Implementierung importieren).


24

Die gängige Praxis ist die Verwendung von @class in Header-Dateien (Sie müssen die Superklasse jedoch noch # importieren) und #import in Implementierungsdateien. Dies vermeidet kreisförmige Einschlüsse und funktioniert einfach.


2
Ich dachte, #import wäre besser als #Include, da es nur eine Instanz importiert?
Matthew Schinckel

2
Wahr. Ich weiß nicht, ob es sich um kreisförmige Einschlüsse oder um falsche Reihenfolge handelt, aber ich habe mich von dieser Regel abgewandt (mit einem Import in einem Header wurden Importe in der Implementierung der Unterklasse nicht mehr benötigt), und bald wurde es wirklich chaotisch. Unterm Strich befolgen Sie diese Regel, und der Compiler wird glücklich sein.
Steph Thirion

1
In den aktuellen Dokumenten heißt es: #import"Entspricht der Anweisung #include von C, stellt jedoch sicher, dass dieselbe Datei nie mehr als einmal enthalten ist." Demnach #importkümmert sich das um kreisförmige Einschlüsse, @classRichtlinien helfen dabei nicht besonders.
Eric

24

Ein weiterer Vorteil: Schnelle Kompilierung

Wenn Sie eine Header-Datei einfügen, wird die aktuelle Datei bei jeder Änderung ebenfalls kompiliert. Dies ist jedoch nicht der Fall, wenn der Klassenname als enthalten ist @class name. Natürlich müssen Sie den Header in die Quelldatei aufnehmen


18

Meine Anfrage ist dies. Wann benutzt man #import und wann benutzt man @class?

Einfache Antwort: Sie #importoder #includewenn es eine physische Abhängigkeit gibt. Andernfalls verwenden Sie Vorwärtsdeklarationen ( @class MONClass, struct MONStruct, @protocol MONProtocol).

Hier sind einige häufige Beispiele für körperliche Abhängigkeit:

  • Beliebiger C- oder C ++ - Wert (ein Zeiger oder eine Referenz ist keine physische Abhängigkeit). Wenn Sie eine CGPointals ivar oder Eigenschaft haben, muss der Compiler die Deklaration von sehen CGPoint.
  • Deine Superklasse.
  • Eine Methode, die Sie verwenden.

Wenn ich eine @ class-Deklaration verwende, wird manchmal eine allgemeine Compiler-Warnung angezeigt, z. B. die folgende: "Warnung: Der Empfänger 'FooController' ist eine Forward-Klasse, und die entsprechende @ -Interface ist möglicherweise nicht vorhanden."

Der Compiler ist in dieser Hinsicht eigentlich sehr nachsichtig. Es werden Hinweise (wie oben) gelöscht, aber Sie können Ihren Stapel leicht in den Papierkorb werfen, wenn Sie sie ignorieren und nicht #importrichtig. Obwohl dies der Fall sein sollte (IMO), erzwingt der Compiler dies nicht. In ARC ist der Compiler strenger, da er für die Referenzzählung verantwortlich ist. Der Compiler greift auf eine Standardeinstellung zurück, wenn er auf eine unbekannte Methode stößt, die Sie aufrufen. Jeder Rückgabewert und Parameter wird angenommen id. Daher sollten Sie jede Warnung aus Ihren Codebasen entfernen, da dies als physische Abhängigkeit betrachtet werden sollte. Dies ist analog zum Aufrufen einer C-Funktion, die nicht deklariert ist. Mit C werden Parameter angenommen int.

Der Grund, warum Sie Forward-Deklarationen bevorzugen, besteht darin, dass Sie Ihre Erstellungszeiten um Faktoren reduzieren können, da nur eine minimale Abhängigkeit besteht. Bei Vorwärtsdeklarationen sieht der Compiler, dass es einen Namen gibt, und kann das Programm korrekt analysieren und kompilieren, ohne die Klassendeklaration oder alle ihre Abhängigkeiten zu sehen, wenn keine physische Abhängigkeit besteht. Saubere Builds benötigen weniger Zeit. Inkrementelle Builds benötigen weniger Zeit. Sicher, Sie werden am Ende etwas mehr Zeit damit verbringen, sicherzustellen, dass alle benötigten Header für jede Übersetzung sichtbar sind. Dies zahlt sich jedoch in kürzeren Erstellungszeiten schnell aus (vorausgesetzt, Ihr Projekt ist nicht winzig).

Wenn Sie #importoder #includestattdessen verwenden, werfen Sie viel mehr Arbeit auf den Compiler als nötig. Sie führen auch komplexe Header-Abhängigkeiten ein. Sie können dies mit einem Brute-Force-Algorithmus vergleichen. Wenn Sie dies #importtun, ziehen Sie Tonnen unnötiger Informationen ein, die viel Speicher, Festplatten-E / A und CPU erfordern, um die Quellen zu analysieren und zu kompilieren.

ObjC ist in Bezug auf die Abhängigkeit nahezu ideal für eine C-basierte Sprache, da NSObjectTypen niemals Werte sind - NSObjectTypen sind immer Zeiger mit Referenzzählung. Sie können also mit unglaublich schnellen Kompilierungszeiten davonkommen, wenn Sie die Abhängigkeiten Ihres Programms entsprechend strukturieren und wenn möglich weiterleiten, da nur sehr wenig physische Abhängigkeit erforderlich ist. Sie können auch Eigenschaften in den Klassenerweiterungen deklarieren, um die Abhängigkeit weiter zu minimieren. Das ist ein großer Bonus für große Systeme - Sie würden den Unterschied kennen, den es macht, wenn Sie jemals eine große C ++ - Codebasis entwickelt haben.

Daher empfehle ich, wenn möglich vorwärts zu verwenden und dann #importdorthin , wo physische Abhängigkeit besteht. Wenn Sie die eine oder andere Warnung sehen, die physische Abhängigkeit impliziert, beheben Sie sie alle. Das Update befindet sich #importin Ihrer Implementierungsdatei.

Während Sie Bibliotheken erstellen, werden Sie wahrscheinlich einige Schnittstellen als Gruppe klassifizieren. In diesem Fall würden Sie die #importBibliothek verwenden, in der physische Abhängigkeit eingeführt wird (z #import <AppKit/AppKit.h>. B. ). Dies kann zu Abhängigkeiten führen, aber die Bibliotheksverwalter können häufig die physischen Abhängigkeiten für Sie nach Bedarf behandeln. Wenn sie eine Funktion einführen, können sie die Auswirkungen auf Ihre Builds minimieren.


Übrigens nette Mühe, die Dinge zu erklären. Aber sie scheinen ziemlich komplex zu sein.
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.nicht ganz wahr. Blöcke werfen eine Lücke in Ihre Antwort und sagen nur.
Richard J. Ross III

@ RichardJ.RossIII… und GCC erlaubt es einem, Werte zu deklarieren und zu verwenden, während Clang dies verbietet. und natürlich muss sich hinter dem Zeiger ein Wert befinden.
Justin

11

Ich sehe viele "Mach es so", aber ich sehe keine Antworten auf "Warum?"

Also: Warum sollten Sie @class in Ihrem Header und #import nur in Ihrer Implementierung verwenden? Sie verdoppeln Ihre Arbeit, indem Sie die ganze Zeit @class und #importieren müssen. Es sei denn, Sie nutzen die Vererbung. In diesem Fall # importieren Sie mehrmals für eine einzelne @ -Klasse. Dann müssen Sie daran denken, aus mehreren verschiedenen Dateien zu entfernen, wenn Sie plötzlich entscheiden, dass Sie keinen Zugriff mehr auf eine Deklaration benötigen.

Das mehrfache Importieren derselben Datei ist aufgrund der Art von #import kein Problem. Das Kompilieren der Leistung ist auch kein wirkliches Problem. Wenn es so wäre, würden wir Cocoa / Cocoa.h oder ähnliches nicht in so ziemlich jede Header-Datei importieren, die wir haben.


1
In der obigen Antwort von Abizem finden Sie ein Beispiel aus der Dokumentation, warum Sie dies tun sollten. Seine defensive Programmierung für den Fall, dass Sie zwei Klassenheader haben, die sich gegenseitig mit Instanzvariablen der anderen Klasse importieren.
Jackslash

7

wenn wir das tun

@interface Class_B : Class_A

Das heißt, wir erben die Klasse_A in die Klasse_B. In der Klasse_B können wir auf alle Variablen der Klasse_A zugreifen.

wenn wir das tun

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

Hier sagen wir, dass wir Class_A in unserem Programm verwenden, aber wenn wir die Class_A-Variablen in Class_B verwenden möchten, müssen wir Class_A in die .m-Datei importieren (ein Objekt erstellen und dessen Funktion und Variablen verwenden).


5

Weitere Informationen zu Dateiabhängigkeiten und #import & @class finden Sie hier:

http://qualitycoding.org/file-dependencies/ Es ist ein guter Artikel

Zusammenfassung des Artikels

Importe in Header-Dateien:

  • #importieren Sie die Superklasse, die Sie erben, und die Protokolle, die Sie implementieren.
  • Deklarieren Sie alles andere vorwärts (es sei denn, es stammt aus einem Framework mit einem Master-Header).
  • Versuchen Sie, alle anderen # Importe zu entfernen.
  • Deklarieren Sie Protokolle in ihren eigenen Headern, um Abhängigkeiten zu reduzieren.
  • Zu viele Vorwärtserklärungen? Sie haben eine große Klasse.

Importe in Implementierungsdateien:

  • Beseitigen Sie nicht verwendete Cruft-Importe.
  • Wenn eine Methode an ein anderes Objekt delegiert und zurückgibt, was sie zurückerhält, versuchen Sie, dieses Objekt vorwärts zu deklarieren, anstatt es zu importieren.
  • Wenn Sie durch das Einfügen eines Moduls gezwungen werden, Ebene für Ebene aufeinanderfolgende Abhängigkeiten einzuschließen, verfügen Sie möglicherweise über eine Reihe von Klassen, die eine Bibliothek werden möchten. Erstellen Sie es als separate Bibliothek mit einem Master-Header, damit alles als einzelner vorgefertigter Block eingefügt werden kann.
  • Zu viele #importe? Sie haben eine große Klasse.

3

Wenn ich mich entwickle, habe ich nur drei Dinge im Sinn, die mir nie Probleme bereiten.

  1. Importieren Sie Superklassen
  2. Importieren Sie Elternklassen (wenn Sie Kinder und Eltern haben)
  3. Importieren Sie Klassen außerhalb Ihres Projekts (wie in Frameworks und Bibliotheken).

Für alle anderen Klassen (Unterklassen und untergeordnete Klassen in meinem Projekt selbst) deklariere ich sie über die Vorwärtsklasse.


3

Wenn Sie versuchen, eine Variable oder eine Eigenschaft in Ihrer Header-Datei zu deklarieren, die Sie noch nicht importiert haben, wird eine Fehlermeldung angezeigt, dass der Compiler diese Klasse nicht kennt.

Ihr erster Gedanke ist es wahrscheinlich #import.
Dies kann in einigen Fällen zu Problemen führen.

Zum Beispiel, wenn Sie eine Reihe von C-Methoden in der Header-Datei oder in Strukturen oder Ähnlichem implementieren, da diese nicht mehrmals importiert werden sollten.

Daher können Sie dem Compiler Folgendes mitteilen @class:

Ich weiß, dass Sie diese Klasse nicht kennen, aber sie existiert. Es wird an anderer Stelle importiert oder implementiert

Grundsätzlich wird der Compiler angewiesen, die Klappe zu halten und zu kompilieren, obwohl nicht sicher ist, ob diese Klasse jemals implementiert wird.

Sie werden normalerweise #importin den .m- und @classin den .h- Dateien verwenden.


0

Leiten Sie die Deklaration nur an den Compiler weiter, damit dieser keinen Fehler anzeigt.

Der Compiler weiß, dass es eine Klasse mit dem Namen gibt, den Sie in Ihrer Header-Datei zum Deklarieren verwendet haben.


Könnten Sie etwas genauer sein?
Sam Spencer

0

Der Compiler wird sich nur beschweren, wenn Sie diese Klasse so verwenden, dass der Compiler ihre Implementierung kennen muss.

Ex:

  1. Dies könnte so sein, als würden Sie Ihre Klasse daraus ableiten oder
  2. Wenn Sie ein Objekt dieser Klasse als Mitgliedsvariable haben möchten (obwohl selten).

Es wird sich nicht beschweren, wenn Sie es nur als Zeiger verwenden. Natürlich müssen Sie es # in die Implementierungsdatei importieren (wenn Sie ein Objekt dieser Klasse instanziieren), da es den Klasseninhalt kennen muss, um ein Objekt zu instanziieren.

HINWEIS: #import ist nicht dasselbe wie #include. Dies bedeutet, dass es nichts gibt, was als zirkulärer Import bezeichnet wird. Import ist eine Art Aufforderung an den Compiler, in einer bestimmten Datei nach Informationen zu suchen. Wenn diese Informationen bereits verfügbar sind, ignoriert der Compiler sie.

Versuchen Sie dies einfach, importieren Sie Ah in Bh und Bh in Ah. Es wird keine Probleme oder Beschwerden geben und es wird auch gut funktionieren.

Wann wird @class verwendet?

Sie verwenden @class nur, wenn Sie nicht einmal einen Header in Ihren Header importieren möchten. Dies könnte ein Fall sein, in dem Sie nicht einmal wissen möchten, wie diese Klasse aussehen wird. Fälle, in denen Sie möglicherweise noch nicht einmal einen Header für diese Klasse haben.

Ein Beispiel hierfür könnte sein, dass Sie zwei Bibliotheken schreiben. Eine Klasse, nennen wir es A, existiert in einer Bibliothek. Diese Bibliothek enthält einen Header aus der zweiten Bibliothek. Dieser Header hat möglicherweise einen Zeiger auf A, muss ihn jedoch möglicherweise nicht verwenden. Wenn Bibliothek 1 noch nicht verfügbar ist, wird Bibliothek B nicht blockiert, wenn Sie @class verwenden. Wenn Sie jedoch Ah importieren möchten, wird der Fortschritt von Bibliothek 2 blockiert.


0

Stellen Sie sich @class so vor, als würde es dem Compiler sagen "Vertrau mir, das gibt es".

Stellen Sie sich #import als Copy-Paste vor.

Sie möchten die Anzahl Ihrer Importe aus verschiedenen Gründen minimieren. Ohne Forschung fällt mir als Erstes ein, dass die Kompilierungszeit verkürzt wird.

Beachten Sie, dass Sie beim Erben von einer Klasse nicht einfach eine Vorwärtsdeklaration verwenden können. Sie müssen die Datei importieren, damit die Klasse, die Sie deklarieren, weiß, wie sie definiert ist.


0

Dies ist ein Beispielszenario, in dem wir @class benötigen.

Überlegen Sie, ob Sie @class verwenden möchten, wenn Sie ein Protokoll in der Header-Datei erstellen möchten, das einen Parameter mit einem Datentyp derselben Klasse enthält. Bitte denken Sie daran, dass Sie Protokolle auch separat deklarieren können. Dies ist nur ein Beispiel.

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.