iOS之RunLoop
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
- 如果有则返回找到的。
- 如果没有,且不需要创建,则返回NULL。
- 如果没有,需要创建,则新建一个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中:
- NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
- NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
- NSModalPanelRunLoopMode,OS X的Modal面板事件
- UITrackingRunLoopMode,拖动事件
- 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的更多相关文章
- ios之runloop笔记
网上关于runloop的文章不计其数,再此,贴个自认为讲的比较简单明了的文章 http://www.jianshu.com/p/536184bfd163 个人理解: ios的runloop应该是类似于 ...
- iOS多线程-RunLoop简介
什么是RunLoop? 从字面上来看是运行循环的意思. 内部就是一个do{}while循环,在这个循环里内部不断的处理各种任务(比如:source/timer/Observer) RunLoop的存在 ...
- iOS - OC RunLoop 运行循环/消息循环
1.RunLoop 1)运行循环: 运行循环在 iOS 开发中几乎不用,但是概念的理解却非常重要. 同一个方法中的代码一般都在同一个运行循环中执行,运行循环监听 UI 界面的修改事件,待本次运行循环结 ...
- iOS关于RunLoop和Timer
RunLoop这个东西,其实我们一直在用,但一直没有很好地理解它,或者甚至没有知道它的存在.RunLoop可以说是每个线程都有的一个对象,是用来接受事件和分配任务的loop.永远不要手动创建一个run ...
- iOS开发RunLoop
最近处于离职状态,时间也多了起来,但是学习还是不能放松,今天总结一下RunLoop,RunLoop属于iOS系统层的东西,还是比较重要的. 一.什么是RunLoop 字面意思看是跑圈,也可以看作运行循 ...
- iOS开发-Runloop详解(简书)
不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对它进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件 ...
- iOS开发RunLoop学习:一:RunLoop简单介绍
一:RunLoop的简单介绍 #import "ViewController.h" @interface ViewController () @end @implementatio ...
- iOS开发RunLoop学习:四:RunLoop的应用和RunLoop的面试题
一:RunLoop的应用 #import "ViewController.h" @interface ViewController () /** 注释 */ @property ( ...
- 关于 IOS Runtime Runloop 2
Runtime 也就是运行时组件,一个纯C语言写的基础库. 我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码 Objective-C编写出来的程序必须得到ru ...
随机推荐
- Android开发遇到手机无法弹出Toast
今天遇到了一个很奇怪的问题,一个很简单的程序,就是点击按钮弹出一个Toast,但在手机上运行起来,却没有正常弹出Toast 第一反应就是看看是否调用了show方法,很显然,并不是这个低级问题,为了确定 ...
- iOS开发之layoutSubviews
当发生下面两种情况该方法会被调用: (1)一个控件的frame发生改变的时候. (2)布局子控件的时候 一般在这里布局内部的子控件(设置子控件的frame) 例如: - (void)layoutSub ...
- 使用MyBatis对数据库中表实现CRUD操作(二)
一.使用MyBatis对表实现CRUD操作 1.定义sql映射 userMapper.xml <?xml version="1.0" encoding="UTF-8 ...
- C—动态内存分配之malloc与realloc的区别
在程序的执行期间分配内存时,内存区域中的这个空间称为堆(heap).还有另一个内存区域,称为堆栈(stack),其中的空间分配给函数的参数和本地变量.在执行完该函数后,存储参数和本地变量的内存空间就会 ...
- 认识J2SE
1. J2SE的定义 J2SE:全称为Java 2 Standard Edition.Java 2平台包括:标准版(J2SE).企业版(J2EE)和微缩版(J2ME)三个版本. J2SE主要包括UI. ...
- 如何在Ubuntu_16_04下使用MySql的GR
一.前言 该文章主要是记录下从一个纯净的系统开始如何安装MySql 5.7.17 并且使用GR,以便于自己后期查看以及分享给他人. 二.安装mysql 因为默认ubuntu的源并不是最新的mysql所 ...
- 浅谈C10K问题
在大型的APP中进行高并发的访问,淘宝,支付宝,微信,QQ,等 C10K问题:高并发的进行访问 C10K问题的最大特点是:设计不够良好的程序,其性能和连接数及机器性能的关系往往 是非线性的.举个例子: ...
- GDOI2014模拟 旅行【SPFA】
旅行(travel) 从前有一位旅者,他想要游遍天下所有的景点.这一天他来到了一个神奇的王国:在这片土地上,有n个城市,从1到n进行编号.王国中有m条道路,第i条道路连接着两个城市ai,bi,由于年代 ...
- 老李推荐:第2章1节《MonkeyRunner源码剖析》了解你的测试对象: NotePad应用简介
老李推荐:第2章1节<MonkeyRunner源码剖析>了解你的测试对象: NotePad应用简介 本书脚本相关的示例常会用到Android SDK自带的NotePad这个应用,所以这 ...
- Java中利用BigInteger类进行大数开方
在Java中有时会用到大数据,基本数据类型的存储范围已经不能满足要求了,如要对10的1000次方的这样一个数据规模的数进行开方运算,很明显不能直接用Math.sqrt()来进行计算,因为已经溢出了. ...