ブログネタ
Objective-C に参加中!

詳解Objective-C 2.0読書会に参加してきた。読書会というのに参加したのは初めてだけど、皆で同じ本を読んで、読んだ部分に対して「ここがわからない」「いやそれは実はこういうことで…」というやりとりは中々面白かった。一人で読んでるとスルーしがちなところも拾ってみると勉強になるなぁ。

それより何より、Objective-Cなんていうマニアックな言語の、読書会なんていうストイックな催しに、24人も集まったのが凄い。これもiPhone効果かーなんて思ったけど、現時点ではiPhoneを買うと表明した人が少なかったのも印象深かった。いや俺も買わないけど。とは言えなんだかんだで衝動買いしかねないけど。どっちだよ。

今日はCHAPTER2とCHAPTER3の途中までを読んだ。CHAPTER2はObjective-Cの基本的な特徴や構文、CHAPTER3がクラス定義と継承だった。内容に関しては詳解Objective-C 2.0を読んでもらうとして、以下は読書会中に挙がった話の補足。

[[Hoge alloc] init]について

ObjCでクラスからオブジェクトを作る場合のイディオムについての話。「allocの後に続けて初期化処理を書かないといけないと言われたことがあるが、それは何故か、どう危険なのか、またそれが必須ならなぜ一纏めにしないのか」との質問が挙がった。

allocの後にinitもしくはinit...で始まるメッセージを続ける理由は、呼び出し側でそれを分けて使うことがまず無いから。別に、

id obj = [Hoge alloc];
/* なんか別な処理 */
[obj init];

としたところで文法上は問題無いんだけど、NSObjectのクラスメソッドであるところのallocは単にメモリを割り当ててオブジェクトを生成しただけで、それがオブジェクトとして振る舞うためには最初にNSObjectのインスタンスメソッドのinitが呼び出されていなければならず、上のコードの変数objは「なんか別な処理」をしている最中には、「何もできないか、何かさせようとすると異常な挙動をする」。詳しくは見てないけど、NSObjectのinitは結構色々やってるはず。そんな不安定な状態のものを放っておくメリットはよっぽど特殊なケースでないと皆無なので、allocと初期化処理は同時にやってしまうようにしましょうと言う話。

じゃあ何でメモリ割り当てと初期化が別なんだ、一緒でいいだろ、と言われるとちょっと勉強不足で即答できないんだけど、内部的には未初期化のオブジェクトが効果的に使われてるところがあるのかもしれない。そういう設計思想だからじゃないか、としかわかんないなー。

ちなみに、「allocしてinitしたオブジェクトを返すクラスメソッド」はある。NSObjectにnewってメソッドが定義されてるので、上記のイディオムの代わりに、

id obj = [Hoge new];

って書いても同じ意味になる。ただこれだと引数なしのinitを呼び出すだけなので、引数付きで同等のことをやりたかったらそういうことをするクラスメソッドを定義してやる必要がある。あと、Cocoaのクラスでも初期化で色々ややこしいことしてるやつは、そういうクラスメソッドが用意されている。例えばNSArrayのarrayWithObjects:とか。ちなみに、Fooクラスのクラスメソッドの「fooWithHoge:」と、Fooクラスのインスタンスメソッドの「initWithHoge:」は対応させるのが慣習になっているようだ。

初期化処理のイディオム

詳解Objective-C 2.0には、初期化処理は通常次のようにする、と書かれている。

- (id)init
{
    self = [super init];
    if (self != nil)
    {
        /* 何らかの初期化処理 */
    }
    return self;
}

これは実は、次のように書いても大抵の場合問題ない。

- (id)init
{
    [super init];
    /* 何らかの初期化処理 */
    return self;
}

スーパークラスの初期化処理を明示的に呼び出すことと、returnを省略できないのは変わらないんだけど、明示的なselfへの代入とnilチェックはしなくても処理できる。スーパークラスの初期化処理を辿ってNSObjectのinitが呼び出されると自動的にselfへオブジェクトが代入されるし、仮にselfがnilでも「nilに対するメッセージ送信は単に無視される」。まぁインスタンス変数の扱いとかは危ういけど。

