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

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];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]: \[runLoop addTimer:myTimer forMode:NSDefaultRunLoopMode\]; //实际上这步是不需要,scheduledTimerWithTimeInterval已经纳入当前线程运行。如果使用timerWithTimeInterval则需要。 引用\[2\]: \[\[NSRunLoop currentRunLoop\] addTimer:_timer forMode:NSDefaultRunLoopMode\]; //_timer = \[NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES\]; 引用\[3\]: GCD的定时器不受RunLoop中Mode的影响(RunLoop内部也是基于GCD实现的,可以根据源码看到), 比如滚动TableView的时候,GCD的定时器不受影响;且比NSTimer更加准时。 问题: NSTimer的mode是什么意思? 回答: NSTimer的mode是指定定时器在运行时所处的运行循环模式。在使用NSTimer时,可以通过指定mode来控制定时器在哪些运行循环模式下运行。比如在引用\[1\]和引用\[2\]中,都使用了NSDefaultRunLoopMode作为定时器的运行循环模式。这意味着定时器会在默认的运行循环模式下运行。而GCD的定时器则不受RunLoop中Mode的影响,可以在任何运行循环模式下运行,如引用\[3\]所示。 #### 引用[.reference_title] - *1* [iOS多线程的初步研究(四)-- NSTimer](https://blog.csdn.net/lengshengren/article/details/12905635)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [NSTimer 基本使用和注意事项](https://blog.csdn.net/wutengwei007/article/details/82221069)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值