Runloop 是和线程紧密相关的一个基础组件,是很多线程有关功能的幕后功臣。尽管在平常使用中几乎不太会直接用到,理解 Runloop 有利于我们更加深入地理解 iOS 的多线程模型。
本文从如下几个方面理解RunLoop的相关知识点。
- RunLoop概念
- RunLoop实现
- RunLoop运行
- RunLoop应用
RunLoop概念
RunLoop介绍
RunLoop 是什么?RunLoop 还是比较顾名思义的一个东西,说白了就是一种循环,只不过它这种循环比较高级。一般的 while 循环会导致 CPU 进入忙等待状态,而 RunLoop 则是一种“闲”等待,这部分可以类比 Linux 下的 epoll。当没有事件时,RunLoop 会进入休眠状态,有事件发生时, RunLoop 会去找对应的 Handler 处理事件。RunLoop 可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。
从代码上看,RunLoop其实就是一个对象,它的结构如下,源码看这里:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //RunLoop对应的线程
uint32_t _winthread;
CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode
CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
CFRunLoopModeRef _currentMode; //当前运行的mode
CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
struct _block_item *_blocks_head;//doblocks的时候用到
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
复制代码
可见,一个RunLoop对象,主要包含了一个线程,若干个Mode,若干个commonMode,还有一个当前运行的Mode。
RunLoop与线程
当我们需要一个常驻线程,可以让线程在需要做事的时候忙起来,不需要的话就让线程休眠。我们就在线程里面执行下面这个代码,一直等待消息,线程就不会退出了。
do {
//获取消息
//处理消息
} while (消息 != 退出)
复
复制代码
上面的这种循环模型被称作 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管理着各种事件,它的结构如下:
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode名称
Boolean _stopped; //mode是否被终止
char _padding[3];
//几种事件
CFMutableSetRef _sources0; //sources0
CFMutableSetRef _sources1; //sources1
CFMutableArrayRef _observers; //通知
CFMutableArrayRef _timers; //定时器
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
复
复制代码
一个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两个版本,它的结构如下:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
复制代码
source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行的时候,必须要先把它标记为signal状态,以下是source0的结构体:
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
复制代码
source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
source1由RunLoop和内核管理,source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
复制代码
Source1除了包含回调指针外包含一个mach port,Source1可以监听系统端口和通过内核和其他线程通信,接收、分发系统事件,它能够主动唤醒RunLoop(由操作系统内核进行管理,例如CFMessagePort消息)。官方也指出可以自定义Source,因此对于CFRunLoopSourceRef来说它更像一种协议,框架已经默认定义了两种实现,如果有必要开发人员也可以自定义,详细情况可以查看官方文档。
CFRunLoopObserver
CFRunLoopObserver是观察者,可以观察RunLoop的各种状态,并抛出回调。
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
复制代码
CFRunLoopObserver可以观察的状态有如下6种:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入run loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//被唤醒但是还没开始处理事件
kCFRunLoopExit = (1UL << 7),//run loop已经退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码
Runloop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 Runloop Observer 来监控 Runloop 本身的状态。 Runloop Observer 可以监控上面的 Runloop 事件,具体流程如下图。
CFRunLoopTimer
CFRunLoopTimer是定时器,可以在设定的时间点抛出回调,它的结构如下:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; //标记fire状态
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加该timer的runloop
CFMutableSetRef _rlModes; //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; //理想时间间隔 /* immutable */
CFTimeInterval _tolerance; //时间偏差 /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
复制代码
另外根据官方文档的描述,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
//取当前所在线程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
//传入当前线程
return _CFRunLoopGet0(pthread_self());
}
复制代码
在CFRunLoopGetCurrent函数内部调用了_CFRunLoopGet0(),传入的参数是当前线程pthread_self()。这里可以看出,CFRunLoopGetCurrent函数必须要在线程内部调用,才能获取当前线程的RunLoop。也就是说子线程的RunLoop必须要在子线程内部获取。
CFRunLoopGetMain
//取主线程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
//传入主线程
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
复制代码
在CFRunLoopGetMain函数内部也调用了_CFRunLoopGet0()
,传入的参数是主线程pthread_main_thread_np()
。可以看出,CFRunLoopGetMain()
不管在主线程还是子线程中调用,都可以获取到主线程的RunLoop。
CFRunLoopGet0
前面两个函数都是使用了CFRunLoopGet0实现传入线程的函数,下面看下CFRunLoopGet0的结构是咋样的。
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// t==0 is a synonym for "main thread" that always works
//根据线程取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
//如果存储RunLoop的字典不存在
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
//创建一个临时字典dict
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//创建主线程的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//把主线程的RunLoop保存到dict中,key是线程,value是RunLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
//释放dict
CFRelease(dict);
}
//释放mainrunloop
CFRelease(mainLoop);
__CFSpinLock(&loopsLock);
}
//以上说明,第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建
//从字典__CFRunLoops中获取传入线程t的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
//如果没有获取到
if (!loop) {
//根据线程t创建一个runloop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFSpinLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//把newLoop存入字典__CFRunLoops,key是线程t
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFSpinUnlock(&loopsLock);
CFRelease(newLoop);
}
//如果传入线程就是当前线程
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
//注册一个回调,当线程销毁时,销毁对应的RunLoop
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
复制代码
这段代码可以得出以下结论:
- 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
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
//看rl中是否已经有这个mode,如果有就什么都不做
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
//把modeName添加到RunLoop的_commonModes中
CFSetAddValue(rl->_commonModes, modeName);
if (NULL != set) {
CFTypeRef context[2] = {rl, modeName};
/* add all common-modes items to new mode */
//这里调用CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer的时候会调用
//__CFRunLoopFindMode(rl, modeName, true),CFRunLoopMode对象在这个时候被创建
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set);
}
} else {
}
__CFRunLoopUnlock(rl);
}
复制代码
可以看得出:
- 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的代码结构如下:
//添加source事件
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
if (!__CFIsValid(rls)) return;
Boolean doVer0Callout = false;
__CFRunLoopLock(rl);
//如果是kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
//如果runloop的_commonModes存在,则copy一个新的复制给set
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//如果runl _commonModeItems为空
if (NULL == rl->_commonModeItems) {
//先初始化
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//把传入的CFRunLoopSourceRef加入_commonModeItems
CFSetAddValue(rl->_commonModeItems, rls);
//如果刚才set copy到的数组里有数据
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* add new item to all common-modes */
//则把set里的所有mode都执行一遍__CFRunLoopAddItemToCommonModes函数
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
//以上分支的逻辑就是,如果你往kCFRunLoopCommonModes里面添加一个source,那么所有_commonModes里的mode都会添加这个source
} else {
//根据modeName查找mode
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
//如果_sources0不存在,则初始化_sources0,_sources0和_portToV1SourceMap
if (NULL != rlm && NULL == rlm->_sources0) {
rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
}
//如果_sources0和_sources1中都不包含传入的source
if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
//如果version是0,则加到_sources0
if (0 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources0, rls);
//如果version是1,则加到_sources1
} else if (1 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources1, rls);
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
//此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来
//可以理解为,source1可以通过内核向其端口发送消息来主动唤醒runloop
CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
__CFPortSetInsert(src_port, rlm->_portSet);
}
}
__CFRunLoopSourceLock(rls);
//把runloop加入到source的_runLoops中
if (NULL == rls->_runLoops) {
rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
}
CFBagAddValue(rls->_runLoops, rl);
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL != rls->_context.version0.schedule) {
doVer0Callout = true;
}
}
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
if (doVer0Callout) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
}
复制代码
通过添加source的这段代码可以得出如下结论:
- 如果modeName传入kCFRunLoopCommonModes,则该source会被保存到RunLoop的_commonModeItems中
- 如果modeName传入kCFRunLoopCommonModes,则该source会被添加到所有commonMode中
- 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个
- 同一个source在一个mode中只能被添加一次
CFRunLoopRemoveSource
remove操作和add操作的逻辑基本一致,很容易理解。
//移除source
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK();
Boolean doVer0Callout = false, doRLSRelease = false;
__CFRunLoopLock(rl);
//如果是kCFRunLoopCommonModes,则从_commonModes的所有mode中移除该source
if (modeName == kCFRunLoopCommonModes) {
if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetRemoveValue(rl->_commonModeItems, rls);
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* remove new item from all common-modes */
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set);
}
} else {
}
} else {
//根据modeName查找mode,如果不存在,返回NULL
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
CFRetain(rls);
//根据source版本做对应的remove操作
if (1 == rls->_context.version0.version) {
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
__CFPortSetRemove(src_port, rlm->_portSet);
}
}
CFSetRemoveValue(rlm->_sources0, rls);
CFSetRemoveValue(rlm->_sources1, rls);
__CFRunLoopSourceLock(rls);
if (NULL != rls->_runLoops) {
CFBagRemoveValue(rls->_runLoops, rl);
}
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL != rls->_context.version0.cancel) {
doVer0Callout = true;
}
}
doRLSRelease = true;
}
if (NULL != rlm) {
__CFRunLoopModeUnlock(rlm);
}
}
__CFRunLoopUnlock(rl);
if (doVer0Callout) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
if (doRLSRelease) CFRelease(rls);
}
复制代码
觉得小编写的不错的可以点赞和评论
加我微信邀请你群15388944845
这有学习ios开发 主要逆向安防、架构设计、多线程,网络进阶,还有底层
的学习的资料