読者です 読者をやめる 読者になる 読者になる

kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

Effective Objective-C 2.0 第1章(Objective-Cに慣れる)メモ

Effective Objective-C 2.0を買ったので、適当にまとめ。
Effective Objective-C 2.0
enumの話は知らなかったけど、第1章は半分ぐらいがC/C++でも当たり前の内容。

項目1 Objective-Cのルーツを知る

ぶっちゃけなんもない。
スタック上にObjective-Cのオブジェクトを宣言できない、ヒープ上に確保する(される)。
スタックスペースを使っているようなもの(CGRectとか)を見かけるが、それは構造体とか非オブジェクト型ですよ。
動的束縛(dinamic binding)によるメッセージング構造を使っているため、型やメッセージに対する実行コードは、コンパイラではなくランタイムが判断するというような内容。

項目2 インポートされるヘッダーに含まれるヘッダーの数は最小限に抑える

  • ヘッダーは可能な限り深い位置でインポートして、密結合を避ける。

ヘッダーで別のヘッダーをインポートするとクラスの相互参照問題が発生する場合があるので、クラスの先行宣言(前方宣言、前方参照)

@class myClass;

を使う。
スーパークラス、準拠しているプロトコルは、前方宣言だけでは不十分でインポートしてないと駄目。
ただし、プロトコルの場合は、ヘッダでインポートせずに実装ファイルで宣言すれば良い場合が多い。

  • 準拠するプロトコルを宣言するときは、可能な限り、class extension (カテゴリ)にプロトコル準拠の宣言を移すことを検討する(前方宣言ではまずい場合がある)。そうできない場合は、プロトコルだけを定義した(小さな)ヘッダーをインポートする。

実装ファイルでプロトコル準拠の宣言をする場合は、↓こんな感じ.

@interface myClass ()
<myDelegate>
@end

項目3 メソッドよりも同じ意味のリテラル構文を使う

  • 今は文字列以外にも様々なリテラル構文が使えるので、メソッドよりそっちを使いましょう。
int x = 1, y = 2;
NSString *str = @"a";
NSNumber *intN = @1; // = [NSNumber numberWithInt:1];
NSNumber *floatN = @1.f;
NSNumber *doubleN = @1.;
NSNumber *boolN = @YES;
NSNumber *charN = @'a';
NSNumber *exp = @(x + y);
    
NSArray *array = @[@1, @2, @3]; // = [NSArray arrayWithObjects: @1, @2, @3, nil];
NSNumber *n = array[0]; // = [array objectAtIndex:1];
NSMutableArray *mArray = [@[@1, @2, @3] mutableCopy];
mArray[0] = @0; //[mArray replaceObjectAtIndex:0 withObject:@0];
//mArray[100] = @4 //bad access
    
NSDictionary *dic =             // = [NSDictionary dictionaryWithObjectsAndKeys:
    @{ @"key1" : @"value1",     //          @"value1", @"key1",
       @"key2" : @"value2",     //          @"value2", @"key2",
       @"num"  : @1 };          //          @1       , @"num" , nil];
n = dic[@"num"]; // = [dic objectForKey:@"num"];
NSMutableDictionary *mDic = [@{ @"num1" : @1, @"num2" : @2 } mutableCopy];
mDic[@"num1"] = @0; //[mDic setObject:@"num1" forKey:@0];
mDic[@"num3"] = @3;

式にも@()を使えばリテラル構文が使用可能。
mutableな配列や辞書はmutableCopyすればおk。無駄な(imutableな)オブジェクトが一つ作られるが可読性はこっちの方が高い。
ARC使ってないなら(mutable)copyしたときにautoreleaseを忘れずにする。

リテラル構文で配列や辞書をつくる場合は、要素にnilを含まないようにしないと下のような例外が発生する。

NSNumber *n = nil;
NSArray *a;
a = [NSArray arrayWithObjects:@1, @2, n, @3, nil]; //ok, but array = @[@1, @2]
a = @[@1, @2, n, @3]; //error
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[1]'

ちなみにnil相当のものをNSArrayやNSDictionaryに入れたい場合は、

[NSNull null]

を使う。シングルトンなので、NSNullかどうかのチェックは==でいいはず。

項目4 プリプロセッサの#defineではなく型付き定数を使う。

#defineだと型情報がないし、再定義できてしまうから型付き定数を使いませう。

  • 翻訳単位固有の定数はstatic constを使う。

グローバルシンボルテーブルに入らないから、名前空間的プレフィックスをつける必要なし。
ポインタの場合は、constの位置に注意。

NSString * const ConstStr = @"const str"; //後ろから読んでconstant pointer to an NSString
//NSString const * str1 = @"str1"; だと pointer to constant NSString
//const NSString * str2 = @"str2"; も   pointer to constant NSString
  • グローバル定数は、ヘッダーファイルでexternal(extern)宣言し、実装ファイルで定義する。定数名には名前空間的プレフィックスをつける。

項目5 状態、オプション、ステータスコードにはenumを使う

C++11にあわせて、若干の変更がある。型の指定が可能になり、そのおかげで前方宣言できるようになった。

typedef enum enum_tag : NSInteger { e1, e2, e3, }myEnum; //enum_tagは省略可能
  • 土台の型を明示してenumを定義するときには、NS_ENUM, NS_OPTIONSマクロを使う。

コンパイラが選んだ型ではなく自分が選んだ型が確実に使われる。
ビット演算したい場合はNS_OPTIONSを使う。C++の場合、enumの土台の型からenum型へは明示的にキャストする必要がある。
キャストしなくても済むようにしてくれるマクロがNS_OPTIONS。

typedef NS_ENUM(NSUInteger, myEnum) { e1, e2, e3 };
typedef NS_OPTIONS(NSUInteger, myOption) {
    o1 = 1 << 0,
    o2 = 1 << 1,
    o3 = 1 << 2,
};

ちなみにC++11では、名前を衝突させないようにenum class(scoped enumeration)が使えるようになったらしい。

enum class myEnum : uint8_t { e1, e2 }; //enum structでも同じ

参考URL
http://ramemiso.hateblo.jp/entry/2013/09/18/184145
http://d.hatena.ne.jp/spinor/20110918/1316321563
http://ja.wikipedia.org/wiki/C%2B%2B11

  • enumを処理するswitch文ではdefaultを使わないようにする

enumに値を追加したとき、追加した値のcaseラベルがないとコンパイラが警告してくれる。