1、概述
-
不同于程序执行完代码就会自动退出,App 启动后要保持运行状态,当用户使用某些功能,程序要执行相应代码作出反馈;所以我们需要一个机制,让程序不退出并随时处理事件
void run() { do { var message = get_next_message(); process_message(message); } while (message != quit); }- 这种模型通常被称作
EventLoop;实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用,在有消息到来时立刻被唤醒
- 这种模型通常被称作
-
RunLoop就是在iOS中的 EventLoop,是一个事件处理循环,属于 线程相关 基础架构的一部分;它用来安排工作,并协调接收传入的事件 -
RunLoop 是一个
do..while循环,和普通循环的区别,在有工作的时候让线程保持忙碌,没有工作时让线程休眠 -
具体内容可参考 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是线程安全的
-
2、RunLoop循环
- Runloop 在底层的封装为
CFRunloop,是一个do..while循环void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { // 1.0e10 : 科学计数 1*10^10 result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } - RunLoop 不是一个常规的死循环
-
设置极大值 1.0e10 来表示 永远 运行
-
其内部流程:
CFRunLoopRun-->CFRunLoopRunSpecific-->__CFRunLoopRun-->__CFRunLoopServiceMachPort -
调用核心代码
mach_msg函数,等待接受mach_port的消息,线程将进入休眠, 直到被下面某一个事件唤
-
3、与线程的关系
- 苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:
// 主运行循环 CFRunLoopRef mainRunloop = CFRunLoopGetMain(); // 当前运行循环 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
3.1、主运行循环
CFRunLoopGetMain函数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; }- 调用
_CFRunLoopGet0函数,通过主线程查找
- 调用
3.2、当前运行循环
CFRunLoopGetCurrent函数CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); // 优先 TSD 中查找 CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; //通过当前线程查找 return _CFRunLoopGet0(pthread_self()); }- 优先
TSD中查找 - 调用 _CFRunLoopGet0 函数,通过当前线程查找
- 优先
3.3、_CFRunLoopGet0
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
// 不存在,默认为主线程
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&loopsLock);
// 创建全局字典,标记为kCFAllocatorSystemDefault
CFMutableDictionaryRef 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);
}
//通过其他线程获取Runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
// 如果没有获取到,创建一个Runloop
CFRunLoopRef 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并不保证线程安全
当前线程内不能操作其他线程的 RunLoop 对象
4、RunLoop结构
-
在 CoreFoundation 里,包含 RunLoop 的五个类:
CFRunLoopRefCFRunLoopModeRefCFRunLoopSourceRefCFRunLoopTimerRefCFRunLoopObserverRef
-
其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装;它们的关系如下:
-
1 个 RunLoop 包含 n 个 Mode,每个 Mode 又包含 n 个
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_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __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 CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
// 4个重要内容
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里
- 一个Mode可以将自己标记为
4.2、CFRunLoopMode
- 获取Runloop下的所有Modes
CFRunLoopRef lp = CFRunLoopGetCurrent(); CFArrayRef modeArray= CFRunLoopCopyAllModes(lp); NSLog(@"modeArray == %@",modeArray); ------------------------- //输出以下内容: modeArray == ( UITrackingRunLoopMode, GSEventReceiveRunLoopMode, kCFRunLoopDefaultMode )- 1个 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,这证明
4.2.1、类型
-
NSDefaultRunLoopMode:默认的Mode,正常情况下都在该Mode下 -
NSConnectionReplyMode:将此模式与NSConnection对象结合使用来 监视回复 -
NSModalPanelRunLoopMode:使用这种模式来识别用于 模态面板的事件 -
NSEventTrackingRunLoopMode:使用该Mode 跟踪来自用户交互事件,例如:UITableView上下滑动 -
NSRunLoopCommonModes:伪模式,该集合默认包括默认、模态和事件跟踪模式
-
Mode主要是用于
指定RunLoop中事件优先级 -
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会直接退出,不进入循环
4.3.1、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.2、CFRunLoopSourceRef
-
CFRunLoopSourceRef 是事件产生的地方;Source 分为
Source0和Source1两种:-
Source0:表示非系统事件,即
用户自定义的事件- 只包含了一个函数指针,它并不能主动触发事件
- 使用时,需要先调用
CFRunLoopSourceSignal(source),将Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件
-
Source1:表示
系统事件,主要负责底层的通讯,具备唤醒能力- 由RunLoop和内核管理,
Mach port驱动 - 包含了一个 mach_port 和一个函数指针,被用于通过内核和其他线程相互发送消息
- 这种Source
能主动唤醒RunLoop的线程
- 由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); #else void * (*getPort)(void *info); void (*perform)(void *info); #endif } CFRunLoopSourceContext1;
4.3.3、CFRunLoopTimerRef
-
CFRunLoopTimerRef 是基于时间的触发器,包含一个时间长度和一个函数指针,当其加入到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.4、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), //即将处理Timers kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Sources kCFRunLoopBeforeSources = (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, lRunLoopTimerCallBack, &context); CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode); } void lzRunLoopTimerCallBack(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参数,加入到Items CFSetAddValue(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 { //如果是其他类型,通过名字寻址Mode CFRunLoopModeRef 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 run CFSetAddValue(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找到本次运行的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和上次的mode一致 rl->_currentMode = currentMode; //初始化一个result为kCFRunLoopRunFinished int32_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); //执行timer Boolean 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函数,执行事务的处理
- 针对timer的处理,调用
- 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找到对应mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
/// 1.通知 Observers: RunLoop 即将进入 loop。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
/// 内部函数,进入loop
result = __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.处理sources0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
/// 处理sources0返回为YES
if (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) LZThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_thread = [[LZThread alloc] initWithBlock:^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"hello");
if (self.isStopping) {
// 先将对象置为nil
self.thread = nil;
// 再退出线程,RunLoop也停止了
[NSThread exit];
}
}];
[[NSRunLoop currentRunLoop] run];
}];
self.thread.name = @"LZ";
[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) LZThread *thread;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) void(^blockTask)(void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_thread = [[LZThread 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);
//将对象置为nil
self.thread = nil;
}
}];
self.thread.name = @"LZ";
[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) LZThread *thread;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_thread = [[LZThread 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 = @"LZ";
[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(&LZLGExceptionHandlers);
}
- (void)lz_handleException:(NSException *)exception{
//处理报错信息,可以写入沙盒文件,下次启动时上传服务器
[self validateAndSaveCriticalApplicationData:exception];
...
while (!self.dismissed) {
for (NSString *mode in (__bridge NSArray *)allModes) {
//快速切换Mode
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
CFRelease(allModes);
NSSetUncaughtExceptionHandler(NULL);
}
-
系统提供的
NSSetUncaughtExceptionHandler函数,传入LZExceptionHandlers函数地址 -
当出现
Crash,会 自动触发 LZExceptionHandlers回调函数 -
在回调函数中,拿到当前RunLoop,监听所有Mode
- 这种方式,相当于应用程序自启的Runloop的平行空间,跟着应用程序保活,并具备响应能力,也就是App的回光返照
7.3、卡顿检测
通过 监听主RunLoop的事务变化 进行卡顿检测
- (void)start{
[self registerObserver];
[self startMonitor];
}
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
LZLGBlockMonitor *monitor = (__bridge LZBlockMonitor *)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对应多个Items
- Source、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:即将处理Timers
- kCFRunLoopBeforeSources:即将处理Sources
- kCFRunLoopBeforeWaiting:进入休眠
- 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) 10.通知Observer即将退出Loop(Observer)