天天看點

NSTimer容易陷進去的坑

前面個項目有用NSTimer做個短信驗證碼的定時器, 遇到了這個問題.  

關于NSTimer, 有一點需要特别注意:NSTimer會持有target(Remember that NSTimer Retains Its Target)。

  • 一個NSTimer對象在觸發時會保留目标直到計時器被顯式的設定無效。(NSTimer invalidate)
  • 如果調用者儲存了NSTimer, 是以用完之後應該立即銷毀定時器(或者退出目前視圖的銷毀等) 否則, 會有記憶體洩漏。
  • 使用塊方式可以使NSTimer對象實作打破這種循環引用的擴充。為了使這個塊實作作為NSTimer的公共接口一部分,可以把這個塊實作方法加到NSTimer的類擴充中。 

    (代碼)

  • // Example showing retain cycle
    #import <Foundation/Foundation.h>
    
    @interface EOCClass : NSObject
    - (void)startPolling;
    - (void)stopPolling;
    @end
    
    @implementation EOCClass {
        NSTimer *_pollTimer;
    }
    
    - (id)init {
        return [super init];
    }
    
    - (void)dealloc {
        [_pollTimer invalidate];
    }
    
    - (void)stopPolling {
        [_pollTimer invalidate];
        _pollTimer = nil;
    }
    
    - (void)startPolling {
        _pollTimer = 
        [NSTimer scheduledTimerWithTimeInterval:5.0
                                         target:self
                                       selector:@selector(p_doPoll)
                                       userInfo:nil
                                        repeats:YES];
    }
    
    - (void)p_doPoll {
        // Poll the resource
    }
    
    @end
    
    
    // Block support for NSTimer
    #import <Foundation/Foundation.h>
    
    @interface NSTimer (EOCBlocksSupport)
    
    + (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats;
    
    @end
    
    @implementation NSTimer (EOCBlocksSupport)
    
    + (void)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats
    {
        return [self scheduledTimerWithTimeInterval:interval
                                             target:self
                               selector:@selector(eoc_blockInvoke:)
                                           userInfo:[block copy]
                                            repeats:repeats];
    }
    
    + (void)eoc_blockInvoke:(NSTimer*)timer {
        void (^block)() = timer.userInfo;
        if (block) {
            block();
        }
    }
    
    @end
    
    
    // Changing to use the block - still a cycle
    - (void)startPolling {
        _pollTimer = 
        [NSTimer eoc_scheduledTimerWithTimeInterval:5.0
                                              block:^{
                                                  [self p_doPoll];
                                                     }
                                            repeats:YES];
    }
    
    
    // No cycle using weak references
    - (void)startPolling {
        __weak EOCClass *weakSelf = self;
        _pollTimer = 
        [NSTimer eoc_scheduledTimerWithTimeInterval:5.0
                                              block:^{
                                   EOCClass *strongSelf = weakSelf;
                                   [strongSelf p_doPoll];
                                                     }
                                            repeats:YES];
               

是以,如果你不想實作block的方式,在dealloc中記得invalidate timer就可以了。 也可以看看這篇文章Defeating NSTimer Retain Cycle

OCT16

Defeating NSTimer Retain Cycle

If you’ve done any coding with NSTimer and ARC you’ll know the pain of having to manually invalidate your timer before its owner can be deallocated.

Typically, you’ll have to have your object’s parent reach into the object’s inner workings and invalidate the timer at the appropriate time. This is inconvenient and breaks encapsulation.

I have a UITableViewCell which utilizes a NSTimer to keep track of energy regeneration, and I was determined to make it work without any outside intervention.

My solution was to utilize the UITableViewCell’s willMoveToWindow: method like so:

- (void)willMoveToWindow:(UIWindow *)newWindow
{
    [super willMoveToWindow:newWindow];
    if (newWindow == (id)[NSNull null] || newWindow == nil) {
        [self stopTimer];
    }
    else {
        [self refreshEnergy];
    }
}

- (void)stopTimer
{
    [timer invalidate];
    timer = nil;
}

- (void)refreshEnergy
{
    if (timer == nil) {
        timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(refreshEnergy) userInfo:nil repeats:YES];
    }
    ...
}
      

Now the UITableVIewCell automatically starts and stops the repeating timer as needed and deallocates correctly.

A note: You might try to simple use a weak reference to self in the NSTimer constructor but you’ll find that the timer must be invalidated to deallocate correctly.This WON’T work:

__weak MyClass *weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:weakSelf selector:@selector(refreshEnergy) userInfo:nil repeats:YES];      

繼續閱讀