文章目录
-
- 1. Runloop是什么
- 2. Runloop作用
- 3. RunLoop与线程的关系
- 4. 详解RunLoop相关类别及作用
-
- 4.1 CFRunLoopRef
-
- 4.1.1 RunLoop的种类
- 4.1.2 CFRunLoopRef的结构
- 4.1.3 CFRunLoopRef的操作
- 4.2 CFRunLoopModeRef
-
- 4.2.1 mode种类
- 4.2.2 CFRunLoopModeRef的结构
- 4.2.3 RunLoop mode的操作
- 4.2.4 Mode间的切换
- 4.3 CFRunLoopSourceRef事件源
-
- 4.3.1 CFRunLoopSource的种类
- 4.3.2 CFRunLoopSourceRef的结构
- 4.3.3 CFRunLoopSource的操作
- 4.4 CFRunLoopTimerRef
-
- 4.4.1 CFRunLoopTimerRef的结构
- 4.4.2CFRunLoopTimerRef的操作
- 4.5CFRunLoopObserverRef
-
- 4.5.1RunLoop的状态
- 4.5.2CFRunLoopObserverRef的结构
- 4.5.3CFRunLoopObserverRef的操作
- 5. RunLoop启动和退出
-
- 5.1RunLoop的启动
- 5.2RunLoop的退出
- 6. RunLoop处理逻辑
-
- 6.1 RunLoop事件处理
- 6.2 RunLoop的休眠与唤醒
- 6.3 RunLoop的超时处理
- 7.苹果用 RunLoop 实现的功能
-
- 7.1 AutoreleasePool
- 7.2 事件响应
- 7.3手势识别
- 7.4 界面更新
- 7.5定时器
- 7.6 PerformSelector
- 7.7关于GCD
- 7.关于网络请求
- 8.RunLoop 实际应用例
-
- 8.1 AFNetworking
- 8.2 AsyncDisplayKit
- 8.3其他
1. Runloop是什么
运行循环是在程序运行过程中做一些事情,如果没有runloop,程序执行后退出。runloop,程序可以一直运行,等待用户的输入。runloop必要时可以自己操作,不操作时可以休眠。可以节省。cpu提高程序性能的资源。
2. Runloop作用
1.保持程序的连续运行。一旦程序启动,主线程将打开,主线程将创建相应的runloop,runloop保证主线程不被销毁,保证程序的持续运行。 2.处理APP触摸事件、定时器事件等各种事件,selector事件 3.节省CPU提高程序性能的资源。runloop当线程没有操作时,线程会休眠并释放CPU资源。当有事情要处理时,会立即唤醒线程进行处理。 图中说明Runloop在运行中,收到input sources(mach port,custom input source,performSelector:onThread:…等)或者Timer sources时就会交给对应的处理方法去处理。没有事件消息传入的时候,runloop就休息了。
3. RunLoop与线程的关系
Runloop是基于pthread管理,pthread基于c的跨平台多线程操作底层API。它是mach thread上层封装(见Kernel Programming Guide),和NSThread一一对应(和NSThread是一套面向对象的API,所以在iOS我们在开发中几乎不需要直接使用pthread)。
苹果开发界面没有直接创建Runloop如果需要使用接口,如果需要使用Runloop通常使用CF的CFRunLoopGetMain()和CFRunLoopGetCurrent()获取两种方法。对应地,可调用Foundation中[NSRunLoop currentRunLoop]获得当前线程RunLoop对象, 调用[NSRunLoop mainRunLoop]获得主线程RunLoop对象。
//获取主线程的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; } //获取当前Runloop,没有就调用_CFRunLoopGet0 CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); } // 查看_CFRunLoopGet0方法内部 CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //t默认为空是主线程 if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); //__CFRunLoops字典为空 if (!__CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 创建相应的主线程RunLoop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // 将主线程-key和RunLoop-Value保存在字典中 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // 将线程作为key从字典中获取一个loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果loop为空,则创建一个新的loop,所以runloop会在第一次获取的时候创建
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 创建好之后,以线程为key,runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
//如果传入的线程和方法调用的线程是同一个,且尚未为该runloop注册销毁函数,则为runloop注册一个销毁函数__CFFinalizeRunLoop
if (pthread_equal(t, pthread_self())) {
//将RunLoop存在线程特定数据区,提高后续查找速度 _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
从上面的代码可以看出: 1.线程和RunLoop 之间是一一对应的,其关系是保存在一个Dictionary里,线程作为key,RunLoop作为value。 2.我们要创建子线程的RunLoop时,只需在子线程中获取当前线程的RunLoop对象即可,使用CFRunLoopGetCurrent()或[NSRunLoop currentRunLoop]。方法调用时,会先看一下字典里有没有存子线程相对用的RunLoop,如果有则直接返回RunLoop,如果没有则会创建一个,并将与之对应的子线程存入字典中。如果不获取,那子线程就不会创建与之相关联的RunLoop。 3.主线程的Runloop比较特殊,任何线程创建之前都会保证主线程已经存在Runloop,而且CFRunLoopGetMain()不管在主线程还是子线程中调用,都可以获取到主线程的RunLoop。 4.RunLoop在第一次获取时创建,在线程结束时销毁。
此外,APP启动时,主线程中UIApplicationMain函数内启动了Runloop,程序不会马上退出,而是保持运行状态。因此每一个应用必须要有一个runloop。
4. 详解RunLoop相关类及作用
在Foundation框架中和RunLoop相关的类包括NSRunLoop。而Core Foundation中关于RunLoop的5个类:
- CFRunLoopRef - 获得当前RunLoop和主RunLoop
- CFRunLoopModeRef - RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作
- CFRunLoopSourceRef - 事件源,输入源
- CFRunLoopTimerRef - 定时器事件
- CFRunLoopObserverRef - 观察者
4.1 CFRunLoopRef
4.1.1 RunLoop的种类
前面我们已经知道了,我们可以通过CFRunLoopGetCurrent函数获取当前线程的RunLoop,通过CFRunLoopGetMain函数获取主线程的RunLoop,以及它们两者之间的一些差别。
4.1.2 CFRunLoopRef的结构
CFRunLoopRef是指向__CFRunLoop结构体的指针。 通过源码我们找到__CFRunLoop结构体:
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;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
除一些记录性属性外,主要来看一下以下两个成员变量 CFRunLoopModeRef _currentMode;指向RunLoop当前的mode, CFMutableSetRef _modes;RunLoop包含的mode。CFMutableSetRef _commonModes中包含了_modes中被标记为common mode的mode name,CFMutableSetRef _commonModeItems中包含了应当被添加到common mode中的mode item。具体在CFRunLoopModeRef中我们还将讨论它。
CFRunLoopModeRef代表RunLoop的运行模式。 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个被称为mode item的Source、Timer、Observer。 RunLoop启动、运行时,只能且必须指定其中一个 Mode,这个Mode被称作Current Mode。 如果需要切换Mode,只能退出当前Mode,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer,RunLoop会立马退出。
4.1.3 CFRunLoopRef的操作
CFRunLoopRun Runs the current thread’s CFRunLoop object in its default mode indefinitely. 让当前线程的RunLoop在default mode下运行。
CFRunLoopRunInMode Runs the current thread’s CFRunLoop object in a particular mode. 让当前线程的RunLoop在指定的mode下运行。
CFRunLoopWakeUp Wakes a waiting CFRunLoop object. 唤醒一个休眠的RunLoop。
CFRunLoopStop Forces a CFRunLoop object to stop running. 停止该RunLoop的运行。
CFRunLoopIsWaiting Returns a Boolean value that indicates whether the run loop is waiting for an event. 判断一个RunLoop是否在休眠,等待事件唤醒。
4.2 CFRunLoopModeRef
4.2.1 mode种类
iOS上系统默认注册的5个Mode,苹果公开提供的 Mode有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操作其对应的 Mode。
这五种运行模式分别是:
- kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode。
这里有个概念叫 “CommonModes”:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
有时你需要一个 Timer,在两个 Mode 中都能得到回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到所有具有”Common”属性的 Mode 里去。
4.2.2 CFRunLoopModeRef的结构
CFRunLoopModeRef 其实是指向__CFRunLoopMode结构体的指针,__CFRunLoopMode结构体源码如下:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
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 */
};
主要查看以下成员变量
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFRunLoopModeRef中我们已经讨论了CFRunLoopModeRef、CFRunLoopModeRef、mode item之间的关系,这里不再赘述。
mode item包括Source1/Source0/Timers/Observer,它们分别代表什么?
- Source1 : 基于Port的线程间通信
- Source0 : 非Port的事件,包括触摸事件,不带延迟的Thread相关的PerformSelectors等
- Timers : 定时器,NSTimer
- Observer : 监听器,用于监听RunLoop的状态
4.2.3 RunLoop mode的操作
在Core Foundation中,针对Mode的操作,苹果只开放了以下3个API(Cocoa中也有功能一样的函数,不再列出):
CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
Adds a mode to the set of run loop common modes.
向当前RunLoop的common modes中添加一个mode。
CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
CFArray
返回当前运行的mode的name
Ref CFRunLoopCopyAllModes(CFRunLoopRef rl)
返回当前RunLoop的所有mode
我们没有办法直接创建一个CFRunLoopMode对象,但是我们可以调用CFRunLoopAddCommonMode传入一个字符串向RunLoop中添加Mode,传入的字符串即为Mode的名字,RunLoop 内部没有对应 mode 时,RunLoop会自动帮你创建对应的 CFRunLoopModeRef。对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。下面来看一下源码。
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);
}
可以看得出:
1.modeName不能重复,modeName是mode的唯一标识符。 2.RunLoop的_commonModes数组存放所有被标记为common的mode的名称。 3.添加commonMode会把commonModeItems数组中的所有source同步到新添加的mode中 4.CFRunLoopMode对象在CFRunLoopAddItemsToCommonMode函数中调用CFRunLoopFindMode时被创建
CFRunLoopCopyCurrentMode和CFRunLoopCopyAllModes的内部逻辑比较简单,直接取RunLoop的_currentMode和_modes返回,就不贴源码了。
4.2.4 Mode间的切换
在主线程中Schedule一个Timer并正常运行,然后滑动界面上的TableView或UIScrollView时,之前的Timer不触发了。为什么呢?
[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doTimer1) userInfo:nil repeats:YES];
这个问题也与RunLoop的RunLoopMode有关。因为schedule一个Timer时,Timer是被加到了RunLoop的NSDefaultRunLoopMode下,而在滑动TableView或UIScrollView时,RunLoop的Mode被切换到了UITrackingRunLoopMode下,RunLoop就只会去跟踪UI上的滑动事件了而Timer被暂停不会被触发了。
CF框架内并没有实现Mode切换的功能,所以UIKit在使用的时候需要自行处理。一般的过程是UIApplication push一个即将要执行的mode,然后wakeup RunLoop。切换回来的时候,只需要pop这个mode,然后再wakeup即可。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FlolCpqS-1652108715759)(https://upload-images.jianshu.io/upload_images/1049809-74da0b6560623ec5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000)]
4.3 CFRunLoopSourceRef事件源
4.3.1 CFRunLoopSource的种类
在我 RunLoopMode 数据结构代码中可以看到这两个东西CFMutableSetRef _source0 和 CFMutableSetRef _source1,首先这两个东西是 Set(集合),集合中存放的是一堆数据结构,那这个 source 到底是个什么东西呢,他们其实也是一个数据结构 CFRunLoopSourceRef。
CFRunLoopSource是对input sources的抽象。CFRunLoopSource分source0和source1两个版本。
Source0:非基于Port的,用于用户主动触发的事件(点击button或点击屏幕,不带delay的Thread相关的PerformSelectors是source0,带delay的是timer)。 Source1:基于Port的,通过内核和其他线程相互发送消息(与内核相关)。
4.3.2 CFRunLoopSourceRef的结构
CFRunLoopSourceRef是指向__CFRunLoopSource结构体的指针,它的结构如下:
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* source0的数据结构 */
CFRunLoopSourceContext1 version1; /* source1的数据结构 */
} _context;
};
数据结构__CFRunLoopSource中包含一个 _context成员,他的类型是 CFRunLoopSourceContext 或者是 CFRunLoopSourceContext1,指明了该source的类型。
source0是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备执行的时候,必须要先把它标记为signal状态,以下是source0的结构体:
//source0
typedef struct {
CFIndex version; // 版本号,用来区分是source1还是source0
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, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
source0是非基于Port的。只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
source1由RunLoop和内核管理,source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体
//source1
typedef struct {
CFIndex version; // 版本号,用来区分是source1还是source0
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来说它更像一种协议,框架已经默认定义了两种实现,如果有必要开发人员也可以自定义,详细情况可以查看官方文档。
4.3.3 CFRunLoopSource的操作
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中,并被添加到所有common Mode的mode items中。 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个 同一个source在一个mode中只能被添加一次
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);
}
Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode); 判断RunLoop中指定mode下是否存在指定的source
这里简单总结一下: 1.CFRunLoopSourceRef 是事件产生的地方; 2.这个 CFRunLoopSourceRef 有两个版本就是 source0 和 source1; 3.source0只包含一个回调(函数指针),不能主动出发事件,需要 CFRunLoopSourceSignal(source) 将 Source 标记为待处理,CFRunLoopWakeUp(runloop) 唤醒 RunLoop,让其处理事件 4.source1包含mach_port 和一个回调(函数指针),用于通过内核和其它线程相互发送消息,能主动唤醒 RunLoop。 5.input sources分发异步事件到对应的处理者,引起runUntilDate:退出;而timer分发同步事件给它们的处理者,不会引起RunLoop退出。 6.perform selector是一种custom input source。perform selector请求在目标线程上被串行化接收。selector被执行后就会将自身从runloop中移除,而基于端口的source不会。runloop每次运行会处理所有的selector,而不是每次处理一个。
4.4 CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器,它和 NSTimer是toll-free bridged 的,可以混用。因此,NSTimer是对RunLoopTimer的封装。CFRunLoopTimerRef包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
4.4.1 CFRunLoopTimerRef的结构
CFRunLoopTimerRef是指向__CFRunLoopTimer结构的指针。
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFMutableSetRef _rlModes;
CFAbsoluteTime _nextFireDate;
CFTimeInterval _interval; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
4.4.2CFRunLoopTimerRef的操作
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode) 向指定RunLoop的指定mode下添加指定的timer
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode) 移除指定RunLoop的指定mode下的指定的timer
Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode); 判断指定RunLoop的指定mode下是否存在指定的timer
CFAbsoluteTime CFRunLoopGetNextTimerFireDate(CFRunLoopRef rl, CFRunLoopMode mode); 获取指定RunLoop的指定mode下注册的所有timer的最近的触发时间
####4.4.3 Timer的实现 由于篇幅原因,在另外一篇里介绍。
4.5CFRunLoopObserverRef
4.5.1RunLoop的状态
CFRunLoopObserverRef是消息循环中的一个监听器,能够监听RunLoop的状态改变,随时通知外部当前RunLoop的运行状态(它包含一个函数指针_callout_将当前状态及时告诉观察者)。
RunLoop 的状态(CFOptionFlags)包括下面几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // runLoop即将处理 Timers
kCFRunLoopBeforeSources = (1UL << 2), // runLoop即将处理 Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // runLoop即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // runLoop刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
4.5.2CFRunLoopObserverRef的结构
CFRunLoopObserverRef的结构如下:
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //observer对应的runLoop
CFIndex _rlCount; //observer当前监测的runLoop数量
CFOptionFlags _activities; //observer观测runLoop的状态,枚举类型
CFIndex _order; //CFRunLoopMode中是数组形式存储的observer,_order就是他在数组中的位置
CFRunLoopObserverCallBack _callout; //observer观察者的函数回调
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
这里有个_order字段,决定了observer的位置,数值越小越靠前,就能更早被调用到。类似地,__CFRunLoopSource,__CFRunLoopTimer以及__CFRunLoopObserver都有这么字段。在7.1 AutoreleasePool中还会详细介绍_order的用处。
这里我们要注意_callout这个字段。在开发过程中几乎所有的操作都是通过Call out进行回调的(无论是Observer的状态通知还是Timer、Source的处理),而系统在回调时通常使用如下几个函数进行回调(换句话说你的代码其实最终都是通过下面几个函数来负责调用的,即使你自己监听Observer也会先调用下面的函数然后间接通知你,所以在调用堆栈中经常看到这些函数):
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_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info)内部会调用rlo->_callout指向的函数。
我们在控制器的touchBegin中打入断点查看堆栈(由于UIEvent是Source0,所以可以看到一个Source0的Call out函数CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION调用):
4.5.3CFRunLoopObserverRef的操作
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode) 向RunLoop指定mode下添加指定观察者
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef * mode) 移除RunLoop指定mode下的指定观察者
Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode); 判断RunLoop指定mode下是否存在指定观察者
讨论: 添加observer和timer的内部逻辑和添加source大体类似。 区别在于observer和timer只能被添加到一个RunLoop的一个或者多个mode中,比如一个timer被添加到主线程的RunLoop中,则不能再把该timer添加到子线程的RunLoop,而source没有这个限制,不管是哪个RunLoop,只要mode中没有,就可以添加。 这个区别在前面的一些结构体中也可以发现,CFRunLoopSource结构体中有保存RunLoop对象的数组,而CFRunLoopObserver和CFRunLoopTimer只有单个RunLoop对象。
5. RunLoop的启动与退出
5.1RunLoop的启动
Foundation中启动一个runloop有以下三种方法,这三种方式无论通过哪一种方式启动runloop,如果没有一个输入源或者timer附加于runloop上,runloop就会立刻退出。
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (void)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
(1) 第一种方式,runloop会一直运行下去,在此期间会处理来自输入源的数据,并且会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法; (2) 第二种方式,可以设置超时时间,在超时时间到达之前,runloop会一直运行,在此期间runloop会处理来自输入源的数据,并且也会在NSDefaultRunLoopMode模式下重复调用runMode:beforeDate:方法; (3) 第三种方式,runloop的一次执行,被指定mode中的input source所锁定,超时时间到达或者第一个input source被处理,本次执行就退出。
前两种启动方式会重复调用runMode:beforeDate:方法。
我们来看CF中启动RunLoop的源码:
// 用DefaultMode启动
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
//指定mode的一次运行
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
我们发现RunLoop确实是do while通过判断result的值实现的。
5.2RunLoop的退出
1.主线程销毁,RunLoop退出。(线程销毁退出) 2.Mode中有一些Timer 、Source,这些保证Mode不为空时,RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出。(但是苹果并不建议我们这么做,因为系统内部有可能会在当前线程的runloop中添加一些输入源,所以通过手动移除input source或者timer这种方式,并不能保证runloop一定会退出。)(无mode item退出) 3.我们在启动RunLoop的时候可以设置什么时候停止。(超时退出)
NSRunLoop的退出方式
6. RunLoop处理逻辑
6.1 RunLoop事件处理
前面我们已经介绍了CF中RunLoop的启动,RunLoop是如何运行的?这就跟其中的函数CFRunLoopRunSpecific有关了。
在CFRunLoopRun函数中调用了CFRunLoopRunSpecific函数,runloop参数传入当前RunLoop对象,modeName参数传入kCFRunLoopDefaultMode。
在CFRunLoopRunInMode函数中也调用了CFRunLoopRunSpecific函数,runloop参数传入当前RunLoop对象,modeName参数继续传递CFRunLoopRunInMode传入的modeName。
我们来看看CFRunLoopRunSpecific的源码。
/*
* 指定mode运行runloop
* @param rl 当前运行的runloop
* @param modeName 需要运行的mode的name
* @param seconds runloop的超时时间
* @param returnAfterSourceHandled 是否处理完事件就返回
*/
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
//根据modeName找到本次运行的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环
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);
//保存上一次运行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//更新本次mode
rl->_currentMode = currentMode;
//初始化一个result为kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 1.通知observer即将进入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//10.通知observer已退出runloop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
通过CFRunLoopRunSpecific的内部逻辑,我们可以得出:
- 如果指定了一个不存在的mode来运行RunLoop,那么会失败,mode不会被创建,所以这里传入的mode必须是存在的
- 如果指定了一个mode,但是这个mode中不包含任何modeItem,那么RunLoop也不会运行,所以必须要传入至少包含一个modeItem的mode
- 在进入run loop之前通知observer,状态为kCFRunLoopEntry
- 在退出run loop之后通知observer,状态为kCFRunLoopExit
RunLoop的运行的最核心函数是__CFRunLoopRun,接下来我们分析__CFRunLoopRun的源码。 __CFRunLoopRun:
/**
* 运行run loop
*
* @param rl 运行的RunLoop对象
* @param rlm 运行的mode
* @param seconds run loop超时时间
* @param stopAfterHandle true:run loop处理完事件就退出 false:一直运行直到超时或者被手动终止
* @param previousMode 上一次运行的mode
*
* @return 返回4种状态
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//获取系统启动后的CPU运行时间,用于控制超时时间
uint64_t startTSR = mach_absolute_time();
//如果RunLoop或者mode是stop状态,则直接return,不进入循环
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//mach端口,在内核中,消息在端口之间传递。 初始为0
mach_port_name_t dispatchPort = MACH_PORT_NULL;
//判断是否为主线程
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
//如果在主线程 && runloop是主线程的runloop && 该mode是commonMode,则给mach端口赋值为主线程收发消息的端口
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
//mode赋值为dispatch端口_dispatch_runloop_root_queue_perform_4CF
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//GCD管理的定时器,用于实现runloop超时机制
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;
}
//seconds为超时时间,超时时执行__CFRunLoopTimeout函数
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, 0, 0, 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(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
}
//永不超时
else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
//标志位默认为true
Boolean didDispatchPortLastTime = true;
//记录最后runloop状态,用于return
int32_t retVal = 0;
do {
//初始化一个存放内核消息的缓冲池
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#endif
//取所有需要监听的port
__CFPortSet waitSet = rlm->_portSet;
//设置RunLoop为可以被唤醒状态
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知observer,即将触发timer回调,处理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer,即将触发Source0回调
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.处理source0事件
//有事件处理返回true,没有事件返回false
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//执行加入当前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果没有Sources0事件处理 并且 没有超时,poll为false
//如果有Sources0事件处理 或者 超时,poll都为true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do..whil循环不会走该分支,因为didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//从缓冲区读取消息
msg = (mach_msg_header_t *)msg_buffer;
//5.接收dispatchPort端口的消息,(接收source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的话,前往第9步开始处理msg
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//6.通知观察者RunLoop即将进入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//设置RunLoop为休眠状态
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//这里有个内循环,用于接收等待端口的消息
//进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
//7.__CFRunLoopServiceMachPort调用mach_msg等待接受mach_port的消息。线程将进入休眠,知道被下面某个事件唤醒:一个基于Port的Source事件,也就是Source1;一个Timer到时间了;RunLoop自身超时时间到了;被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//收到消息之后,livePort的值为msg->msgh_local_port,
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
//取消runloop的休眠状态
__CFRunLoopUnsetSleeping(rl);
//8.通知观察者runloop被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.处理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通过CFRunloopWake唤醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
//什么都不干,跳回2重新循环
// do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//如果是定时器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1 处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
//如果是定时器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
//9.1处理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
//如果是dispatch到main queue的block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
//9.2执行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有source1事件待处理
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
//9.2 处理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
//判断是否要退出 RunLoop
if (sourceHandledThisLoop && stopAfterHandle) {
//进入run loop时传入的参数,处理完事件就返回
retVal = kCFRunLoopRunHandledSource;
}else if (timeout_context->termTSR < mach_absolute_time()) {
//run loop超时
retVal = kCFRunLoopRunTimedOut;
}else if (__CFRunLoopIsStopped(rl)) {
//run loop被手动终止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}else if (rlm->_stopped) {
//调用_CFRunLoopStopMode使mode被终止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//mode中没有要处理的事件
retVal = kCFRunLoopRunFinished;
}
//除了上面这几种情况,都继续循环
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
这里要注意的一点是宏USE_DISPATCH_SOURCE_FOR_TIMERS在Mac上是1,在iOS上是0.因此这里有些代码在iOS上并不存在。 在iOS上__CFRunLoopRun只有一层do-while循环,可以看做是RunLoop的整个生命周期,直到满足一些条件后,循环结束,意味着RunLoop也结束了。这些条件包括source被处理且要求source被处理后RunLoop需要被停下来,RunLoop超时,RunLoop的当前mode被停止,当前mode下的mode item为空了。__CFRunLoopServiceMachPort用来处理休眠及唤醒。 根据官网描述,每次RunLoop启动的时候,线程的RunLoop都会去处理一些待处理的事件,为注册的观察者产生通知。处理的顺序是特定的: 1.通知observers:进入RunLoop。 2.通知observers:即将处理timer。 3.通知observers:即将处理source0 4.处理就绪的source0,没有处理的话后续会进入休眠,处理的话不会休眠 5.如果当前是主线程的runloop,并且主线程有事件要处理,跳到第9步,也就是不会休眠;但是没有要处理的事件,也会进入休眠。 6.通知observers:线程即将休眠 7.使线程休眠,直到下列任一种事件发生
- source1事件发生
- timer触发
- RunLoop本身超时
- RunLoop被手动唤醒
8.通知observers:线程被唤醒 9.处理待处理的事件
- 如果用户定义的timer触发了,处理该timer并重启循环,转到2
- 如果当前是主线程的runloop,处理主线程的事件,来源只有5
- source1发生,处理该事件。(如果调用的是CFRunLoopRun,就转到2;调用的是CFRunLoopRunInMode,根据最后一个参数是结束循环还是跳到2继续循环)
- 如果该RunLoop被手动唤醒,并且没有超时,就重启循环,转到2
10.通知observers:RunLoop已退出
下面的图不一定准确