iOSの、画面遷移時のメモリリークが止まらなかった話

先日、画面遷移時にメモリが開放されず、徐々にメモリ利用率が上昇する現象に苦しまされた。 Instrumentsで調べてみても、リークは見られなかった。何が問題だったのか。それはdispatch_afterを用いたループするアニメーションだった。

f:id:jeffsuke:20141002085323p:plain

dispatch_afterや、NSRunLoopNSTimer等を用いてループ処理を実行していると、ownerとなるオブジェクトが解放されようとしても、これらのオブジェクトが強参照するために、解放されないようだ。

今回実装していた物

UIImageViewのカスタムクラスの上に、UIImageが乗っており、animationImagesNSTimerによって、フェードイン・アウトするアニメーションの挙動を実装した。 参考:iphone fading of images

f:id:jeffsuke:20141002085957g:plain

元々の実装

これだと、ループが回り続け、ownerのオブジェクトは永遠に開放されない

NSTimer *timer = [NSTimer timerWithTimeInterval:4.0 
                                              target:self 
                                            selector:@selector(onTimer) 
                                            userInfo:nil 
                                             repeats:YES];

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [timer fire];

対策後

NSTimerをpropertyとして持ち、Viewが消える時に、invalidateし、解放する必要がある。

@interface JSKSwipeViewController ()
@property (nonatomic) NSTimer *timer;
@end

@implementation JSKSwipeViewController
- (void)startAnimation
{
    _timer = [NSTimer timerWithTimeInterval:4.0
                                     target:self
                                   selector:@selector(onTimer)
                                   userInfo:nil
                                    repeats:YES];
}

- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    
    // stop animation and release
    [self.timer invalidate];
    self.timer = nil;
    
}