概念
一般来说线程执行完任务就会退出,而runloop作用就在于,线程一直在处理事件并且不会退出。
要实现runloop这种模式关键点就在于怎么管理事件/消息,让线程在没有处理消息时休眠以避免资源占用,在有消息到来的时候立刻被唤醒。
runloop就是一个可以管理需要处理的消息与事件的一个对象,CFRunloopRef可以理解为CoreFoundation框架内的NSRunLoop,它提供了纯C函数的API,但是它的API都是线程安全的,而NSRunLoop却不是线程安全的。
RunLoop和线程的关系
两者是一一对应的关系,app启动之后,程序进入主线程,Apple帮我们在主线长启动了一个RunLoop,如果我们开辟新的线程,默认是不开启RunLoop的,需要我们手动开启。子线程的RunLoop是懒加载形式的。
NSThread是pthread_t的OC层面封装。
下面我们看一下CoreFoundation中获取CFRunLoop的核心逻辑:(这个方法只有调用:
NSRunLoop currentRunLoop
CFRunLoopGetCurrent()
类似方法时才执行
)
objc
//根据一个pthread线程,获取其对应的RunLoop,如果还没有就创建一个,并保存到全局字典中
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {//如果传入空线程就默认获取主线程的RunLoop
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {//如果全局字典还没创建就先创建
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
kCFAllocatorSystemDefault,
0,
NULL,
&kCFTypeDictionaryValueCallBacks
);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());//不管咋样先创建主线程RunLoop,系统优先将它放进全局管理表中
CFDictionarySetValue(
dict,
pthreadPointer(pthread_main_thread_np()),
mainLoop
);//dict[主线程] = 主线程RunLoop;
//如果当前线程失败了,说明别的线程已经设置成功了,所以自动释放
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);//字典已经持有了mainLoop,所以释放自己的那一份
__CFLock(&loopsLock);
}
//从全局字典中查找当前线程的RunLoop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(
__CFRunLoops,
pthreadPointer(t)
);
__CFUnlock(&loopsLock);
//如果没找到就创建新的RunLoop
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
//双重检查,防止当前线程没有持有锁的时候有别的线程创建了这个线程的runloop,并且放进了字典中
loop = (CFRunLoopRef)CFDictionaryGetValue(
__CFRunLoops,
pthreadPointer(t)
);
//真正执行插入字典
if (!loop) {
CFDictionarySetValue(
__CFRunLoops,
pthreadPointer(t),
newLoop
);
loop = newLoop;
}
__CFUnlock(&loopsLock);
CFRelease(newLoop);//释放newloop,分两种情况。第一种就是newloop被存放进入字典了,字典会持有强引用,释放是合理的。第二种情况就是newloop还没有被放入字典,那么这个newloop就是多余的,也应该释放掉
}
if (pthread_equal(t, pthread_self())) {//如果当前函数获取的实现当前线程自己的RunLoop,就做一些额外的处理
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);//将RunLoop存入线程本地存储,便于以后线程的直接获取
//设置runloop声明周期清理计数器:当线程结束时,系统会调用TSD析构函数
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {//取出一个RunLoop清理计数器,如果是0,说明当前线程还没有设置过runloop的退出清理逻辑
_CFSetTSD(
__CFTSDKeyRunLoopCntr,
(void *)(PTHREAD_DESTRUCTOR_ITERATIONS - 1),//线程退出时,线程本地存储的析构函数最多会被反复调用多少轮
(void (*)(void *))__CFFinalizeRunLoop//线程销毁时,清理该线程对应的RunLoop资源
);
}
}
return loop;
}
RunLoop对外接口
在CoreFoundation中,关于RunLoop有五个类:
objc
CFRunLoopRef // RunLoop 本体
CFRunLoopModeRef // RunLoop 的运行模式
CFRunLoopSourceRef // 输入源 / 事件源
CFRunLoopTimerRef // 定时器
CFRunLoopObserverRef // 观察者

