当前位置:   article > 正文

iOS RunLoop详解

cfrunloopaddcommonmode

Runloop 是和线程紧密相关的一个基础组件,是很多线程有关功能的幕后功臣。尽管在平常使用中几乎不太会直接用到,理解 Runloop 有利于我们更加深入地理解 iOS 的多线程模型。

本文从如下几个方面理解RunLoop的相关知识点。

  • RunLoop概念
  • RunLoop实现
  • RunLoop运行
  • RunLoop应用

RunLoop概念

RunLoop介绍

RunLoop 是什么?RunLoop 还是比较顾名思义的一个东西,说白了就是一种循环,只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙等待状态,而 RunLoop 则是一种“闲”等待,这部分可以类比 Linux 下的 epoll。当没有事件时,RunLoop 会进入休眠状态,有事件发生时, RunLoop 会去找对应的 Handler 处理事件。RunLoop 可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。

从代码上看,RunLoop其实就是一个对象,它的结构如下,源码看这里:

  1. struct __CFRunLoop {
  2. CFRuntimeBase _base;
  3. pthread_mutex_t _lock; /* locked for accessing mode list */
  4. __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
  5. Boolean _unused;
  6. volatile _per_run_data *_perRunData; // reset for runs of the run loop
  7. pthread_t _pthread; //RunLoop对应的线程
  8. uint32_t _winthread;
  9. CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode
  10. CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
  11. CFRunLoopModeRef _currentMode; //当前运行的mode
  12. CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
  13. struct _block_item *_blocks_head;//doblocks的时候用到
  14. struct _block_item *_blocks_tail;
  15. CFTypeRef _counterpart;
  16. };
  17. 复制代码

可见,一个RunLoop对象,主要包含了一个线程,若干个Mode,若干个commonMode,还有一个当前运行的Mode。

RunLoop与线程

当我们需要一个常驻线程,可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。我们就在线程里面执行下面这个代码,一直等待消息,线程就不会退出了。

  1. do {
  2. //获取消息
  3. //处理消息
  4. } while (消息 != 退出)
  5. 复制代码

上面的这种循环模型被称作 Event Loop,事件循环模型在众多系统里都有实现,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。

下图描述了Runloop运行流程(基本描述了上面Runloop的核心流程,当然可以查看官方The Run Loop Sequence of Events描述):

整个流程并不复杂(需要注意的就是_黄色_区域的消息处理中并不包含source0,因为它在循环开始之初就会处理),整个流程其实就是一种Event Loop的实现,其他平台均有类似的实现,只是这里叫做RunLoop。

RunLoop与线程的关系如下图

图中展现了 Runloop 在线程中的作用:从 input source 和 timer source 接受事件,然后在线程中处理事件。

Runloop 和线程是绑定在一起的。每个线程(包括主线程)都有一个对应的 Runloop 对象。我们并不能自己创建 Runloop 对象,但是可以获取到系统提供的 Runloop 对象。

主线程的 Runloop 会在应用启动的时候完成启动,其他线程的 Runloop 默认并不会启动,需要我们手动启动。

RunLoop Mode

Mode可以视为事件的管家,一个Mode管理着各种事件,它的结构如下:

  1. struct __CFRunLoopMode {
  2. CFRuntimeBase _base;
  3. pthread_mutex_t _lock; /* must have the run loop locked before locking this */
  4. CFStringRef _name; //mode名称
  5. Boolean _stopped; //mode是否被终止
  6. char _padding[3];
  7. //几种事件
  8. CFMutableSetRef _sources0; //sources0
  9. CFMutableSetRef _sources1; //sources1
  10. CFMutableArrayRef _observers; //通知
  11. CFMutableArrayRef _timers; //定时器
  12. CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
  13. __CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
  14. CFIndex _observerMask;
  15. #if USE_DISPATCH_SOURCE_FOR_TIMERS
  16. dispatch_source_t _timerSource;
  17. dispatch_queue_t _queue;
  18. Boolean _timerFired; // set to true by the source when a timer has fired
  19. Boolean _dispatchTimerArmed;
  20. #endif
  21. #if USE_MK_TIMER_TOO
  22. mach_port_t _timerPort;
  23. Boolean _mkTimerArmed;
  24. #endif
  25. #if DEPLOYMENT_TARGET_WINDOWS
  26. DWORD _msgQMask;
  27. void (*_msgPump)(void);
  28. #endif
  29. uint64_t _timerSoftDeadline; /* TSR */
  30. uint64_t _timerHardDeadline; /* TSR */
  31. };
  32. 复制代码

