@classと#import


709

ClassAがClassBヘッダーを含める必要があり、ClassBが循環インクルージョンを回避するためにClassAヘッダーを含める必要がある場合は、フォワードクラス宣言を使用する必要があることを理解しています。また、インクルードが1回だけ行われるように、は#importシンプルであることも理解してifndefいます。

私の問い合わせはこれです:いつ使用し#import、いつ使用し@classますか?@class宣言を使用すると、次のような一般的なコンパイラ警告が表示されることがあります。

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

これを理解したいのですが、単に@class前方宣言を削除して#import、コンパイラーから出された警告を黙らせるために投げ込むだけです。


10
フォワード宣言は、コンパイラに「わからないことを宣言していることはわかっていますが、@ MyClassと言うときは、実装に#importすることを約束します」と伝えます。
JoeCortopassi 14年

回答:


754

この警告が表示された場合:

警告:レシーバー 'MyCoolClass'は転送クラスであり、対応する@インターフェイスが存在しない可能性があります

#importファイルに必要ですが、実装ファイル(.m)でそれを行うことができ、@class、ヘッダーファイルで宣言できます。

@class(通常は)#importファイルの必要性がなくなるわけではありません。単に、要件が情報の役に立つ場所に移動するだけです。

例えば

と言った@class MyCoolClass場合、コンパイラは次のように表示されることを認識しています。

MyCoolClass *myObject;

MyCoolClass有効なクラス以外のことを心配する必要はありません。また、クラスへのポインタ(実際にはポインタのみ)のための領域を予約する必要があります。したがって、ヘッダーでは、@class 90%の時間十分です。

ただし、myObjectのメンバーを作成またはアクセスする必要がある場合は、それらのメソッドが何であるかをコンパイラーに通知する必要があります。この時点で(おそらく実装ファイルに)、#import "MyCoolClass.h"「これはクラスです」以外の追加情報をコンパイラに伝える必要があります。


5
すばらしい答え、ありがとう。将来の参考のために:これ@classは、.hファイル内の何かが#import.mでそれを忘れて、@classedオブジェクトのメソッドにアクセスしようとすると、次のような警告が表示される状況も扱いますwarning: no -X method found
Tim

24
@classの代わりに#importが必要になるのは、.hファイルにデータ型やクラスのインターフェースに必要なその他の定義が含まれている場合です。
Ken Aspeslagh、2010

2
ここで言及されていないもう1つの大きな利点は、迅速なコンパイルです。Venkateshwarの回答を参照してください
MartinMoizard

@BenGottlieb「myCoolClass」の「m」は大文字にしないでください。「MyCoolClass」のように?
バジルブルク

182

3つの単純なルール:

  • #importヘッダーファイル(.hファイル)のスーパークラスと採用されたプロトコルのみ。
  • #import すべてのクラス、およびプロトコル、実装でメッセージを送信します(.mファイル)。
  • その他すべての宣言を転送します。

実装ファイルでフォワード宣言を行うと、おそらく何か間違ったことをします。


22
ヘッダーファイルでは、クラスが採用するプロトコルを定義する#importも必要になる場合があります。
タイラー

hインターフェースファイルまたはm実装ファイルで#importを宣言することに違いはありますか?
サミュエルG

そして、クラスからインスタンス変数を使用する場合は#import
user151019

1
@Mark-ルール#1でカバーされている場合でも、スーパークラスのivarにのみアクセスします。
PeyloW、2012年

@タイラーはなぜプロトコルの宣言を転送しないのですか?
JoeCortopassi

110

ADCに関するObjective-Cプログラミング言語のドキュメントをご覧ください。

クラスの定義に関するセクション| これが行われる理由を記述するクラスインターフェイス:

@classディレクティブは、コンパイラーとリンカーが認識するコードの量を最小限に抑えるため、クラス名の前方宣言を行う最も簡単な方法です。単純であるため、他のファイルをインポートするファイルのインポートに伴う潜在的な問題を回避できます。たとえば、あるクラスが別のクラスの静的に型指定されたインスタンス変数を宣言し、それらの2つのインターフェースファイルが相互にインポートする場合、どちらのクラスも正しくコンパイルされない可能性があります。

