A Day In The Life

とあるプログラマの備忘録

CLLocationManagerDelegate のメソッド内で CLLocationManager インスタンスを release すると EXC_BAD_ACCESS で落ちる

CLLocationManagerDelegate の didUpdateToLocation メソッド内で CLLocationManager のインスタンスを release すると EXC_BAD_ACCESS が発生してアプリが落ちるという現象にハマったのでその対処方法をまとめます。

@interface HogeController : UIViewController <CLLocationManagerDelegate> {
    CLLocationManager *locationManager;
}
- (IBAction)respondToButtonClick:(id)sender;
@end

@implementation HogeController
- (IBAction)respondToButtonClick:(id)sender {
  locationManager = [[CLLocationManager alloc] init];
  locationManager.delegate = self;
  [locationManager startUpdatingLocation];
}
#pragma mark CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {
  :
  : 処理
  :
  [locationManager stopUpdatingLocation];
  locationManager.delegate = nil;
  // EXC_BAD_ACCESS で落ちる
  [locationManager release], locationManager = nil;
}
@end

クラッシュログはこのようになっていました。CLLocationManager onClientEventLocation: という見慣れないメッソド表示がありました。

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0xffffffff
Crashed Thread:  0

Thread 0 name:  Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0   libobjc.A.dylib               	0x34499c96 objc_msgSend + 14
1   CoreLocation                  	0x34457c48 -[CLLocationManager onClientEventLocation:] + 808
2   CoreLocation                  	0x34457f42 -[CLLocationManager onClientEvent:supportInfo:] + 46
3   CoreLocation                  	0x34455a64 OnClientEvent + 16
4   CoreLocation                  	0x3445178a CLClientInvokeCallback(__CLClient*, CLClientEvent, __CFDictionary const*) + 46
5   CoreLocation                  	0x34453c40 CLClientHandleDaemonDataLocation(__CLClient*, CLClientLocation const*, __CFDictionary const*) + 208
6   CoreLocation                  	0x34453d96 CLClientHandleDaemonData(__CFMessagePort*, long, __CFData const*, void*) + 290
7   CoreFoundation                	0x3094e706 __CFMessagePortPerform + 242
8   CoreFoundation                	0x30957a90 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 20
9   CoreFoundation                	0x30959838 __CFRunLoopDoSource1 + 160
10  CoreFoundation                	0x3095a606 __CFRunLoopRun + 514
11  CoreFoundation                	0x308eaebc CFRunLoopRunSpecific + 224
12  CoreFoundation                	0x308eadc4 CFRunLoopRunInMode + 52
13  GraphicsServices              	0x30269418 GSEventRunModal + 108
14  GraphicsServices              	0x302694c4 GSEventRun + 56
15  UIKit                         	0x30a10d62 -[UIApplication _run] + 398
16  UIKit                         	0x30a0e800 UIApplicationMain + 664
17  Sample                        	0x0000233a main (main.m:14)
18  Sample                        	0x00002304 start + 32

メインスレッドでアプリが落ちているのがわかります。これだけ見ても何が原因かさっぱりわかりませんでしたが、didUpdateToLocation メソッドはメインスレッドで実行されるので、このメソッド内の処理が怪しいことだけは確実です。

対処方法その1

以下のようにデリゲートの無効化とインスタンスのリリース処理をメソッドに切り出して performSelector で呼び出してやると落ちずに動きました。

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation 
           fromLocation:(CLLocation *)oldLocation {
  :
  : 処理
  :
  [locationManager stopUpdatingLocation];
  [self performSelector:@selector(discardLocationManager) 
               onThread:[NSThread currentThread] 
             withObject:nil waitUntilDone:NO];
}

- (void)discardLocationManager {
  locationManager.delegate = nil;
  [locationManager release], locationManager = nil;
}

didUpdateToLocation メソッドの処理を終わらせてからリリースしないと落ちるようです。

対処方法その2

NSZombieEnabled を YES に設定すると落ちません。かなり謎です。普通は EXC_BAD_ACCESS で落ちる場面で実行が止まってエラーログが吐き出されるのですが、NSZombieEnabled を YES に設定すると何事もなかったかのように動きます。メッセージすら表示されません。
NSZombieEnabled の設定方法は以下を参考にしてください。

まとめ

didUpdateToLocation メソッドのなかで CLLocationManager のインスタンス解放することはあまりおすすめできません。viewDidLoad メソッドで CLLocationManager のインスタンス作成して dealloc で解放する方が良いように思います。