每一个runloop中包含若干个Mode,每一个Mode包含若干个Source、Obserser、Timer
mode(运行模式)
一个RunLoop可以包含多个 Mode,但 RunLoop 一次只能运行在一个 Mode 下。这个Mode成为CurrentMode,如果需要切换Mode,需要先退出Loop,指定一个Mode进入,这是为了分隔开不同组的Source/Timer/Observer,让其互不影响。
objc
struct __CFRunLoopMode {
CFStringRef _name; // Mode 名称
CFMutableSetRef _sources0; //非基于Port的事件源,特点:不会主动唤醒RunLoop(需要别人唤醒),需要手动标记为待处理。 手动还从RunLoop然后RunLoop才会处理它
CFMutableSetRef _sources1; //基于 Port 的事件源,通常和 Mach Port 有关。可以主动唤醒RunLoop,由内核消息驱动,RunLoop可以睡眠等待Source1的消息
CFMutableArrayRef _timers;//保存当前Mode下的所有定时器
CFMutableArrayRef _observers;//监听当前RunLoop,观察RunLoop正走到哪个阶段
};
苹果提供的几种Mode如下:
- 常见iOS/CF/F中:
| Mode 名称 | 所属层级 | 是否真实 Mode | 作用 | 典型场景 |
|---|---|---|---|---|
kCFRunLoopDefaultMode |
Core Foundation | 是 | 主线程空闲时默认运行模式 | C 层 RunLoop 默认 Mode。 |
NSDefaultRunLoopMode |
Foundation | 是 | 主线程空闲时默认运行模式 | OC 层默认 Mode,本质对应 kCFRunLoopDefaultMode,处理常规任务(Timer、网络回调等) |
UITrackingRunLoopMode |
UIKit | 是 | UI 跟踪模式,处理滚动、触摸等交互事件 | 滚动、拖拽、触摸追踪时自动激活,优先保证流程性 |
kCFRunLoopCommonModes |
Core Foundation | 否 | 通用模式,包含default和Tracking | C 层把 Source / Timer / Observer 加入常用 Mode 集合 |
NSRunLoopCommonModes |
Foundation | 否 | 通用模式,包含default和Tracking | OC 层常用,用来解决 Timer 滑动时不触发问题 |
- iOS内部常见:
| Mode 名称 | 是否公开推荐使用 | 作用 | 说明 |
|---|---|---|---|
UIInitializationRunLoopMode |
否 | App 启动初始化阶段使用 | 启动完成后一般不会继续使用 |
GSEventReceiveRunLoopMode |
否 | 接收系统事件 | 和底层 GraphicsServices 事件分发有关,系统内部事件,开发者无需手动调用 |
UIApplicationLaunchOptionsRemoteNotificationKey 相关启动过程中的临时 Mode |
否 | 启动阶段事件处理 | 属于系统内部调度细节 |
com.apple.main-thread 相关内部 Mode |
否 | 系统内部主线程调度 | 调试时可能看到,不作为业务使用 |
当我们程序运行而画面静止,它处于kCFRunLoopDefaultMode,如果进行滚动,自动切换到UITrackingRunLoopMode模式,如果我们需要定时器在两种情况下都生效,则需要手动的设置到NSRunLoopCommonModes状态。
objc
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(nextPage) userInfo:nil repeats:YES];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[loop addTimer:self.timer forMode:NSRunLoopCommonModes];
Observer(观察者)
可以监听RunLoop的工作状态,什么时候休眠,什么时候被唤醒,通过对应状态通知系统执行对应工作。
objc
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入 Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出 Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 所有的状态
};
其中的Timer和Source就是runloop需要完成的任务
Mach Port 是 macOS / iOS 内核提供的一种"进程/线程间通信入口",RunLoop 底层就是通过等待这些 Port 上的消息来实现休眠与唤醒的。
iOS的内核是基于Mach的,Mach提供了一套IPC(进程间通信机制)
当线程睡眠时,等待某些Mach Port上出现消息,当发现又消息时,内核会唤醒这个线程,RunLoop继续往下执行
| 类型 | 是否基于 Mach Port | 能否主动唤醒 RunLoop | 例子 |
|---|---|---|---|
Source0 |
否 | 不能 | 自定义事件、performSelector 某些场景 |
Source1 |
是 | 能 | Mach Port 消息、系统事件、线程间通信(有消息发送到Port时,内核会唤醒等待这个port的线程来处理) |
Mach Port 是 iOS / macOS 内核层的消息通信端口,RunLoop 在休眠时会进入内核等待相关 Port 的消息。Source1 收到 Mach Port 消息可以直接唤醒 RunLoop;Timer 到时间会让 RunLoop 从定时等待中返回;CFRunLoopWakeUp 是人为把睡眠中的 RunLoop 叫醒;GCD 主队列任务会通知主线程 RunLoop 醒来执行 block;而超时则是 RunLoop 等待时间到了,即使没有新事件也要返回。
子线程保活
objc
@property (nonatomic, strong) NSCondition *portCondition;
@property (nonatomic, assign) BOOL portReady;
@property (nonatomic, strong)NSMachPort* port;
@property (nonatomic, strong)NSThread* thread;
- (void)start;
- (void)sendDemoMessage;
- (void)start {
self.portCondition = [[NSCondition alloc] init];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadMain) object:nil];
[self.thread start];
}
- (void)threadMain {
@autoreleasepool {
self.port = [NSMachPort port];
self.port.delegate = self;
[[NSRunLoop currentRunLoop] addPort:self.port forMode:NSDefaultRunLoopMode];
[self.portCondition lock];
self.portReady = YES;
[self.portCondition signal];
[self.portCondition unlock];
NSLog(@"MachPort 子线程 RunLoop 已启动: %@", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] run];
}
}
通过向当前线程的RunLoop中添加一个基于Mach Port的输入源,使得Runloop能够进入休眠等待状态,不会因为没有任务直接退出
然后通过实现代理方法接收Port消息:(当 Port 收到消息时,RunLoop 会被唤醒,然后执行这个方法)
objc
- (void)handleMachMessage:(void *)msg {
mach_msg_header_t *header = (mach_msg_header_t *)msg;
NSLog(@"收到 Mach 消息,当前线程:%@", [NSThread currentThread]);
NSLog(@"message id: %u, size: %u", header->msgh_id, header->msgh_size);
}
接下来就是发送消息给这个Port:
objc
- (void)sendDemoMessage {
[self.portCondition lock];
while (!self.portReady) {
[self.portCondition wait];
}
[self.portCondition unlock];
NSPort *replyPort = [NSMachPort port];
NSMutableArray *components = [NSMutableArray array];
NSData *textData = [@"hello from main thread" dataUsingEncoding:NSUTF8StringEncoding];
[components addObject:textData];
BOOL success = [self.port sendBeforeDate:[NSDate dateWithTimeIntervalSinceNow:1] msgid:1001 components:components from:replyPort reserved:0];
NSLog(@"主线程发送 Mach 消息%@,当前线程:%@", success ? @"成功" : @"失败", [NSThread currentThread]);
}
timer
基于时间的触发器,包含一个时间长度和一个回调,当其加入到runloop时,runloop会注册对应的时间点,当时间点到时,runloop会被唤醒以执行对应回调。(其实就是在对应秒数间隔的时候,将需要完成的任务加入到任务列表)
source
source 是 RunLoop 的事件源抽象,不是数据类,也不是 Objective-C 协议;它表示 RunLoop 可以从哪里接收事件,并在事件到来时执行对应回调。NSMachPort 加入 RunLoop 后,本质上就是给 RunLoop 添加了一个基于端口的 Source1。
源码中定义了两种Source:
- source0 是 RunLoop 中"非基于端口的事件源",主要处理 App 内部逻辑事件。它本身不能靠内核消息自动唤醒 RunLoop,通常需要 App 或框架主动标记并唤醒 RunLoop。
objc
typedef struct {
CFIndex version;
void * info;//传给source的自定义数据,如果我们希望回调中能拿到某个对象、结构体、任务队列,就可以将其放到info中
const void *(*retain)(const void *info);//当source需要持有info时调用,如果传的是普通C指针,或者自己管理生命周期的话,可以设为null
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);//source的描述信息
Boolean (*equal)(const void *info1, const void *info2);//判断两个source的info时都相等
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);//当这个 Source 被加入某个 RunLoop 的某个 mode 时,会调用 schedule,用于记录source被添加到了哪个线程的RunLoop上
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);//Source 从某个 RunLoop 的某个 mode 中移除时调用,主要用作一些清理工作
void (*perform)(void *info);//source被触发时调用
} CFRunLoopSourceContext;
CFRunLoopSourceContext说白了就是告诉RunLoop这个source0怎么保存数据,怎么加入,怎么移除,被触发时具体执行啥。
Source1:负责把系统事件从外部送进 App,并唤醒 RunLoop
Source0:负责 App 内部进一步分发和处理这个事件
使用示例
objc
#import "CustomRunLoopSourceDemo.h"
@interface CustomRunLoopSourceDemo ()
@property (nonatomic, strong) NSThread *workerThread;
@property (nonatomic, strong) NSCondition *condition;
// 子线程对应的 RunLoop。主线程后面需要用它来 wake up。
@property (nonatomic, assign) CFRunLoopRef workerRunLoop;
// 自定义的 RunLoop Source。主线程通过 signal 它来制造"事件到来"。
@property (nonatomic, assign) CFRunLoopSourceRef source;
// 用来标记 source 是否已经创建完成,避免主线程过早触发。
@property (nonatomic, assign) BOOL sourceReady;
- (void)performSourceTask;
@end
static void CustomSourcePerformRoutine(void *info) {
// C 级别的 RunLoop 回调不能直接捕获 Objective-C 对象,所以这里通过 context.info 把 self 桥接回来。
CustomRunLoopSourceDemo *demo = (__bridge CustomRunLoopSourceDemo *)info;
[demo performSourceTask];
}
@implementation CustomRunLoopSourceDemo
- (void)dealloc {
if (_source) {
CFRelease(_source);
}
if (_workerRunLoop) {
CFRelease(_workerRunLoop);
}
}
- (void)start {
// 条件锁只做一件事:保证主线程触发 Source 之前,子线程已经把 RunLoop 和 Source 都准备好
self.condition = [[NSCondition alloc] init];
// 专门开一个常驻子线程,后续这个线程会一直跑自己的 RunLoop。
self.workerThread = [[NSThread alloc] initWithTarget:self selector:@selector(workerThreadEntry) object:nil];
[self.workerThread start];
}
- (void)workerThreadEntry {
@autoreleasepool {
// 取到"当前子线程"的 RunLoop,并持有一份引用,
// 这样主线程后面可以对它执行 CFRunLoopWakeUp。
self.workerRunLoop = (CFRunLoopRef)CFRetain(CFRunLoopGetCurrent());
CFRunLoopSourceContext context = {0};
// 把 self 塞进 context.info,供 perform 回调时取回。
context.info = (__bridge void *)self;
// 指定 Source 被处理时真正执行的函数。
context.perform = CustomSourcePerformRoutine;
// 创建一个纯手动触发的 Source,它不会像 Timer/Port 那样自动来事件,必须我们自己 signal。
self.source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 把 Source 挂到这个子线程的默认 RunLoop 模式里。
// 只有加入 RunLoop 之后,它才有机会被 RunLoop 调度执行。
CFRunLoopAddSource(self.workerRunLoop, self.source, kCFRunLoopDefaultMode);
[self.condition lock];
self.sourceReady = YES;
[self.condition signal];
[self.condition unlock];
NSLog(@"子线程 RunLoop 和自定义 Source 已就绪: %@", [NSThread currentThread]);
// 启动 RunLoop,线程会在这里进入事件循环。
// 没有事件时它会休眠;被 wake up 或有事件到来时会继续处理。
CFRunLoopRun();
}
}
- (void)triggerFromMainThread {
// 如果子线程的 Source 还没准备好,主线程在这里等待。
[self.condition lock];
while (!self.sourceReady) {
[self.condition wait];
}
[self.condition unlock];
NSLog(@"主线程开始 Signal + WakeUp: %@", [NSThread currentThread]);
// 第一步:signal
// 只是把 source 标记为"待处理",并不会立刻执行回调。
CFRunLoopSourceSignal(self.source);
// 第二步:wake up
// 如果子线程 RunLoop 此时正在休眠,单独 signal 还不够,
// 需要显式唤醒它,这样它才会尽快去处理刚刚被标记的 source。
CFRunLoopWakeUp(self.workerRunLoop);
}
- (void)performSourceTask {
// 当子线程的 RunLoop 发现这个 source 已被 signal 后,
// 就会回调到这里。注意这里执行线程是子线程,不是主线程。
NSLog(@"自定义 Source 回调执行,当前线程:%@", [NSThread currentThread]);
}
@end
//controller中:
self.runLoopSourceDemo = [[CustomRunLoopSourceDemo alloc] init];
[self.runLoopSourceDemo start];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.runLoopSourceDemo triggerFromMainThread];
});
- Source2:由RunLoop内核管理,Mach Port驱动,包含了一个mach_port和一个回调,被用于用过内核和其他线程相互发送消息,能够主动唤醒主线程。
objc
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
source1可以监听系统端口和通过内核和其他线程通信,接受、分发系统事件。(端口通信、线程唤醒、系统事件传递、进程间通信)
RunLoop底层原理
objc
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode,
1.0e10,
false);//stopAfterHandle 为 false,表示处理完一个事件后不退出,而是继续循环
}
//指定mode启动runloop(RunLoop 每次只能运行在一个 Mode 下)
int CFRunLoopRunInMode(CFStringRef modeName,
CFTimeInterval seconds,
Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
modeName,
seconds,
stopAfterHandle);
}
//根据传入的modeName,找到当前runloop中对应的mode对象
int CFRunLoopRunSpecific(CFRunLoopRef runloop,
CFStringRef modeName,
CFTimeInterval seconds,
Boolean stopAfterHandle) {
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop,
modeName,
false);
if (__CFRunLoopModeIsEmpty(currentMode)) {//如果mode中啥也没有,那么runloop没有任何时间可以监听,也没有理由睡眠,会直接返回
return kCFRunLoopRunFinished;
}
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);//通知所有监听了kCFRunLoopRNtry状态的observer,即将进入RunLoop
int result = __CFRunLoopRun(runloop,
currentMode,
seconds,
stopAfterHandle);
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopExit);
return result;
}
//真正循环部分
int __CFRunLoopRun(CFRunLoopRef runloop,
CFRunLoopModeRef currentMode,
CFTimeInterval seconds,
Boolean stopAfterHandle) {
Boolean sourceHandledThisLoop = false;
int retVal = 0;
do {
sourceHandledThisLoop = false;
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);//通知observer即将处理timer
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);//通知即将处理source
__CFRunLoopDoBlocks(runloop, currentMode);//处理一些RunLoop内部维护的block
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop,
currentMode,
stopAfterHandle);//处理source0,这个过程中可能向RunLoop中添加了新的block
__CFRunLoopDoBlocks(runloop, currentMode);//这里需要再处理一次可能添加的新block
//某些source0执行过程中可能间接触发基于端口的消息,发现Mach Port消息准备好了,所以runloop会先尝试一些取消息
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg);
if (hasMsg) {
goto handle_msg;
}
}
//如果本轮没有source0,那么RunLoop准备休眠(主线程的 AutoreleasePool 通常会在这个阶段释放旧池并创建新池)
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
//进入内核等待
__CFRunLoopServiceMachPort(waitSet,
&msg,
sizeof(msg_buffer),
&livePort);
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);//被唤醒,通知observer
handle_msg://RunLoop 醒来后,手里会拿到一个消息,需要先判断消息的类型
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop,
currentMode,
mach_absolute_time());
}
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
else {
//处理source1的任务
CFRunLoopSourceRef source1 =
__CFRunLoopModeFindSourceForMachPort(runloop,
currentMode,
livePort);
sourceHandledThisLoop =
__CFRunLoopDoSource1(runloop,
currentMode,
source1,
msg);
if (sourceHandledThisLoop) {
mach_msg(reply,
MACH_SEND_MSG,
reply);
}
}
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
}
else if (timeout) {
retVal = kCFRunLoopRunTimedOut;
}
else if (__CFRunLoopIsStopped(runloop)) {
retVal = kCFRunLoopRunStopped;
}
else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (retVal == 0);
return retVal;
}
先找到当前 Mode,如果 Mode 里有事件源,就进入循环;每一轮先处理 Source0 和 Blocks,没事就通过 mach_msg 进入睡眠;等 Timer、Source1、GCD 主队列任务或手动唤醒到来后,再醒来处理消息;最后根据是否超时、是否被停止、Mode 是否为空来决定继续还是退出。

RunLoop应用
- autoreleasePool
苹果底层注册了两个Observer,第一个用来监听RunLoop的entry,在这个时候创建一个自动释放池,并提供最高优先级,保证释放池的创建发生在其他回调之前。第二个observer监听两个事件,一个 BeforeWaiting,一个 Exit,BeforeWaiting 的时候,干两件事,先释放旧的池,然后创建一个新的池,所以这个时候,自动释放池就会有一次释放的操作,是在 RunLoop 即将进入休眠的时候。Exit 的时候,再次释放自动释放池,这里也有一次释放的操作
- tableView延时加载图片,保证流畅
用户在快速滑动tableView时,滑动过的图片会一直加载,但滑动过的图片可能并不需要呈现出来,加载会浪费CPU资源,我们可以使用RunLoop的mode避免滑动时加载图片。如下,只允许指定mode情况下才能加载图片:
objc
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName.png"] afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
- 线程保活:前面有过示例代码,减少线程反复退出,频繁创建与销毁线程造成的效率低下。