一个CFRunLoopMode对象有一个name,若干source0、source1、timer、observer和若干port,可见事件都是由Mode在管理,而RunLoop管理Mode。

从源码很容易看出,Runloop总是运行在某种特定的CFRunLoopModeRef下(每次运行**__CFRunLoopRun()函数时必须指定Mode)。而通过CFRunloopRef对应结构体的定义可以很容易知道每种Runloop都可以包含若干个Mode,每个Mode又包含Source/Timer/Observer。每次调用Runloop的主函数__CFRunLoopRun()时必须指定一种Mode,这个Mode称为 _currentMode**,当切换Mode时必须退出当前Mode,然后重新进入Runloop以保证不同Mode的Source/Timer/Observer互不影响。

如图所示,Runloop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的 Source,Timer 和 Observer 隔绝开来。Runloop 在某个时刻只能跑在一个 Mode 下,处理这一个 Mode 当中的 Source,Timer 和 Observer。

苹果文档中提到的 Mode 有五个,分别是:

  • NSDefaultRunLoopMode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • NSEventTrackingRunLoopMode
  • NSRunLoopCommonModes

iOS 中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode(注意:并不是说Runloop会运行在kCFRunLoopCommonModes这种模式下,而是相当于分别注册了 NSDefaultRunLoopMode和 UITrackingRunLoopMode。当然你也可以通过调用CFRunLoopAddCommonMode()方法将自定义Mode放到 kCFRunLoopCommonModes组合)。 五种Mode的介绍如下图:

RunLoop Source

Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。

CFRunLoopSource

根据官方的描述,CFRunLoopSource是对input sources的抽象。CFRunLoopSource分source0和source1两个版本,它的结构如下:

  1. struct __CFRunLoopSource {
  2. CFRuntimeBase _base;
  3. uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
  4. pthread_mutex_t _lock;
  5. CFIndex _order; /* immutable */
  6. CFMutableBagRef _runLoops;
  7. union {
  8. CFRunLoopSourceContext version0; /* immutable, except invalidation */
  9. CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
  10. } _context;
  11. };
  12. 复制代码

source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行的时候,必须要先把它标记为signal状态,以下是source0的结构体:

  1. typedef struct {
  2. CFIndex version;
  3. void * info;
  4. const void *(*retain)(const void *info);
  5. void (*release)(const void *info);
  6. CFStringRef (*copyDescription)(const void *info);
  7. Boolean (*equal)(const void *info1, const void *info2);
  8. CFHashCode (*hash)(const void *info);
  9. void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
  10. void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
  11. void (*perform)(void *info);
  12. } CFRunLoopSourceContext;
  13. 复制代码

source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

source1由RunLoop和内核管理,source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体

  1. typedef struct {
  2. CFIndex version;
  3. void * info;
  4. const void *(*retain)(const void *info);
  5. void (*release)(const void *info);
  6. CFStringRef (*copyDescription)(const void *info);
  7. Boolean (*equal)(const void *info1, const void *info2);
  8. CFHashCode (*hash)(const void *info);
  9. #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
  10. mach_port_t (*getPort)(void *info);
  11. void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
  12. #else
  13. void * (*getPort)(void *info);
  14. void (*perform)(void *info);
  15. #endif
  16. } CFRunLoopSourceContext1;
  17. 复制代码

Source1除了包含回调指针外包含一个mach port,Source1可以监听系统端口和通过内核和其他线程通信,接收、分发系统事件,它能够主动唤醒RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)。官方也指出可以自定义Source,因此对于CFRunLoopSourceRef来说它更像一种协议,框架已经默认定义了两种实现,如果有必要开发人员也可以自定义,详细情况可以查看官方文档。

