BAT 批处理程序 2017-05-09
RunLoop是iOS线程相关的比较重要的一个概念,无论是主线程还是子线程,都对应一个RunLoop,如果没有RunLoop,线程会马上被系统回收。
本文主要CFRunLoop的源码解析,并简单阐述一下CFRunLoop的原理。
CFRunLoop是开源的,开源地址在:http://opensource.apple.com/tarballs/CF/
先看一张图,这是主线程的RunLoop调用函数截图:

我们找到相应的CFRunLoop源码:
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
} 可以看到,系统建立了一个do while循环,当状态在stop或者finished时,就会退出循环,RunLoop会结束,线程会被回收。
注:1.0e10,这个表示1.0乘以10的10次方,这个参数主要是规定RunLoop的时间,传这个时间,表示线程常驻。
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
} 先执行:__CFRunLoopFindMode,查找是否有Mode
看代码标注:
/* call with rl locked, returns mode locked */
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
CHECK_FOR_FORK();
CFRunLoopModeRef rlm;
struct __CFRunLoopMode srlm;
memset(&srlm, 0, sizeof(srlm));
_CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
srlm._name = modeName;
rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
if (NULL != rlm) { //如果有则返回
__CFRunLoopModeLock(rlm);
return rlm;
}
if (!create) { //情况2
return NULL;
}
//情况3
rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
if (NULL == rlm) {
return NULL;
}
....//后面为创建一个Mode并赋初始值 __CFRunLoopMode我自己理解为一种运行类型,它表示了当前线程运行在哪种类型下,会被哪种类型的事件唤醒。就好比你在程序中设置一个定时器Timer,运行在DefaultMode下,但是如果你滑动UIScrollview,系统会将当前线程的Mode改为UITrackingRunLoopMode,这时你的Timer就不会得到调用,因为当前线程的Mode和你Timer的Mode不同。当然,如果你想无论在哪种Mode下,Timer都想得到调用的话,你需要将Mode设置为CommonMode。
那么,为什么要这样设计呢?我理解为这样设计更为灵活。你可以指定事件需要在当前RunLoop是什么Mode的时候被调用,跟上面举的例子一样。
系统提供了对应于__CFRunLoopMode的五种类型到NSRunloopMode中:
我们经常在代码中使用的是NSDefaultRunLoopMode和NSRunLoopCommonModes。你也可以使用自定义的Mode。
__CFRunLoopMode的结构体中包含了:Source0,Source1,Observers,Timers,Ports等。
Source0:处理App内部事件,如UIEvent、CFSocket,对应(CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION)这个函数。
Source1:由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort。对应(CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION)这个函数。
Observers:主要负责修改RunLoop的状态。状态包括:
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
}; Timers:负责让App响应NSTimers,延迟的perform事件,对应(CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION)这个函数。我们可以在viewDidLoad里面加入如下代码:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:target:self selector:@selector(test) userInfo:nil repeats:YES];
[timer fire];
- (void)test{
NSLog(@"abc");
} 可以得到如下图:

这里简单介绍下DoTimers和DoTimer两个函数。DoTimers是一个for循环,在for循环里面调用DoTimer。然后调用CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION,这个函数代码很简单,就是调用NSTimer或者Perform的回调。
Ports:port是用来做进程或者线程间通信的,分为CFMachPort, CFMessagePort, CFSocketPort,详细介绍可以看 这篇文章
再接着代码往下看:
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
_per_run_data是用来描述当前CFRunLoop的状态的,有三种状态:初始状态,wake,stop三种。注意到 volatile 这个关键字,它的意思是告诉编译器不要优化这个变量,要每次都从内存中读取该变量。
接着往下:
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
通知观察者开始进入RunLoop。主要是通过(CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION)这个函数来完成。
接下来就要进入__CFRunLoopRun这个核心函数了。这个函数比较复杂,跟port相关的就忽略不讲了。
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
} else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
} else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
} 这一段逻辑比较清晰,使用GCD建立一个定时任务,在指定的时间内,使用__CFRunLoopTimeOut唤醒RunLoop。因为使用了DISPATCH_TIME_FOREVER,所以__CFRunLoopTimeOut只会调用一次。
__CFRunLoopDoBlocks(rl, rlm); //执行RunLoop中的block
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
//执行Mode里面的Source0的事件,如perform,uievent等//不知道为什么__CFRunLoopDoBlocks要执行两次 后续会有两次状态的改变:kCFRunLoopBeforeWaiting和kCFRunLoopAfterWaiting,接着就是跟port相关的了,就不写了。
最后一段:
if (sourceHandledThisLoop && stopAfterHandle) {
// 进入loop时参数说处理完事件就返回。
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)) {
// source/timer/observer一个都没有了
retVal = kCFRunLoopRunFinished;
} 总体来讲,RunLoop比较基础但是也是比较复杂,在阅读源码的过程中也遇到不少疑惑,有些疑惑可能需要在后续的研究中才能慢慢发现答案。