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

  1. 如果有则返回找到的。
  2. 如果没有,且不需要创建,则返回NULL。
  3. 如果没有,需要创建,则新建一个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, , 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是什么

__CFRunLoopMode我自己理解为一种运行类型,它表示了当前线程运行在哪种类型下,会被哪种类型的事件唤醒。就好比你在程序中设置一个定时器Timer,运行在DefaultMode下,但是如果你滑动UIScrollview,系统会将当前线程的Mode改为UITrackingRunLoopMode,这时你的Timer就不会得到调用,因为当前线程的Mode和你Timer的Mode不同。当然,如果你想无论在哪种Mode下,Timer都想得到调用的话,你需要将Mode设置为CommonMode。

那么,为什么要这样设计呢?我理解为这样设计更为灵活。你可以指定事件需要在当前RunLoop是什么Mode的时候被调用,跟上面举的例子一样。

系统提供了对应于__CFRunLoopMode的五种类型到NSRunloopMode中:

  1. NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
  2. NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
  3. NSModalPanelRunLoopMode,OS X的Modal面板事件
  4. UITrackingRunLoopMode,拖动事件
  5. NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式

我们经常在代码中使用的是NSDefaultRunLoopMode和NSRunLoopCommonModes。你也可以使用自定义的Mode。

__CFRunLoopMode包含了什么及其作用

__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 << ),
kCFRunLoopBeforeTimers = (1UL << ),
kCFRunLoopBeforeSources = (1UL << ),
kCFRunLoopBeforeWaiting = (1UL << ),
kCFRunLoopAfterWaiting = (1UL << ),
kCFRunLoopExit = (1UL << ),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

Timers:负责让App响应NSTimers,延迟的perform事件,对应(CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION)这个函数。我们可以在viewDidLoad里面加入如下代码:

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

iOS之RunLoop的更多相关文章

  1. ios之runloop笔记

    网上关于runloop的文章不计其数,再此,贴个自认为讲的比较简单明了的文章 http://www.jianshu.com/p/536184bfd163 个人理解: ios的runloop应该是类似于 ...

  2. iOS多线程-RunLoop简介

    什么是RunLoop? 从字面上来看是运行循环的意思. 内部就是一个do{}while循环,在这个循环里内部不断的处理各种任务(比如:source/timer/Observer) RunLoop的存在 ...

  3. iOS - OC RunLoop 运行循环/消息循环

    1.RunLoop 1)运行循环: 运行循环在 iOS 开发中几乎不用,但是概念的理解却非常重要. 同一个方法中的代码一般都在同一个运行循环中执行,运行循环监听 UI 界面的修改事件,待本次运行循环结 ...

  4. iOS关于RunLoop和Timer

    RunLoop这个东西,其实我们一直在用,但一直没有很好地理解它,或者甚至没有知道它的存在.RunLoop可以说是每个线程都有的一个对象,是用来接受事件和分配任务的loop.永远不要手动创建一个run ...

  5. iOS开发RunLoop

    最近处于离职状态,时间也多了起来,但是学习还是不能放松,今天总结一下RunLoop,RunLoop属于iOS系统层的东西,还是比较重要的. 一.什么是RunLoop 字面意思看是跑圈,也可以看作运行循 ...

  6. iOS开发-Runloop详解(简书)

    不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件 ...

  7. iOS开发RunLoop学习:一:RunLoop简单介绍

    一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...

  8. iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题

    一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...

  9. 关于 IOS Runtime Runloop 2

    Runtime 也就是运行时组件,一个纯C语言写的基础库. 我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码 Objective-C编写出来的程序必须得到ru ...

随机推荐

  1. 任何一款IDE的设计思路

    我们以Windows操作系统为例.现在,基于操作系统的任何计算机语言,我们说都是高级语言,从C开始.无论是哪一种,都是通过操作系统的API与计算机交互.即便.Net的FrameWork库从一定意义上何 ...

  2. CSS.06 -- 尚合网页模拟

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. trimpath javascript的学习

    TrimPath是javascript模板引擎. 这几天有一个项目涉及到trimpath这个框架,所以就花了一点时间研究了一下,这个框架和别的javascript框架不太一样的地方就是模板的概念,就是 ...

  4. 20155214 2016-2017-2 《Java程序设计》第5周学习总结

    20155214 2016-2017-2 <Java程序设计>第5周学习总结 教材学习内容总结 1.错误会被包装为可抛出的对象,继承自java.lang.Throwable类. 2.可以利 ...

  5. ReactJS入门2:组件状态

    React组件可以简单看做是包含props和states的函数. 上一节总结了创建新组建和数据属性的传递.本节主要讲解组件的状态. React认为UI是不同状态的展现.在React中,开发者只需更新组 ...

  6. JS弹出框

    一.JS三种最常见的对话框 1.alert()警告框      alert是警告框,只有一个按钮"确定"无返回值,警告框经常用于确保用户可以得到某些信息.当警告框出现后,用户需要点 ...

  7. Python入门(一):PTVS写Python程序,调试模式下input()提示文字乱码问题

    前两天写了Python入门(一),里面提到,使用VS2013+PTVS进行Python开发. 就在准备为第二篇写个demo的时候,发现了一个问题,各种解决无果,有些纠结 Python中输入函数是inp ...

  8. C#对文件操作(基本的读写以及压缩和解压)

    主要是针对单个文件进行读写操作和压缩操作:用到的主要C#类有FileStream.FileInfo.StreamWrite.StreamRead.GZipStream. 字符数组和字节数组的转换: ] ...

  9. Angular2入门-数据绑定

    ▓▓▓▓▓▓ 大致介绍 Angular2中数据绑定的方式默认是以单向方式,数据绑定的方式可以分为: 1.属性绑定和插值表达式 组件类-> 模板 2.事件绑定:模板 -> 组件类 3.双向绑 ...

  10. 前后端分离之vue2.0+webpack2 实战项目 -- webpack介绍

    webpack的一点介绍 Webpack 把任何一个文件都看成一个模块,模块间可以互相依赖(require or import),webpack 的功能是把相互依赖的文件打包在一起.webpack 本 ...