iOS中的定时器(NSTimer)

902 阅读2分钟

「这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

在日常开发中,经常会用到定时器,常用的定时器用以下几种:

  • NSTimer
  • CADisplayLink
  • GCD定时器

NSTimer

关于 NSTimer

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

NSTimer 常用方法

  • 需要手动将 timer 加入 runloop 中,

    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
    //NSRunLoopCommonModes 包含了 NSDefaultRunLoopMode 和 UITrackingRunLoopMode(所以滑动的时候也能响应定时器)
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    
  • 默认会自动添加到 NSDefaultRunLoopMode

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
    
  • timer 的销毁

    • invalidate 方法 会停止计时器的再次触发,并在RunLoop中将其移除
    • invalidate 方法 是将 timer 对象从 runloop 中移除的唯一方法
    • 调用 invalidate 方法会删除 runlooptimer 的强引用,以及 timertargetuserInfo 的强引用
    [timer invalidate];
    timer = nil;
    

NSTimer 在线程中的使用

  • 在子线程上直接使用是没有反应的,因为 runloop 在子线程上,需要手动把当前的 runloop 开启:[[NSRunLoop currentRunLoop] run]

    dispatch_queue_t queue = dispatch_get_global_queue(0,0);
    
        dispatch_async(queue, ^{
    
            self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
            //timer 加入runloop
            [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
            //开启runloop
            [[NSRunLoop currentRunLoop] run];
    
        });
    
  • 在子线程上创建的定时器,必须要在子线程中销毁,不要在主线程中销毁,否者会造成内存泄漏

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        dispatch_queue_t queue = dispatch_get_global_queue(0,0);
        dispatch_async(queue, ^{
            self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
            [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
            [[NSRunLoop currentRunLoop] run];
        });
    }
    
    static int i = 0;
    - (void)run{
        if (i == 4) {
            //子线程销毁
            [self.timer invalidate];
        }
        i++;
    }
    
  • runloop 的创建方式不是通过 alloc init 是通过 [NSRunLoop currentRunLoop] 来直接获取

  • 如果当前线程中有大量的复杂操作,会导致定时器的卡住 示例:

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        dispatch_queue_t queue = dispatch_get_global_queue(0,0);
        dispatch_async(queue, ^{
            self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
            [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
            [[NSRunLoop currentRunLoop] run];
        });
    }
    
    - (void)run{
        //线程中的耗时操作
        NSUInteger count = 0xFFFFFFF;
        CGFloat num = 0;
        for (int i = 0; i < count; i ++) {
            num = i/count;
        }
        NSLog(@"%@",[NSThread currentThread]);
    }
    

    log:

    Snip20211120_1.png

NSTimer 在UI中的使用

使用类似 scrollViewtableView 这样的控件,滑动UI的时候,定时器会暂停,原因是默认的 Timer 模式是 NSDefaultRunLoopMode ,但是在滑动的时候 runloop 的模式是 UITrackingRunLoopModerunloop 同一时刻只能在一个 mode 上来运行,其他 mode 上的任务暂停,所以在 Timer 中最好是设置 mode 的模式为 NSRunLoopCommonModes ,因为 NSRunLoopCommonModes 包含了 NSDefaultRunLoopModeUITrackingRunLoopMode,所以滑动的时候也能响应定时器