区块链技术博客
www.b2bchain.cn

iOS——解密RunLoop原理求职学习资料

本文介绍了iOS——解密RunLoop原理求职学习资料,有助于帮助完成毕业设计以及求职,是一篇很好的资料。

对技术面试,学习经验等有一些体会,在此分享。

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,恳请留言纠正,谢谢。

前言

RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多数开发者很少直接使用RunLoop,但是理解RunLoop可以帮助开发者更好的利用多线程编程模型,同时也可以帮助开发者解答日常开发中的一些疑惑。

这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的

一、什么是 RunLoop?

可以理解为字面意思:Run 表示运行,Loop 表示循环。结合在一起就是运行的循环的意思。NSRunloop是CFRunloop的封装,CFRunloop是一套C接口。

RunLoop 这个对象,在 iOS 里由 CFRunLoop 实现。简单来说,RunLoop 是用来监听输入源,进行调度处理的。这里的输入源可以是输入设备、网络、周期性或者延迟时间、异步回调。RunLoop 会接收两种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件。

二、源码解析runloop流程

苹果runloop源码

1、入口方法CFRunLoopRun

void CFRunLoopRun(void) {    /* DOES CALLOUT */     int32_t result;     do {         result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);         CHECK_FOR_FORK();     } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }

通过代码可以看出来,如果kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result,则一直会循环执行CFRunLoopRunSpecific函数

2、循环执行的函数

CFRunLoopRun -> CFRunLoopRunSpecific -> CFRunLoopRun

  • 2.1 CFRunLoopRunSpecific源码
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; }

主要逻辑代码:

    //通知 observers      if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);     //进入 loop     result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

通知 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop。

  • 2.1 CFRunLoopRun源码(由于源码量比较大,这里就不全部贴出来了,只贴出来核心步骤)

第一步

开启一个 do while 来保活线程。通知 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着执行加入的 block

// 通知 Observers RunLoop 会触发 Timer 回调 if (rlm->_observerMask & kCFRunLoopBeforeTimers)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);  // 通知 Observers RunLoop 会触发 Source0 回调 if (rlm->_observerMask & kCFRunLoopBeforeSources)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);  // 执行 block __CFRunLoopDoBlocks(rl, rlm);

接下来,触发 Source0 回调,如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息。

if (MACH_PORT_NULL != dispatchPort ) {     Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)     if (hasMsg) goto handle_msg; }

source0和source1的区别,后面会介绍!!

第二步
回调触发后,通知 Observers:RunLoop 的线程将进入休眠(sleep)状态。

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (!poll && (currentMode->_observerMask & kCFRunLoopBeforeWaiting)) {     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); }

第三步
进入休眠后,会等待 mach_port 的消息,以再次唤醒。只有在下面四个事件出现时才会被再次唤醒:

  • 基于 port 的 Source 事件;
  • Timer 时间到;
  • RunLoop 超时;
  • 被调用者唤醒。
