RunLoop是一个与线程相关的非常基础的功能,正如名字所说的那样它是一个事件处理循环,用于调度和和协调接收到的事件,RunLoop的目的就是让线程在有任务的时候busy,在没任务的时候sleep。
RunLoop的管理不是全部自动的,你必需在适当的时机让你的线程启动RunLoop以及响应事件。Cocoa和Cocoa Foundation提供了RunLoop对象用来管理线程的RunLoop,你的应用不必显示地创建RunLoop对象,每个线程包括主线程都有一个关联的RunLoop对象,只有副线程需要显示地运行RunLoop(主线程的创建运行都是app框架自动完成的)。下面主要从RunLoop的构成、创建、运行、模式、事件执行顺序、添加事件源、停止方面来讲解。
RunLoop解剖
RunLoop正如它的名字一样是个循环,主要处理异步输入事件。你的代码中提供了while或者for循环来驱动RunLoop,其伪代码如下:
- (void)runLoop
{
init();
do{
//接收输入事件
var message = getNextMessage();
//处理事件
HandeleMessage(message);
}while (condition);
}
RunLoop处理两类事件,一类为输入事件(异步投递,经常来自其他线程),输入事件主要包括端口(网口、蓝牙)、自定义、performSelector;另一类为定时器(同步投递,产生于内部时钟调度)。另外为了处理输入源,RunLoop也会在某些时刻发送一些通知,让观察者们进行一些额外的处理,RunLoop的整体结构如下图所示:

RunLoop创建
Cocoa框架不能显示地创建RunLoop对象,只需掉用相关API获取即可,示例代码如下:
//获取当前线程的RunLoop(相当于创建RunLoop)
[NSRunLoop currentRunLoop];
//获取主线程的RunLoop
[NSRunLoop mainRunLoop];
RunLoop运行
NSRunLoop类提供了3个API启动RunLoop,其本质都是调用- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;示例代码如下:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
RunLoop运行模式
RunLoop Mode是一个用来监听输入源和Time以及用于通知观察者的集合,每当你运行你的RunLoop时需要指定一个特定的Mode,一个RunLoop有多个Mode。只有在相关联的Mode下输入源和Timer才会被监听,一般事件都运行在NSDefaultRunLoopMode下,详细的模式及说明见下图所示:

这里需要注意的地方是:一般在主线程添加timer默认都运行在NSDefaultRunLoopMode下,而用户操作事件比如滑动ScrollView时RunLoop会优先运行在NSEventTranckingRunLoopMode下,就会导致Timer不运行。解决办法:把Timer添加到NSRunLoopCommonModes下。
RunLoop事件顺序
每次运行RunLoop,RunLoop都会处理挂起事件和给相关的Observer产生通知,每一个步骤都是特定的,执行顺序如下图所示:

RunLoop添加事件源
创建好RunLoop后,需要至少向RunLoop添加一个待监听的事件,否则RunLoop会直接退出。AFNetworing的RunLoop中就添加了无用的事件,主要目的就是不让RunLoop直接退出,具体代码如下:
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//添加一个监听事件,防止RunLoop直接退出
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
RunLoop退出
RunLoop有2种方法退出,分别为:
- 配置RunLoop的超时时间到,自动退出
- 显示地退出RunLoop
如果你能管理RunLoop的话优先选用配置超时时间这种方法,因为这种方法可以让RunLoop正常地退出(在退出前可以给Observer发送相关通知),这两种方法的示例代码如下:
//超时退出RunLoop
NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:10];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate];
//显示地退出RunLoop
CFRunLoopRef currentRunLoopRef = [[NSRunLoop currentRunLoop] getCFRunLoop];
CFRunLoopStop(currentRunLoopRef);