2008年12月17日水曜日

CS193P 11日目 非同期処理をやってみる

  • 非同期処理を行う方法はいくつかある
  • URLフェッチ処理ならば、NSURLConnectionクラスをつかっておけば一発
  • さらに簡単にURLフェッチ処理を行いたいのであればこのライブラリをおすすめ
  • URLフェッチ以外の処理を行うならば、NSThreadを使うか、NSOperationとNSOperationQueueを併用する
  • NSThreadは従来どおり、本当にスレッド処理を記述する必要があるため非常に大変
  • 対するNSOperationはインスタンスをつくってキューにぶち込んだら後は勝手にやってくれる、楽
  • UIViewやUIViewControllerに対する処理(要するに画面に対する処理)は、必ずメインスレッドから呼び出す必要がある
  • スレッドセーフではないため
  • 要するに[object performSelectorOnMainThread:withObject:waitUntilDone:modes:]を使えば解決する

いきなり日付が飛んで11日目です。
このあたりからは課題1つにつき3日分ぐらいのの講義内容が含まれていて、難易度がどんどん高くなってきました。
母さん、おいらスタンフォード大学の学生にはなれそうもないよ。



さて、今回の内容は非同期処理です。
現在の課題ではTwitterのタイムラインをJSON形式で取得して表示を行っているのですが、
メインスレッド(プログラムのメインループが走っているスレッド)の上から直接URLに対してHTTPアクセスを行っているため、
処理が返ってくるまでメインスレッドがブロックされ、結果フリーズしたように見えるという問題がありました。
これを非同期処理にしてブロックしないようにしましょうね、と言うのが今回の課題の内容。

NSURLConnectionと言うクラスを使えばURLのフェッチを自動的に非同期で行ってくれるのですが、
ご丁寧に「NSThreadかNSOperationで処理してね」とご忠告が。
Threadはどうにも使いこなせる気がしないので、ここはより簡単なNSOperationを使おうと思います。
(ゲームなどではおそらくNSThreadを使うことになるんだと思いますが)


NSOperationというクラスを継承して、
mainメソッドをオーバーライドして処理を記述し、
NSOperationQueueに追加すると自動的にThreadを裏で立ち上げて並列処理を行ってくれます。
処理が完了したらKVOという機能を使ってNSOperationから通知を受け取るらしいです。
しかしこのKVOと言う概念がイマイチ理解できないので後回しにして、
より簡単なNSInvocationOperationというクラスを使うことにしました。

使い方はこんな感じです。
NSInvocationOperation *op = [[NSInvocationOperation alloc]
     initWithTarget:self
     selector:@selector(reloadPerson:)
     object:person];
 [self.operationQueue addOperation:op];
 [op release];

これだけで自動的に並列処理をしてくれるんだから凄いと思います。
ということで、今回の課題では以下のように並列構成をしてみました。

スレッド1:メインスレッド
スレッド2:TwitterからTimelineを取得するためのスレッド(NSInvocationOperation + NSOperationQueue)
スレッド3:画像を取得するための並列処理(ImageStoreを利用、内部的にはNSURLConnection)

ところがこれがうまくいきません。
1と2だけを並列処理させたときはうまくいき、1と3だけのときもうまくいくのですが、
1と2と3と並列で動かすとエラーになります。
ああもう!だから並列処理なんて嫌いだ!

デバッガで調査してみるとSocketの取得のあたり?でとまっている感じがしたので、
スレッド2かスレッド3がソケットを捕まえてロックしているのではないかと考え、
使ったらすぐreleaseするようにソースを変えてみたのですが、効果なし。

Google先生にご相談したところ、それらしい回答が。
【iPhone】スレッド中で[UITableView reloadData]を使ってはいけない
なるほど!自分のソースを見直すと、確かにスレッド2の処理の中でUITableViewに対してreloadDataを呼び出しています。
さっそくご指摘のあったとおりにソースを書き直してみました。
if ([delegate respondsToSelector:@selector(mPersonDataSourceDidFinishLoadOfPerson:)])
{
 [delegate performSelectorOnMainThread:@selector(mPersonDataSourceDidFinishLoadOfPerson:) withObject:person waitUntilDone:YES];
}

今度は一発で成功!