CFRunLoopObserver

CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,并抛出回调。

  1. struct __CFRunLoopObserver {
  2. CFRuntimeBase _base;
  3. pthread_mutex_t _lock;
  4. CFRunLoopRef _runLoop;
  5. CFIndex _rlCount;
  6. CFOptionFlags _activities; /* immutable */
  7. CFIndex _order; /* immutable */
  8. CFRunLoopObserverCallBack _callout; /* immutable */
  9. CFRunLoopObserverContext _context; /* immutable, except invalidation */
  10. };
  11. 复制代码

CFRunLoopObserver可以观察的状态有如下6种:

  1. /* Run Loop Observer Activities */
  2. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
  3. kCFRunLoopEntry = (1UL << 0), //即将进入run loop
  4. kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer
  5. kCFRunLoopBeforeSources = (1UL << 2),//即将处理source
  6. kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
  7. kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事件
  8. kCFRunLoopExit = (1UL << 7),//run loop已经退出
  9. kCFRunLoopAllActivities = 0x0FFFFFFFU
  10. };
  11. 复制代码

Runloop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 Runloop Observer 来监控 Runloop 本身的状态。 Runloop Observer 可以监控上面的 Runloop 事件,具体流程如下图。

CFRunLoopTimer

CFRunLoopTimer是定时器,可以在设定的时间点抛出回调,它的结构如下:

  1. struct __CFRunLoopTimer {
  2. CFRuntimeBase _base;
  3. uint16_t _bits; //标记fire状态
  4. pthread_mutex_t _lock;
  5. CFRunLoopRef _runLoop; //添加该timer的runloop
  6. CFMutableSetRef _rlModes; //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
  7. CFAbsoluteTime _nextFireDate;
  8. CFTimeInterval _interval; //理想时间间隔 /* immutable */
  9. CFTimeInterval _tolerance; //时间偏差 /* mutable */
  10. uint64_t _fireTSR; /* TSR units */
  11. CFIndex _order; /* immutable */
  12. CFRunLoopTimerCallBack _callout; /* immutable */
  13. CFRunLoopTimerContext _context; /* immutable, except invalidation */
  14. };
  15. 复制代码

另外根据官方文档的描述,CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互转换。

CFRunLoopTimer is “toll-free bridged” with its Cocoa Foundation counterpart, NSTimer. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object.

所以CFRunLoopTimer具有以下特性:

  • CFRunLoopTimer 是定时器,可以在设定的时间点抛出回调
  • CFRunLoopTimer和NSTimer是toll-free bridged的,可以相互转换
RunLoop实现

下面从以下3个方面介绍RunLoop的实现。

  • 获取RunLoop
  • 添加Mod
  • 添加Run Loop Source
获取RunLoop

从苹果开放的API来看,不允许我们直接创建RunLoop对象,只能通过以下几个函数来获取RunLoop:

  • CFRunLoopRef CFRunLoopGetCurrent(void)
  • CFRunLoopRef CFRunLoopGetMain(void)
  • +(NSRunLoop *)currentRunLoop
  • +(NSRunLoop *)mainRunLoop

前两个是Core Foundation中的API,后两个是Foundation中的API。

那么RunLoop是什么时候被创建的呢?

我们从下面几个函数内部看看。

CFRunLoopGetCurrent
  1. //取当前所在线程的RunLoop
  2. CFRunLoopRef CFRunLoopGetCurrent(void) {
  3. CHECK_FOR_FORK();
  4. CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
  5. if (rl) return rl;
  6. //传入当前线程
  7. return _CFRunLoopGet0(pthread_self());
  8. }
  9. 复制代码

在CFRunLoopGetCurrent函数内部调用了_CFRunLoopGet0(),传入的参数是当前线程pthread_self()。这里可以看出,CFRunLoopGetCurrent函数必须要在线程内部调用,才能获取当前线程的RunLoop。也就是说子线程的RunLoop必须要在子线程内部获取。

