Effective Objective-C 2.0
第一章 Objective-Cに慣れる
「インポートされるヘッダーに含まれるヘッダーの数は最小限に抑える」
@class FooClass; でクラスを先行宣言できる。依存するクラスは先行宣言して、実装ファイルでincludeすると循環参照とかにならなくてよい
プロトコルの場合は、プロトコルだけ定義するヘッダーを作ると良い
「プリプロセッサの#defineではなく型付き定数を使う」
#defineやめてstatic const使いましょう
型便利
kAnimationDurationみたいな風にprefix kをつける
公開するときはヘッダーファイルで extern NSString *const Fooみたいにして、実装ファイルでNSString *const Foo = @"foo"とすればよい
「状態、オプション、ステータスコードにはenumを使う」
便利マクロがある。
NS_ENUMマクロは状態などに。オプションなどにはNS_OPTIONSマクロを使うと便利。
enumをswitchで処理する時に、default:を定義しないでおけば、enum追加した場合に警告が出て良い。
感想
色々Cっぽい
名前空間がないので、プレフィックスとかで頑張る世界観なのはすごい
第二章 オブジェクト、メッセージング、ランタイム
「プロパティの理解」
プロパティのメモリ上の定義
プロパティがあるメモリの位置をハードコードしていると、オブジェクトの仕様が変わった時に追従できない
ABIを導入して、実行時にルックアップできるようにしている @property構文
プロパティにアクセスするためのアクセサを設定する
Person.age という風にドットでアクセスできる
実体はPerson._age
コンパイル時にコードを生成する
@dynamic 構文
実装ファイルで書く
インタフェースに@propertyがあっても、アクセサのコードは自動生成しない
実行時にはある。ということで信頼してコンパイルする
nonatomic
ロック機構が入るかどうか
デフォルトはatomic
iOSだと、atomicにしてると重かった経緯があったのでnonatomicが好んで使われる
アトミックにしたところで、スレッド安全性は保証されないので、そんなに必須ではないと書いてある
readwrite / readonly
その名の通り。
assign / strong / weak / unsafe_unretained / copy
メモリ管理属性。あくまでも表明で、こういう感じで実装するのはプログラマの責務(だと思う mactkg.icon)
assign: スカラー型向け。単純に出し入れする
strong: データを所有する。値をretainして、古い値をreleaseする。
weak: データを所有しない。retainしないし、releaseもしない。assignと似てるけど、nilの可能性があるのに注意。
unsafe_unretained: データを所有しない。retainしないし、releaseもしない。nilの可能性はない(unsafe)。assignのオブジェクト版
copy: データを所有する。値をretainせずに、copyして所有する。値が変わって困るとか、そういうものはcopyつけとく。NSString *とかの場合、NSMutableString *が入ってきてしまうので、copyしておくと安心
getter= / setter=
ゲッターとセッターの名前を決められる。
例: @property(nonatomic, getter=isMorning) BOOL morning
「オブジェクトが等しいとはどういうことか理解しよう」
(BOOL)isEqual:(id)objectと(NSUInteger)hash
isEqualは型が曖昧でルックアップに時間がかかるのでisEqualToString:(NSString)stringとか作っちゃうこともある
isEqualがYESならhashは一致してほしいけど、hashが一致してもisEqualはNOでもよい
hashだからね mactkg.icon
「実装の詳細を隠すために、クラスクラスタパターンを使おう」
言語機能として抽象クラスかどうかとか表現はできないので、ドキュメントとかでやっていく雰囲気
「サブクラスは独自のストレージを定義しなくてはならない」
これ、プロパティはサブクラスが用意しろって言っているんだろうか。そういうものなのかな mactkg.icon
「objc_msgSendの役割を理解する」
objc_msgSend(id self, SEL cmd, ...)
これでダイナミックにテーブルから関数を引いてきてディスパッチする
そんなに重い処理ではない
末尾最適の話
objc_msgSendファミリの関数は正しいメソッド実装に「ジャンプする」
これってアセンブラのjumpと同じ?引数が同じだからジャンプだけで済むってことかな?
関数が最後に行うことが関数の呼び出しだけで、戻り値を使わないので、ジャンプで実装できる。
こうするとスタックフレームをプッシュしなくて良い
「メッセージの転送を理解する」
+ (BOOL)resolveInstanceMethod:(SEL)selector
@dynamicパラメータの実装とかに使われる
代替レシーバ
- (id)forwardingTargetForSelector:(SEL)selector
他に受け取れるレシーバがあるかを返す
転送メカニズム
- (void)forwardInvocation:(NSInvocation*)invocation
NSInvocationはレシーバとかメッセージが入っていて、実行可能なオブジェクト
色々書き換えて、メッセージを転送することができる
正直、何に使うのかはよくわからん mactkg.icon
「クラスオブジェクトとは何かを理解する」
isMemberOfClass / isKindOfClass
オブジェクトの一致を比較する
isEqual:
クラスオブジェクトの一致を比較する
==
[object class] == [FooClass class]
objectはFooClassのインスタンスである
オブジェクトはメッセージの転送を使っている可能性があるので、オブジェクトを直接比較せずにイントロスペクションメソッドを使ったほうが良い
第3章 インターフェイス設計とAPI設計
「名前空間の衝突を避けるためにプレフィックスを使う」
組み込みの名前空間機能がない
「指定イニシャライザを用意せよ」
必ず通るinit* を指定しておくとよい
- (id)init はデフォルト値を指定して指定イニシャライザを呼ぶか、例外を発生させる
継承した場合
継承元の指定イニシャライザが有効になってしまっている場合があるので、オーバーライドした方がよい
その場合は、継承先(オーバーライドする方)の指定イニシャライザを呼ぶ作りにした方がよい
例外でもいいけど、例外を投げるのはエラーが致命的という意味なので、注意して使う
「descriptionメソッドの実装」
NSLog(@"%@", object) とした場合にはdescriptionメッセージがオブジェクトに送られる
NSDictionaryはいい感じに表示してくれるので、objectの様子を表現する場合は、NSString stringWithFormat:を使ってフォーマットするときに、objectの形式で値をわたしてあげると捗る
code:description.m(objc)
- (NSString*)description {
return [NSString stringWithFormat:@"<%@: %p, %@>",
self,
@{@"title":_title},
@"latitude":@(_latitude),
@"longitude":@(_longitude)}
}
}
デバッガでpoしたときにはdebugDescriptionが呼ばれる。実装してない時はdescriptionにフォールバック
「できるだけイミュータブルなオブジェクトを使う」
整合性保つために、オブジェクトは必要なときに限りミュータブルにした方がよい
設計の基本という感じはする mactkg.icon
内部からのみ書き換えたい場合
ヘッダーファイルにはreadonlyで定義しておく
クラス延長カテゴリを使って、readwriteに宣言しなおす。(実装ファイルでもう一度インタフェースを定義する)
KVCを使うと 弄れてしまう。紳士的に振る舞うべし コレクションをミュータブルにするかどうか
ヘッダーでは@property (nonatomic, strong, readonly) NSSet *friendsという感じで宣言する
実装ファイルで- (NSSet*)friends{ return *_internalFriends }という感じで内部で管理しているコレクションを返すようにする
しかし、これは結局オブジェクトのポインタを渡してるので、書き換え可能ではある。
イントロスペクションしてみれば、NSMutableArrayが親にいることはわかる。(NSSetはNSMutableArrayを継承している)
だからといって書き換えてはいけない。@property (readonly)なわけなので。
「明快で首尾一貫した名前を使う」
しかし、長いメソッド名を使うことを恐れてはならない。メソッド名に必要な長さをかならず与えれば、メソッドは言っているとおりのことをするようになる。しかし、とてつもなく長い名前も避けるべきだ。メソッド名は、簡にして要を得たものでなければならない。
「非公開メソッドにはプレフィックスをつける」
Rubyと同じ感じだった
呼べてしまうので注意
「Objective-Cエラーモデルを理解する」
例外はARC非対応
アプリケーションを終了するほどのシナリオのときだけ、例外を投げる
その他のエラーは?
nil, 0
NSError
だいたい、デリゲートで通知される
例: NSURLConnectionDelegate
ポインタのポインタを引数で渡すパターンもある
第4章 プロトコルとカテゴリ
「オブジェクト間通信には、DelegateとData Sourceプロトコルを使う」
Delegate: 処理の移譲。ビジネスロジックからデータを分離できる
Data Source: クラスにデータを供給する
setDelegateを実装すれば、delegateへの代入をhookできるから、delegateメソッドが実装されているかイントロスペクションを使って記録しておくとよい
delegateメソッドは、@optionalにしておくとよい
「カテゴリを使ってクラスの実装を管理できるサイズに分割せよ」
カテゴリに分ける
カテゴリに分けたら、分けた部分を使いたい場合はヘッダをincludeする必要がある
勝手に呼べちゃうとかは無くて安全ぽい気がする mactkg.icon
デバッグ時に、スタックトレースで[Foo(Bar) addFriend:]って感じに表示されるから、デバッグしやすい。
よくやるのは、Foo(Private)っていうカテゴリを作って、公開APIにしたくないものを入れておく
ヘッダをリリースしなければ外から見えない
「サードパーティクラスに追加するカテゴリで使う名前にはかならずプレフィックスを付ける」
次は22から