iPhoneアプリ開発時のメモリ管理で気をつけること(マルチスレッド編)
以前iPhoneアプリ開発時のメモリ管理で気をつけることという記事を書いたのですがマルチスレッド時のメモリ管理に関して全く触れてなかったのでまとめてみました。
NSAutoreleasePool はスレッドごとに作成する
処理を別スレッドで実行する場合、スレッドごとに NSAutoreleasePool が必要になります。NSAutoreleasePool の作成を忘れるとメモリリークします。
以下のように main 関数から Sample クラスのインスタンスを生成してメソッドを呼び出している場合、シングルスレッドの場合とマルチスレッドの場合で NSAutoreleasePool の作成タイミングが異なります。
@interface Sample { } - (void)hoge; - (void)foo; - (void)bar; - (void)baz; @end int main(int argc, char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; Sample *s = [[[Sample alloc] init] autorelease]; [s hoge]; [pool release]; return 0; }
通常のメソッド呼び出し(シングルスレッド)
呼び出し元ですでに NSAutoreleasePool が作成されているので NSAutoreleasePool を作成する必要がありません。
@implementation Sample - (void)hoge { // 普通のメソッド呼び出し [self foo]; [self bar]; [self baz]; } - (void)foo { // 処理 } - (void)bar { // 処理 } - (void)baz { // 処理 } @end
マルチスレッドによるメソッド呼び出し
main 関数で作成された NSAutoreleasePool は別スレッドで動いているインスタンスを解放することができません。スレッドごとに NSAutoreleasePool を作成する必要があります。
@implementation Sample - (void)hoge { // 別スレッドでメソッド呼び出し [NSThread detachNewThreadSelector:@selector(foo) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(bar) toTarget:self withObject:nil]; [NSThread detachNewThreadSelector:@selector(baz) toTarget:self withObject:nil]; } - (void)foo { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 処理 [pool release]; [NSThread exit]; } - (void)bar { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 処理 [pool release]; [NSThread exit]; } - (void)baz { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 処理 [pool release]; [NSThread exit]; } @end
図で表すとこんな感じです。
図のうすい青色の枠が NSAutoreleasePool の有効範囲です。
マルチスレッドの場合、スレッドごとに別のスタック領域を使います(ヒープは同じ領域を使います)。このスタック領域ごとに NSAutoreleasePool を作成していればメモリリークは防げるはずです。
メモリリークする例
以下のコードは NSAutoreleasePool を作成してないのでメモリリークします。
@implementation Sample - (void)hoge { [NSThread detachNewThreadSelector:@selector(foo) toTarget:self withObject:nil]; } - (void)foo { // メモリリークします。 NSArray *array = [NSArray array]; NSDictionary *dic = [[[NSDictionary alloc] init] autorelease]; } @end
NSOperation の main メソッドで NSAutoreleasePool を作成する必要があるか?
NSOperation の main メソッドの処理は基本的にマルチスレッドで並列実行されますが main メソッドで NSAutoreleasePool を作成する必要はありません。
以下のクラスで autorelease を使ってメモリがきちんと解放されるか試してみます。
@interface Sample : NSObject { } - (void)doSomething; @end @implementation Sample - (void)doSomething { NSLog(@"%@", @"Test"); } - (void)dealloc { NSLog(@"%@", @"Dealloc!!!"); [super dealloc]; } @end
@interface OperationNormal : NSOperation { } @end @implementation OperationNormal - (void)main { NSLog(@"Thread:%@", [NSThread currentThread]); Sample *s = [[[Sample alloc] init] autorelease]; [s doSomething]; } @end - (void)doSomething { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; OperationNormal *ope1 = [[[OperationNormal alloc] init] autorelease]; OperationNormal *ope2 = [[[OperationNormal alloc] init] autorelease]; OperationNormal *ope3 = [[[OperationNormal alloc] init] autorelease]; [queue addOperation:ope1]; [queue addOperation:ope2]; [queue addOperation:ope3]; }
実行結果
Sample[1566:6003] Thread:<NSThread: 0x6132030>{name = (null), num = 3} Sample[1566:1803] Thread:<NSThread: 0x4b188f0>{name = (null), num = 4} Sample[1566:6103] Thread:<NSThread: 0x4b1af00>{name = (null), num = 5} Sample[1566:6003] Test Sample[1566:6103] Test Sample[1566:1803] Test Sample[1566:6103] Dealloc!!! Sample[1566:6003] Dealloc!!! Sample[1566:1803] Dealloc!!!
1566番のプロセスで6003,1803,6103の3つのスレッドが実行さました。きちんと dealloc メソッドが呼ばれているのがわかります。
続きます。
ブロックやperformSelector:withObject:afterDelay 等まだネタがあるので順次書き足していきます。