RunLoop是一个运行循环,保证App能够持续运行,处理各种事件,节省CPU资源,没事处理的时候就进入休眠。简单的RunLoop机制如下:
int main(int argc, const char * argv[]) {
@autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));//这行代码会默认创建一个runloop,循环做一些事情,使程序能够持续运行下去大概类似于下面代码实现:
int retVal = 0;
do {
//睡眠中等待消息
int message = sleep_and_wait();
//处理消息
retVal = process_message(message);
} while (0 == retVal);
}
return 0;
}
NSRunLoop是iOS的Foundation对象,CFRunLoopRef是c语言的对象,两个都是一样的。NSRunLoop是对CFRunLoopRef的封装。
runloop与线程的关系
- 每一条线程都有唯一的一个与之对应的RunLoop
[NSRunLoop currentRunLoop];//获取当前线程的RunLoop,如果当前线程没有,那会新建一个。
- 主线程的RunLoop是默认创建好并开启的,子线程的RunLoop默认是没有的,RunLoop会在第一次获取它时创建,然后手动开启。
- RunLoop是保存在一个全局的字典里,线程作为Key,RunLoop作为Value。
- RunLoop会在线程结束时销毁。
RunLoop本质
RunLoop的底层原理是这样一个结构体:
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
}
每一个RunLoop里面对应只有一个CurrentMode,每个Mode是长这样:
struct __CFRunLoopMode {
CFString _name;
CFMutableSetRef _source0;//点击事件,刷新UI界面,performSelector:onThread:等
CFMutableSetRef _cource1;//系统内部的事件,基于Port的线程通信,系统事件捕捉等
CFMutableArrayRef _observers;//监听器,UI刷新
CFMutableArrayRef _timers;//定时器
}
一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
RunLoop启动时只能选择其中一个Mode,作为CurrentMode
如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入。
这样的好处是,不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响。
常见的2种Mode
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于Scrollview追踪触摸滑动,保证界面滑动时不受其他Mode影响。
RunLoop执行流程
RunLoop执行源码
//通知Observers:进入RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// 具体要做的事情
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// 通知Observers:退出RunLoop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
int32_t retVal = 0;
do {
//通知Observers:即将处理Timers
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知Observers:即将处理Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
//处理Source0
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
//处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//判断有无Source1
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//如果有Source1
goto handle_msg;
}
didDispatchPortLastTime = false;
//通知Observers:即将休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//休眠
__CFRunLoopSetSleeping(rl);
do {//等待别的消息来唤醒当前线程,即等待Source1唤醒线程
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
__CFRunLoopUnsetSleeping(rl);
//通知Observers:结束休眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
else if (被Timer唤醒) {
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
}
else if (被GCD唤醒) {
// 处理GCD相关的事情
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {
//被Source1唤醒
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
}
//处理Blocks
__CFRunLoopDoBlocks(rl, rlm);
//设置返回值
if (sourceHandledThisLoop && stopAfterHandle) {
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
return retVal;
}
RunLoop在实际开发中的应用
控制线程的生命周期(线程保活)
解决NSTimer在滑动时停止工作的问题
监控应用卡顿
性能优化
面试题
RunLoop如何响应用户操作?具体流程是怎样的?
首先由Source1捕捉到这个事件,即用户点击屏幕的时候,这个事件会交给Source1处理,Source1会把这个事件包装成事件队列Event Queue,然后由Source0处理
RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息休息
实现线程阻塞的方式
- while(1)循环
- RunLoop休眠
RunLoop如何做到休眠?
通过调用mach_msg给内核发送消息,达到真正休眠的目的,不干事情,不占用CPU资源