« スカイウォーター | トップページ | みんなピカが技研のじゃ »

2012.06.06

NSArrayとNSMutableArrayの実体について

 今回もまたもや技術的なハナシ。OS XかiOSに関して。

 NSArrayやNSMutableArrayというのはよく使う。Foundationを使った開発をしていて、NSArrayを使った事がない、という人はおらん。

 単に配列にセット、配列からゲットということをしているだけなら、今回説明する問題は起こらない。今回は、NSArrayとNSMutableArrayにカテゴリで初期化メソッドを実装する際に起こる問題について書く。

 まずはテスト1を実行してみる。

 テスト1は、NSArrayとNSMutableArrayのインスタンスを作り、それのクラス名を表示する、というものだ。結果は、

NSArray __NSArrayI
NSMutableArray __NSArrayM

こうなる。allocする際のクラスの名前と、作成後取得したクラスの名前が違う。なんでかは知らんが、おそらくToll-free bridge関係でCFArray、CFMutableArrayを実装する際の何かではないかと推測する。必ずこの名前になってくれているのなら、使う側が合わせればいいのでたいした問題ではない。

 配列の本名がわかったところで、テスト2を実行する。

-[__NSPlaceholderArray initWithCapacity:]: unrecognized selector sent to instance 0x7f8e68415610
-[__NSPlaceholderArray initWithCapacity:]: unrecognized selector sent to instance 0x7f8e68415610

 これはエラーメッセージか。エラーの内容からして、ないメソッドを呼び出したようだ。2回出ているのはNSArrayとNSMutableArrayの分か。だが何かおかしい。__NSPlaceholderArrayというのは何だ? そして、NSMutableArrayの方でしか呼び出していないはずのinitWithCapacity:で2回エラーになっているのも謎だ。
 ステップ実行してみると驚くべき事が判明する。[[NSArray alloc] initNantara]が、NSMutbleArrayのinitNantaraを呼び出している。これだとinitWithCapacity:が2度呼び出されるのでエラーが2回起こるのは当然だ。
 実行結果からすると、[NSArray alloc]と[NSMutableArray alloc]は、どちらも__NSPlaceholderArrayを作り出した事になる。そして、それらがメソッドを呼ぶと、NSMutableArrayの方が呼ばれるという事がわかる。
 ようわからんのは、[NSMutableArray alloc]で作られたインスタンスなのに、NSMutableArrayのメソッドinitWithCapacity:が呼び出せないという事だ。__NSPlaceholderArrayはサナギマンのようなものか? イナズマンとしての機能は有していないのか?

 ここまでで動作はわかったが、なぜそうなっているのかという理由はわからん。

 じゃあいつ__NSPlaceholderArrayは__NSArrayIや__NSArrayMに生まれ変わるのか。テスト3を実行する。

NSMutableArray __NSPlaceholderArray
NSMutableArray __NSPlaceholderArray

 initに入ったところではまだ変わっていない事がわかる。続いてテスト4を実行する。

NSArray alloc __NSPlaceholderArray
NSArray init __NSArrayI
NSMutableArray alloc __NSPlaceholderArray
NSMutableArray init __NSArrayM

 allocで__NSPlaceholderArrayになり、initで__NSArrayIや__NSArrayMに変わる事を確認した。普通に使う分には何の問題もなく動作する。アタリマエだ。動作しなかったら即座に何十万人もの人に気付かれる(笑)
 まだ疑問は解消されない。作られた時はどちらも__NSPlaceholderArrayなのに、何でNSArrayとNSMutableArrayのinitが呼び分けられるのか。これは、Foundationを実装した人しか知らない何らかの秘密があるに違いないが、どうなってるのかはわからない。

 クラスの内部から同じ事を観測してみよう。テスト5を実行する。

NSMutableArray alloc __NSPlaceholderArray
NSMutableArray init __NSArrayI
NSMutableArray alloc __NSPlaceholderArray
NSMutableArray init __NSArrayM

 [self init]を呼ぶとそれぞれの実体に変わるのはわかった。そして、initを呼ぶとselfが変わるのもわかった。これまで漫然とサンプルコードとかの通りinitの戻り値をselfに入れてきたが、こういう仕組みがあったのか。だがこれは他の言語では存在しないアクロバットだ。途中でインスタンスのポインタが変わるってことだからな。

 とりあえずここまでで、NSArrayとNSMutableArrayを作るのには成功したが、同じ名前の初期化メソッドを呼び分ける、という事に関しては全く成功していない。NSMutableArrayはNSArrayを継承しているので、同じ名前のメソッドを実装して違う処理をさせる、などというのは当たり前にできてほしい。

 そもそも、インスタンスが途中で別人に変わったり、同じクラスから2つの別のクラスに変わっている以上、これはこっちが手をつけることができない領域の話になっている。ということで、結論は、NSArrayとNSMutableArrayに、カテゴリで同名の初期化メソッドを実装する事はできない、ということにする。

P.S.
 NSArrayとNSMutableArrayがこのような仕組みになっているのは、実装する側から考えると何となく理解できる。この2つのクラスは、初期化後に書き換えられない、というだけで、読み出すには全く同じように使える。可変の配列に必要なのは、動的に確保したメモリのポインタとサイズぐらいなので、コードを共通化し、書き換えるインターフェースを持つか持たないかという事で区別しているのではないか。となれば、他のNSMutableなんとかも同様の実装かもしれない。

|

« スカイウォーター | トップページ | みんなピカが技研のじゃ »

コメント

コメントを書く



(ウェブ上には掲載しません)




トラックバック


この記事へのトラックバック一覧です: NSArrayとNSMutableArrayの実体について:

« スカイウォーター | トップページ | みんなピカが技研のじゃ »