background処理&delegate通知はサクサク動かすために結構よく使いますが、一歩間違えるとEXE BAD ACCESSだらけになってしまいます。(経験談)

どうやったらEXE BAD ACCESSを防げるのか、なんとなくわかったのでメモってみます。

おおまかな問題点

基本的にはbackgroundの処理が終わる前にdelegate先のオブジェクトが解放されてしまうのが問題です。
しかし、これは単純にオブジェクトが解放されるときにdelegateをnilにするだけでは解決できません。

delegateで呼び出されたメソッドを実行中にオブジェクトが解放される可能性もあるのです。
この場合、delegate先のメソッドが呼ばれてるのでdelegateをnilにしても意味がなく、その後selfやメンバ変数を呼び出してしまうとエラーが出てくるわけです。

回避法

まず、よく使っていたbackground処理&delegate通知の形式を書いてみます。
本題の部分以外はいろいろ省略してます。

BackgroundClass.m
[objc]
@implementation BackgroundClass

-(void)backgroundProcess{
//background処理
[self.delegate finishedBackgroundProcess:self];
}
@end
[/objc]

mainViewController.m
[objc]
@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
[/objc]

これはバックグラウンドで処理をし、処理が完了したらラベルにfinishedと表示する単純なバックグラウンド処理です。
このままだとfinishedBackgroundProcessが呼ばれたあとで、statusLabelに文字が代入される前にmainViewControllerがreleaseされるとexe bad accessが出ます。
(selfが解放されているのにselfを参照しようとしたため)

簡易的に書いているサンプルなのでここでオブジェクトが解放されることはものすごく確立が低いです。
しかし、finishedBackgroundProcessが長くなれば長くなるほど確率は増えていきます。

statusLabelへ文字を代入する直前に10秒待ちの命令でも入れて試してみればexe bad accessを簡単に再現できると思います。


これを対処するにはmainViewController.mを次のように変えればOKです
mainViewController.m
[objc]
@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
[/objc]

synchronizedはobjective-cでの簡易的なロックの方法です。
このように書くと、finishedBackgroundProcessが呼ばれている間はdeallocが呼ばれてもfinishedBackgroundProcessを抜けるまで待ちます。

つまり、finishedBackgroundProcessが呼ばれている最中にオブジェクトの開放をしようとするとfinishedBackgroundProcessが終わるまで待つわけです。

ちょっと強引な感じはしますが、自分はこれで頻繁に落ちていたのがほぼ0になりました。

※synchronizedを使うときはデッドロックに注意してください

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

次のHTML タグと属性が使えます: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>