これがお役に立てば幸いです。


48

必要に応じてヘッダーファイルでフォワード宣言を#import使用し、実装で使用しているクラスのヘッダーファイルを使用します。つまり、常に#import実装で使用しているファイルあなたであり、ヘッダーファイルのクラスを参照する必要がある場合は、前方宣言も使用します。

例外これには、あなたがすべきことである#importクラスまたは正式なプロトコルを使用して、(その場合、あなたは、実装でそれをインポートする必要はありません)あなたのヘッダファイル内から継承しています。


24

一般的な方法は、ヘッダーファイルで@classを使用します(ただし、スーパークラスを#importする必要があります)、および実装ファイルで#importを使用します。これにより、循環インクルージョンが回避され、機能します。


2
#importは1つのインスタンスしかインポートしないという点で#Includeよりも優れていると思いましたか?
Matthew Schinckel、2008年

2
本当です。それが循環インクルージョンに関するものか、誤った順序に関するものかはわかりませんが、私はそのルールから離れて冒険しました(ヘッダーに1つのインポートがあるため、インポートはサブクラスの実装で不要になりました)。結論としては、そのルールに従ってください。そうすれば、コンパイラーは満足します。
Steph Thirion、2008年

1
現在のドキュメントはすなわち#import、「それは確かに同じファイルが複数回含まれないことになることを除いて、Cの#includeディレクティブのようなものです。」したがって、これによると#import循環インクルージョンを処理し、@classディレクティブは特に役立ちません。
Eric

24

別の利点:迅速なコンパイル

ヘッダーファイルを含める場合、ヘッダーファイルに変更を加えると現在のファイルもコンパイルされますが、クラス名がとして含まれている場合はそうではありません@class name。もちろん、ソースファイルにヘッダーを含める必要があります


18

お問い合わせはこちらです。いつ#importを使用し、いつ@classを使用しますか?

簡単な答え:あなた#importまたは#include身体的な依存関係がある場合。そうでなければ、あなたは(前方宣言を使用し@class MONClassstruct MONStruct@protocol MONProtocol)。

身体依存の一般的な例をいくつか示します。

  • 任意のCまたはC ++値(ポインターまたは参照は物理的な依存関係ではありません)。をCGPointivarまたはプロパティとして持っている場合、コンパイラは次の宣言を確認する必要があります。CGPointます。
  • あなたのスーパークラス。
  • 使用する方法。

@class宣言を使用すると、次のような一般的なコンパイラ警告が表示されることがあります。「警告:レシーバー 'FooController'は転送クラスであり、対応する@インターフェイスが存在しない可能性があります。」

この点に関して、コンパイラーは実際には非常に寛大です。ヒント(上記のものなど)をドロップしますが、それらを無視して#import適切に実行しないと、スタックを簡単に破棄できます。必要がありますが(IMO)、コンパイラーはこれを強制しません。ARCでは、コンパイラは参照カウントを担当するため、より厳密です。コンパイラーは、呼び出した不明なメソッドを検出すると、デフォルトにフォールバックします。すべての戻り値とパラメータはと見なされますid。したがって、これは物理的な依存と見なされるべきなので、コードベースからのすべての警告を根絶する必要があります。これは、宣言されていないC関数の呼び出しに似ています。Cでは、パラメーターはint

前方宣言を優先する理由は、依存関係が最小限であるため、要因によってビルド時間を短縮できるためです。前方宣言を使用すると、コンパイラーは名前があることを認識し、物理的な依存関係がない場合にクラス宣言またはそのすべての依存関係を確認せずにプログラムを正しく解析およびコンパイルできます。クリーンビルドは時間がかかりません。インクリメンタルビルドは時間がかかりません。確かに、結果として、必要なすべてのヘッダーがすべての翻訳に表示されることを確認するためにもう少し時間を費やすことになりますが、これはビルド時間の短縮にすぐに役立ちます(プロジェクトが小さくない場合)。

#importまたはを使用する場合#include、必要以上に多くの作業がコンパイラで発生します。複雑なヘッダーの依存関係も導入しています。これはブルートフォースアルゴリズムにたとえることができます。すると#import、大量のメモリ、ディスクI / O、およびソースを解析およびコンパイルするためのCPUを必要とする大量の不要な情報をドラッグしていることになります。

NSObject型は決して値ではないため、ObjCは依存関係に関してCベースの言語にとって理想にかなり近いです-NSObjectます。型は常に参照カウントポインターです。したがって、プログラムの依存関係を適切に構成し、可能な限り転送することで、物理的な依存関係がほとんど必要ないため、コンパイル時間を大幅に短縮できます。依存関係をさらに最小限に抑えるために、クラス拡張でプロパティを宣言することもできます。これは、大規模なシステムにとって大きなメリットです。大規模なC ++コードベースを開発したことがあれば、その違いがわかるでしょう。

したがって、私の推奨は、可能な限りフォワードを使用してから#import、物理的な依存があるところに使用することです。身体的依存を示唆する警告または別のメッセージが表示された場合は、すべて修正してください。修正は#import、実装ファイルを修正することです。

ライブラリを構築するとき、いくつかのインターフェイスをグループとして分類する可能性があります。その場合#import、物理的な依存関係が導入されているライブラリ(たとえば#import <AppKit/AppKit.h>)になります。これにより依存関係が発生する可能性がありますが、ライブラリのメンテナは必要に応じて物理的な依存関係を処理できることがよくあります。機能を導入すると、ビルドへの影響を最小限に抑えることができます。


ところで物事を説明するための素晴らしい努力。。しかし、それらは非常に複雑なようです。
Ajay Sharma

NSObject types are never values -- NSObject types are always reference counted pointers.完全に真実ではありません。ブロックは答えだけに抜け穴を投げます。
リチャードJ.ロスIII

@ RichardJ.RossIII…そしてGCCでは、clangで禁止されている値を宣言して使用できます。そしてもちろん、ポインタの後ろに値がなければなりません。
2012

11

「このようにしてください」はたくさんありますが、「なぜ?」に対する答えはありません。

だから:ヘッダーで@classを使用し、実装でのみ#importする必要があるのはなぜですか?@class #importを常に行う必要があるため、作業が2倍になります。継承を利用しない限り。その場合は、1つの@classに対して複数回#importします。その後、突然宣言にアクセスする必要がなくなった場合は、複数の異なるファイルから削除することを忘れないでください。

#importの性質上、同じファイルを複数回インポートしても問題にはなりません。コンパイルのパフォーマンスも実際には問題ではありません。もしそうなら、私たちが持っているほとんどすべてのヘッダーファイルにCocoa / Cocoa.hなどを#importingすることはありません。


1
なぜこれを行う必要があるかについてのドキュメントの例については、上記のAbizemの回答を参照してください。他のクラスのインスタンス変数を使用して相互にインポートする2つのクラスヘッダーがある場合の防御的なプログラミング。
jackslash

7

これを行うと

@interface Class_B : Class_A

Class_AをClass_Bに継承していることを意味します。Class_Bでは、class_Aのすべての変数にアクセスできます。

もしこれをしているなら

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

ここでは、プログラムでClass_Aを使用していると言いますが、Class_BでClass_A変数を使用する場合は、.mファイルで#import Class_Aを使用する必要があります(オブジェクトを作成し、その関数と変数を使用します)。


5

ファイルの依存関係と#importと@classに関する追加情報については、これをチェックしてください:

http://qualitycoding.org/file-dependencies/これは 良い記事です

記事の要約

ヘッダーファイルへのインポート:

  • #継承するスーパークラスと実装するプロトコルをインポートします。
  • マスターヘッダーのあるフレームワークからのものを除き、その他すべてを前方宣言します。
  • 他のすべての#importを削除するようにしてください。
  • 依存関係を減らすために、独自のヘッダーでプロトコルを宣言します。
  • 前方宣言が多すぎますか?あなたはラージクラスを持っています。

実装ファイルへのインポート:

  • 使用されない#importを除去します。
  • メソッドが別のオブジェクトに委任し、それが返すものを返す場合は、#importするのではなく、そのオブジェクトを前方宣言してみてください。
  • モジュールをインクルードすることにより、連続する依存関係のレベルを次々にインクルードする必要がある場合、ライブラリになりたい一連のクラスがある可能性があります。マスターヘッダーを使用して個別のライブラリとしてビルドします。これにより、すべてを1つの事前構築されたチャンクとして取り込むことができます。
  • #importが多すぎますか?あなたはラージクラスを持っています。

3

私が開発するとき、私は問題を引き起こさないことを3つだけ心に留めています。

  1. スーパークラスをインポートする
  2. 親クラスをインポートする(子供と親がいる場合)
  3. プロジェクトの外にクラスをインポートする(フレームワークやライブラリなど)

他のすべてのクラス(プロジェクト自体のサブクラスと子クラス)については、フォワードクラスを介して宣言します。


3

まだインポートしていないヘッダーファイルで変数またはプロパティを宣言しようとすると、コンパイラーがこのクラスを認識していないことを示すエラーが発生します。

あなたの最初の考えはおそらく#importそれです。
これにより、問題が発生する場合があります。

たとえば、ヘッダーファイル、構造体、または類似のものに一連のCメソッドを実装する場合、それらは複数回インポートする必要がないためです。

したがって、次のようにコンパイラーに指示できます@class

あなたはそのクラスを知らないのは知っていますが、それは存在します。他の場所にインポートまたは実装されます

このクラスが実装されるかどうかは不明ですが、基本的にはコンパイラーにシャットダウンしてコンパイルするように指示します。

通常#import.mファイルと.hファイル@classで使用します。


0

コンパイラがエラーを表示しないようにするために、宣言を転送します。

コンパイラは、ヘッダーファイルで宣言した名前のクラスがあることを認識します。


もう少し具体的に教えてください。
Sam Spencer

0

コンパイラは、コンパイラがその実装を知る必要があるような方法でそのクラスを使用する場合にのみ、文句を言います。

例:

  1. これは、クラスを派生させるか、
  2. そのクラスのオブジェクトをメンバー変数として使用する場合(まれですが)。

ポインタとして使用するだけなら文句は言わないでしょう。もちろん、オブジェクトをインスタンス化するにはクラスの内容を知っている必要があるため、(そのクラスのオブジェクトをインスタンス化している場合は)実装ファイルに#importする必要があります。

注:#importは#includeと同じではありません。これは、循環インポートと呼ばれるものがないことを意味します。importは、コンパイラーが特定のファイルを調べて情報を探す要求の一種です。その情報がすでに利用可能な場合、コンパイラはそれを無視します。

これを試して、AhをBhに、BhをAhにインポートしてください。問題や苦情はなく、問題なく動作します。

@classを使用する場合

@classは、ヘッダーにヘッダーをインポートしたくない場合にのみ使用します。これは、そのクラスがどうなるかを知らなくてもかまいません。そのクラスのヘッダーがまだない場合。

この例として、2つのライブラリを作成している場合があります。1つのクラスは、Aと呼び、1つのライブラリに存在します。このライブラリには、2番目のライブラリのヘッダーが含まれています。そのヘッダーはAのポインターを持っているかもしれませんが、再びそれを使用する必要がないかもしれません。ライブラリ1がまだ利用できない場合、@ classを使用してもライブラリBはブロックされません。ただし、Ahをインポートする場合は、ライブラリ2の進行がブロックされます。


0

@classを、コンパイラに「私を信じて、これは存在する」と伝えると考えてください。

#importをコピー/貼り付けと考えてください。

さまざまな理由から、インポートの数を最小限にしたい場合。何の研究もせずに最初に頭に浮かぶのは、コンパイル時間を短縮することです。

クラスから継承する場合、単純にforward宣言を使用できないことに注意してください。宣言するクラスがどのように定義されているかがわかるように、ファイルをインポートする必要があります。


0

これは、@ classが必要なシナリオの例です。

同じクラスのデータ型のパラメーターを持つヘッダーファイル内にプロトコルを作成する場合は、@ classを使用できます。プロトコルを個別に宣言することもできます。これは単なる例です。

// DroneSearchField.h

#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.