Objective-Cでは、インスタンス変数をどこに宣言するのが正しいのか?

  • 141
    いいね
  • 3
    コメント
この記事は最終更新日から1年以上が経過しています。

Objective-Cでインスタンス変数が宣言可能なのは次の3カ所です。

Case 1:ヘッダー内

SampleClass.h
@interface SampleClass : NSObject{
    int i;
}

Case 2:クラス拡張(Class extension)内

SampleClass.m
@interface ViewController (){
    int i;
}

Case 3:implementationの直後

SampleClass.m
@implementation ViewController{
    int i;
}

では、どこで宣言するのがベストなのでしょうか?
こちらのAppleの公式ドキュメントを参考にすると、
https://developer.apple.com/jp/devcenter/ios/library/documentation/ObjC.pdf
38ページの先頭に以下の記述があります。

インスタンス変数は実装詳細であり、通常、クラス自身の外からアクセスされることはあり
ません。さらに、実装ブロック内に宣言すること、あるいは宣言済みプロパティから自動生
成させることも可能です。したがって通常は、インスタンス変数宣言をパブリックインター
フェイスで行うべきではないので、波括弧も省略してください。

このように、Apple側はCase1のようにヘッダーファイル内でメンバ変数の宣言することを推奨していないようです。
また、このドキュメントでは、Case3のようにimplementationの直後でインスタンス変数の宣言をしていますね。

ところが、Case3の場合ちょっと問題があります。
Storyboardにおいて、Outletの接続を行う場合です。
例えば、以下のように書いてStoryboardでOutletの接続を行おうとすると、

@implementation ViewController{
    IBOutlet UILabel *label;
}

Storyboardの当該箇所にこのインスタンス名がすぐには表示されません。
クリーン後、しばらく時間が経つと表示される場合が多いです(Xcode 5.0.2で確認)。
これはXcodeの不具合(?)かと思います。
以下の箇所に書けば当該箇所にインスタンス名が即反映されます。

SampleClass.h
@interface SampleClass : NSObject{
    IBOutlet UILabel *label;
}
SampleClass.m
@interface ViewController (){
    IBOutlet UILabel *label;
}

上記の理由で、StoryboardとOutletの接続を行う場合は、Case3のimplementationの直後以外で宣言した方がいいかと思います。しかし、Storyboardを使わないのであればここに宣言するのもありかと思います。

Case1のヘッダーファイル内で宣言した場合ですが、前述のAppleの公式ドキュメントの43ページにあるように、通常はインスタンス変数の宣言前に@protectedのディレクティブがついていることになっています。
従って、意図的に@publicをつけない限りサブクラス以外のクラス外からアクセスしようとするとコンパイルエラーが発生してしまうので、アクセスはサブクラスに限られるようです。
サブクラスからも隠蔽したい場合は@protectedをつければ大丈夫です。
従って、他のクラスからの意図しないアクセスが問題になることはそれほど無いように思えます。
ただし、.演算子や->演算子で他のクラスからアクセスしようとすると自動補完候補に挙がってしまうようです。

Case2のクラス拡張内に宣言する場合、今のところ特にデメリットは無いように思えます。
それにしても、このクラス拡張というものは他に用途はあるのでしょうか。
前述のAppleのドキュメントを読んでも、使いどころが思い浮かびません(^^;

Case1〜3のどれを選択するかは好みの問題も多いとは思いますが、個人的な好みでは実装ファイルが肥大化するのが嫌なのと、ファイルの先頭までスクロールするのが嫌なのでヘッダーファイル内に宣言することが多いです。
Appleの中の人ごめんなさい(^^;