RunLoop基础

RunLoop是让线程能随时处理事件但不退出的机制。RunLoop 实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 “接受消息->等待->处理” 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

在主线程中,Main RunLoop直接配合任务的执行,负责处理UI时间、定时器以及其他相关时间。当自己启动一个线程,如果只是用于处理单一的事件,则该线程在执行完之后就退出了。所以当我们需要让该线程监听某项事务时,就得让线程一直不退出,RunLoop就是这么一个循环,没有事件源的时候,一直休眠;有事件源进来的时候就处理事件。

特点和作用

  1. 当有事件发生时,RunLoop会根据具体的事件类型通知应用程序作出响应;
  2. 当没有事件发生时,RunLoop会进入休眠状态,从而达到省电的目的;
  3. 当事件再次发生时,RunLoop会被重新唤醒,处理事件,调用解耦,节约了CPU时间。

什么时候使用RunLoop?

当需要和该线程进行交互的时候才会使用RunLoop.比如,在一个单独的线程中需要长久的监测某个事件。

怎么用?

添加事件源:

每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。RunLoop接收两种源事件:input sourcestimer sources

runloopMode

当RunLoop即将启动或已经启动通知观察者之前,RunLoop还要找到对应的Mode,以及判断Mode里面有没有source/timer/observer,如果没有就会直接返回。

  • CFRunLoopModeRef 代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer;
  • 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
  • 如果需要切换Mode,只能先退出Loop,再重新指定一个Mode进入,每一个Mode都是相互独立的;
  • [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象。
循环驱动:

RunLoop,是线程进入和被线程用来响应事件以及调用事件处理函数的地方.需要在代码中使用控制语句实现RunLoop的循环,也就是说,需要代码提供while或者for循环来驱动RunLoop.在这个循环中,使用一个RunLoop对象执行接收消息,调用对应的处理函数.

Runloop常见的获取方式

Core Foundation
1
2
CFRunLoopGetCurrent(); //获得当前线程的RunLoop对象
CFRunLoopGetMain(); //获得主线程的RunLoop对象
Foundation
1
2
[NSRunLoop currentRunLoop]; //获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; //获得主线程的RunLoop对象

常见用法和示例

定时器
1
2
NSTimer *timer = [NSTimer timerWithTimeInterval:3.0 target:self selector:@selector(goOn) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

在前面我们讲到有五种模式,其中NSRunLoopCommonModes不是一种真正的模式,它就是一种标记,表示如果其它四种之一被标记为CommonModes,就会在该模式下工作。在主线程RunLoop中kCFRunLoopDefaultModeUITrackingRunLoopMode这两个 Mode 都已经被标记为”Common”属性,所以该代码在这两种模式下都能正常工作。此时你无论怎样拖拽tableview,定时器都会正常工作。

添加事件源启动runloop
1
2
3
4
5
//1.给RunLoop对象增加一个port,相当于增加一个Source,看到这如果你对AFNetworking有一定的了解的话,你会发现该作者就是这样干的
[[NSRunLoop currentRunLoop]addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

//2.让RunLoop跑起来
[[NSRunLoop currentRunLoop]run];