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比较基础但是也是比较复杂,在阅读源码的过程中也遇到不少疑惑,有些疑惑可能需要在后续的研究中才能慢慢发现答案。