什么是RunLoop
一般来说,一个线程一次只能执行一个任务,执行完成后线程就会退出。就比如之前学OC时使用的命令行程序,执行完程序就结束了。 而runloop,运行循环,可使线程能随时处理时间但并不退出,这也就是手机app在运行时不会退出的原因。当没有事件时,runloop会进入休眠状态,有事件发生时,runloop再进行相应的处理事件。runloop可以让线程在需要做事的时候忙起来,不需要的时候让线程休眠。
runloop基本作用
- 保持程序的持续运行
- 处理app中各种事件
- 节省CPU资源,提高程序性能:该做事时做事,该休眠时休眠。休眠时不占用CPU
runloop实际上是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行下面图片中的逻辑。线程执行了这个函数后,就会处于这个函数内部的循环中,直到循环结束,函数返回。 
获取runloop
iOS中有2套API来访问和使用runloop
- Foundation:NSRunLoop
- Core Foundation:CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的一层oc封装 
苹果不允许直接创建runloop对象,可通过下面的方法获取runloop对象。
NSRunLoop *runloop1 = [NSRunLoop mainRunLoop];
CFRunLoopRef runloop2 = CFRunLoopGetMain();
NSRunLoop *runloop3 = [NSRunLoop currentRunLoop];
CFRunLoopRef runloop4 = CFRunLoopGetCurrent();
下面的打印显示,虽然两种runloop打印的地址不一样,但是33行打印NSRunLoop时显示的地址却是CFRunLoop,也就说明了两种RunLoop之间的关系就如同上面图片一样,NSRunLoop是对CFRunLoop的一层oc封装。 
CFRunLoopGetMain、CFRunLoopGetCurrent的实现如下
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL;
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np());
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
这两个方法其实都是调用了_CFRunLoopGet0,_CFRunLoopGet0的实现如下
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
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());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
- 线程与runloop之间是一一对应的,保存在一个全局的字典中,线程作为key,runloop作为value
- 线程刚创建时并没有runloop,如果不主动获取,那这个线程就一直不会有。
- runloop的创建发生在是第一次获取时,runloop的销毁是发生在线程结束时
主线程的runloop在程序运行启动时就会启动 在main.m函数中,通过UIApplicationMain开启主线程的runloop 
RunLoop结构
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock;
__CFPort _wakeUpPort;
Boolean _unused;
volatile _per_run_data *_perRunData;
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
上述的RunLoop结构也可以简化如下(图的右半部分)  一个runloop对象中,主要包含了一个线程,若干个mode,若干个commonMode,还有一个当前运行的mode
- CFRunLoopModeRef代表runloop的运行模式
- 一个RunLoop包含若干个Mode,每一个Mode又包含若干个Source0/Source1/Timer/Observer(下面有Mode的结构)
- RunLoop启动时只能选择其中一个Mode,作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入
Mode切换不会导致程序退出 不同mode中的Source0/Source1/Timer/Observer能分隔开来,互不影响 - 如果Mode中没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
关于上图中的5个类
- CFRunLoopRef:代表了RunLoop的对象(RunLoop)
- CFRunLoopModeRef:RunLoop的运行模式(Mode)
- CFRunLoopSourceRef:RunLoop模型图中的输入源/事件源(Source)
- CFRunLoopTimerRef:RunLoop模型图中的定时源(Timer)
- CFRunLoopObserverRef:观察者,能够监听RunLoop的状态变化
CFRunLoopModeRef
CFRunLoopModeRef的结构简化如下
struct __CFRunLoopMode {
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
Mode实际上是Source,Timer 和 Observer 的集合,不同的Mode把不同组的Source、timer、Observer隔绝开来。runloop在某一时刻只能运行在一个mode下,处理这一个mode中的source、timer、observer。
五种Mode
- kCFDefaultRunLoopMode:App默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- UIInitializationRunLoopMode:在刚启动App时第一个mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
- GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
- KCFRunLoopCommonModes:并不是一种模式
只是一个标记,当mode标记为common时,将mode添加到runloop中的_commonModes中。runloop中的_commonModes实际上是一个Mode的集合,可使用CFRunLoopAddCommonMode()将Mode放到CommonModes中。 每当RunLoop的内容发生变化时,RunLoop都会将_commonModeItems里的同步到具有Common标记的所有的Mode里。
RunLoop中包含多个Mode,但只有一个作为当前Mode 
对于CGRunLoopModeRef,常见的两种mode
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
- UItrackingRunLoopMode:界面追踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
Mode中的Source、Observer、Timer三种,他们统称为ModeItem。 下面分别介绍对应的类
CFRunLoopSourceRef
根据官方描述,CFRunLoopSourceRef是input sources的抽象。 CFRunLoopSource分为Source0和Source2两个版本。 它的结构如下
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order;
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0;
CFRunLoopSourceContext1 version1;
} _context;
};
Source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行时,必须要先把它标为signal状态,以下是source0结构体
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);
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
source1结构体
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_OSX || 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;
- source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时要先调用 CFRunLoopSourceSignal(source),将这个source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop) 来唤醒RunLoop,让其处理这个事件
- Source1包含了mach_port和一个回调(函数指针),Source1可以监听系统端口,通过内核和其他线程通信,接收、分发系统事件,他能主动唤醒RunLoop(由操作系统内核进行管理)
CFRunLoopTimerRef
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval;
CFTimeInterval _tolerance;
uint64_t _fireTSR;
CFIndex _order;
CFRunLoopTimerCallBack _callout;
CFRunLoopTimerContext _context;
};
- CFRunLoopTimer是基于时间的触发器,其包含一个时间长度、一个回调(函数指针)。当其加入runloop时,runloop会注册对应的时间点,当时间点到时,runloop会被唤醒以执行那个回调
- CFRunLoopTimer和NSTimer是toll-free bridged(对象桥接),可以相互转换
CFRunLoopObserverRef
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities;
CFIndex _order;
CFRunLoopObserverCallBack _callout;
CFRunLoopObserverContext _context;
};
CFRunLoopObserverRef是观察者可以观察Runloop的各种状态,每个Observer都包含了一个回调(函数指针),当runloop的状态发生变化时,观察者就能通过回调接收到这个变化。
RunLoop的状态有6种 如下
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
创建监听RunLoop的observer(其中一个方法,比较简单) 
当我们父视图上创建一个ScrollerView,创建Observer监听runloop。 滑动ScrollerView从开始到结束的过程中,runloop的mode切换以及runloop状态(仅进入runloop、退出runloop状态)如下