CFRunLoopGetMain
  1. //取主线程的RunLoop
  2. CFRunLoopRef CFRunLoopGetMain(void) {
  3. CHECK_FOR_FORK();
  4. static CFRunLoopRef __main = NULL; // no retain needed
  5. //传入主线程
  6. if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
  7. return __main;
  8. }
  9. 复制代码

在CFRunLoopGetMain函数内部也调用了_CFRunLoopGet0(),传入的参数是主线程pthread_main_thread_np()。可以看出,CFRunLoopGetMain()不管在主线程还是子线程中调用,都可以获取到主线程的RunLoop。

CFRunLoopGet0

前面两个函数都是使用了CFRunLoopGet0实现传入线程的函数,下面看下CFRunLoopGet0的结构是咋样的。

  1. static CFMutableDictionaryRef __CFRunLoops = NULL;
  2. static CFSpinLock_t loopsLock = CFSpinLockInit;
  3. // t==0 is a synonym for "main thread" that always works
  4. //根据线程取RunLoop
  5. CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  6. if (pthread_equal(t, kNilPthreadT)) {
  7. t = pthread_main_thread_np();
  8. }
  9. __CFSpinLock(&loopsLock);
  10. //如果存储RunLoop的字典不存在
  11. if (!__CFRunLoops) {
  12. __CFSpinUnlock(&loopsLock);
  13. //创建一个临时字典dict
  14. CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
  15. //创建主线程的RunLoop
  16. CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  17. //把主线程的RunLoop保存到dict中,key是线程,value是RunLoop
  18. CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  19. //此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
  20. if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
  21. //释放dict
  22. CFRelease(dict);
  23. }
  24. //释放mainrunloop
  25. CFRelease(mainLoop);
  26. __CFSpinLock(&loopsLock);
  27. }
  28. //以上说明,第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建
  29. //从字典__CFRunLoops中获取传入线程t的runloop
  30. CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  31. __CFSpinUnlock(&loopsLock);
  32. //如果没有获取到
  33. if (!loop) {
  34. //根据线程t创建一个runloop
  35. CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  36. __CFSpinLock(&loopsLock);
  37. loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  38. if (!loop) {
  39. //把newLoop存入字典__CFRunLoops,key是线程t
  40. CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
  41. loop = newLoop;
  42. }
  43. // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  44. __CFSpinUnlock(&loopsLock);
  45. CFRelease(newLoop);
  46. }
  47. //如果传入线程就是当前线程
  48. if (pthread_equal(t, pthread_self())) {
  49. _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
  50. if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
  51. //注册一个回调,当线程销毁时,销毁对应的RunLoop
  52. _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
  53. }
  54. }
  55. return loop;
  56. }
  57. 复制代码

这段代码可以得出以下结论:

  • RunLoop和线程的一一对应的,对应的方式是以key-value的方式保存在一个全局字典中
  • 主线程的RunLoop会在初始化全局字典时创
  • 子线程的RunLoop会在第一次获取的时候创建,如果不获取的话就一直不会被创建
  • RunLoop会在线程销毁时销毁
添加Mode

在Core Foundation中,针对Mode的操作,苹果只开放了以下3个API(Cocoa中也有功能一样的函数,不再列出):

  • CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
  • CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
  • CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)

CFRunLoopAddCommonMode Adds a mode to the set of run loop common modes. 向当前RunLoop的common modes中添加一个mode。 CFRunLoopCopyCurrentMode Returns the name of the mode in which a given run loop is currently running. 返回当前运行的mode的name CFRunLoopCopyAllModes Returns an array that contains all the defined modes for a CFRunLoop object. 返回当前RunLoop的所有mode

我们没有办法直接创建一个CFRunLoopMode对象,但是我们可以调用CFRunLoopAddCommonMode传入一个字符串向RunLoop中添加Mode,传入的字符串即为Mode的名字,Mode对象应该是此时在RunLoop内部创建的。下面来看一下源码。

