Objective-Cの循環参照について
2年ほど前に書いた記事 #import の使いどころ:循環参照しないために。 が未だにそれなりにアクセスがあるのですが、これは間違いがあります。正確にいうと、間違いでは無いのですが、冗長であまり意味のない記述でした。
#import は C言語の #includeを拡張して、一度だけしかヘッダファイルを読み込まないようになっています。そのため基本的には循環参照は起こらないようになっています。
では、何が問題になるか。
先の記事のコードは、以下のようなものでした。
ClassA.h
#import "ClassB.h" @interface ClassA { ClassB variableB; } @end
ClassB.h
#import "ClassA.h" @interface ClassB { ClassA variableA; } @end
これをコンパイルすると、以下のような処理が行われます。
- ClassA.h を読み込む
- ClassA.h の先頭の#import 宣言により、ClassB.h を読み込む
- ClassB.h を読み込む
- ClassB.h の先頭の#import 宣言により、ClassA.h を読み込もうとするが、既に読み込み済みのためスキップする。
- @interface で ClassB を宣言
- 型 ClassA の 変数 variableA を宣言
- エラー!! 型 ClassA が見つからない!!
ClassB.h を読み込んだときに、まだClassAの宣言が見つからないため、このエラーが発生します。そこで、@class ディレクティブで、ClassA はクラスだということを教えてあげる必要があります。@class 宣言を追加した正しいコードは以下の通り。
ClassA.h
#import "ClassB.h" @class ClassB; @interface ClassA { ClassB variableB; } @end
ClassB.h
#import "ClassA.h" @class ClassA; @interface ClassB { ClassA variableA; } @end
実装ファイルとヘッダファイルにimportを分ける必要は特にありません。全部ヘッダファイルに書いてOKです。
もう1点。
そのクラスのメンバーにアクセスしない場合は、import宣言は特に不要です。メソッドの戻り値のように、実装ファイルの中でそのオブジェクトのメンバーにアクセスする必要が無い場合などは、@class でクラス宣言だけしてやればOKです。インスタンス変数やメソッド引数のような場合は、実装中で使用するはずですので、importは必要でしょう。
@class は、その型がクラスであることを示しているだけです。逆に言うと使用するクラスを全部 @class で宣言しておいても特に問題はありません。(めんどくさいので普通はやらない) 循環しそうだな−、というのは @class で宣言しておくと幸せになれるかもしれません。
ということで、まとめ。
- 循環参照しそうな場合は、ヘッダに @class を宣言しとくと良い
- 実装ファイルで別途 importする必要は無い。
こちらも是非参考に:
Objective-Cのクラスの前方宣言がないと困ること