So-net無料ブログ作成
検索選択

retainしないCFSet [プログラミング - NSSetとライフゲーム]

おとといコメントもらった内容をおさらいしておく。

ライフゲームのひとつの生きているセルが、隣にいるセルへのポインタを保持するという方法で自分が生き残れるかどうかの判断を速くする、ということを考えた。そのポインタを保持するためにNSMutableSetを使った。NSMutableSetは保持するオブジェクトに対してretainメッセージを送る。こうすると隣りあうセルはお互いをNSMutableSetに保持しあってretainを送りあう。これは「リテインループ」と言って不要になったオブジェクトになってもお互いに必要だと言いあって破棄されない、ということになる。これはretain/releaseメカニズムであってもGabage Collectionであっても同じ。

これはNSMutableSetが保持しているオブジェクトにretainメッセージを送らなければいい。つまりただポインタの値を持っているだけであればこの問題は起こらない。しかしCocoaのFoundationフレームワークのコレクションオブジェクトはすべて必ずretainを送る。これは自分の保持しているオブジェクトが知らない間に破棄されていた場合、そのオブジェクトにアクセスするとクラッシュしてしまうからで保持しているからには生きていてもらわないと困る、ということである。

厳密に言えばGabage Collection環境であればFoundationのクラスの中でもNSHashTableなど、Gabage Collectionを妨げないクラスもある。しかし残念ながらNSHashTableはNSMutableSetに比べて機能が劣る。このあたりは目的の違いというのもある。ということで本当はオブジェクトをretainしないNSMutableSetがあれば今回の使い勝手に合致する。

ということで、そのためのクラスを独自実装しよう、考えていたけれど昨日のSYさんのコメントで簡単にできるよ、と教えてもらった。これが実に面白い。どういうことかというとCore Foundationのオブジェクトを使う、と言うアイデア。たとえばCFMutableSetはNSMutableSetとToll-Free Bridgeで結ばれている。つまり同一視していい、ということである。ところがCFMutableSet(もちろんこれだけではなくCore Foundationのコレクションクラスはみんな同じ)はCore FoundationやCocoaのオブジェクト以外も保持できるという仕様になっている。これはCarbon環境やそれ以外のCベースの環境でも利用可能な汎用のコレクションクラスとするための基本的な仕様である。

そのためにCore Foundationの多くのコレクションクラスは(Cベースなので)メモリ管理やその他のためにコールバック関数を指定できる機能を持っている。このコールバックには、例えばCFMutableSet(CFSetも同じ)にはCocoaのFoundationでいう
  • retain
  • release
  • description
  • isEqual:
  • hash
の関数を設定できる。独自のオブジェクトを定義してメモリ管理の方法を決めたとしたらこのretainとreleaseのコールバックに、それを制御する関数を定義して渡せばいいようになっている。そのほかのコールバックもコレクションが自分の機能を行使するために必要な関数を渡してもらうためのものになっている。

コレクションクラスのオブジェクトに保持させるオブジェクトがCocoaやあるいはCore Foundationの標準オブジェクトならデフォルトのコールバックが用意されている。CFMutableSet(CFSet)にはCFSetCallBacks型の
kCFTypeSetCallBacks
という定数が定義されていてこの中はCore Foundation(ともちろんCocoaも)のオブジェクトだけを使うのならここにCocoa互換のコールバック関数へのポインタが入っているので、これを使えばいい、ということになる。普通はこれで十分。

さて、ここでコールバック関数にNULLを指定できる。この場合、コールバックは呼ばれない。つまりその機能は使われない、ということになる。そこでコメントいただいた話になる。つまりretain用のコールバックにNULLを指定すると保持しているオブジェクトにretainを送らないコレクションクラスができる。たとえば
    CFSetCallBacks  cbDefault = kCFTypeSetCallBacks;
    CFSetCallBacks  cbNoRetain = kCFTypeSetCallBacks;
    cbNoRetain.retain = NULL;
    cbNoRetain.release = NULL;

    NSMutableSet    *cfset = (NSMutableSet *)CFSetCreateMutable(
                                    kCFAllocatorDefault, 0, &cbDefault);
    NSMutableSet    *cfsetNR = (NSMutableSet *)CFSetCreateMutable(
                                    kCFAllocatorDefault, 0, &cbNoRetain);
とふたつのCFMutableSetを作ってNSMutableSetであるとみなした。CFMutableSetはNSMutableSetとToll-Freeなのでこのキャストは問題ない。ひとつめのcfsetオブジェクトはCFMutableSetそのまんま、つまりコールバック関数はkCFTypeSetCallBacksそのままで、cfsetNRはretainとreleaseのコールバックにNULLを指定している。こうすれば本来retainとreleaseする場面で何もしない、というコレクションが実現できる、というもの。

例えばこのふたつのコンテナに保持させてretainCountを見てみると、
    id  obj = [[NSObject alloc] init];
    NSLog(@"before add. retainCount = %d", [obj retainCount]);
    [cfsetNR addObject:obj];
    NSLog(@"after add to cfsetNR. retainCount = %d", [obj retainCount]);
    [cfset addObject:obj];
    NSLog(@"after add to cfset. retainCount = %d", [obj retainCount]);
    [cfset release];
    NSLog(@"after release cfset. retainCount = %d", [obj retainCount]);
    [obj release];
//    NSLog(@"after release obj. cfset contains %@", [obj allObjects]);
とすると
2010-10-27 21:52:18.667 RetainTest[561:a0f] before add. retainCount = 1
2010-10-27 21:52:18.671 RetainTest[561:a0f] after add to cfsetNR. retainCount = 1
2010-10-27 21:52:18.672 RetainTest[561:a0f] after add to cfset. retainCount = 2
2010-10-27 21:52:18.672 RetainTest[561:a0f] after release cfset. retainCount = 1
となる。あきらかにcfsetNRが保持してもretainCountが増えていない。ということで実に簡単にオブジェクトをretainしないNSMutableSetができてしまう。retainしていないので当然最後の行のコメントをはずして保持しているオブジェクトにアクセスしようとするとEXC_BAD_ACCESSシグナルが送られてきてクラッシュする。どうでもいいけど他に何もしていないマシンで、これだけのことをするのに5msecもかかってるのはなんでだろう。全然別の話だけど。

このあと追記してもらったhashも面白い。こっちはまた明日解説する。

本当に面白いアイデアをいただいてありがとうございます。感謝。
nice!(0)  コメント(0)  トラックバック(0) 

nice! 0

コメント 0

コメントを書く

お名前:
URL:
コメント:
画像認証:
下の画像に表示されている文字を入力してください。

トラックバック 0

メッセージを送る