do {     __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {         // 基于 port 的 Source 事件、调用者唤醒         if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {             break;         }         // Timer 时间到、RunLoop 超时         if (currentMode->_timerFired) {             break;         } } while (1);

第四步

唤醒时通知 Observer:RunLoop 的线程刚刚被唤醒了。

if (!poll && (currentMode->_observerMask & kCFRunLoopAfterWaiting))     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

第五步
RunLoop 被唤醒后就要开始处理消息了:

  • 如果是 Timer 时间到的话,就触发 Timer 的回调;
  • 如果是 dispatch 的话,就执行 block;
  • 如果是 source1 事件的话,就处理这个事件。

消息执行完后,就执行加到 loop 里的 block。

handle_msg: // 如果 Timer 时间到,就触发 Timer 回调 if (msg-is-timer) {     __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) }  // 如果 dispatch 就执行 block else if (msg_is_dispatch) {     __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); }   // Source1 事件的话,就处理这个事件 else {     CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);     sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);     if (sourceHandledThisLoop) {         mach_msg(reply, MACH_SEND_MSG, reply);     } }

第六步
根据当前 RunLoop 的状态来判断是否需要走下一个 loop。当被外部强制停止或 loop 超时时,就不继续下一个 loop 了,否则继续走下一个 loop 。

if (sourceHandledThisLoop && stopAfterHandle) {      // 事件已处理完     retVal = kCFRunLoopRunHandledSource; } else if (timeout) {     // 超时     retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) {     // 外部调用者强制停止     retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {     // mode 为空,RunLoop 结束     retVal = kCFRunLoopRunFinished; }

整个 RunLoop 过程,我们可以总结为如下所示的一张图片。
iOS——解密RunLoop原理

总结
将整个流程总结成伪代码如下:

int32_t __CFRunLoopRun() {     // 通知即将进入runloop     __CFRunLoopDoObservers(KCFRunLoopEntry);      do     {         // 通知将要处理timer和source         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);         __CFRunLoopDoObservers(kCFRunLoopBeforeSources);          // 处理非延迟的主线程调用         __CFRunLoopDoBlocks();         // 处理Source0事件         __CFRunLoopDoSource0();           // 处理Source0事件         if (sourceHandledThisLoop) {             __CFRunLoopDoBlocks();         }          /// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。         if (__Source0DidDispatchPortLastTime) {             Boolean hasMsg = __CFRunLoopServiceMachPort();             if (hasMsg) goto handle_msg;         }          /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。         if (!sourceHandledThisLoop) {             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);         }          // GCD dispatch main queue         CheckIfExistMessagesInMainDispatchQueue();          // 即将进入休眠         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);          // 等待内核mach_msg事件         mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();          // 等待。。。          // 从等待中醒来         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);          // 处理因timer的唤醒         if (wakeUpPort == timerPort)             __CFRunLoopDoTimers();          // 处理异步方法唤醒,如dispatch_async         else if (wakeUpPort == mainDispatchQueuePort)             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()          // 处理Source1         else             __CFRunLoopDoSource1();          // 再次确保是否有同步的方法需要调用         __CFRunLoopDoBlocks();      } while (!stop && !timeout);      // 通知即将退出runloop     __CFRunLoopDoObservers(CFRunLoopExit); }

下图描述了Runloop运行流程
iOS——解密RunLoop原理

注意的是尽管CFRunLoopPerformBlock在上图中作为唤醒机制有所体现,但事实上执行CFRunLoopPerformBlock只是入队,下次RunLoop运行才会执行,而如果需要立即执行则必须调用CFRunLoopWakeUp。

`

三、Runloop Mode

1、一”码”当先
“`
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems; // 在commonModes状态下运行的对象(例如Timer)
CFRunLoopModeRef _currentMode; //在当前loop下运行的mode
CFMutableSetRef _modes; // 运行的所有模式(CFRunloopModeRef类)
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

struct __CFRunLoopMode {

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,恳请留言纠正,谢谢。

前言

RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多数开发者很少直接使用RunLoop,但是理解RunLoop可以帮助开发者更好的利用多线程编程模型,同时也可以帮助开发者解答日常开发中的一些疑惑。

这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的

一、什么是 RunLoop?

可以理解为字面意思:Run 表示运行,Loop 表示循环。结合在一起就是运行的循环的意思。NSRunloop是CFRunloop的封装,CFRunloop是一套C接口。

RunLoop 这个对象,在 iOS 里由 CFRunLoop 实现。简单来说,RunLoop 是用来监听输入源,进行调度处理的。这里的输入源可以是输入设备、网络、周期性或者延迟时间、异步回调。RunLoop 会接收两种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件。

二、源码解析runloop流程

苹果runloop源码

1、入口方法CFRunLoopRun

void CFRunLoopRun(void) {    /* DOES CALLOUT */     int32_t result;     do {         result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);         CHECK_FOR_FORK();     } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }

通过代码可以看出来,如果kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result,则一直会循环执行CFRunLoopRunSpecific函数

2、循环执行的函数

CFRunLoopRun -> CFRunLoopRunSpecific -> CFRunLoopRun

  • 2.1 CFRunLoopRunSpecific源码
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; }

主要逻辑代码:

    //通知 observers      if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);     //进入 loop     result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

通知 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop。

  • 2.1 CFRunLoopRun源码(由于源码量比较大,这里就不全部贴出来了,只贴出来核心步骤)

第一步

开启一个 do while 来保活线程。通知 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着执行加入的 block

// 通知 Observers RunLoop 会触发 Timer 回调 if (rlm->_observerMask & kCFRunLoopBeforeTimers)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);  // 通知 Observers RunLoop 会触发 Source0 回调 if (rlm->_observerMask & kCFRunLoopBeforeSources)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);  // 执行 block __CFRunLoopDoBlocks(rl, rlm);

接下来,触发 Source0 回调,如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息。

if (MACH_PORT_NULL != dispatchPort ) {     Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)     if (hasMsg) goto handle_msg; }

source0和source1的区别,后面会介绍!!

第二步
回调触发后,通知 Observers:RunLoop 的线程将进入休眠(sleep)状态。

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (!poll && (currentMode->_observerMask & kCFRunLoopBeforeWaiting)) {     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); }

第三步
进入休眠后,会等待 mach_port 的消息,以再次唤醒。只有在下面四个事件出现时才会被再次唤醒:

  • 基于 port 的 Source 事件;
  • Timer 时间到;
  • RunLoop 超时;
  • 被调用者唤醒。
do {     __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {         // 基于 port 的 Source 事件、调用者唤醒         if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {             break;         }         // Timer 时间到、RunLoop 超时         if (currentMode->_timerFired) {             break;         } } while (1);

第四步

唤醒时通知 Observer:RunLoop 的线程刚刚被唤醒了。

if (!poll && (currentMode->_observerMask & kCFRunLoopAfterWaiting))     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

第五步
RunLoop 被唤醒后就要开始处理消息了:

  • 如果是 Timer 时间到的话,就触发 Timer 的回调;
  • 如果是 dispatch 的话,就执行 block;
  • 如果是 source1 事件的话,就处理这个事件。

消息执行完后,就执行加到 loop 里的 block。

handle_msg: // 如果 Timer 时间到,就触发 Timer 回调 if (msg-is-timer) {     __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) }  // 如果 dispatch 就执行 block else if (msg_is_dispatch) {     __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); }   // Source1 事件的话,就处理这个事件 else {     CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);     sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);     if (sourceHandledThisLoop) {         mach_msg(reply, MACH_SEND_MSG, reply);     } }

第六步
根据当前 RunLoop 的状态来判断是否需要走下一个 loop。当被外部强制停止或 loop 超时时,就不继续下一个 loop 了,否则继续走下一个 loop 。

if (sourceHandledThisLoop && stopAfterHandle) {      // 事件已处理完     retVal = kCFRunLoopRunHandledSource; } else if (timeout) {     // 超时     retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) {     // 外部调用者强制停止     retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {     // mode 为空,RunLoop 结束     retVal = kCFRunLoopRunFinished; }

整个 RunLoop 过程,我们可以总结为如下所示的一张图片。
iOS——解密RunLoop原理

总结
将整个流程总结成伪代码如下:

int32_t __CFRunLoopRun() {     // 通知即将进入runloop     __CFRunLoopDoObservers(KCFRunLoopEntry);      do     {         // 通知将要处理timer和source         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);         __CFRunLoopDoObservers(kCFRunLoopBeforeSources);          // 处理非延迟的主线程调用         __CFRunLoopDoBlocks();         // 处理Source0事件         __CFRunLoopDoSource0();           // 处理Source0事件         if (sourceHandledThisLoop) {             __CFRunLoopDoBlocks();         }          /// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。         if (__Source0DidDispatchPortLastTime) {             Boolean hasMsg = __CFRunLoopServiceMachPort();             if (hasMsg) goto handle_msg;         }          /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。         if (!sourceHandledThisLoop) {             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);         }          // GCD dispatch main queue         CheckIfExistMessagesInMainDispatchQueue();          // 即将进入休眠         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);          // 等待内核mach_msg事件         mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();          // 等待。。。          // 从等待中醒来         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);          // 处理因timer的唤醒         if (wakeUpPort == timerPort)             __CFRunLoopDoTimers();          // 处理异步方法唤醒,如dispatch_async         else if (wakeUpPort == mainDispatchQueuePort)             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()          // 处理Source1         else             __CFRunLoopDoSource1();          // 再次确保是否有同步的方法需要调用         __CFRunLoopDoBlocks();      } while (!stop && !timeout);      // 通知即将退出runloop     __CFRunLoopDoObservers(CFRunLoopExit); }

下图描述了Runloop运行流程
iOS——解密RunLoop原理

注意的是尽管CFRunLoopPerformBlock在上图中作为唤醒机制有所体现,但事实上执行CFRunLoopPerformBlock只是入队,下次RunLoop运行才会执行,而如果需要立即执行则必须调用CFRunLoopWakeUp。

`

三、Runloop Mode

1、一”码”当先
“`
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems; // 在commonModes状态下运行的对象(例如Timer)
CFRunLoopModeRef _currentMode; //在当前loop下运行的mode
CFMutableSetRef _modes; // 运行的所有模式(CFRunloopModeRef类)
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

struct __CFRunLoopMode {

本文篇幅比较长,创作的目的为了自己日后温习知识所用,希望这篇文章能对你有所帮助。如发现任何有误之处,恳请留言纠正,谢谢。

前言

RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多数开发者很少直接使用RunLoop,但是理解RunLoop可以帮助开发者更好的利用多线程编程模型,同时也可以帮助开发者解答日常开发中的一些疑惑。

这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理。之后会介绍一下在 iOS 中,苹果是如何利用 RunLoop 实现自动释放池、延迟回调、触摸事件、屏幕刷新等功能的

一、什么是 RunLoop?

可以理解为字面意思:Run 表示运行,Loop 表示循环。结合在一起就是运行的循环的意思。NSRunloop是CFRunloop的封装,CFRunloop是一套C接口。

RunLoop 这个对象,在 iOS 里由 CFRunLoop 实现。简单来说,RunLoop 是用来监听输入源,进行调度处理的。这里的输入源可以是输入设备、网络、周期性或者延迟时间、异步回调。RunLoop 会接收两种类型的输入源:一种是来自另一个线程或者来自不同应用的异步消息;另一种是来自预订时间或者重复间隔的同步事件。

二、源码解析runloop流程

苹果runloop源码

1、入口方法CFRunLoopRun

void CFRunLoopRun(void) {    /* DOES CALLOUT */     int32_t result;     do {         result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);         CHECK_FOR_FORK();     } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }

通过代码可以看出来,如果kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result,则一直会循环执行CFRunLoopRunSpecific函数

2、循环执行的函数

CFRunLoopRun -> CFRunLoopRunSpecific -> CFRunLoopRun

  • 2.1 CFRunLoopRunSpecific源码
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; }

主要逻辑代码:

    //通知 observers      if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);     //进入 loop     result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

通知 observers:RunLoop 要开始进入 loop 了。紧接着就进入 loop。

  • 2.1 CFRunLoopRun源码(由于源码量比较大,这里就不全部贴出来了,只贴出来核心步骤)

第一步

开启一个 do while 来保活线程。通知 Observers:RunLoop 会触发 Timer 回调、Source0 回调,接着执行加入的 block

// 通知 Observers RunLoop 会触发 Timer 回调 if (rlm->_observerMask & kCFRunLoopBeforeTimers)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);  // 通知 Observers RunLoop 会触发 Source0 回调 if (rlm->_observerMask & kCFRunLoopBeforeSources)      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);  // 执行 block __CFRunLoopDoBlocks(rl, rlm);

接下来,触发 Source0 回调,如果有 Source1 是 ready 状态的话,就会跳转到 handle_msg 去处理消息。

if (MACH_PORT_NULL != dispatchPort ) {     Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)     if (hasMsg) goto handle_msg; }

source0和source1的区别,后面会介绍!!

第二步
回调触发后,通知 Observers:RunLoop 的线程将进入休眠(sleep)状态。

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (!poll && (currentMode->_observerMask & kCFRunLoopBeforeWaiting)) {     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); }

第三步
进入休眠后,会等待 mach_port 的消息,以再次唤醒。只有在下面四个事件出现时才会被再次唤醒:

  • 基于 port 的 Source 事件;
  • Timer 时间到;
  • RunLoop 超时;
  • 被调用者唤醒。
do {     __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {         // 基于 port 的 Source 事件、调用者唤醒         if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {             break;         }         // Timer 时间到、RunLoop 超时         if (currentMode->_timerFired) {             break;         } } while (1);

第四步

唤醒时通知 Observer:RunLoop 的线程刚刚被唤醒了。

if (!poll && (currentMode->_observerMask & kCFRunLoopAfterWaiting))     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

第五步
RunLoop 被唤醒后就要开始处理消息了:

  • 如果是 Timer 时间到的话,就触发 Timer 的回调;
  • 如果是 dispatch 的话,就执行 block;
  • 如果是 source1 事件的话,就处理这个事件。

消息执行完后,就执行加到 loop 里的 block。

handle_msg: // 如果 Timer 时间到,就触发 Timer 回调 if (msg-is-timer) {     __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) }  // 如果 dispatch 就执行 block else if (msg_is_dispatch) {     __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); }   // Source1 事件的话,就处理这个事件 else {     CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);     sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);     if (sourceHandledThisLoop) {         mach_msg(reply, MACH_SEND_MSG, reply);     } }

第六步
根据当前 RunLoop 的状态来判断是否需要走下一个 loop。当被外部强制停止或 loop 超时时,就不继续下一个 loop 了,否则继续走下一个 loop 。

if (sourceHandledThisLoop && stopAfterHandle) {      // 事件已处理完     retVal = kCFRunLoopRunHandledSource; } else if (timeout) {     // 超时     retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) {     // 外部调用者强制停止     retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {     // mode 为空,RunLoop 结束     retVal = kCFRunLoopRunFinished; }

整个 RunLoop 过程,我们可以总结为如下所示的一张图片。
iOS——解密RunLoop原理

总结
将整个流程总结成伪代码如下:

int32_t __CFRunLoopRun() {     // 通知即将进入runloop     __CFRunLoopDoObservers(KCFRunLoopEntry);      do     {         // 通知将要处理timer和source         __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);         __CFRunLoopDoObservers(kCFRunLoopBeforeSources);          // 处理非延迟的主线程调用         __CFRunLoopDoBlocks();         // 处理Source0事件         __CFRunLoopDoSource0();           // 处理Source0事件         if (sourceHandledThisLoop) {             __CFRunLoopDoBlocks();         }          /// 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。         if (__Source0DidDispatchPortLastTime) {             Boolean hasMsg = __CFRunLoopServiceMachPort();             if (hasMsg) goto handle_msg;         }          /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。         if (!sourceHandledThisLoop) {             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);         }          // GCD dispatch main queue         CheckIfExistMessagesInMainDispatchQueue();          // 即将进入休眠         __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);          // 等待内核mach_msg事件         mach_port_t wakeUpPort = SleepAndWaitForWakingUpPorts();          // 等待。。。          // 从等待中醒来         __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);          // 处理因timer的唤醒         if (wakeUpPort == timerPort)             __CFRunLoopDoTimers();          // 处理异步方法唤醒,如dispatch_async         else if (wakeUpPort == mainDispatchQueuePort)             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()          // 处理Source1         else             __CFRunLoopDoSource1();          // 再次确保是否有同步的方法需要调用         __CFRunLoopDoBlocks();      } while (!stop && !timeout);      // 通知即将退出runloop     __CFRunLoopDoObservers(CFRunLoopExit); }

下图描述了Runloop运行流程
iOS——解密RunLoop原理

注意的是尽管CFRunLoopPerformBlock在上图中作为唤醒机制有所体现,但事实上执行CFRunLoopPerformBlock只是入队,下次RunLoop运行才会执行,而如果需要立即执行则必须调用CFRunLoopWakeUp。

`

三、Runloop Mode

1、一”码”当先
“`
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //线程
uint32_t _winthread;
CFMutableSetRef _commonModes; // commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
CFMutableSetRef _commonModeItems; // 在commonModes状态下运行的对象(例如Timer)
CFRunLoopModeRef _currentMode; //在当前loop下运行的mode
CFMutableSetRef _modes; // 运行的所有模式(CFRunloopModeRef类)
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};

struct __CFRunLoopMode {

部分转自互联网,侵权删除联系

赞(0) 打赏
部分文章转自网络,侵权联系删除b2bchain区块链学习技术社区 » iOS——解密RunLoop原理求职学习资料
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

b2b链

联系我们联系我们