CFRunLoopAddCommonMode

  1. void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
  2. CHECK_FOR_FORK();
  3. if (__CFRunLoopIsDeallocating(rl)) return;
  4. __CFRunLoopLock(rl);
  5. //看rl中是否已经有这个mode,如果有就什么都不做
  6. if (!CFSetContainsValue(rl->_commonModes, modeName)) {
  7. CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
  8. //把modeName添加到RunLoop的_commonModes中
  9. CFSetAddValue(rl->_commonModes, modeName);
  10. if (NULL != set) {
  11. CFTypeRef context[2] = {rl, modeName};
  12. /* add all common-modes items to new mode */
  13. //这里调用CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer的时候会调用
  14. //__CFRunLoopFindMode(rl, modeName, true),CFRunLoopMode对象在这个时候被创建
  15. CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
  16. CFRelease(set);
  17. }
  18. } else {
  19. }
  20. __CFRunLoopUnlock(rl);
  21. }
  22. 复制代码

可以看得出:

  • modeName不能重复,modeName是mode的唯一标识符
  • RunLoop的_commonModes数组存放所有被标记为common的mode的名称
  • 添加commonMode会把commonModeItems数组中的所有source同步到新添加的mode
  • CFRunLoopMode对象在CFRunLoopAddItemsToCommonMode函数中调用CFRunLoopFindMode时被创建
CFRunLoopCopyCurrentMode/CFRunLoopCopyAllModes

CFRunLoopCopyCurrentMode和CFRunLoopCopyAllModes的内部逻辑比较简单,直接取RunLoop的_currentMode和_modes返回,就不贴源码了。

添加Run Loop Source(ModeItem)

我们可以通过以下接口添加/移除各种事件:

  • void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode)
  • void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode)
  • void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef * mode)
  • void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
  • void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode)
CFRunLoopAddSource

CFRunLoopAddSource的代码结构如下:

  1. //添加source事件
  2. void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
  3. CHECK_FOR_FORK();
  4. if (__CFRunLoopIsDeallocating(rl)) return;
  5. if (!__CFIsValid(rls)) return;
  6. Boolean doVer0Callout = false;
  7. __CFRunLoopLock(rl);
  8. //如果是kCFRunLoopCommonModes
  9. if (modeName == kCFRunLoopCommonModes) {
  10. //如果runloop的_commonModes存在,则copy一个新的复制给set
  11. CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
  12. //如果runl _commonModeItems为空
  13. if (NULL == rl->_commonModeItems) {
  14. //先初始化
  15. rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
  16. }
  17. //把传入的CFRunLoopSourceRef加入_commonModeItems
  18. CFSetAddValue(rl->_commonModeItems, rls);
  19. //如果刚才set copy到的数组里有数据
  20. if (NULL != set) {
  21. CFTypeRef context[2] = {rl, rls};
  22. /* add new item to all common-modes */
  23. //则把set里的所有mode都执行一遍__CFRunLoopAddItemToCommonModes函数
  24. CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
  25. CFRelease(set);
  26. }
  27. //以上分支的逻辑就是,如果你往kCFRunLoopCommonModes里面添加一个source,那么所有_commonModes里的mode都会添加这个source
  28. } else {
  29. //根据modeName查找mode
  30. CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
  31. //如果_sources0不存在,则初始化_sources0,_sources0和_portToV1SourceMap
  32. if (NULL != rlm && NULL == rlm->_sources0) {
  33. rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
  34. rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
  35. rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
  36. }
  37. //如果_sources0和_sources1中都不包含传入的source
  38. if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
  39. //如果version是0,则加到_sources0
  40. if (0 == rls->_context.version0.version) {
  41. CFSetAddValue(rlm->_sources0, rls);
  42. //如果version是1,则加到_sources1
  43. } else if (1 == rls->_context.version0.version) {
  44. CFSetAddValue(rlm->_sources1, rls);
  45. __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
  46. if (CFPORT_NULL != src_port) {
  47. //此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来
  48. //可以理解为,source1可以通过内核向其端口发送消息来主动唤醒runloop
  49. CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
  50. __CFPortSetInsert(src_port, rlm->_portSet);
  51. }
  52. }
  53. __CFRunLoopSourceLock(rls);
  54. //把runloop加入到source的_runLoops中
  55. if (NULL == rls->_runLoops) {
  56. rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
  57. }
  58. CFBagAddValue(rls->_runLoops, rl);
  59. __CFRunLoopSourceUnlock(rls);
  60. if (0 == rls->_context.version0.version) {
  61. if (NULL != rls->_context.version0.schedule) {
  62. doVer0Callout = true;
  63. }
  64. }
  65. }
  66. if (NULL != rlm) {
  67. __CFRunLoopModeUnlock(rlm);
  68. }
  69. }
  70. __CFRunLoopUnlock(rl);
  71. if (doVer0Callout) {
  72. // although it looses some protection for the source, we have no choice but
  73. // to do this after unlocking the run loop and mode locks, to avoid deadlocks
  74. // where the source wants to take a lock which is already held in another
  75. // thread which is itself waiting for a run loop/mode lock
  76. rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
  77. }
  78. }
  79. 复制代码

