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. system()、exec()、fork()三个与进程有关的函数的比较

    启动新进程(system函数) system()函数可以启动一个新的进程. int system (const char *string ) 这个函数的效果就相当于执行sh –c string. 一般 ...

  2. Javascript:面试经典套路-查重(reduce)

    今天在偶然间查看到了一段代码,代码使用了很短的篇幅完成了字符串统计相同字符次数这个经典面试题,其中用到了reduce这个方法,网上查了查,没有查到什么有价值的东西,导致浪费了我一些时间才看懂,现将我的 ...

  3. python混账的编码问题解决之道

    下面的代码作用是修改文件的编码格式.代码很简单,但是也很牛逼(在我看来),这是在segment上找到的解决办法,废话不多说,直接上代码. import codecsdef ReadFile(fileP ...

  4. python的try方法中的else和finally的区别

    #coding=utf-8__author__ = '14356_000'try: print '1'except: print '2'else: print '3'finally: print '4 ...

  5. 一步步教你使用rem适配不同屏幕的移动设备

    1.先说说几个前端常用的几个单位的概论: 1.px (pixel,像素):是一个虚拟长度单位,是计算机系统的数字化图像长度单位,如果px要换算成物理长度,需要指定精度DPI(Dots Per Inch ...

  6. git操作详解

    前言:一般公司git的master主干与线上代码保持一致,在使用git的时候,偶尔会发生一些莫名其妙的事情,很容易导致运营事故.so- 总结一下经常使用的git命令以及git的一些小坑,方便日后查阅 ...

  7. 创建你的第一个webdriver python代码

    前言 今天我们开始我们的第一个python webdriver自动化测试脚本.并就测试脚本进行一一解释说明. webdriver python代码 本示例代码演示了使用Ie浏览器访问百度进行搜索测试. ...

  8. Mybatis基础学习(二)—开发Dao方式

    一.原始Dao开发方式 UserDao.java public interface UserDao{ public User findUserByID(Serializable id); public ...

  9. List分组 用于客服对话分组场景

    工作用可能会用到会话分组: Message是消息实体对象,里面有toId和fromId 指明接收方ID和发送方Id,通过组合形式"12-22-" 为map的key public M ...

  10. JS中的函数、Bom、DOM及JS事件

    本期博主给大家带来JS的函数.Bom.DOM操作,以及JS各种常用的数据类型的相关知识,同时,这也是JavaScript极其重要的部分,博主将详细介绍各种属性的用法和方法. 一.JS中的函数 [函数的 ...