1. 概述
正常情况下,程序执行完代码,就会自动退出
int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@"执行任务");}return 0;}
但App程序不能这样。App启动后要保持运行状态,当用户使用某些功能,程序要执行相应代码作出反馈。所以我们需要一个机制,让程序不退出并随时处理事件,例如:
void run() {do {var message = get_next_message();process_message(message);} while (message != quit);}
这种模型通常被称作EventLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用,在有消息到来时立刻被唤醒
EventLoop在很多系统和框架里都有实现,例如:OSX/iOS里的RunLoop
RunLoop是一个事件处理循环,属于线程相关基础架构的一部分。它用来安排工作,并协调接收传入的事件
RunLoop是一个do..while循环,和普通循环的区别,在有工作的时候让线程保持忙碌,没有工作时让线程休眠
官方文档:RunLoop
推荐视频:RunLoop的线下分享,by @sunnyxx
推荐文章:深入理解RunLoop
1.1 RunLoop作用
保持程序的持续运行
处理
App中的各种事件(触摸、定时器、performSelector)节省
CPU资源,该做事就做事,该休息就休息
1.2 RunLoop API
OSX/iOS系统中,提供了NSRunLoop和CFRunLoopRef两个对象:
NSRunLoop来自Foundation框架,是基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的CFRunLoopRef来自CoreFoundation框架,它提供了C函数的API,这些API是线程安全的
1.3 RunLoop源码
CFRunLoopRef的代码是开源的,可以下载 CoreFoundation 源码进行查看:Swift开源后,苹果又维护了一个跨平台的 swift-corelibs-foundation 版本,和现有OC中的实现略不一样
2. RunLoop循环
Runloop在底层的封装为CFRunloop,一个do..while循环
void CFRunLoopRun(void) { /* DOES CALLOUT */int32_t result;do {// 1.0e10 : 科学计数 1*10^10result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);CHECK_FOR_FORK();} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);}
从表面看,RunLoop就是一个死循环,但其内部CFRunLoopRun→CFRunLoopRunSpecific→__CFRunLoopRun→__CFRunLoopServiceMachPort中,调用核心代码mach_msg函数,等待接受mach_port的消息。线程将进入休眠, 直到被下面某一个事件唤
所以,RunLoop并不是一个普通的死循环,它能保持程序的持续运行,但没事做的时候线程将进入休眠,从而节省CPU资源
3. 与线程的关系
苹果不允许直接创建RunLoop,它只提供了两个自动获取的函数:
// 主运行循环CFRunLoopRef mainRunloop = CFRunLoopGetMain();// 当前运行循环CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
2.1 主运行循环
进入CFRunLoopGetMain函数
CFRunLoopRef CFRunLoopGetMain(void) {CHECK_FOR_FORK();static CFRunLoopRef __main = NULL; // no retain neededif (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __main;}
- 调用
_CFRunLoopGet0函数,通过主线程查找
2.2 当前运行循环
进入CFRunLoopGetCurrent函数
CFRunLoopRef CFRunLoopGetCurrent(void) {CHECK_FOR_FORK();//优先TSD中查找CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);if (rl) return rl;//通过当前线程查找return _CFRunLoopGet0(pthread_self());}
- 优先
TSD中查找 - 调用
_CFRunLoopGet0函数,通过当前线程查找
2.3 _CFRunLoopGet0
进入_CFRunLoopGet0函数
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {if (pthread_equal(t, kNilPthreadT)) {//不存在,默认为主线程t = pthread_main_thread_np();}__CFSpinLock(&loopsLock);if (!__CFRunLoops) {__CFSpinUnlock(&loopsLock);//创建全局字典,标记为kCFAllocatorSystemDefaultCFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);//通过主线程 创建主运行循环CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());// dict : key value//利用dict,进行key-value绑定操作,线程和Runloop是一一对应的CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {CFRelease(dict);}CFRelease(mainLoop);__CFSpinLock(&loopsLock);}//通过其他线程获取RunloopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFSpinUnlock(&loopsLock);if (!loop) {//如果没有获取到,创建一个RunloopCFRunLoopRef newLoop = __CFRunLoopCreate(t);__CFSpinLock(&loopsLock);loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if (!loop) {//与线程进行key-value绑定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)) {_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);}}return loop;}
Runloop与线程一一对应RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候主线程的
RunLoop对象由系统自动创建,而子线程的RunLoop对象需要开发者主动创建RunLoop并不保证线程安全当前线程内部不能操作其他线程的
RunLoop对象

4. RunLoop结构
在CoreFoundation里,包含RunLoop的五个类:
CFRunLoopRefCFRunLoopModeRefCFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef
其中CFRunLoopModeRef类并没有对外暴露,只是通过CFRunLoopRef的接口进行了封装。它们的关系如下:
一个RunLoop包含多个Mode,每个Mode又包含多个Source/Timer/Observer
每次调用RunLoop的主函数时,只能指定其中一个Mode,这个Mode称之为CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入。这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
4.1 CFRunLoopRef
创建RunLoop,返回CFRunLoopRef类型的对象
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {CFRunLoopRef loop = NULL;CFRunLoopModeRef rlm;uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);if (NULL == loop) {return NULL;}(void)__CFRunLoopPushPerRunData(loop);__CFRunLoopLockInit(&loop->_lock);loop->_wakeUpPort = __CFPortAllocate();if (CFPORT_NULL == loop->_wakeUpPort) HALT;__CFRunLoopSetIgnoreWakeUps(loop);loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);loop->_commonModeItems = NULL;loop->_currentMode = NULL;loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);loop->_blocks_head = NULL;loop->_blocks_tail = NULL;loop->_counterpart = NULL;loop->_pthread = t;#if DEPLOYMENT_TARGET_WINDOWSloop->_winthread = GetCurrentThreadId();#elseloop->_winthread = 0;#endifrlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);if (NULL != rlm) __CFRunLoopModeUnlock(rlm);return loop;}
CFRunLoopRef也是一个对象,本质是__CFRunLoop结构体类型的指针
typedef struct __CFRunLoop * CFRunLoopRef;
__CFRunLoop结构体的定义:
struct __CFRunLoop {CFRuntimeBase _base;pthread_mutex_t _lock; /* locked for accessing mode list */__CFPort _wakeUpPort; // used for CFRunLoopWakeUpBoolean _unused;volatile _per_run_data *_perRunData; // reset for runs of the run looppthread_t _pthread;uint32_t _winthread;CFMutableSetRef _commonModes;CFMutableSetRef _commonModeItems;CFRunLoopModeRef _currentMode;CFMutableSetRef _modes;struct _block_item *_blocks_head;struct _block_item *_blocks_tail;CFTypeRef _counterpart;};
_commonModes和_commonModeItems都是集合类型,表示在Runloop中可能会对应多个
这里有个概念叫commonModes:
一个Mode可以将自己标记为Common属性,通过ModeName添加到RunLoop的commonModes中
每当RunLoop的内容发生变化时,RunLoop都会自动将commonModeItems里的Source/Observer/Timer同步到具有Common标记的所有Mode里
4.2 CFRunLoopMode
获取Runloop下的所有Modes
CFRunLoopRef lp = CFRunLoopGetCurrent();CFArrayRef modeArray= CFRunLoopCopyAllModes(lp);NSLog(@"modeArray == %@",modeArray);-------------------------//输出以下内容:modeArray == (UITrackingRunLoopMode,GSEventReceiveRunLoopMode,kCFRunLoopDefaultMode)
- 一个
RunLoop对应多个Modes
创建一个NSTimer,将其加入Runloop并运行
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);}];[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 添加到
RunLoop中,必须指定Mode,这证明Timer的运行依赖于RunLoop的Mode
获取当前Runloop正在运行的Mode
CFRunLoopRef lp = CFRunLoopGetCurrent();CFRunLoopMode mode = CFRunLoopCopyCurrentMode(lp);NSLog(@"mode == %@",mode);-------------------------//输出以下内容:mode == kCFRunLoopDefaultMode
4.2.1 类型
NSDefaultRunLoopMode:默认的Mode,正常情况下都在该Mode下NSConnectionReplyMode:将此模式与NSConnection对象结合使用来监视回复NSModalPanelRunLoopMode:使用这种模式来识别用于模态面板的事件NSEventTrackingRunLoopMode:使用该Mode跟踪来自用户交互的事件,例如:UITableView上下滑动NSRunLoopCommonModes:伪模式,该集合默认包括默认、模态和事件跟踪模式
Mode主要是用于指定RunLoop中事件优先级
官方文档中,共提及以上五种Mode类型。而iOS中,只公开暴露了NSDefaultRunLoopMode和NSRunLoopCommonModes两种
而NSRunLoopCommonModes是伪模式,本质是Mode的集合,包含NSDefaultRunLoopMode、NSEventTrackingRunLoopMode
4.2.2 结构
__CFRunLoopMode的结构体定义:
struct __CFRunLoopMode {CFRuntimeBase _base;pthread_mutex_t _lock; /* must have the run loop locked before locking this */CFStringRef _name;CFMutableSetRef _sources0;CFMutableSetRef _sources1;CFMutableArrayRef _observers;CFMutableArrayRef _timers;...};
4.2.3 作用
主线程的RunLoop里有两个预置的Mode:kCFRunLoopDefaultMode和UITrackingRunLoopMode。DefaultMode是App平时所处的状态,TrackingRunLoopMode是追踪ScrollView滑动时的状态
当创建一个Timer并加到DefaultMode时,Timer会得到重复回调,但此时滑动一个TableView时,RunLoop会将mode切换为TrackingRunLoopMode,这时Timer就不会被回调,并且也不会影响到滑动操作
如果Timer需要在两个Mode中都能得到回调:
【方式一】将这个
Timer分别加入这两个Mode【方式二】将
Timer加入到顶层的RunLoop的kCFRunLoopCommonModes中,commonModeItems会被RunLoop自动更新到所有具有Common属性的Mode里去
4.2.4 API
CFRunLoop对外暴露的管理Mode的API:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);CFRunLoopRunInMode(CFStringRef modeName, ...);
只能通过modeName来操作内部的mode。当传入一个新的modeName,但RunLoop内部没有对应mode时,RunLoop会自动帮你创建对应的CFRunLoopModeRef
对于一个RunLoop来说,其内部的mode只能增加不能删除
4.3 Items
Source/Timer/Observer被统称item,一个item可以被同时加入多个mode。但一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,则RunLoop会直接退出,不进入循环
Mode暴露的管理item的API:
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
4.3.1 CFRunLoopSourceRef
CFRunLoopSourceRef是事件产生的地方。Source分为Source0和Source1两种:
Source0:表示非系统事件,即用户自定义的事件只包含了一个函数指针,它并不能主动触发事件
使用时,需要先调用
CFRunLoopSourceSignal(source),将Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件
Source1:表示系统事件,主要负责底层的通讯,具备唤醒能力由
RunLoop和内核管理,Mach port驱动包含了一个
mach_port和一个函数指针,被用于通过内核和其他线程相互发送消息这种
Source能主动唤醒RunLoop的线程
__CFRunLoopSource的定义:
struct __CFRunLoopSource {CFRuntimeBase _base;uint32_t _bits;pthread_mutex_t _lock;CFIndex _order; /* immutable */CFMutableBagRef _runLoops;union {CFRunLoopSourceContext version0; /* immutable, except invalidation */CFRunLoopSourceContext1 version1; /* immutable, except invalidation */} _context;};
Source0和Source1在联合体中,实际使用中为其中的一种
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;
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);#elsevoid * (*getPort)(void *info);void (*perform)(void *info);#endif} CFRunLoopSourceContext1;
4.3.2 CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器,包含一个时间长度和一个函数指针。当其加入到RunLoop时,RunLoop会注册对应的时间点。当时间点到时,RunLoop会被唤醒以执行那个回调
__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.3.3 CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,每个Observer都包含了一个函数指针,当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化
__CFRunLoopObserver的定义:
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 */};
可监听以下几种RunLoop的状态变化:
/* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {//进入kCFRunLoopEntry = (1UL << 0),//即将处理TimerskCFRunLoopBeforeTimers = (1UL << 1),//即将处理SourceskCFRunLoopBeforeSources = (1UL << 2),//进入休眠kCFRunLoopBeforeWaiting = (1UL << 5),//被唤醒kCFRunLoopAfterWaiting = (1UL << 6),//退出kCFRunLoopExit = (1UL << 7),kCFRunLoopAllActivities = 0x0FFFFFFFU};
5. 事务处理
Runloop可处理以下事务类型:
Block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__调用
timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__响应
source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__响应
source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
5.1 Timer的示例
创建CFRunLoopTimer,将其加入到Runloop并执行
- (void)cfTimerDemo{CFRunLoopTimerContext context = {0,((__bridge void *)self),NULL,NULL,NULL};CFRunLoopRef rlp = CFRunLoopGetCurrent();/**参数一:用于分配对象的内存参数二:在什么是触发 (距离现在)参数三:每隔多少时间触发一次参数四:未来参数参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0参数六:回调,比如触发事件,我就会来到这里参数七:上下文记录信息*/CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context);CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);}void lgRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){NSLog(@"%@---%@",timer,info);}
进入CFRunLoopAddTimer函数
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {CHECK_FOR_FORK();if (__CFRunLoopIsDeallocating(rl)) return;if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;__CFRunLoopLock(rl);//匹配kCFRunLoopCommonModes,伪模式,集合类型if (modeName == kCFRunLoopCommonModes) {//获取集合CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;//获取RunLoop下的事务if (NULL == rl->_commonModeItems) {//为空需要重新创建rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);}//将传入的CFRunLoopTimer参数,加入到ItemsCFSetAddValue(rl->_commonModeItems, rlt);//集合不为空if (NULL != set) {CFTypeRef context[2] = {rl, rlt};/* add new item to all common-modes *///设置回调函数,添加到common-modes中CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);CFRelease(set);}} else {//如果是其他类型,通过名字寻址ModeCFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);if (NULL != rlm) {if (NULL == rlm->_timers) {CFArrayCallBacks cb = kCFTypeArrayCallBacks;cb.equal = NULL;rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);}}if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {__CFRunLoopTimerLock(rlt);if (NULL == rlt->_runLoop) {rlt->_runLoop = rl;} else if (rl != rlt->_runLoop) {__CFRunLoopTimerUnlock(rlt);__CFRunLoopModeUnlock(rlm);__CFRunLoopUnlock(rl);return;}//如果匹配,将Runloop加进去,执行依赖于runloop runCFSetAddValue(rlt->_rlModes, rlm->_name);__CFRunLoopTimerUnlock(rlt);__CFRunLoopTimerFireTSRLock();__CFRepositionTimerInMode(rlm, rlt, false);__CFRunLoopTimerFireTSRUnlock();if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {// Normally we don't do this on behalf of clients, but for// backwards compatibility due to the change in timer handling...if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);}}if (NULL != rlm) {__CFRunLoopModeUnlock(rlm);}}__CFRunLoopUnlock(rl);}
在CFRunLoopAddTimer源码中,没有找到执行事务的函数
- 所以,真正事务的执行,依赖于
runloop run
5.2 __CFRunLoopRun
进入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);}
- 通过
CFRunLoopRunSpecific函数,得到result结果 1.0e10:科学计数法,1 * 10 ^ 10
进入CFRunLoopRunSpecific函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */CHECK_FOR_FORK();if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;__CFRunLoopLock(rl);//根据modeName找到本次运行的modeCFRunLoopModeRef 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);//取上一次运行的modeCFRunLoopModeRef previousMode = rl->_currentMode;//如果本次mode和上次的mode一致rl->_currentMode = currentMode;//初始化一个result为kCFRunLoopRunFinishedint32_t result = kCFRunLoopRunFinished;if (currentMode->_observerMask & kCFRunLoopEntry )/// 1. 通知 Observers: RunLoop 即将进入 loop。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);/// 真正做事情的代码,调用__CFRunLoopRun函数result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);if (currentMode->_observerMask & kCFRunLoopExit )/// 10. 通知 Observers: RunLoop 即将退出。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);__CFRunLoopModeUnlock(currentMode);__CFRunLoopPopPerRunData(rl, previousPerRun);rl->_currentMode = previousMode;__CFRunLoopUnlock(rl);return result;}
进入__CFRunLoopRun函数
- 每一种类型都有特定的处理函数调用
5.3 __CFRunLoopDoTimer
进入__CFRunLoopDoTimers函数
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */Boolean timerHandled = false;CFMutableArrayRef timers = NULL;//程序中正在运行的timer可能不止一个for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {//遍历timer,从Mode中获取CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {if (rlt->_fireTSR <= limitTSR) {if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);CFArrayAppendValue(timers, rlt);}}}for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);//执行timerBoolean did = __CFRunLoopDoTimer(rl, rlm, rlt);timerHandled = timerHandled || did;}if (timers) CFRelease(timers);return timerHandled;}
进入__CFRunLoopDoTimer函数
- 进行
timer的回调
5.4 处理流程
通过
CFRunLoopAddTimer将timer加入到指定Mode中事务的执行依赖于
runloop run- 调用
CFRunLoopRun→CFRunLoopRunSpecific函数,在Runloop进入和离开之间,调用__CFRunLoopRun函数
- 调用
__CFRunLoopRun中,包含对Observers、Source0、Source1、Timer的逻辑处理针对
timer的处理,调用__CFRunLoopDoTimers,遍历当前正在运行的timer针对单个
timer,调用__CFRunLoopDoTimer函数,执行事务的处理
RunLoop事务处理的流程图:
6. 底层原理
6.1 CFRunLoopRunSpecific伪代码
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */CHECK_FOR_FORK();if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;__CFRunLoopLock(rl);/// 首先根据modeName找到对应modeCFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);/// 1.通知 Observers: RunLoop 即将进入 loop。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);/// 内部函数,进入loopresult = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);/// 10.通知 Observers: RunLoop 即将退出。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);return result;}
6.2 __CFRunLoopRun伪代码
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {int32_t retVal = 0;do { // itmes do/// 2.通知 Observers: 即将处理timer事件__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);/// 3.通知 Observers: 即将处理Source事件__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)/// 处理Blocks__CFRunLoopDoBlocks(rl, rlm);/// 4.处理sources0Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);/// 处理sources0返回为YESif (sourceHandledThisLoop) {/// 处理Blocks__CFRunLoopDoBlocks(rl, rlm);}/// 5.判断有无端口消息(Source1)if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {/// 如果有Source1,跳到第9步,处理消息goto handle_msg;}/// 6.通知 Observers: 即将进入休眠__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);__CFRunLoopSetSleeping(rl);/// 7.等待被唤醒__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);// user callouts now OK again__CFRunLoopUnsetSleeping(rl);/// 8.通知 Observers: 被唤醒,结束休眠__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);/// 9.处理唤醒时收到的消息handle_msg:if (被Timer唤醒) {/// 处理Timers__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());} else if (被GCD唤醒) {/// 处理gcd__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);} else if (被Source1唤醒) {/// 被Source1唤醒,处理Source1__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)}/// 处理block__CFRunLoopDoBlocks(rl, rlm);if (sourceHandledThisLoop && stopAfterHandle) {/// 进入loop时参数说处理完事件就返回retVal = kCFRunLoopRunHandledSource;} else if (timeout_context->termTSR < mach_absolute_time()) {/// 超出传入参数标记的超时时间了retVal = kCFRunLoopRunTimedOut;} else if (__CFRunLoopIsStopped(rl)) {/// 被外部调用者强制停止了__CFRunLoopUnsetStopped(rl);retVal = kCFRunLoopRunStopped;} else if (rlm->_stopped) {rlm->_stopped = false;retVal = kCFRunLoopRunStopped;} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {/// source/timer/observer一个都没有了retVal = kCFRunLoopRunFinished;}/// 如果没超时,mode里没空,loop也没被停止,那继续loop。} while (0 == retVal);return retVal;}
6.3 流程图

7. RunLoop实战应用
7.1 线程保活
在iOS开发中,有些耗时操作会阻塞主线程,导致界面卡顿,此时我们会创建一个子线程,而后把这些耗时的操作放在子线程中处理
一个常规线程,任务执行完后就会销毁。如果耗时操作经常需要在子线程中执行,频繁创建和销毁线程,会造成资源浪费。 这时我们要用一种方式,让该线程长时间存活而不被销毁
线程保活的三种方式:
NSTimer条件锁(
NSCondition)RunLoop(Port)
7.1.1 NSTimer
@interface ViewController ()@property (nonatomic, assign) BOOL isStopping;@property (nonatomic, strong) LGThread *thread;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];_thread = [[LGThread alloc] initWithBlock:^{[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {NSLog(@"hello");if (self.isStopping) {// 先将对象置为nilself.thread = nil;// 再退出线程,RunLoop也停止了[NSThread exit];}}];[[NSRunLoop currentRunLoop] run];}];self.thread.name = @"LG";[self.thread start];}-(void)viewDidAppear:(BOOL)animated{[super viewDidAppear:animated];//为常驻线程添加耗时任务[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];}-(void)test{NSLog(@"耗时任务开始");sleep(5);NSLog(@"耗时任务结束");}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{self.isStopping = YES;}@end
7.1.2 条件锁(NSCondition)
@interface ViewController ()@property (nonatomic, assign) BOOL isStopping;@property (nonatomic, strong) LGThread *thread;@property (nonatomic, strong) NSCondition *condition;@property (nonatomic, strong) void(^blockTask)(void);@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];_thread = [[LGThread alloc] initWithBlock:^{@autoreleasepool {do {NSLog(@"hello");[self.condition lock];if(self.blockTask){self.blockTask();self.blockTask = nil;}NSLog(@"闲等...");[self.condition wait];[self.condition unlock];} while (!self.isStopping);//将对象置为nilself.thread = nil;}}];self.thread.name = @"LG";[self.thread start];}-(void)viewDidAppear:(BOOL)animated{[super viewDidAppear:animated];//为常驻线程添加耗时任务self.blockTask = ^(void){NSLog(@"耗时任务开始");sleep(5);NSLog(@"耗时任务结束");};//通知来任务了,唤醒条件锁[self.condition signal];}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{self.isStopping = YES;[self.condition signal];}- (NSCondition *)condition {if(!_condition){_condition = [[NSCondition alloc] init];}return _condition;}@end
7.1.3 RunLoop(Port)
@interface ViewController ()@property (nonatomic, assign) BOOL isStopping;@property (nonatomic, strong) LGThread *thread;@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];_thread = [[LGThread alloc] initWithBlock:^{NSRunLoop *loop = [NSRunLoop currentRunLoop];[loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];while (!self.isStopping) {[loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];}self.thread = nil;}];self.thread.name = @"LG";[self.thread start];}-(void)viewDidAppear:(BOOL)animated{[super viewDidAppear:animated];//为常驻线程添加耗时任务[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];}-(void)test{NSLog(@"耗时任务开始");sleep(5);NSLog(@"耗时任务结束");}- (void)exitThread{self.isStopping = YES;}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{[self performSelector:@selector(exitThread) onThread:self.thread withObject:nil waitUntilDone:NO];}@end
这种方式,需要注意以下几点:
- 在子线程中创建的
RunLoop,必须添加Timer、Source0、Source1中的其中一项。如果都不存在,RunLoop无法运行。但是主RunLoop则不同,即使都不存在也会持续运行

addPort本质上就是添加了一个Source1,它能主动唤醒RunLoop的线程。Source1的存储采用字典,以当前Source1 Prot为key,CFRunLoopSourceRef为value。而Source0直接使用数组存储,它并不能主动触发事件

- 需要使用
runMode:方法运行RunLoop,不能使用run方法。后者是永久循环,无法退出

- 使用
runMode:方法,传入的Mode为NSDefaultRunLoopMode,不能传入NSRunLoopCommonModes。后者会被标记为kCFRunLoopRunFinished,导致RunLoop直接退出

7.2 App回光返照
+ (void)installUncaughtExceptionHandler {NSSetUncaughtExceptionHandler(&LGExceptionHandlers);}- (void)lg_handleException:(NSException *)exception{//处理报错信息,可以写入沙盒文件,下次启动时上传服务器[self validateAndSaveCriticalApplicationData:exception];...while (!self.dismissed) {for (NSString *mode in (__bridge NSArray *)allModes) {//快速切换ModeCFRunLoopRunInMode((CFStringRef)mode, 0.001, false);}}CFRelease(allModes);NSSetUncaughtExceptionHandler(NULL);}
系统提供的
NSSetUncaughtExceptionHandler函数,传入LGExceptionHandlers函数地址当出现
Crash,会自动触发LGExceptionHandlers回调函数在回调函数中,拿到当前
RunLoop,监听所有Mode
这种方式,相当于应用程序自启的Runloop的平行空间,跟着应用程序保活,并具备响应能力,也就是App的回光返照
7.3 卡顿检测
通过监听主RunLoop的事务变化进行卡顿检测
- (void)start{[self registerObserver];[self startMonitor];}static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){LGBlockMonitor *monitor = (__bridge LGBlockMonitor *)info;monitor->activity = activity;// 发送信号dispatch_semaphore_t semaphore = monitor->_semaphore;dispatch_semaphore_signal(semaphore);}- (void)registerObserver{CFRunLoopObserverContext context = {0, (__bridge void*)self, NULL, NULL};//NSIntegerMax : 优先级最小CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,NSIntegerMax,&CallBack,&context);CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);}- (void)startMonitor{// 创建信号_semaphore = dispatch_semaphore_create(0);// 在子线程监控时长dispatch_async(dispatch_get_global_queue(0, 0), ^{while (YES){// 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));if (st != 0){if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting){if (++self->_timeoutCount < 2){NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);continue;}// 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!NSLog(@"检测到超过两次连续卡顿");}}self->_timeoutCount = 0;}});}
定义信号量,在全局并发队列中,加入异步函数,创建
while死循环,内部让信号量进入休眠状态,定义一秒的超时时间监听主
RunLoop的所有事务,在回调方法中,对信号量发送释放的通知如果信号量时间,检查
Observer的状态。如果是处理Sources或处于唤醒状态,证明还在做事情,将超时次数+1如果连续两次,视为卡顿
解决方式:
可以考虑将部分业务代码,在Observer回调函数中,RunLoop处于kCFRunLoopBeforeWaiting状态时运行。因为RunLoop进入即将休眠状态时,此时没有其他任务,CPU等资源相对空闲。这种方式相当于代码的错峰执行,可以对卡顿有一定程度的优化
总结
RunLoop作用:
保持程序的持续运行
处理
App中的各种事件(触摸、定时器、performSelector)节省
CPU资源,该做事就做事,该休息就休息
与线程的关系:
RunLoop与线程一一对应RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候主线程的
RunLoop对象由系统自动创建,而子线程的RunLoop对象需要开发者主动创建RunLoop并不保证线程安全当前线程内部不能操作其他线程的
RunLoop对象
结构:
线程与
RunLoop一对一一个
RunLoop对应多个Modes一个
Mode对应多个ItemsSource、Timer、Observer统称Item
Mode:
Mode主要是用于指定RunLoop中事件优先级NSDefaultRunLoopMode:默认的Mode,正常情况下都在该Mode下NSConnectionReplyMode:将此模式与NSConnection对象结合使用来监视回复NSModalPanelRunLoopMode:使用这种模式来识别用于模态面板的事件NSEventTrackingRunLoopMode:使用该Mode跟踪来自用户交互的事件,例如:UITableView上下滑动NSRunLoopCommonModes:伪模式,该集合默认包括默认、事件跟踪模式
Runloop可处理以下事务(Item)类型:
Block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__调用
timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__响应
source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__响应
source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
Source:
可以唤醒
RunLoop的一些事件Source0:表示非系统事件,即用户自定义的事件Source1:表示系统事件,主要负责底层的通讯,具备唤醒能力
Timer:
- 常用
NSTimer定时器
Observer:
用于监听
RunLoop的状态变化,并作出一定响应kCFRunLoopEntry:进入kCFRunLoopBeforeTimers:即将处理TimerskCFRunLoopBeforeSources:即将处理SourceskCFRunLoopBeforeWaiting:进入休眠kCFRunLoopAfterWaiting:被唤醒kCFRunLoopExit:退出
事务处理流程:
以
timer为例,通过CFRunLoopAddTimer将timer加入到指定Mode中事务的执行依赖于
runloop run__CFRunLoopRun中,包含对Observers、Source0、Source1、Timer的逻辑处理
底层原理:
【第一步】通知
Observer即将进入Loop(Observer)【第二步】通知
Observer将要处理Timer(Observer)【第三步】通知
Observer将要处理Source0(Observer)【第四步】处理
Source0(Source0)【第五步】如果有
Source1,跳到【第九步】(Source1)【第六步】通知
Observer线程即将休眠(Observer)【第七步】休眠,等待唤醒。可通过
Source1(port)、Timer、外部手动唤醒【第八步】通过
Observer线程刚被唤醒(Observer)【第九步】处理唤醒时收到的消息,之后跳回【第二步】(
Timer、Source1)【第十步】通知
Observer即将退出Loop(Observer)