以上的Source、Timer、Observer被统称为mode item,一个item可以被同时加入多个Mode中,但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则runloop会直接退出。
RunLoop的内部逻辑

void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
if (__CFRunLoopModeIsEmpty(currentMode)) return;
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(runloop, currentMode);
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
__CFRunLoopDoBlocks(runloop, currentMode);
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
mach_msg(msg, MACH_RCV_MSG, port);
}
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
handle_msg:
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
else {
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);
}
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
实际上RunLoop就是这样一个函数,其内部就是一个do-while循环,当调用CF RunLoopRun()时,线程就会一直停留在这个循环里,直到超时或被手动停止,该函数才会被返回。
RunLoop回调
- 当App启动时,系统会默认注册五个上面说过的5个mode
- 当RunLoop进行回调时,一般都是通过一个很长的函数调出去(call out),当在代码中加断点调试时,通常能在调用栈上看到这些函数。这就是runloop的流程:
{
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
mach_msg() -> mach_msg_trap();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
} while (...);
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
RunLoop休眠的实现原理  从用户态切换到内核态,在内核态让线程进行休眠,有消息时唤起线程,回到用户态处理消息
RunLoop在实际开发中的应用
- 控制线程生命周期(线程保活)
- 解决NSTimer在滑动时停止工作的问题
- 监控应用卡顿
- 性能优化
解决NSTimer在滑动时停止工作的问题

