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^10
result = 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 needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __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);
//创建全局字典,标记为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
对象
4. RunLoop结构
在CoreFoundation
里,包含RunLoop
的五个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
其中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_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;
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);
#else
void * (*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),
//即将处理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, 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参数,加入到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
函数,执行事务的处理
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) 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) {
// 先将对象置为nil
self.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);
//将对象置为nil
self.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) {
//快速切换Mode
CFRunLoopRunInMode((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
对应多个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
)【第十步】通知
Observer
即将退出Loop
(Observer
)