background処理&delegate通知はサクサク動かすために結構よく使いますが、一歩間違えるとEXE BAD ACCESSだらけになってしまいます。(経験談) どうやったらEXE BAD ACCESSを防げるのか、なんとなくわかったのでメモってみます。
おおまかな問題点
基本的にはbackgroundの処理が終わる前にdelegate先のオブジェクトが解放されてしまうのが問題です。 しかし、これは単純にオブジェクトが解放されるときにdelegateをnilにするだけでは解決できません。 delegateで呼び出されたメソッドを実行中にオブジェクトが解放される可能性もあるのです。 この場合、delegate先のメソッドが呼ばれてるのでdelegateをnilにしても意味がなく、その後selfやメンバ変数を呼び出してしまうとエラーが出てくるわけです。
回避法
まず、よく使っていたbackground処理&delegate通知の形式を書いてみます。 本題の部分以外はいろいろ省略してます。 BackgroundClass.m
@implementation BackgroundClass -(void)backgroundProcess{ //background処理 [self.delegate finishedBackgroundProcess:self]; } @end
mainViewController.m
@implementation mainViewController -(id)init{ if((self = [super init])){ _back = [[BackgroundClass alloc] init]; _back.delegate = self; [_back performSelectorInBackground:@selector(backgroundProcess) withObject:nil]; } } -(void)finishedBackgroundProcess{ self.statusLabel.text = @"finished"; } -(void)dealloc{ _back.delegate = nil; [_back release]; [super dealloc]; } @end
これはバックグラウンドで処理をし、処理が完了したらラベルにfinishedと表示する単純なバックグラウンド処理です。 このままだとfinishedBackgroundProcessが呼ばれたあとで、statusLabelに文字が代入される前にmainViewControllerがreleaseされるとexe bad accessが出ます。 (selfが解放されているのにselfを参照しようとしたため) 簡易的に書いているサンプルなのでここでオブジェクトが解放されることはものすごく確立が低いです。 しかし、finishedBackgroundProcessが長くなれば長くなるほど確率は増えていきます。 statusLabelへ文字を代入する直前に10秒待ちの命令でも入れて試してみればexe bad accessを簡単に再現できると思います。
これを対処するにはmainViewController.mを次のように変えればOKです mainViewController.m
@implementation mainViewController -(id)init{ if((self = [super init])){ _back = [[BackgroundClass alloc] init]; _back.delegate = self; [_back performSelectorInBackground:@selector(backgroundProcess) withObject:nil]; } } -(void)finishedBackgroundProcess{ @synchronized(self){ self.statusLabel.text = @"finished"; } } -(void)dealloc{ @synchronized(self){ _back.delegate = nil; [_back release]; [super dealloc]; } } @end
synchronizedはobjective-cでの簡易的なロックの方法です。 このように書くと、finishedBackgroundProcessが呼ばれている間はdeallocが呼ばれてもfinishedBackgroundProcessを抜けるまで待ちます。 つまり、finishedBackgroundProcessが呼ばれている最中にオブジェクトの開放をしようとするとfinishedBackgroundProcessが終わるまで待つわけです。 ちょっと強引な感じはしますが、自分はこれで頻繁に落ちていたのがほぼ0になりました。 ※synchronizedを使うときはデッドロックに注意してください