返回

RunLoop 的多模式:多模式的 NSTimer 保障顺畅交互

IOS

前言

在之前的文章中,我们了解了RunLoop的基本概念和工作原理,以及如何使用NSTimer在主线程上创建定时器。但在实际开发中,我们经常需要在子线程上使用定时器,比如在后台下载数据、播放音乐等场景。那么,在子线程上使用NSTimer需要注意什么呢?

RunLoop与NSTimer的关系

在子线程上使用NSTimer,就必须开启子线程的RunLoop,否则定时器无法生效。这是因为NSTimer是通过RunLoop来工作的,RunLoop负责管理和调度线程中的任务,包括定时器任务。当RunLoop开启时,它会不断地从待处理事件队列中取出事件并执行,定时器任务就是其中一种事件。因此,如果RunLoop没有开启,定时器任务就无法执行。

RunLoop的多模式

RunLoop同一时间只能运行在一种模式下,当我们滑动tableview/scrollview的时候RunLoop会切换到UITrackingRunLoop模式,此时RunLoop会处理触摸事件,而定时器任务则会被暂停执行。为了保证定时器任务能够在子线程上正常执行,我们需要使用RunLoop的多模式特性。

RunLoop有三种模式:

  • Default RunLoop Mode: 这是RunLoop的默认模式,它主要处理普通事件,比如定时器任务、网络请求等。
  • UITracking RunLoop Mode: 这种模式主要处理触摸事件,当用户在屏幕上滑动、点击等操作时,RunLoop会切换到此模式。
  • Custom RunLoop Mode: 我们可以自定义RunLoop的模式,以便在不同的场景下处理不同的事件。

在子线程上使用NSTimer

为了在子线程上使用NSTimer,我们需要手动开启子线程的RunLoop,并指定RunLoop的模式为Default RunLoop Mode。我们可以使用NSThread类来创建子线程,并在子线程中开启RunLoop。代码如下:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
[thread start];

- (void)threadMain {
    // 开启RunLoop
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    // 指定RunLoop模式为Default RunLoop Mode
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    // 启动RunLoop
    [runLoop run];
}

在子线程的RunLoop开启后,我们就可以使用NSTimer来创建定时器了。代码如下:

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFire:) userInfo:nil repeats:YES];

这样,定时器就会在子线程上每隔1秒执行一次timerFire:方法。

结语

通过这篇文章,我们了解了RunLoop的多模式特性,以及如何在子线程上使用NSTimer。在实际开发中,我们可以根据不同的场景选择不同的RunLoop模式,以便保证应用程序的流畅性和响应性。