ClassAがClassBヘッダーを含める必要があり、ClassBがClassAヘッダーを含める必要がある場合には、クラスの前方宣言を使用して循環的な包含を回避する必要があることを私は理解しています。 #import
は単純なifndef
なので、インクルードは一度だけ行われることも理解しています。
私の質問はこれです:いつ#import
を使うのですか、そしていつ@class
を使うのですか? @class
宣言を使用すると、次のような一般的なコンパイラの警告が表示されることがあります。
warning: receiver 'FooController' is a forward class and corresponding @interface may not exist.
@class
の前方宣言を削除して#import
をスローしてコンパイラからの警告を黙らせるのではなく、本当にこれを理解したいと思います。
この警告が表示されたら:
警告:受信側 'MyCoolClass'はフォワードクラスであり、対応する@interfaceが存在しない可能性があります。
ファイルを#import
する必要がありますが、実装ファイル(.m)でそれを行い、ヘッダーファイルで@class
宣言を使用することができます。
@class
は、(通常は)#import
ファイルの必要性を排除するものではなく、単に情報が役立つ場所の近くに要件を移動させるだけです。
例
@class MyCoolClass
と言うと、コンパイラは次のように表示されることを認識しています。
MyCoolClass *myObject;
MyCoolClass
が有効なクラスであること以外に心配する必要はありません、そしてそれはそれへのポインタ(実際には、単にポインタ)のためのスペースを確保するべきです。したがって、ヘッダーの@class
は90%の時間で十分です。
ただし、myObject
のメンバーを作成またはアクセスする必要がある場合は、それらのメソッドが何であるかをコンパイラに知らせる必要があります。この時点で(おそらくあなたの実装ファイルの中で)、#import "MyCoolClass.h"
を実行する必要があります。これは、単に「これはクラスです」という以外の追加情報をコンパイラーに伝えるためです。
3つの簡単な規則
#import
ファイル)には、スーパークラスと採用されているプロトコルだけを.h
で記述してください。#import
すべてのクラスとプロトコルは、実装内でメッセージを送ります(.m
ファイル)。実装ファイルで前方宣言をしている場合は、おそらく何か問題があります。
ADC のObjective-Cプログラミング言語のドキュメントをご覧ください。
クラスの定義のセクションの下にあります。これが行われる理由を説明するクラスインタフェース
@classディレクティブは、コンパイラとリンカから見えるコード量を最小限に抑えるため、クラス名を前方宣言するための最も簡単な方法です。単純なので、それはまだ他のファイルをインポートするファイルをインポートすることに伴うかもしれない潜在的な問題を避けます。たとえば、あるクラスが別のクラスの静的型付きインスタンス変数を宣言し、それらの2つのインタフェースファイルが互いにインポートした場合、どちらのクラスも正しくコンパイルされない可能性があります。
これが役に立つことを願っています。
必要に応じてヘッダーファイルで前方宣言を使用し、実装で使用しているすべてのクラスのヘッダーファイルを#import
にします。言い換えれば、あなたはいつもあなたがあなたの実装で使っているファイルを#import
し、あなたがあなたのヘッダファイルでクラスを参照する必要があるなら、同様に前方宣言を使います。
これに対する例外は、あなたが継承しているクラスまたは正式なプロトコルをあなたのヘッダファイルの中で#import
する必要があるということです(その場合は必要ないでしょう)。実装にそれをインポートするため)。
一般的な方法は、ヘッダーファイルで@classを使用することです(ただし、スーパークラスを#importする必要があります)。実装ファイルでは#importを使用します。これは循環的な包含を避けるでしょう、そしてそれはただうまくいきます。
もう一つの利点:クイックコンパイル
ヘッダファイルをインクルードした場合、それを変更すると現在のファイルもコンパイルされますが、クラス名が@class name
として含まれている場合はそうではありません。もちろん、ソースファイルにヘッダを含める必要があります。
私の質問はこれです。いつ#importを使用し、いつ@classを使用しますか?
簡単な答え:物理的な依存関係があるときは#import
または#include
です。それ以外の場合は、前方宣言(@class MONClass
、struct MONStruct
、@protocol MONProtocol
)を使用します。
これは、身体的依存の一般的な例です。
CGPoint
をivarまたはプロパティとして持っている場合、コンパイラはCGPoint
の宣言を見る必要があります。@class宣言を使用すると、次のような一般的なコンパイラの警告が表示されることがあります。 "warning:receiver 'FooController'はフォワードクラスであり、対応する@interfaceは存在しない可能性があります。"
この点に関して、コンパイラは実際には非常に寛大です。ヒント(上のようなもの)を落としますが、それらを無視して正しく#import
していなければ、スタックを簡単にゴミ箱に捨てることができます。それは(IMO)あるべきですが、コンパイラはこれを強制しません。 ARCでは、コンパイラは参照カウントを担当するため、より厳密です。あなたが呼び出す未知のメソッドに遭遇すると、コンパイラはデフォルトに戻ります。すべての戻り値とパラメータはid
とみなされます。したがって、これは肉体的依存と考えられるべきであるので、あなたはあなたのコードベースからすべての警告を根絶するべきです。これは、宣言されていないC関数を呼び出すのと似ています。 Cでは、パラメータはint
とみなされます。
前方宣言を好む理由は、最小限の依存関係しかないため、要素によってビルド時間を短縮できることです。前方宣言では、コンパイラは名前があることを認識し、物理的な依存関係がない場合はクラス宣言やそのすべての依存関係を見なくてもプログラムを正しく解析してコンパイルできます。クリーンビルドは短時間で完了します。インクリメンタルビルドは時間がかかりません。確かに、結果としてすべての翻訳に必要なすべてのヘッダーが見えるようにするためにもう少し時間を費やすことになりますが、これは短期間のうちに短縮されます(プロジェクトが小さいとは限らないため)。
代わりに#import
または#include
を使用すると、コンパイラーで必要以上に多くの作業が発生します。あなたはまた複雑なヘッダ依存関係を導入しています。これはブルートフォースアルゴリズムにたとえることができます。あなたが#import
するとき、あなたはたくさんの不必要な情報をドラッグしています、それはソースを解析してコンパイルするために多くのメモリ、ディスクI/O、そしてCPUを必要とします。
NSObject
型は決して値ではありません - NSObject
型は常に参照カウントポインタであるため、ObjCは依存性に関してCベースの言語には非常に理想的です。したがって、プログラムの依存関係を適切に構築し、可能な場合には物理的な依存関係がほとんどないため、可能な限り前方に進めば、驚くほど高速なコンパイル時間を回避できます。依存性をさらに最小限に抑えるために、クラス拡張でプロパティを宣言することもできます。これは大規模システムにとっては大きなボーナスです。大規模なC++コードベースを開発したことがあれば、その違いがわかるでしょう。
したがって、私のお勧めは、可能な場合はフォワードを使用し、次に身体的な依存がある場合は#import
を使用することです。肉体的依存を意味する警告などが表示された場合は、それらすべてを修正してください。実装ファイルの#import
を修正してください。
ライブラリを構築するとき、あなたはおそらくいくつかのインタフェースをグループとして分類するでしょう。その場合、あなたは物理的依存が導入されているそのライブラリを#import
するでしょう(例えば#import <AppKit/AppKit.h>
)。これにより依存関係が生じることがありますが、ライブラリメンテナは必要に応じて物理的な依存関係を処理できることが多くあります。機能を導入すれば、ビルドへの影響を最小限に抑えることができます。
私は多くの「このようにしていますか」を見ますが、「なぜ」に対する答えは見当たりません。
それで、なぜをあなたのヘッダに@classし、#importをインプリメンテーションにのみ含めるべきか?常に@classと#importを実行する必要があるため、作業は2倍になります。あなたが継承を利用しない限り。その場合、単一の@クラスに対して複数回#importingすることになります。その後、宣言にアクセスする必要がなくなったと突然決心した場合は、複数の異なるファイルから削除することを忘れないでください。
#importの性質上、同じファイルを複数回インポートしても問題ありません。パフォーマンスのコンパイルも実際には問題ではありません。もしそうであれば、私たちが持っているほとんどすべてのヘッダファイルにCocoa/Cocoa.hなどを#importingすることはないでしょう。
これをしたら
@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ファイルでClass_A変数を#importする必要があります(オブジェクトを作成してその関数と変数を使用します)。
ファイルの依存関係についての追加情報と#importと@classをチェックしてください。
http://qualitycoding.org/file-dependencies/ それは良い記事です
記事のまとめ
ヘッダファイルにインポートする:
- #継承しているスーパークラスと、実装しているプロトコルをインポートします。
- 他のすべてのものを前方宣言します(マスターヘッダーを持つフレームワークから来たものでない限り)。
- 他のすべての#importを排除するようにしてください。
- 依存関係を減らすために、独自のヘッダーでプロトコルを宣言してください。
- 前方宣言が多すぎますか。あなたはラージクラスを持っています。
実装ファイルにインポートする:
- 使用されていない#importsを削除します。
- メソッドが別のオブジェクトに委譲して戻ってきたものを返す場合は、#importするのではなく、そのオブジェクトを前方宣言するようにしてください。
- モジュールをインクルードすることで、連続した依存関係のレベルを次々にインクルードする必要がある場合は、ライブラリになりたい一連のクラスがあるかもしれません。マスターヘッダを持つ別のライブラリとしてそれを構築するので、すべてを単一のビルド済みチャンクとして取り込むことができます。
- #importが多すぎますか?あなたはラージクラスを持っています。
まだインポートしていない変数またはヘッダーファイルでプロパティを宣言しようとすると、コンパイラがこのクラスを認識していないというエラーが発生します。
あなたの最初の考えはおそらくそれ#import
です。
これは場合によっては問題を引き起こす可能性があります。
たとえば、ヘッダーファイル、C構造体、またはそれに似たものにCメソッドの束を実装した場合、それらは複数回インポートしてはいけません。
したがって、@class
でコンパイラに伝えることができます。
私はあなたがそのクラスを知らないことを知っていますが、それは存在します。他の場所にインポートまたは実装される予定です。
このクラスが実装されるかどうかは定かではありませんが、基本的にはコンパイラに終了してコンパイルするように指示します。
通常は.mに#import
、に@class
を使用します。hファイル。
私が成長するとき、私は私に何の問題も引き起こさないことを心に留めている3つだけの事を持っています。
他のすべてのクラス(私のプロジェクトselfのサブクラスと子クラス)では、それらをforward-classで宣言します。
これは@classが必要なシナリオの例です。
ヘッダファイル内に同じクラスのデータ型を持つパラメータを持つプロトコルを作成したい場合は、@ classを使用できます。プロトコルを別々に宣言することもできますが、これは単なる例です。
// DroneSearchField.h
#import <UIKit/UIKit.h>
@class DroneSearchField;
@protocol DroneSearchFieldDelegate<UITextFieldDelegate>
@optional
- (void)DroneTextFieldButtonClicked:(DroneSearchField *)textField;
@end
@interface DroneSearchField : UITextField
@end
コンパイラは、その実装を知る必要があるような方法でそのクラスを使用しようとしている場合にのみ文句を言います。
例:
あなたがポインタとしてそれを使うつもりならそれは文句を言わないでしょう。オブジェクトをインスタンス化するにはクラスの内容を知っている必要があるので、実装ファイル内で#importする必要があります(そのクラスのオブジェクトをインスタンス化する場合)。
注:#importは#includeと同じではありません。これは循環インポートと呼ばれるものが何もないことを意味します。 importは、コンパイラが情報を得るために特定のファイルを調べるように要求するようなものです。その情報がすでに利用可能な場合、コンパイラはそれを無視します。
これを試して、A.hをB.hに、B.hをA.hにインポートしてください。問題や苦情はありませんし、それもうまく動作します。
@classを使う場合
あなたのヘッダにヘッダをインポートしたくない場合でも、@ classを使用します。これは、そのクラスがどのようなものになるのかを知りたくても気にしない場合です。そのクラスのヘッダがまだない場合もあります。
この例としては、2つのライブラリを書いていることが考えられます。一つのクラスは、それをAと呼びましょう、一つのライブラリに存在します。このライブラリには、2番目のライブラリのヘッダが含まれています。そのヘッダはAのポインタを持っているかもしれませんが、やはりそれを使う必要はないかもしれません。ライブラリ1がまだ利用できない場合は、@ classを使用してもライブラリBはブロックされません。しかし、A.hをインポートしようとしているのであれば、図書館2の進歩は妨げられています。
@classが「信頼してくれ、これは存在する」とコンパイラに伝えていると考えてください。
#importをコピー&ペーストとして考えてください。
あなたはいくつかの理由であなたが持っている輸入の数を最小にしたいです。何の研究もしなければ、最初に頭に浮かぶのはコンパイル時間を減らすことです。
クラスから継承するときは、単純に前方宣言を使用することはできません。ファイルをインポートする必要があるので、宣言しているクラスはそのファイルの定義方法を認識しています。
コンパイラにエラーが表示されるのを防ぐためだけに宣言を進めます。
宣言するためにヘッダファイルで使用した名前のクラスがあることをコンパイラが認識します。