2009/11/06

iPhone NSTimer NSTimerを止める。

NSTimerを止めるとき、単に[timer release]だけではだめで、コンソールに下記のようなエラーがでる
  1. malloc: *** error for object 0x3fe29f0: double free  
  2. *** set a breakpoint in malloc_error_break to debug  

メインループがこの NSTimer を参照しているのが原因のようです。単純にタイマーを止めるには[timer invalidate]を使うようです。またNSTimerのインスタンスを作っている場合はこれを解放する必要があるので別途[timer release]としたほうが良さそうですが、これをやるとエラーが発生してプログラムが落ちます。

デベロッパドキュメントによると
NSTimer:invalidate:はメインループのNSRunLoopオブジェクトからTimerを解放するメソッドです。
このメソッドがNSRunLoopからタイマーを取り除く唯一の方法と書かれています。NSRunLoopから解放されて、さらにreleaseされると書かれています。下の例の用にNSTimerを変数として保持してない場合は、invalidateするだけでreleaseまで行われていると考えて良さそうです。releaseを実行すると落ちます。
  1. - (void)viewDidLoad {  
  2.  [super viewDidLoad];  
  3.  count = 0;  
  4.  someTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCount:) userInfo:nil repeats:YES];  
  5. }  
  6.   
  7. - (void)updateCount:(NSTimer *)timer  
  8. {  
  9.  count++;  
  10.  NSString *str = [[NSString alloc] initWithFormat:@"Count %d",count];  
  11.  countLabel.text = str;  
  12.  [str release];  
  13.  if (count > 5) {  
  14.   [timer invalidate];  
  15.  }  
  16. }  

NSTimerを呼び出すあたりが曖昧になっていると、結構はまる。NSTimerオブジェクトを以下のように呼び出した場合
  1. [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateCount:) userInfo:nil repeats:YES];  
デベロッパドキュメントによると
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:はクラスメソッドで、その説明は
Returns a new NSTimer object, scheduled the current NSRunLoop object in the default mode
デフォルトモードが何かは無視して、このメソッドは既存のNSRunLoopにスケジュールされているNSTimerを返すということらしい。つまり生成したNSTimerオブジェクトの管理はNSRunLoopで行われていて、自分のコントローラ内で勝手に消すと怒られるということのよう。
viewDidLoadのような一回だけ呼ばれるメソッドでNSTimerを宣言するときはいいのですが、[self startTimer]とかして、何回でもそのメソッドが叩けるとその数だけNSTimerオブジェクトが生成されてしまう。NSTimerをいつどこで作るかはちゃんと考えてからやった方が良さそう。



ちなみにNSTimerのfireメソッドでスケジュールに無いタイミングでメソッドを実行できるらしい。