今日、「スレッドインスペクタ」が原因でThousandがクラッシュするらしい、と名取さんから連絡をいただきました。スレッドを開こうとするとクラッシュするらしい。クラッシュログも送ってくださったわけなんですが、どうもNSURLDownload絡みのところで落ちてるらしい。よく調べると解放済みのオブジェクトにメッセージを送ろうとしてたらしい。
NSURLDownloadには、3つのデリゲートメソッドがある。次の3つね。
- download:decideDestinationWithSuggestedFilename:
- download:didCreateDestination:
- downloadDidFinish:
デリゲートオブジェクトには、これらのメソッドが順に呼び出されるわけです。ていうかそうだという前提でコーディングしてたわけなんですよ。ところがですね、僕はNSURLDownloadをT2ThreadProcessingの- processThread:appendingIndex:
メソッドの中で使ってたんですが、このメソッドはスレッドを1回開く時に2回呼ばれるんですよ。検証してないけど、多分ログを取得する前と後で1回づつ読んでるんでしょうね。で、そうなると先ほどの前提が崩れてしまい、運が悪いと次の順番に呼び出されてしまうわけです。
- download:decideDestinationWithSuggestedFilename: // 1回目の呼び出し
- download:didCreateDestination: // 1回目の呼び出し
- download:decideDestinationWithSuggestedFilename: // 2回目の呼び出し
- download:didCreateDestination: // 2回目の呼び出し
- downloadDidFinish: // 1回目の呼び出し
- downloadDidFinish: // 2回目の呼び出し
こうなると、話がややこしくなる。実は、- download:decideDestinationWithSuggestedFilename:
の中であるオブジェクトをretainして、- downloadDidFinish:
の中でreleaseしてたんです。でも上の順番で呼び出されてしまうと、最初にretainされたオブジェクトはどこからも参照されなくなり、2回目にretainされたオブジェクトも最後のメソッドが呼ばれる段階ではすでに解放済みなわけです。そしてここで解放済みオブジェクトにメッセージを送ろうとして、クラッシュ。ずいぶんと初歩的なミス
実はこのバグ、v0.1.xだけで発生する問題。v0.2では看板画像の表示を実装する際にこの部分を書き換えたから問題無し。チェックしたらメモリリークがいくつかあったけど、開いた瞬間にクラッシュなんてことはない。でも一応v0.2.1を出しといた。
Javaとかにあるガベージコレクション、よくわからないんだけど、これがあればこういうミスを防げるんでしょうかね。Objective-CもLeopardでサポートされるらしいですから、待ち通しですよ。G4で動くならば。
そうなんですよ。アンカーとかIDとかの情報は保存されてるんじゃなくて、スレッドを開くときに作ってるんです。それで、ローカルのdatを読んだ後と、リモートの追加分を読んだときの2回呼ばれるはずです。そのときに、もう処理した分をスルーするためにappendingIndexを使ってます。たしか最初は0で、次は追加分の最初のレス番-1ですね。
返信削除なるほど、appendingIndexってそういう使い方をするんですか。
返信削除でしたら今回の場合はメソッドが呼ばれるたびに処理を行うんじゃなくて、appendingIndex==0のときだけ処理を行う方が良さそうですね。