1.performSelectorInBackground:withObject:メソッドを呼ぶ
NSObjectにはperformSelectorInBackground:withObject:というメソッドが用意されていて、バックグラウンドで処理を行うメソッドのセレクタを指定するだけで、非常に簡単に別スレッドで実行してくれます。
例えばこんな感じ。
- (void)runVeryHeavyMethodInBackground {
[self performSelectorInBackground:@selector(veryHeavyMethod) withObject:nil];
}
バックグラウンドで動作させるメソッドを次のように用意しておきます。
- (void)veryHeavyMethod {
// 時間のかかる処理をここで行う。
}
2.バックグラウンドで動作させる入り口のメソッドでAutoreleasePoolをつくる
基本的にはこれだけでちゃんとバックグラウンドで実行してくれるのですが、実際にこのまま実行してみると次のような警告メッセージが大量に出てしまいました。
__NSAutoreleaseNoPool(): Object 0x710e9c0 of class NSCFString autoreleased with no pool in place - just leaking
どうやら、Autorelease Poolが存在しないところでautoreleaseが呼ばれたので、このままではメモリを開放できず、リークしてしまうということのようです。
ことのときに、デバッガで止めてスタックトレースを見てみると、こんな感じになっていました。
#0 -[TestObject veryHeavyMethod] (self=0x527830, _cmd=0x148efe) at xxxx
#1 0x3354a388 in -[NSThread main] ()
#2 0x335bc5cc in __NSThread__main__ ()
#3 0x35daf310 in _pthread_start ()
#4 0x35db0bbc in thread_start ()
なるほど、メインスレッドから呼ばれる場合は、フレームワークの方でAutorelease Poolを作ってくれているのですが、自分でスレッドを作った場合には、Autorelease Poolも自分で作らなければならないわけですね。
そこで、別スレッドで実行するメソッドを次のように変更します。
- (void)veryHeavyMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 時間のかかる処理をここで行う。
[pool release];
}
これで、上の警告メッセージも出なくなりました。
3.バックグラウンドでの動作が完了したことをメインスレッドに戻して通知する
このままだと、バックグラウンドでの処理がいつ完了したのかわかりません。完了したタイミングでメッセージを表示したり、UIActivityIndicatorを止めたりといった処理をするには、何らかの方法で通知を受け取る必要があります。
通知を受ける方法はいろいろあると思いますが、ここではdelegateを使った方法を試してみました。
- (void)veryHeavyMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 時間のかかる処理をここで行う。
// デリゲートに通知する
if ([delegate respondsToSelector:@selector(testObjectDidFinishVeryHeavyMethod)]) {
[delegate testObjectDidFinishVeryHeavyMethod];
}
[pool release];
}
すると、ちゃんと通知を受けることはできるのですが、このままだとバックグラウンドのスレッドでずっと実行され続けてしまいます。メインスレッドからでなければ実行できない処理も多いので、このままでは困ります。
調べてみると、NSObjectにはメインスレッドに処理を戻すperformSelectorOnMainThread:withObject:waitUntilDone:というメソッドがありました。これを使って、次のように書き換えます。
- (void)veryHeavyMethod {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 時間のかかる処理をここで行う。
// デリゲートに通知する
[self performSelectorOnMainThread:@selector(notifyVeryHeavyMethodDidFinish) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)notifyVeryHeavyMethodDidFinish {
if ([delegate respondsToSelector:@selector(testObjectDidFinishVeryHeavyMethod)]) {
[delegate testObjectDidFinishVeryHeavyMethod];
}
}
これで、デリゲートで通知を受けたときには既にメインスレッドに処理が戻っているので、続けて他の処理を行うことができるようになりました。
Cocoaには、他にもマルチスレッドで処理を行う方法が充実しているようですが、ちょっと時間のかかる処理をバックグラウンドで行いたいだけの時には、この方法は手軽で便利です。