创建timer使用了带有scheduledTimer的方法,创建的timer是在runloop默认模式下,也就是NSDefaultRunLoopMode。 当拖动模拟机上的scrollView时,定时器就会失效,停止拖动,定时器恢复。说明定时器并不在UITrackingRunLoopMode模式(mode)下。 只需要将这个timer也添加到UITrackingRunLoopMode模式下就可以正常工作
改为这个样子就可以了 
timer的另一种创建方法 
线程保活
平时创建子线程时,线程上的任务执行完这个线程就会销毁掉。 有时我们会需要经常在一个子线程中执行任务,频繁的创建和销毁线程就会造成很多的开销,这时我们可以通过runloop来控制线程的生命周期
RunLoop启动方法
三种启动RunLoop的方法
- run,无条件
- runUntilDate, 设置时间限制
- runMode:before:Date:,在特定模式下
对于上面三种方法,文档中的总结如下
- 第一种方法,无条件地进入运行循环是最简单的选项,但也是最不理想的选择。无条件地运行runloop将线程放入永久循环,这使您无法控制运行循环本身。停止runloop的唯一方法是杀死它。也没有办法在自定义模式下运行循环。
- 第二种设置了超时时间,超过这个时间runloop结束,优于第一种
- 相对比较好的方式,可以指定runloop以哪种模式运行
实际上run方法的实现就是无限调用runMode:before:Date:方法 runUntilDate:也会重复调用runMode:before:Date:方法,区别在于它超时就不会再调用
RunLoop关闭方法
在处理事件之前,有两种方法可以让运行循环退出:
这里需要注意,虽然删除runloop的输入源、定时器可能会导致运行循环的退出,但这并不是个可靠的方法,系统可能会添加一些输入源到runloop中,但在我们的代码中可能并不知道这些输入源,因此无法删除它们,导致无法退出runloop。
我们可以通过2、3方法来启动runloop,设置超时时间。但是如果需要对这个线程和它的RunLoop有最精确的控制,而并不是依赖超时机制,这时我们可以通过 CFRunLoopStop() 方法来手动结束一个 RunLoop。 但是 CFRunLoopStop() 方法只会结束当前的runMode:beforeDate: 调用,而不会结束后续的调用
在下面的代码中,因为runMode:beforeDate:方法是单次调用,我们需要给它加上一个循环,否则调用一次runloop就结束了,和不使用runloop的效果一样
这个循环的条件默认设置成yes,当调用stop方法中,执行CFRunLoopStop() 方法结束本次runMode:beforeDate:,同时将循环中的条件设置为NO,使循环停止,runloop退出。
#import "SecondViewController.h"
#import "FTThread.h"
@interface SecondViewController ()
@property (nonatomic, strong) FTThread *thread;
@property (nonatomic, assign) BOOL stopped;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor greenColor];
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.view addSubview:button];
[button addTarget:self action:@selector(pressPrint) forControlEvents:UIControlEventTouchUpInside];
[button setTitle:@"执行任务" forState:UIControlStateNormal];
button.frame = CGRectMake(100, 200, 100, 20);
UIButton *stopButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[self.view addSubview:stopButton];
[stopButton addTarget:self action:@selector(pressStop) forControlEvents:UIControlEventTouchUpInside];
[stopButton setTitle:@"停止RunLoop" forState:UIControlStateNormal];
stopButton.frame = CGRectMake(100, 400, 100, 20);
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.thread = [[FTThread alloc] initWithBlock:^{
NSLog(@"ft新线程");
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"end");
}];
[self.thread start];
}
- (void)pressPrint {
[self performSelector:@selector(print) onThread:_thread withObject:nil waitUntilDone:NO];
}
- (void)print {
NSLog(@"%s, %@", __func__, [NSThread currentThread]);
}
- (void)pressStop {
if (_stopped == NO ) {
[self performSelector:@selector(stop) onThread:_thread withObject:nil waitUntilDone:YES];
}
}
- (void)stop {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s, %@", __func__, [NSThread currentThread]);
self.thread = nil;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self pressStop];
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)dealloc {
NSLog(@"%s", __func__);
}
@end
参考 线程保活
|