じゃあ何でわざわざselfへの代入を明示的にするのかと言えば、「selfに自分自身じゃないオブジェクトを代入してもいい」上に「スーパークラスのinitの返り値が自分自身とは限らない」から。そういうトリッキーな実装も可能なので(例えばNSStringとかは実は結構トリッキーなことをやってる。その話が出てくるのは大分先だけど)、何か特殊なことをするのでない時は上のイディオムで書いた方がいい。てかselfが自分自身じゃなくてもいいって凄いな。お前は誰だ。

superの意味

selfが単にオブジェクトだったのに対し、superは変数に代入したり付け替えたりはできない。多分予約語なんだよな。superは何かと言うと、「自分より上位の継承関係を辿って行って最初に見つけたクラスの実装を使って、自分のコンテキストでその処理を実行する」という構文。難しいね。

基本的には一つ上のスーパークラスを見て、なければさらに上へ…を繰り返して最終的にはNSObjectまで行く。ここで「一つ上からじゃなくて、スキップしていくつか上のクラスのメソッドを呼び出せるか」という話で盛り上がった。聞いて驚いたんだけど、実はすごくごちゃくごちゃしたコードを書けばできるらしい。けど、実行時に既存のクラスの書き換えまでやってのけるObjective-Cにおいて、コード上の継承関係を元にややこしい処理を行なうのは危険すぎる。最終的には「できるかできないかより、そんなことをしなきゃならないような設計の方が問題だよね」というオチがついた。そりゃそうだ。

メッセージセレクタ

「メソッドを呼び出す際に引数をつけるときには最後に:を付ける、複数の引数があるときは"keyword:"を並べる」というのを読んで、「第二引数以降にキーワードを付けるのは分かったけど、第一引数の前にはメソッド名があるだけでキーワードが無いけど、付ける方法はないのか」というの質問が上がった。ここはちょっと誤解しやすいポイントかも。多分、メソッド名とかメッセージキーワードとかいう語彙が悪い。

例えば、

[obj setValue:@"hoge" forKey:@"name"];

こういうときの「メッセージセレクタ」は「setValue:forKey:」のひとまとまりであって、その際に:や後ろのキーワードも含む。ちょっと無理矢理にRuby風に書くと、以下のようなイメージ。

# Rubyではメソッド名に:は使えないけど、仮に使えるとしたら
obj.setValue:forKey:("hoge", "name")
# もっと言うと、こういうイメージかも
# この方が「メッセージパッシング」っぽいし、
# 実際こういうメソッド呼び出しの方法はObjCにもある、
# というか内部的にはCの関数でこういうことをしている
obj.__send__(:setValue:forKey:, "hoge", "name")

なので、「最初の一個目がメソッド名で、二個目以降がキーワード」というのは誤り。一見分かれてるようだけど「全部くっつけたもの = メソッド名」だと思った方がいいし、セレクタ(SEL)を使って上のRubyの__send__みたいなことをやるときは正に、

SEL sel = @selector(setValue:forKey:);

などとして「メソッド名を生成」する。

ちなみに、最初の「一個目の引数の説明はどこに書くのか」に対しては、「何の引数を取るのかわかるメソッド名にする」が一応答えかな。Cocoaだと一個の引数を取るメソッドは例えば、

id url = [NSURL URLWithString:@"http://www.apple.com"];

のように、何の引数を取るか分かるように命名するのが慣習になっている。さっきのsetValue:forKey:も、「最初が値になるオブジェクトで、次がキーになるオブジェクト」ってのが分かるようになってるね。

ところで、ちょっと違う話になるけど、

# これはさっきの偽Ruby
obj.setValue:forKey:("hoge", "name")

これ何か見覚えあるなぁと思ったら、

# これはRubyCocoa
obj.setValue_forKey("hoge", "name")

RubyCocoaでOSX::NSObjectを継承したクラスのメソッドを呼び出すときとほぼ一緒(まぁ、そういう風に作ってあるのだけども)。RubyCocoaからMacアプリの世界に入った人は案外、ObjCのメソッド送信がすんなり分かるかもしれない。でもそれ以前にCocoaを知らないとRubyCocoaは分かりづらいって話はあるけど。ジレンマ。

詳解Objective-C 2.0読書会の今後

今日はとりあえず「音読して、途中途中で質疑応答タイムを設ける」っていう形式だったけど、今後は色々試行錯誤していくとのこと。あと、今回は休日だったけど、2時間程度であれば別に平日でも良いのでは?との意見から次回は試しに平日にしてみることになった。多分今月中にもう一回ぐらい開催されることになりそうなので、興味ある人はメーリングリストに参加してみるといいよ!