CFRunloop 源码阅读笔记
2015-02-03 10:04
309 查看
第一次接触 runloop 时,是在用 Timer 写重复动画的时候(用 Timer 写动画?很傻逼是吧,我也这么觉得)。大概是这么一行代码启动一个定时器,然后每隔0.1秒去翻转一张 Loading 图的角度。
那时候经常遇到问题。当首页的 TableView 在滚动的时候,Loading 图却不转了。经过一翻查找,大概知道了 Runloop 这个概念。当时刚入门,找到解决方法后也没继续深入。最近又经常接触到这家伙,便去找了 Core Foundation 的源码来看了个大概。
在CFRunLoop里面定义了以下6种函数:
这些销魂的名字仅仅是用来帮助调试的。CFRunLoop都是通过这些函数来调用对应的事件。
这个Observers有些特殊。CFRunLoop 提供CFRunLoopObserver API能让你在应用中观察CFRunLoop的状态。当Run Loop 进入的执行,Run Loop 处理一个Timer,Run Loop 处理一个Input Source,Run Loop 进入睡眠,Run Loop 被唤醒,在唤醒它的事件被处理之前Run Loop 停止等等。这些状态非常有用,系统某些框架工作,比如CoreAnimation就运行在观察者通知回调中。
正如名字一样,这个函数则是用来调用那些由CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))添加的 Blocks。
__CFRunLoopRun()退出的情况有:
调用通过 CFRunLoopPerformBlock() API 这个函数添加的 Blocks。
检查Version0 Sources,如果有的话,则调用 perform 函数。
轮询内部分发队列。
如果没事件发生则进入睡眠。当有消息到达时,runloop被唤醒。(因为要兼容Win32,所以源代码加了许多#ifdef,#elif, #endif。但主要是通过mach_msg()来配置等待的ports。这样做可以让Timer,GCD,手动唤醒,或version0 Sources都能在同一时间唤醒runloop。)
runloop 被唤醒,依次检查以下:
手动唤醒。仅是继续执行runloop blocks或者version0 Source。
一个或多个定时事件唤醒。执行事件函数。
GCD唤醒,执行Blocks。调用的是 dispatch_queue API 中特殊的_dispatch_main_queue_callback_4CF()函数。
查找并执行由内核发出version0 的Sources。
再次调用由CFRunLoopPerformBlock() API 这个函数添加的 Blocks。
检查退出条件(Finished, Stopped, TimedOut, HandledSource)。
进行下一次循环。
伪代码如下:
以上便是__CFRunLoopRun()的主要流程。很容易,对不对?但是CoreFoundation用C实现的,并不是很好理解。目前也没有看到苹果在 Swift 中重写这一部分内容。只能自己多研究研究了~
注:本文是关于iOS Runloop的学习笔记。有不对的地方,请指正。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:.1f target:self selector:@selector(rotationLoading:) userInfo:nil repeats:YES]
那时候经常遇到问题。当首页的 TableView 在滚动的时候,Loading 图却不转了。经过一翻查找,大概知道了 Runloop 这个概念。当时刚入门,找到解决方法后也没继续深入。最近又经常接触到这家伙,便去找了 Core Foundation 的源码来看了个大概。
runloop 是什么
runloop 大致跟 Windows 下的消息循环机制一样。一个runloop是一种消息机制,用于线程间、异步通信。runloop的主要功能是等待事件的发生,并将该事件分发到相应的处理方法。比如,用户点击按钮,定时器,线程间通信,异步代码,网络请求回调,等等。runloop接收到消息后,将该消息投递到相关的处理方法中去。简单来说,runloop在线程中可以简单等价于以下伪代码:run (runloop) { do { runloop.waitForSomeEvent(); var message = runloop.messageQueue.dequeue(); dispatchMessage(message); } while (YES); } post (runloop, message) { runloop.messageQueue.queue(message); runloop.wakeup(); }在iOS中,替线程干这种脏活的叫,NSRunloop。NSRunloop是对CFRunloop的封装。并远强大于上述的伪代码。除了初始化、手动启动的线程及GCD中分发到后台线程的block,你写的大部分代码都是在合适的时间被CFRunloop调用。
CFRunloop
CFRunLoop中有一项重要的特性——运行模式(CFRunLoopModes)。同一时间内CFRunLoop运行在特定的RunLoopModes下。CFRunLoop中运行着不同的Run Loop Sources。 Sources与一个或多个runloopModes绑定。也就是说runloop会自动过滤和其他Mode相关的事件源,而只监视和当前设置Mode相关的源。此外CFRunLoop也可以在应用中手动激活。你仅需调用CFRunLoopRun()方法。在CFRunLoop里面定义了以下6种函数:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(); static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
这些销魂的名字仅仅是用来帮助调试的。CFRunLoop都是通过这些函数来调用对应的事件。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__( CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
这个Observers有些特殊。CFRunLoop 提供CFRunLoopObserver API能让你在应用中观察CFRunLoop的状态。当Run Loop 进入的执行,Run Loop 处理一个Timer,Run Loop 处理一个Input Source,Run Loop 进入睡眠,Run Loop 被唤醒,在唤醒它的事件被处理之前Run Loop 停止等等。这些状态非常有用,系统某些框架工作,比如CoreAnimation就运行在观察者通知回调中。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__( void (^block)(void));
正如名字一样,这个函数则是用来调用那些由CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))添加的 Blocks。
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__( void *msg);这个函数当然是用来调用GCD中Main Dispatch Queue的Blocks。显然,在主线程中,至少同时运行着GCD 和 CFRunLoop。尽管GCD可以创建没有CFRunLoop的线程,在只有一条线程时(也就是只有主线程时),GCD将被阻塞。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__( CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info);用来调用定时器的方法。在iOS中,上层的类NSTimer或方法performSelector:afterDelay:都是用CFRunLoop timers来实现。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__( void (*perform)(void *), void *info);这个函数是用来处理 Version0的事件源。CFRunLoopSources有两个版本的源。Version0 和 Version1。虽然他们长得很像,而且有共同的API,但实际上它们是不太相同的东西。Version0的Sources只是在应用内只能由手动处理的消息机制。比如UI事件。当你收到一个Version0的Sources的信号( 可由CFRunLoopSourceSignal()发起),必须唤醒CFRunLoop(调用CFRunLoopWakeUp()函数)来处理Version0的Sources。
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__( void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info), mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply, void (*perform)(void *), void *info);有处理Version0的函数当然也就有处理 Version1的函数。Version1的Sources通过mach ports来处理内核事件。由代码看来,这对CFRunLoop非常重要。当没有时间发生时,CFRunLoop将在mach_msg处等待事件发生。
__CFRunLoopRun()
不管你是用CFRunLoopRun() 或 CFRunLoopRunInMode()来启动runloop,CFRunLoop的核心是__CFRunLoopRun()函数。__CFRunLoopRun()退出的情况有:
kCFRunLoopRunTimedOut:超时 kCFRunLoopRunFinished:所有的Sources都被移除: kCFRunLoopRunHandledSource:与returnAfterSourceHandled共同决定一旦事件被分发处理 kCFRunLoopRunStopped: 手动调用CFRunLoopStop()如果没出现这几种情况,runloop将会一直运行。并且内部将依次按以下顺序处理消息
调用通过 CFRunLoopPerformBlock() API 这个函数添加的 Blocks。
检查Version0 Sources,如果有的话,则调用 perform 函数。
轮询内部分发队列。
如果没事件发生则进入睡眠。当有消息到达时,runloop被唤醒。(因为要兼容Win32,所以源代码加了许多#ifdef,#elif, #endif。但主要是通过mach_msg()来配置等待的ports。这样做可以让Timer,GCD,手动唤醒,或version0 Sources都能在同一时间唤醒runloop。)
runloop 被唤醒,依次检查以下:
手动唤醒。仅是继续执行runloop blocks或者version0 Source。
一个或多个定时事件唤醒。执行事件函数。
GCD唤醒,执行Blocks。调用的是 dispatch_queue API 中特殊的_dispatch_main_queue_callback_4CF()函数。
查找并执行由内核发出version0 的Sources。
再次调用由CFRunLoopPerformBlock() API 这个函数添加的 Blocks。
检查退出条件(Finished, Stopped, TimedOut, HandledSource)。
进行下一次循环。
伪代码如下:
setupTimeoutTimer(); do { observerCallbacks(kCFRunLoopBeforeTimers); observerCallbacks(kCFRunLoopBeforeSources); performBlocks(); var hasHandledSource0 = performVersion0Sources(); if (hasHandledSources0) performBlocks(); if (checkIfExistMessageInMainQueue) { do { performNextBlockInMainQueue(); }while(hasBlocksInMainQueue); } else { observerCallbacks(kCFRunLoopBeforeWaiting); var event = sleepAndWaitForEventWakeup();//这里runloop 进入睡眠直到有 Timer 的 fire 事件、GCD 中dispatch block 到 main_queue 或 source 信号。 observerCallbacks(kCFRunLoopAfterWaiting); if (event == timer) { performTimers(); } else if (event == mainDispatchQueue) { do { performNextBlockInMainQueue(); } while(hasBlocksInMainQueue); } else { performVersion1Sources(); } } performBlocks(); } while (true);
以上便是__CFRunLoopRun()的主要流程。很容易,对不对?但是CoreFoundation用C实现的,并不是很好理解。目前也没有看到苹果在 Swift 中重写这一部分内容。只能自己多研究研究了~
注:本文是关于iOS Runloop的学习笔记。有不对的地方,请指正。
相关文章推荐
- x264源码阅读笔记2
- ServerSocket,ClientSocket控件源码阅读笔记
- FastDFS源码阅读笔记(二)
- Boa 源码阅读笔记
- winvnc源码阅读笔记(三)---------vncClient::SendUpdate线程
- “Multithreaded Job Queue”源码阅读笔记
- apache1.3.39源码alloc.c阅读笔记
- 非典型2D游戏引擎 Orx 源码阅读笔记(6) C语言实现的面向对象
- 非典型2D游戏引擎 Orx 源码阅读笔记(5) core部分(config,event)
- apache1.3.39源码alloc.c阅读笔记
- linux-Tcp IP协议栈源码阅读笔记【转】
- SDL源码阅读笔记(1) 基本模块
- linux 0.11源码阅读笔记
- 非典型2D游戏引擎 Orx 源码阅读笔记 完结篇(7) 渲染流程
- 非典型2D游戏引擎 Orx 源码阅读笔记(2) 基础模块与模块管理模块
- LZ77源码阅读笔记
- linux-Tcp IP协议栈源码阅读笔记【转】
- SDL源码阅读笔记(3)渲染模块
- YunTable源码阅读笔记(1)
- apache1.3.39源码alloc.c阅读笔记