nstimer_nstimer暂停 - CSDN
精华内容
参与话题
  • 我们先用 NSTimer 来做个简单的计时器,每隔5秒钟在控制台输出 Fire 。比较想当然的做法是这样的: @interface DetailViewController () @property (nonatomic, weak) NSTimer *timer; @end @implementation ...

    我们先用 NSTimer 来做个简单的计时器,每隔5秒钟在控制台输出 Fire 。比较想当然的做法是这样的:

    @interface DetailViewController ()
    @property (nonatomic, weak) NSTimer *timer;
    @end
    @implementation DetailViewController
    - (IBAction)fireButtonPressed:(id)sender {
        _timer = [NSTimer scheduledTimerWithTimeInterval:3.0f
                                                  target:self
                                                selector:@selector(timerFire:)
                                                userInfo:nil
                                                 repeats:YES];
        [_timer fire];
    }
    -(void)timerFire:(id)userinfo {
        NSLog(@"Fire");
    }
    @end

    运行之后确实在控制台每隔3秒钟输出一次 Fire ,然而当我们从这个界面跳转到其他界面的时候却发现:控制台还在源源不断的输出着 Fire 。看来 Timer 并没有停止。

    invalidate
    既然没有停止,那我们在 DemoViewController 的 dealloc 里加上 invalidate 的方法:

    -(void)dealloc {
        [_timer invalidate];
        NSLog(@"%@ dealloc", NSStringFromClass([self class]));
    }

    再次运行,还是没有停止。原因是 Timer 添加到 Runloop 的时候,会被 Runloop 强引用:

    Note in particular that run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.

    然后 Timer 又会有一个对 Target 的强引用(也就是 self ):

    Target is the object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.

    也就是说 NSTimer 强引用了 self ,导致 self 一直不能被释放掉,所以也就走不到 self 的 dealloc 里。

    既然如此,那我们可以再加个 invalidate 按钮:

    
    - (IBAction)invalidateButtonPressed:(id)sender {
        [_timer invalidate];
    }

    嗯这样就可以了。(在 SOF 上有人说该在 invalidate 之后执行 _timer = nil ,未能理解为什么,如果你知道原因可以告诉我:)
    在 invalidate 方法的文档里还有这这样一段话:

    You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.

    NSTimer 在哪个线程创建就要在哪个线程停止,否则会导致资源不能被正确的释放。看起来各种坑还不少。
    dealloc
    那么问题来了:如果我就是想让这个 NSTimer 一直输出,直到 DemoViewController 销毁了才停止,我该如何让它停止呢?

    NSTimer 被 Runloop 强引用了,如果要释放就要调用 invalidate 方法。

    但是我想在 DemoViewController 的 dealloc 里调用 invalidate 方法,但是 self 被 NSTimer 强引用了。

    所以我还是要释放 NSTimer 先,然而不调用 invalidate 方法就不能释放它。

    然而你不进入到 dealloc 方法里我又不能调用 invalidate 方法。

    嗯…

    HWWeakTimer
    weakSelf

    问题的关键就在于 self 被 NSTimer 强引用了,如果我们能打破这个强引用问题自然而然就解决了。所以一个很简单的想法就是:weakSelf:

    __weak typeof(self) weakSelf = self;
    _timer = [NSTimer scheduledTimerWithTimeInterval:3.0f
                                              target:weakSelf
                                            selector:@selector(timerFire:)
                                            userInfo:nil
                                             repeats:YES];

    然而这并没有什么卵用,这里的 weak 和 strong 唯一的区别就是:如果在这两行代码执行的期间 self 被释放了, NSTimer 的 target 会变成 nil 。

    target
    既然没办法通过 __weak 把 self 抽离出来,我们可以造个假的 target 给 NSTimer 。这个假的 target 类似于一个中间的代理人,它做的唯一的工作就是挺身而出接下了 NSTimer 的强引用。类声明如下:

    @interface HWWeakTimerTarget : NSObject
    @property (nonatomic, weak) id target;
    @property (nonatomic, assign) SEL selector;
    @property (nonatomic, weak) NSTimer* timer;
    @end
    @implementation HWWeakTimerTarget
    - (void) fire:(NSTimer *)timer {
        if(self.target) {
            [self.target performSelector:self.selector withObject:timer.userInfo];
        } else {
            [self.timer invalidate];
        }
    }
    @end

    然后我们再封装个假的 scheduledTimerWithTimeInterval 方法,但是在调用的时候已经偷梁换柱了:

    + (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          target:(id)aTarget
                                        selector:(SEL)aSelector
                                        userInfo:(id)userInfo
                                         repeats:(BOOL)repeats {
        HWWeakTimerTarget* timerTarget = [[HWWeakTimerTarget alloc] init];
        timerTarget.target = aTarget;
        timerTarget.selector = aSelector;
        timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                             target:timerTarget
                                                           selector:@selector(fire:)
                                                           userInfo:userInfo
                                                            repeats:repeats];
        return timerTarget.timer;
    }

    再次运行,问题解决。
    block
    如果能用 block 来调用 NSTimer 那岂不是更好了。我们可以这样来实现:

    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                          block:(HWTimerHandler)block
                                       userInfo:(id)userInfo
                                        repeats:(BOOL)repeats {
        return [self scheduledTimerWithTimeInterval:interval
                                             target:self
                                           selector:@selector(_timerBlockInvoke:)
                                           userInfo:@[[block copy], userInfo]
                                            repeats:repeats];
    }
    + (void)_timerBlockInvoke:(NSArray*)userInfo {
        HWTimerHandler block = userInfo[0];
        id info = userInfo[1];
        // or `!block ?: block();` @sunnyxx
        if (block) {
            block(info);
        }
    }

    这样我们就可以直接在 block 里写相关逻辑了:

    
    - (IBAction)fireButtonPressed:(id)sender {
        _timer = [HWWeakTimer scheduledTimerWithTimeInterval:3.0f block:^(id userInfo) {
            NSLog(@"%@", userInfo);
        } userInfo:@"Fire" repeats:YES];
        [_timer fire];
    }

    嗯就是这样。
    More
    把上面的的代码简单的封装到了 HWWeakTimer 中,欢迎试用。

    参考文献:
    NStimer
    How to stop/invalidate NStimer
    Weak Reference to NSTimer Target To Prevent Retain Cycle
    performSelector may cause a leak because its selector is unknown

     

     

     

     

     
     
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

            UIButton *start = [[UIButton alloc] initWithFrame:CGRectMake(200, 30, 60, 30)];

            [start setBackgroundColor:[UIColor magentaColor]];

            [self.contentView addSubview:start];

            [start setTitle:@"开始" forState:UIControlStateNormal];

            [start addTarget:self action:@selector(start) forControlEvents:UIControlEventTouchUpInside];

            

            UIButton *pause = [[UIButton alloc] initWithFrame:CGRectMake(270, 30, 60, 30)];

            [pause setBackgroundColor:[UIColor magentaColor]];

            [self.contentView addSubview:pause];

            [pause setTitle:@"暂停" forState:UIControlStateNormal];

            [pause addTarget:self action:@selector(pause) forControlEvents:UIControlEventTouchUpInside];

            

            _timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeCountDown) userInfo:nil repeats:YES];

            [_timer setFireDate:[NSDate distantFuture]];// 定时器默认是暂停的

            [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

     

     

     

     

    - (void)start {

        

        NSLog(@"开始");

        [_timer setFireDate:[NSDate date]];

    }

     

    - (void)pause {

        

        NSLog(@"暂停");

        [_timer setFireDate:[NSDate distantFuture]];

    }

     

    - (void)timeCountDown {

        

        NSLog(@"倒计时");

    }

     



    注意事项:

    如果采用NSProxy的方式解除循环引用的话,

    必须在使用NSTimer的类的dealloc方法中使定时器失效即 调用invalidate方法

    展开全文
  • NSTimer 基本使用和注意事项

    千次阅读 2018-08-30 14:52:21
    NSTimer的基本使用 NSTimer在线程中的使用 NSTimer在ScrollView中的使用 NSTimer/CADisplayLink循环引用的问题和解决 GCD实现定时器 NSTimer的基本使用 NSTimer: 一个在确定时间间隔内执行一次或多次我们指定...
    
    
    

    NSTimer的基本使用

    • NSTimer: 一个在确定时间间隔内执行一次或多次我们指定对象方法的对象。

    • 基本使用:

    两个比较常用的方法:
    timerWithTimeInterval: target: selector: userInfo: repeats:;
    
    scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:;

    区别:

    • 第一个需要手动添加到Runloop 中。第二个不需要,自动就添加到了当前的Runloop 中。
    第一种方式的使用:
    _timer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    //默认是 NSDefaultRunLoopMode
    //NSRunLoopCommonModes 包含了 NSDefaultRunLoopMode 和 UITrackingRunLoopMode,所以滑动的时候也能响应定时器
    [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    
    第二种方式:
    //默认会自动添加到 NSDefaultRunLoopMode
    _timer = [NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    
    - (void)timerAction {
        NSLog(@" === timerAction == ");
    }

    NSTimer在线程中的使用

    • 在子线程上直接使用是没有反应的,因为runloop 在子线程上,需要手动去开启当前的runloop [[NSRunLoop currentRunLoop] run];
    • 在子线程上创建的定时器,必须要在子线程中销毁,不要在主线程中销毁,否者会造成runloop 资源泄露[self performSelector:@selector(invalidateTimer) withObject:nil afterDelay:3];
    • runloop 的创建方式不是通过alloc init 是通过 [NSRunLoop currentRunLoop] 来直接获取的
    • 如果当前线程中有大量的复杂操作,会导致定时器的卡住
    //子线程中使用定时器
    [NSThread detachNewThreadSelector:@selector(threadTimer) toTarget:self withObject:nil];
    
    //NSTimer 在线程中的使用
    - (void)threadTimer {
        NSLog(@"%@",[NSThread currentThread]);
        //直接调用是没有任何反应的
        //runloop 在子线程中是需要手动开启的
        _timer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
    
        //子线程上的定时器,必须要在子线程中销毁,不要在主线程中销毁,否者会造成runloop 资源泄露
        [self performSelector:@selector(invalidateTimer) withObject:nil afterDelay:3];
    
        //手动开启runoop
        [[NSRunLoop currentRunLoop] run];
    
        //runloop 的创建方式不是通过alloc init 是通过 [NSRunLoop currentRunLoop] 来直接获取的
        NSLog(@" === 子线程的timer 销毁了 === ");
    }
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            //如果在当前线程有复杂的操作,会导致定时器卡住
            [self busyCalculate];
        });
    
    //如果在当前线程有复杂的操作,会导致定时器卡住
    - (void)busyCalculate {
        NSUInteger count = 0xFFFFFFF;
        CGFloat num = 0;
        for (int i = 0; i < count; i ++) {
            num = i/count;
        }
    }

    NSTimer在UIScrollView中的使用

    • 当在scrollView 中滑动的时候,定时器会暂停,原因是默认的Timer是在NSDefaultRunLoopMode ,但是在滑动的时候runloop是UITrackingRunLoopMode,
    • runloop 同一时刻只能在一个mode 上来运行,其他 mode 上的任务暂停。
    • 所以在Timer 中最好是设置 mode为 NSRunLoopCommonModes ,因为NSRunLoopCommonModes 包含了 NSDefaultRunLoopMode 和 UITrackingRunLoopMode,所以滑动的时候也能响应定时器

    NSTimer循环引用的问题和解决

    • NSTimer 的销毁(invalidate):
      • invalidate方法 会停止计时器的再次触发,并在RunLoop中将其移除。
      • invalidate 方法 是将NSTimer对象从RunLoop 中移除的唯一方法。
      • 调用invalidate方法会删除RunLoop对NSTimer的强引用,以及NSTimer对target和userInfo的强引用!
    Description: 
    //invalidate方法 会停止计时器的再次触发,并在RunLoop中将其移除。
    Stops the timer from ever firing again and requests its removal from its run loop.
    
    //invalidate 方法 是将NSTimer对象从RunLoop 中移除的唯一方法。
    This method is the only way to remove a timer from an NSRunLoop object.
    
    //调用invalidate方法会删除RunLoop对NSTimer的强引用,以及NSTimer对target和userInfo的强引用!
    The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.
    If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.
    
    • NSTimer的循环引用(从NSTimer的描述和方法的描述可以看出)
      • 计时器与运行循环一起工作。RunLoop 维持着对定时器的强引用。
      • 当计时器触发后,在调用invalidated 之前会一直保持对target的强引用
    //计时器与运行循环一起工作。RunLoop 维持着对定时器的强引用。
    Timers work in conjunction with run loops. Run loops maintain strong references to their timers, so you don’t have to maintain your own strong reference to a timer after you have added it to a run loop.
    //当计时器触发后,在调用invalidated 之前会一直保持对target的强引用
    The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
    • 内存泄露原因:控制器对 NSTimer 强引用,NSTimer 又对控制器强引用,RunLoop 对NSTimer 也强引用。这样就造成了循环引用。

    循环引用

    @interface TimerViewController ()
    @property (nonatomic,strong) NSTimer *timer;
    @end
    
    @implementation TimerViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        _timer = [NSTimer timerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
    
       //_timer = [NSTimer scheduledTimerWithTimeInterval:1.f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
    
    }
    
    - (void)timerAction {
        NSLog(@" === timerAction == ");
    }
    
    - (void)dealloc {
        [_timer invalidate];
        _timer = nil;
        NSLog(@" _timer invalidate 销毁了");
    }
    
    @end
    

    以上代码平时我们一般都是这样使用的,但是这样dealloc 方法一般是不会走到的,这样定时器是永远都不会销毁的。

    • 解决循环引用的方法有三种

      • 一般情况下在直接在 viewWillDisappear 中手动去销毁定时器
      • 自己实现一个带block的定时器分类,实现一个不保留目标对象的定时器
      • 通过NSProxy 来处理内存泄露的问题
    • 1、在viewWillDisappear 中手动去销毁定时器

    //可以手动在 viewWillDisappear 中去销毁定时器
    //在控制器即将销毁的时候销毁定时器,这样定时器对控制的强引用就解除了,循环引用也解除了
    - (void)viewWillDisappear:(BOOL)animated {
        [super viewWillDisappear:animated];
        [_timer invalidate];
        _timer = nil;
    }
    • 2、自己实现一个带block的定时器分类,实现一个不保留目标对象的定时器(把保留转移到了定时器的类对象身上,这样就避免了实例对象被保留。)
    @interface NSTimer (RCTimer)
    
    + (instancetype)timerWithTimeInterval:(NSTimeInterval)ti block:(void(^)(void))block repeats:(BOOL)repeats;
    
    @end
    
    @implementation NSTimer (RCTimer)
    
    + (instancetype)timerWithTimeInterval:(NSTimeInterval)ti block:(void (^)(void))block repeats:(BOOL)repeats {
    
        return [NSTimer timerWithTimeInterval:ti target:self selector:@selector(timerAction:) userInfo:block repeats:repeats];
    }
    
    + (void)timerAction:(NSTimer *)timer {
        void(^block)(void) = [timer userInfo];
        if (block) {
            block();
        }
    }
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        //引起循环引用的主要是 targrt:self
        //自己实现一个不保留目标的定时器
        __weak typeof(self)Weakself = self;
        _timer = [NSTimer timerWithTimeInterval:1.f block:^{
            [Weakself timerAction];
        } repeats:YES];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    }
    - (void)timerAction {
        NSLog(@" === timerAction == ");
    }
    
    - (void)dealloc {
          // 务必在当前线程调用invalidate方法,使得Runloop释放对timer的强引用
         [_timer invalidate];
         _timer = nil;
    }
     //在iOS 10 之后系统提供了类似的block 的方法来解决循环引用的问题
        __weak typeof(self)Weakself = self;
        _timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
            [Weakself timerAction];
        }];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
    
        //schedule方式
        _timer = [NSTimer scheduledTimerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
            [Weakself timerAction];
        }];
    • 3、通过NSProxy 来处理循环引用的问题(参考YYKit)
    #import <Foundation/Foundation.h>
    
    @interface RCProxy : NSProxy
    @property (nonatomic, weak, readonly) id target;
    -(instancetype)initWithTarget:(id)target;
    +(instancetype)proxyWithTarget:(id)target;
    @end
    
    @implementation RCProxy
    -(instancetype)initWithTarget:(id)target {
        _target = target;
        return self;
    }
    +(instancetype)proxyWithTarget:(id)target {
        return [[RCProxy alloc] initWithTarget:target];
    }
    -(id)forwardingTargetForSelector:(SEL)selector {
        return _target;
    }
    -(void)forwardInvocation:(NSInvocation *)invocation {
        void *null = NULL;
        [invocation setReturnValue:&null];
    }
    -(NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
        return [NSObject instanceMethodSignatureForSelector:@selector(init)];
    }
    -(BOOL)respondsToSelector:(SEL)aSelector {
        return [_target respondsToSelector:aSelector];
    }
    -(BOOL)isEqual:(id)object {
        return [_target isEqual:object];
    }
    -(NSUInteger)hash {
        return [_target hash];
    }
    -(Class)superclass {
        return [_target superclass];
    }
    -(Class)class {
        return [_target class];
    }
    -(BOOL)isKindOfClass:(Class)aClass {
        return [_target isKindOfClass:aClass];
    }
    -(BOOL)isMemberOfClass:(Class)aClass {
        return [_target isMemberOfClass:aClass];
    }
    -(BOOL)conformsToProtocol:(Protocol *)aProtocol {
        return [_target conformsToProtocol:aProtocol];
    }
    -(BOOL)isProxy {
        return YES;
    }
    -(NSString *)description {
        return [_target description];
    }
    -(NSString *)debugDescription {
        return [_target debugDescription];
    }
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];    
        _timer = [NSTimer scheduledTimerWithTimeInterval:1
                                                      target:[RCProxy proxyWithTarget:self]
                                                    selector:@selector(timerAction)
                                                    userInfo:nil
                                                     repeats:YES];
        }
    }
    
    - (void)timerAction {
        NSLog(@" === timerAction == ");
    }
    
    - (void)dealloc {
        [_timer invalidate];
        _timer = nil;
        NSLog(@" _timer invalidate 销毁了");
    }
    • NSProxy本身是一个抽象类,它遵循NSObject协议,提供了消息转发的通用接口,NSProxy通常用来实现消息转发机制和惰性初始化资源。不能直接使用NSProxy。需要创建NSProxy的子类,并实现init以及消息转发的相关方法,才可以用。
    • RCProxy继承了NSProxy,定义了一个弱引用的target对象,通过重写消息转发等方法,让target对象去处理接收到的消息。在整个引用链中,Controller对象强引用NSTimer对象,NSTimer对象强引用RCProxy对象,而RCProxy对象弱引用Controller对象,所以在YYWeakProxy对象的作用下,Controller对象和NSTimer对象之间并没有相互持有,完美解决循环引用的问题。

    这里写图片描述

    GCD实现定时器

    • GCD的定时器不受RunLoop中Mode的影响(RunLoop内部也是基于GCD实现的,可以根据源码看到), 比如滚动TableView的时候,GCD的定时器不受影响;且比NSTimer更加准时。
    @property (nonatomic,strong) dispatch_source_t timer;
    
    - (void)gcdTimer:(NSTimeInterval)timeInterval repeats:(BOOL)repeats {
        //获取队列
        dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        //创建一个定时器(dispatch_source_t本质还是个OC对象,创建出来的对象需要强引用)
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //设置定时器的各种属性(什么时候开始任务,每隔多久执行一次)  GCD的时间参数,一般是纳秒(1秒 = 10的9次方纳秒)
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, timeInterval * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
        //设置回调
        dispatch_source_set_event_handler(_timer, ^{
            if (!repeats) {
                dispatch_cancel(_timer);  //取消定时器
                _timer = nil;
            }else {
                [self timerAction];
            }
        });
        //启动定时器
        dispatch_resume(_timer);
    
        //只执行一次操作
    //    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    //
    //    });
    }
    展开全文
  • NSTimer的使用

    2018-11-26 09:05:07
    NSTimer的基础用法以及程序挂起后NSTimer仍然可以在后台运行计时 1. 关于NSTimer一些基本的知识,网上应该有很多讲解,废话不多少,直接上代码 (1) 下面是简单的实现代码 #import "NSTimerController.h&...

    NSTimer的基础用法以及程序挂起后NSTimer仍然可以在后台运行计时

    1. 关于NSTimer一些基本的知识,网上应该有很多讲解,废话不多少,直接上代码

    (1) 下面是简单的实现代码

    #import "NSTimerController.h"
    
    #define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
    #define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
    static const NSInteger button_tag = 100;
    
    @interface NSTimerController ()
    
    @property (nonatomic,strong) NSTimer* timer; /*定时器*/
    @property (nonatomic,assign) NSInteger secondsCountDown; /*倒计时的时间数*/
    @property (nonatomic,strong) UIButton* count_button; /*点击的按钮*/
    @property (nonatomic,assign) NSInteger space; /*宽度*/
    @property (nonatomic,assign) BOOL isClick; /*防止连续点击*/
    
    @end
    
    @implementation NSTimerController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
        _secondsCountDown = 20; /*初使时间20秒*/
        _space = 300; /*按钮的宽度*/
        [self count_button];
    }
    
    -(void) viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        [_timer invalidate]; /*将定时器从运行循环中移除*/
        _timer = nil; /*销毁定时器, 这样可以避免控制器不死*/
    }
    
    -(UIButton*) count_button
    {
        if (!_count_button)
        {
            _count_button = [UIButton buttonWithType:UIButtonTypeCustom];
            [self.view addSubview:_count_button];
            _count_button.frame = CGRectMake(SCREEN_WIDTH / 2 - 150, SCREEN_HEIGHT - 300, _space, 40);
            _count_button.tag = button_tag;
            _count_button.layer.cornerRadius = 5;
            [_count_button setTitle:@"重新获取" forState:UIControlStateNormal];
            _count_button.backgroundColor = [UIColor orangeColor];
            [_count_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [_count_button addTarget:self action:@selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
        }
        return _count_button;
    }
    
    -(void) buttonAction:(UIButton*) sender
    {
        if (!_isClick)
        {
            _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeFireMethod) userInfo:nil repeats:YES];
    
            /*注意点:
             将计数器的repeats设置为YES的时候,self的引用计数会加1。
             因此可能会导致self(即viewController)不能release。
             所以,必须在viewWillAppear的时候,将计数器timer停止,否则可能会导致内存泄露。*/
            
            /*手动启动Runloop,然后使其在线程池里运行*/
            /* 
             1: 下面这个方法切忌不要轻易使用,避免网络请求的线程会被杀死:[[NSRunLoop currentRunLoop] run];
    
             2: 如果想用,建议如下操作:
             // dispatch_async(dispatch_get_global_queue(0, 0), ^{
             //  _countDownTimer = [NSTimer  scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timeFireMethod) userInfo:nil repeats:YES];
             // [[NSRunLoop currentRunLoop] run];
                        });
              */
    
            //正确用法:
             [[NSRunLoop currentRunLoop] addTimer:_timer  forMode:NSRunLoopCommonModes];
        }
    }
    
    -(void) timeFireMethod
    {
        _isClick = YES;
        _secondsCountDown--;
        [_count_button setTitle:[NSString stringWithFormat:@"重新获取 (%ld)",(long)_secondsCountDown] forState:UIControlStateNormal];
        [_count_button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
        NSLog(@"_secondsCountDown:%ld",(long)_secondsCountDown);
        if (_secondsCountDown <= 0)
        {
            _isClick = NO;
            [_timer invalidate];
            _secondsCountDown = 20;
            [_count_button setTitle:@"重新获取" forState:UIControlStateNormal];
            [_count_button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        }
    }
    
    @end
    
    

    下面两张图是简易的效果图:

    图1.png

    图2.png

    (2) 上面的代码中,有关于一些细节的说明,但是还有一些其他的重点细节,在下面说下

    在页面即将消失的时候关闭定时器,之后等页面再次打开的时候,又开启定时器(只要是防止它在后台运行,暂用CPU)

    -(void) viewWillAppear:(BOOL)animated
    {
        [super viewWillAppear:animated];
        /*开启继续运行NSTimer*/
        [_timer setFireDate:[NSDate distantPast]];
    }
    
    -(void) viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
        /*关闭NSTimer*/
        [_timer setFireDate:[NSDate distantFuture]];
    }
    
    
    

    2. 程序挂起后NSTimer仍然可以在后台运行计时

    具体操作如下:

    步骤一: 在info里面如下设置:
    info -->添加 Required background modes -->设置 App plays audio or streams audio/video using AirPlay

    3C1F3F3C-6860-4A29-84C1-65554EFDF687.png

    步骤二:在AppDelegate.m里面调用

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        
        /*定时器后台运行*/
        NSError *setCategoryErr = nil;
        NSError *activationErr  = nil;
        /*设置Audio Session的Category 一般会在激活之前设置好Category和mode。但是也可以在已激活的audio session中设置,不过会在发生route change之后才会发生改变*/
        [[AVAudioSession sharedInstance] setCategory: AVAudioSessionCategoryPlayback error: &setCategoryErr];
        /*激活Audio Session*/
        [[AVAudioSession sharedInstance] setActive: YES error: &activationErr];
     }
    
    /*
     通过UIBackgroundTaskIdentifier可以实现有限时间内在后台运行程序
     程序进入后台时调用applicationDidEnterBackground函数,
     */
    - (void)applicationDidEnterBackground:(UIApplication *)application{
        
        UIApplication* app = [UIApplication sharedApplication];
        __block UIBackgroundTaskIdentifier bgTask;
        
        /*注册一个后台任务,告诉系统我们需要向系统借一些事件*/
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (bgTask != UIBackgroundTaskInvalid)
                {
                    /*销毁后台任务标识符*/
                    /*不管有没有完成,结束background_task任务*/
                    bgTask = UIBackgroundTaskInvalid;
                }
            });
        }];
        
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (bgTask != UIBackgroundTaskInvalid)
                {
                    /*销毁后台任务标识符*/
                    /*不管有没有完成,结束background_task任务*/
                    bgTask = UIBackgroundTaskInvalid;
                }
            });
        });
    }
    

    参考如下链接知识,深表感谢:

    IOS音频播放学习参考
    IOS后台挂起时运行程序UIBackgroundTaskIdentifier

     

    UITableView滚动时NSTimer不执行

    2016年07月06日 01:59:08 gx_wqm 阅读数:656

    版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/gx_wqm/article/details/51835775

    解决方法增加timer的runloop模式:

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; 或
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

    原因分析:

    NSTImer的实现时基于runLoop的,而runloop又有以下5种模式,某一时刻只能处理对应模式下的事件。

     

    • Default模式
      定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
      描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。
    • Connection模式
      定义:NSConnectionReplyMode(Cocoa)
      描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。
    • Modal模式
      定义:NSModalPanelRunLoopMode(Cocoa)
      描述:处理modal panels事件。
    • Event tracking模式
      定义:

      UITrackingRunLoopMode

      (iOS) NSEventTrackingRunLoopMode(cocoa)
      描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。
    • Common模式
      定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)
      描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。

     

     

    获取当前线程的run loop mode:

    NSString* runLoopMode = [[NSRunLoopcurrentRunLoop]currentMode];

    NSTimer默认处于NSDefaultRunLoopMode,在没有滚动时,runloop处于NSDefaultRunLoopMode下,滚动时处于UITrackingRunLoopMode,导      致NSTimer事件源被过滤了,所以不执行,那怎么办,只需要给NSTimer再添加一个模式:

    [NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]或

    干脆就所有模式下都能执行:

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

     

    IOS中定时器NSTimer的开启与关闭

    调用一次计时器方法:

    myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(scrollTimer) userInfo:nil repeats:NO];

    1. //不重复,只调用一次。timer运行一次就会自动停止运行

    2. 重复调用计时器方法

    3. timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(function:) userInfo:nil repeats:YES];

    4. //每1秒运行一次function方法。

    注意:将计数器的repeats设置为YES的时候,self的引用计数会加1。因此可能会导致self(即viewController)不能release,所以,必须在viewDidDisappear的时候,将计数器timer停止,否则可能会导致内存泄露。

    停止timer的运行,但这个是永久的停止:(注意:停止后,一定要将timer赋空,否则还是没有释放。不信?你自己试试~)

    1. //取消定时器

    2. [timer invalidate];

    3. timer = nil;

    4. 要想实现:先停止,然后再某种情况下再次开启运行timer,可以使用下面的方法:

    首先关闭定时器不能使用上面的方法,应该使用下面的方法:

    1. //关闭定时器

    2. [myTimer setFireDate:[NSDate distantFuture]];


    然后就可以使用下面的方法再此开启这个timer了:

    1. //开启定时器

    2. [myTimer setFireDate:[NSDate distantPast]];


    例子:比如,在页面消失的时候关闭定时器,然后等页面再次打开的时候,又开启定时器。

     

    (主要是为了防止它在后台运行,暂用CPU)可以使用下面的代码实现:

    1. //页面将要进入前台,开启定时器

    2. -(void)viewWillAppear:(BOOL)animated

    3. {

    4. //开启定时器

    5. [scrollView.myTimer setFireDate:[NSDate distantPast]];

    6. }

    7.  
    8. //页面消失,进入后台不显示该页面,关闭定时器

    9. -(void)viewDidDisappear:(BOOL)animated

    10. {

    11. //关闭定时器

    12. [scrollView.myTimer setFireDate:[NSDate distantFuture]];

    13. }


    OK,搞定。

    展开全文
  • NSTimer你真的会用了吗

    千次阅读 2018-08-14 16:39:37
    NSTimer你真的会用了吗  看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个能够定时的完成任务的东西吗?  我想说你知道NSTimer会retain你添加调用方法
    原文地址为:NSTimer你真的会用了吗

    NSTimer你真的会用了吗

      看到这个标题,你可能会想NSTimer不就是计时器吗,谁不会用,不就是一个能够定时的完成任务的东西吗?

      我想说你知道NSTimer会retain你添加调用方法的对象吗?你知道NSTimer是要加到runloop中才会起作用吗?你知道NSTimer会并不是准确的按照你指定的时间触发的吗?你知道NSTimer就算添加到runloop了也不一定会按照你想象中的那样执行吗?

      如果上面提出的哪些问题,你并不全部了解,那么请细心的看完下面的文章,上面的那几个问题我会一一说明,并给出详细的例子。

    一、什么是NSTimer

      官方给出解释是“A timer provides a way to perform a delayed action or a periodic action. The timer waits until a certain time interval has elapsed and then fires, sending a specified message to a specified object. ” 翻译过来就是timer就是一个能在从现在开始的后面的某一个时刻或者周期性的执行我们指定的方法的对象。

     

    二、NSTimer和它调用的函数对象间到底发生了什么

       从前面官方给出的解释可以看出timer会在未来的某个时刻执行一次或者多次我们指定的方法,这也就牵扯出一个问题,如何保证timer在未来的某个时刻触发指定事件的时候,我们指定的方法是有效的呢?

      解决方法很简单,只要将指定给timer的方法的接收者retain一份就搞定了,实际上系统也是这样做的。不管是重复性的timer还是一次性的timer都会对它的方法的接收者进行retain,这两种timer的区别在于“一次性的timer在完成调用以后会自动将自己invalidate,而重复的timer则将永生,直到你显示的invalidate它为止”。

      下面我们看个小例子:

     

    SvTestObject.m
    //
    //  SvTestObject.m
    //  SvTimerSample
    //
    //  Created by  maple on 12/19/12.
    //  Copyright (c) 2012 maple. All rights reserved.
    //
    
    #import "SvTestObject.h"
    
    @implementation SvTestObject
    
    - (id)init
    {
        self = [super init];
        if (self) {
            NSLog(@"instance %@ has been created!", self);
        }
        
        return self;
    }
    
    - (void)dealloc
    {
        NSLog(@"instance %@ has been dealloced!", self);
        
        [super dealloc];
    }
    
    - (void)timerAction:(NSTimer*)timer
    {
        NSLog(@"Hi, Timer Action for instance %@", self);
    }
    
    @end
    SvTestObject.h
    //
    //  SvTestObject.h
    //  SvTimerSample
    //
    //  Created by  maple on 12/19/12.
    //  Copyright (c) 2012 maple. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    
    @interface SvTestObject : NSObject
    
    /*
     * @brief timer响应函数,只是用来做测试
     */
    - (void)timerAction:(NSTimer*)timer;
    
    @end
    SvTimerAppDelegate.m
    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
        
        // test Timer retain target
        [self testNonRepeatTimer];
    //    [self testRepeatTimer];
    }
    
    - (void)testNonRepeatTimer
    {
        NSLog(@"Test retatin target for non-repeat timer!");
        SvTestObject *testObject = [[SvTestObject alloc] init];
        [NSTimer scheduledTimerWithTimeInterval:5 target:testObject selector:@selector(timerAction:) userInfo:nil repeats:NO];
        [testObject release];
        NSLog(@"Invoke release to testObject!");
    }
    
    - (void)testRepeatTimer
    {
        NSLog(@"Test retain target for repeat Timer");
        SvTestObject *testObject2 = [[SvTestObject alloc] init];
        [NSTimer scheduledTimerWithTimeInterval:5 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
        [testObject2 release];
        NSLog(@"Invoke release to testObject2!");
    }

      上面的简单例子中,我们自定义了一个继承自NSObject的类SvTestObject,在这个类的init,dealloc和它的timerAction三个方法中分别打印信息。然后在appDelegate中分别测试一个单次执行的timer和一个重复执行的timer对方法接受者是否做了retain操作,因此我们在两种情况下都是shedule完timer之后立马对该测试对象执行release操作。

      测试单次执行的timer的结果如下:

      观察输出,我们会发现53分58秒的时候我们就对测试对象执行了release操作,但是知道54分03秒的时候timer触发完方法以后,该对象才实际的执行了dealloc方法。这就证明一次性的timer也会retain它的方法接收者,直到自己失效为之。

      测试重复性的timer的结果如下:

      观察输出我们发现,这个重复性的timer一直都在周期性的调用我们为它指定的方法,而且测试的对象也一直没有真正的被释放。

      通过以上小例子,我们可以发现在timer对它的接收者进行retain,从而保证了timer调用时的正确性,但是又引入了接收者的内存管理问题。特别是对于重复性的timer,它所引用的对象将一直存在,将会造成内存泄露。

      有问题就有应对方法,NSTimer提供了一个方法invalidate,让我们可以解决这种问题。不管是一次性的还是重复性的timer,在执行完invalidate以后都会变成无效,因此对于重复性的timer我们一定要有对应的invalidate。

      突然想起一种自欺欺人的写法,不知道你们有没有这么写过,我承认之前也有这样写过,哈哈,代码如下:

    SvCheatYourself.m
    SvCheatYourself.m
    
    //
    //  SvCheatYourself.m
    //  SvTimerSample
    //
    //  Created by  maple on 12/19/12.
    //  Copyright (c) 2012 maple. All rights reserved.
    //
    //  以下这种timer的用法,企图在dealloc中对timer进行invalidate是一种自欺欺人的做法
    //  因为你的timer对self进行了retain,如果timer一直有效,则self的引用计数永远不会等于0
    
    #import "SvCheatYourself.h"
    
    @interface SvCheatYourself () {
        NSTimer *_timer;
    }
    
    @end
    
    @implementation SvCheatYourself
    
    - (id)init
    {
        self = [super init];
        if (self) {
            _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer:) userInfo:nil repeats:YES];
        }
        
        return self;
    }
    
    - (void)dealloc
    {
        // 自欺欺人的写法,永远都不会执行到,除非你在外部手动invalidate这个timer
        [_timer invalidate];
        
        [super dealloc];
    }
    
    - (void)testTimer:(NSTimer*)timer
    {
        NSLog(@"haha!");
    }
    
    @end

     综上: timer都会对它的target进行retain,我们需要小心对待这个target的生命周期问题,尤其是重复性的timer。

     

    三、NSTimer会是准时触发事件吗

      答案是否定的,而且有时候你会发现实际的触发时间跟你想象的差距还比较大。NSTimer不是一个实时系统,因此不管是一次性的还是周期性的timer的实际触发事件的时间可能都会跟我们预想的会有出入。差距的大小跟当前我们程序的执行情况有关系,比如可能程序是多线程的,而你的timer只是添加在某一个线程的runloop的某一种指定的runloopmode中,由于多线程通常都是分时执行的,而且每次执行的mode也可能随着实际情况发生变化。

      假设你添加了一个timer指定2秒后触发某一个事件,但是签好那个时候当前线程在执行一个连续运算(例如大数据块的处理等),这个时候timer就会延迟到该连续运算执行完以后才会执行。重复性的timer遇到这种情况,如果延迟超过了一个周期,则会和后面的触发进行合并,即在一个周期内只会触发一次。但是不管该timer的触发时间延迟的有多离谱,他后面的timer的触发时间总是倍数于第一次添加timer的间隙。

      原文如下“A repeating timer reschedules itself based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so far that it passes one or more of the scheduled firing times, the timer is fired only once for that time period; the timer is then rescheduled, after firing, for the next scheduled firing time in the future.”

      下面请看一个简单的例子:

    Simulate Thread Busy
    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        SvTestObject *testObject2 = [[SvTestObject alloc] init];
        [NSTimer scheduledTimerWithTimeInterval:1 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
        [testObject2 release];
        
        NSLog(@"Simulate busy");
        [self performSelector:@selector(simulateBusy) withObject:nil afterDelay:3];
    }
    
    // 模拟当前线程正好繁忙的情况
    - (void)simulateBusy
    {
        NSLog(@"start simulate busy!");
        NSUInteger caculateCount = 0x0FFFFFFF;
        CGFloat uselessValue = 0;
        for (NSUInteger i = 0; i < caculateCount; ++i) {
            uselessValue = i / 0.3333;
        }
        NSLog(@"finish simulate busy!");
    }

      例子中首先开启了一个timer,这个timer每隔1秒调用一次target的timerAction方法,紧接着我们在3秒后调用了一个模拟线程繁忙的方法(其实就是一个大的循环)。运行程序后输出结果如下:

      观察结果我们可以发现,当线程空闲的时候timer的消息触发还是比较准确的,但是在36分12秒开始线程一直忙着做大量运算,知道36分14秒该运算才结束,这个时候timer才触发消息,这个线程繁忙的过程超过了一个周期,但是timer并没有连着触发两次消息,而只是触发了一次。等线程忙完以后后面的消息触发的时间仍然都是整数倍与开始我们指定的时间,这也从侧面证明,timer并不会因为触发延迟而导致后面的触发时间发生延迟。

      综上: timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。

     

    四、NSTimer为什么要添加到RunLoop中才会有作用

      前面的例子中我们使用的是一种便利方法,它其实是做了两件事:首先创建一个timer,然后将该timer添加到当前runloop的default mode中。也就是这个便利方法给我们造成了只要创建了timer就可以生效的错觉,我们当然可以自己创建timer,然后手动的把它添加到指定runloop的指定mode中去。

      NSTimer其实也是一种资源,如果看过多线程变成指引文档的话,我们会发现所有的source如果要起作用,就得加到runloop中去。同理timer这种资源要想起作用,那肯定也需要加到runloop中才会又效喽。如果一个runloop里面不包含任何资源的话,运行该runloop时会立马退出。你可能会说那我们APP的主线程的runloop我们没有往其中添加任何资源,为什么它还好好的运行。我们不添加,不代表框架没有添加,如果有兴趣的话你可以打印一下main thread的runloop,你会发现有很多资源。 

      下面我们看一个小例子:  

    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        [self testTimerWithOutShedule];
    }
    
    - (void)testTimerWithOutShedule
    {
        NSLog(@"Test timer without shedult to runloop");
        SvTestObject *testObject3 = [[SvTestObject alloc] init];
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO];
        [testObject3 release];
        NSLog(@"invoke release to testObject3");
    }
    
    - (void)applicationWillResignActive:(UIApplication *)application
    {
        NSLog(@"SvTimerSample Will resign Avtive!");
    }

      这个小例子中我们新建了一个timer,为它指定了有效的target和selector,并指出了1秒后触发该消息,运行结果如下:

      观察发现这个消息永远也不会触发,原因很简单,我们没有将timer添加到runloop中。

      综上: 必须得把timer添加到runloop中,它才会生效。


    五、NSTimer加到了RunLoop中但迟迟的不触发事件

      为什么明明添加了,但是就是不按照预先的逻辑触发事件呢???原因主要有以下两个:

    1、runloop是否运行

      每一个线程都有它自己的runloop,程序的主线程会自动的使runloop生效,但对于我们自己新建的线程,它的runloop是不会自己运行起来,当我们需要使用它的runloop时,就得自己启动。

      那么如果我们把一个timer添加到了非主线的runloop中,它还会按照预期按时触发吗?下面请看一段测试程序:

    - (void)applicationDidBecomeActive:(UIApplication *)application
    {
        [NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil];
    }
    
    // 测试把timer加到不运行的runloop上的情况
    - (void)testTimerSheduleToRunloop1
    {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        
        NSLog(@"Test timer shedult to a non-running runloop");
        SvTestObject *testObject4 = [[SvTestObject alloc] init];
        NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:NO];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
        // 打开下面一行输出runloop的内容就可以看出,timer却是已经被添加进去
        //NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);
        
        // 打开下面一行, 该线程的runloop就会运行起来,timer才会起作用
        //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
        
        [testObject4 release];
        NSLog(@"invoke release to testObject4");
    
        [pool release];
    }
    
    - (void)applicationWillResignActive:(UIApplication *)application
    {
        NSLog(@"SvTimerSample Will resign Avtive!");
    }

      上面的程序中,我们新创建了一个线程,然后创建一个timer,并把它添加当该线程的runloop当中,但是运行结果如下:

      观察运行结果,我们发现这个timer知道执行退出也没有触发我们指定的方法,如果我们把上面测试程序中“//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];”这一行的注释去掉,则timer将会正确的掉用我们指定的方法。

    2、mode是否正确

      我们前面自己动手添加runloop的时候,可以看到有一个参数runloopMode,这个参数是干嘛的呢?

      前面提到了要想timer生效,我们就得把它添加到指定runloop的指定mode中去,通常是主线程的defalut mode。但有时我们这样做了,却仍然发现timer还是没有触发事件。这是为什么呢?

      这是因为timer添加的时候,我们需要指定一个mode,因为同一线程的runloop在运行的时候,任意时刻只能处于一种mode。所以只能当程序处于这种mode的时候,timer才能得到触发事件的机会。

      举个不恰当的例子,我们说兄弟几个分别代表runloop的mode,timer代表他们自己的才水桶,然后一群人去排队打水,只有一个水龙头,那么同一时刻,肯定只能有一个人处于接水的状态。也就是说你虽然给了老二一个桶,但是还没轮到它,那么你就得等,只有轮到他的时候你的水桶才能碰上用场。

      最后一个例子我就不贴了,也很简单,需要的话,我qq发给你。

      综上: 要让timer生效,必须保证该线程的runloop已启动,而且其运行的runloopmode也要匹配。

     

      很多知识我们都以为自己学会了,但往往对细节了解的还不是很清楚,这也就埋了一些隐患,导致一些奇奇怪怪的bug。所以说平时项目中要注意细节,有不清楚的地方就多查查资料,今天写的这些官方文档中都有,希望大家能一起进步。

      哦对了,还有一个小时就该到末日了,哈哈,不想写什么总结之类的,人活着开心就好。

    注:欢迎转载,转载请注明出处。同时欢迎加我qq,期待与你一起探讨更多问题。  


    转载请注明本文地址:NSTimer你真的会用了吗
    展开全文
  • NSTimer

    2017-09-27 16:12:14
    NSTimer**创建NSTimer** 创建NSTimer的常用方法是:+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats...
  • (001)NSTimer浅析

    2020-03-06 16:05:05
    NSTimer 在学习NSTimer的时候,遇到的一些问题整理。 通过 A页面 push 到 B页面,在B页面开启NSTimer,然后pop回到A页面的时候,发现没有执行dealloc。这个时候定时器还在运行,造成内存泄露。 代码如下: #...
  • NSTimer的使用方法 1、初始化 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; + (NSTimer *)...
  • NSTimer 暂停 继续 停止 的方法

    万次阅读 2016-03-02 11:39:04
    NSTimer 自带的方法中是没有暂停和继续的方法的, 但是NSTimer有个方法是 setFireDate 可以利用这个方法实现暂停和继续 暂停: 原理是把触发时间设置在未来,既很久之后,这样定时器自动进入等待触发的状态,估计...
  • NSTimer方法不执行的问题

    千次阅读 2016-12-13 14:18:52
    最近, 在使用NSTimer的时候,发现了一个问题,在当前界面NSTimer的方法是可以执行的,但是当我push到下一界面, 做完相关操作,pop回来的时候,UI刷新,再次调用定时器时,发现方法竟然不执行了, 开始以为是线程...
  • IOS中定时器NSTimer的开启与关闭

    万次阅读 热门讨论 2017-11-21 12:18:52
    myTimer = [NSTimer scheduledTimerWithTimeInterval:1.5 target:self selector:@selector(scrollTimer) userInfo:nil repeats:NO]; //不重复,只调用一次。timer运行一次就会自动停止运行 重复调用计时器方法: ...
  • NSTimer用法,暂停,继续,初始化

    万次阅读 2012-07-13 09:54:33
    NSTimer的使用方法 1、初始化 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo; + (NSTimer *)...
  • NSTimer在后台保持运行

    千次阅读 2016-01-05 12:58:52
    使用NSTimer的时候,发现每次APP进入后台,或者屏幕休眠后,NSTimer就会暂停。为了解决这个问题,翻阅了各种博客和网页。最终在伟大的stackoverflow上找到了一个简单并且真正可行的解决方案!代码如下: [...
  • 之前对定时器的使用有误区,所以特地研究了一下,这里也跟大家分享一下:开启定时器,关闭定时器,取消定时器
  • NSTimer 详解

    千次阅读 2014-02-01 23:08:16
    关于NSTimer, 有一点需要特别注意:NSTimer会持有target(Remember that NSTimer Retains Its Target)。 一个NSTimer对象在触发时会保留目标直到计时器被显式的设置无效。(NSTimer invalidate) 如果...
  • ios 解决NSTimer 进入后台循环失效

    千次阅读 2017-05-09 11:10:17
    UIBackgroundTaskIdentifier taskId;//声明 - (void)applicationDidEnterBackground:(UIApplication *)application {//APP进入后台 //开启一个后台任务 taskId = [application ...
  • NSTimer用法

    千次阅读 2012-06-20 15:41:19
    NSTimer 可以精确控制50 - 100ms 的时间,如何Timer 处理函数比较耗时,真实的间隔时间可以比预设时间长,core function framework 也提供相似的对象, CFRunLoopTimerRef, 而且CFRunLoopTimerRef 可以 和NSTimer ...
  • NSTimer的使用 停止 暂停 重启

    千次阅读 2016-10-13 09:40:39
    NSTimer 调用了invalidate()方法 再调用fire方法是启动不了的,因为调用了invalidate()方法 是将timer作废了 需要重新创建对象才行 如果需要暂停 可以调用fireDate来设置 distantFuture()来实现暂停 需要重启 可以...
  • iOS多线程的初步研究(四)-- NSTimer

    千次阅读 2013-10-21 10:38:45
    ...理解run loop后,才能彻底理解NSTimer的实现原理,也就是说NSTimer实际上依赖run loop实现的。 先看看NSTimer的两个常用方法: + (NSTimer *)timerWithTimeInterval:(NSTimeInte
  • NSTimer 怎么暂停继续

    千次阅读 2013-03-13 13:04:07
    那,API里面NSTimer 是木有暂停继续的方法的,只有fire和invalidate,前者是开工的意思,后者是废掉的意思,如果用废掉来代替暂停的功能?显然是不对的。 那肿么办呢? 其实NSTimer 有一个属性叫 fireDate ,啥...
  • [iOS]NSTimer 不触发事件的解决

    千次阅读 2016-03-16 10:13:12
    1.创建NSTimer使用scheduledTimerWithTimeInterval方法创建的NSTimer会以默认方式加入当前NSRunLoop中使用 timerWithTimeInterval initWithFireDate 创建需要手动加入一个NSRunLoop中scheduledTimerWithTimeInterval...
1 2 3 4 5 ... 20
收藏数 16,577
精华内容 6,630
关键字:

nstimer