通过添加source的这段代码可以得出如下结论:

  • 如果modeName传入kCFRunLoopCommonModes,则该source会被保存到RunLoop的_commonModeItems中
  • 如果modeName传入kCFRunLoopCommonModes,则该source会被添加到所有commonMode中
  • 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个
  • 同一个source在一个mode中只能被添加一次
CFRunLoopRemoveSource

remove操作和add操作的逻辑基本一致,很容易理解。

  1. //移除source
  2. void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
  3. CHECK_FOR_FORK();
  4. Boolean doVer0Callout = false, doRLSRelease = false;
  5. __CFRunLoopLock(rl);
  6. //如果是kCFRunLoopCommonModes,则从_commonModes的所有mode中移除该source
  7. if (modeName == kCFRunLoopCommonModes) {
  8. if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
  9. CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
  10. CFSetRemoveValue(rl->_commonModeItems, rls);
  11. if (NULL != set) {
  12. CFTypeRef context[2] = {rl, rls};
  13. /* remove new item from all common-modes */
  14. CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
  15. CFRelease(set);
  16. }
  17. } else {
  18. }
  19. } else {
  20. //根据modeName查找mode,如果不存在,返回NULL
  21. CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
  22. if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
  23. CFRetain(rls);
  24. //根据source版本做对应的remove操作
  25. if (1 == rls->_context.version0.version) {
  26. __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
  27. if (CFPORT_NULL != src_port) {
  28. CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
  29. __CFPortSetRemove(src_port, rlm->_portSet);
  30. }
  31. }
  32. CFSetRemoveValue(rlm->_sources0, rls);
  33. CFSetRemoveValue(rlm->_sources1, rls);
  34. __CFRunLoopSourceLock(rls);
  35. if (NULL != rls->_runLoops) {
  36. CFBagRemoveValue(rls->_runLoops, rl);
  37. }
  38. __CFRunLoopSourceUnlock(rls);
  39. if (0 == rls->_context.version0.version) {
  40. if (NULL != rls->_context.version0.cancel) {
  41. doVer0Callout = true;
  42. }
  43. }
  44. doRLSRelease = true;
  45. }
  46. if (NULL != rlm) {
  47. __CFRunLoopModeUnlock(rlm);
  48. }
  49. }
  50. __CFRunLoopUnlock(rl);
  51. if (doVer0Callout) {
  52. // although it looses some protection for the source, we have no choice but
  53. // to do this after unlocking the run loop and mode locks, to avoid deadlocks
  54. // where the source wants to take a lock which is already held in another
  55. // thread which is itself waiting for a run loop/mode lock
  56. rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */
  57. }
  58. if (doRLSRelease) CFRelease(rls);
  59. }
  60. 复制代码

觉得小编写的不错的可以点赞和评论

加我微信邀请你群15388944845

这有学习ios开发 主要逆向安防、架构设计、多线程,网络进阶,还有底层 的学习的资料

转载于:https://juejin.im/post/5d5537d56fb9a06aea618ddc

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/273907
推荐阅读
相关标签
  

闽ICP备14008679号