jarinosuke blog

about software engineering, mostly about iOS

モダンなインスタンス変数の扱いとプロパティ宣言

導入

最近、最近ふと 詳解 Objective-C 2.0 初版 を手に取る(電子化しているので実際に手に取るのは Kindle ですが)機会がありました。

もちろん最新の3版も持っているのですが、初版と比べて読み進めてみると色んな事を振り返る事ができて楽しいんですね、もう歴史です。

言うまでもなく、Objective-C は僕が関わってきた約5年近くだけでも凄まじい発展を遂げています。

コミュニティも WWDC や Developer Forum を初めとして、最近では CocoaPods など 3rd party のものまで活発になっています。

そのような環境の中でも、たまにそれらの範囲外では数年前の「構造」のままのコードを見る事があったりもします。

「構造」と少々分かり辛く書いているのは、以下のような「コーディング規約」を超えた考え方だと思ったので分けて書いています。

もちろんどちらが重要とか、そういう話では全く無いです。

そのような「構造」のズレや遅れを少しでも解決できればなぁと思い、この記事を書いています。

今回はそのような「構造」の中でも、OOP では欠かす事の出来ないインスタンス変数に関するものを3つ挙げました。

どれも僕の今までの iOS 開発による経験が主ですので、批判など大歓迎です。

1.インスタンス変数の定義とアクセス方法

いきなりですが、以下のようにヘッダファイルにインスタンス変数を定義してるなら今すぐやめましょう。

///JRNCustomClass.h

@interface JRNCustomClass : NSObject {
    NSNumber *numberOne;
}
@end

上記の記述には以下に挙げた二重の問題があると思っています。

結論となる解決策から言いますと、

@property ディレクティブを用いてプロパティ定義を行い、そのプロパティにはメッセージ式なりドット記法で必ずアクセスする。

(アクセス方法は「コーディング規約」の領域まで入ってきそうなので、曖昧にしてます。) 書くとしたら、こんな感じです。

///JRNCustomClass.m

@interface JRNCustomClass ()
@property (nonatomic) NSNumber *numberOne;
@end

@implementation JRNCustomClass

- (NSNumber *)numberOne
{
     if (!_numberOne) {
          _numberOne = [NSNumber numberWithInteger:1];
     }
     return _numberOne;
}

@end 

こう書くと、出てくる反論としては以下のようなものがあると思います。

これらの問いは以下の3つの利点によって、ほぼ無意味になると僕は考えます。

確かに getter/setter 経由でプロパティにアクセスする事で、CPU cycle を9倍消費するがそれは無いに等しいし、何回それを行ったとしてもボトルネックにはならないでしょう。

  • 2.一貫性

getter/setter 経由か、インスタンス変数への直接のアクセスか、どちらかによる副作用などのデバッグを行う必要が無くなります。

プロパティにアクセスする時に breakpoint を打ちたい時は getter/setter 内に打てば良いんです。無闇矢鱈に breakpoint を打つ必要が無くなります。

こう見ると、マジでインスタンス変数を定義したり、synthesize 経由で直接インスタンス変数にアクセスする必要なんて無いです。(もちろん getter/setter は除いて)

initializer と dealloc に関しては神経質になるなら、synthesize 経由でも良いと思います。そういう人は以下を参照して下さい。

Advanced Memory Management Programming Guide : Don’t Use Accessor Methods in Initializer Methods and dealloc

2.ヘッダでの読み取り専用プロパティの宣言

プロパティ宣言には属性指定ができ、そのなかでも読み取り専用を指定する readonly 属性はオブジェクト間での隠蔽性を保つ上で有用です。

しかし、上記で「必ずプロパティ宣言で生成された getter/setter 経由でアクセスする」と挙げているので、どうやれば?となりますね。

読み取り専用プロパティを定義しつつ getter/setter からのみのアクセスを維持するには、 以下のように実装ファイルにも同様のプロパティを readwrite 属性として宣言する事で実現可能です。

///JRNCustomClass.h

@interface JRNCustomClass
@property (nonatomic, readonly) NSNumber *numberOne;
@end


///JRNCustomClass.m
@interface JRNCustomClass
@property (nonatomic, readwrite) NSNumber *numberOne;
@end

3.適切な BOOL 型プロパティ宣言

少し「コーディング規約」にまでリーチする話ですが。

正直これは僕もたまに忘れてしまいます、BOOL 型のプロパティ宣言には適切な getter を設定しましょうというものです。

@property (nonatomic, getter = isHuman) BOOL human;

まとめ

大まかに3つ、問題提起と解決方法を記述しました。 既に知っている人達には当たり前だと思う事がほとんどだと思います。

今後も他に「構造」について思うところがあれば書いていきます。 このような記事を書こうと思った詳解 Objective-C 2.0には感謝感謝です。

詳解 Objective-C 2.0 第3版

詳解 Objective-C 2.0 第3版

参考

Structuring Modern Objective-C