RunLoop总结:RunLoop基础知识
没有实际应用场景,很难理解一些抽象空洞的东西,所以前面几篇文章先介绍了RunLoop的几个使用场景。
另外AsyncDisplayKit中也有大量使用RunLoop的示例。
关于实际的使用RunLoop的案例和使用场景就不总结了,今天总结一点RunLoop的基础知识和概念。
什么是RunLoop?
顾名思义,它就是一个运行循环。一个RunLoop就是一个用于处理既定工作和接收到的外来事件的事件处理循环。RunLoop的存在目的就是当线程中有任务时,保证线程忙着干活;当线程中没有任务时,让线程睡眠,以节省资料(想想看,你是在房间里一直转圈抗饿还是躺在床上睡觉更抗饿?)。
理解了EventLoop就能很好的理解RunLoop了。
简单的用伪代码来表示就是这样的:
functionloop(){
initialize();
while(message!=quit){
varmessage=get_next_message();
process_message(message);
}
}
关于RunLoop,苹果的Cocoa和CoreFoundation框架都分别提供了NSRunLoop和CFRunLoopRef供开发者调用和执行操作。CFRunLoopRef只是一个结构体,而NSRunLoop是一个NSObject对象,必然是苹果将CFRunLoopRef进行了封装。
需要注意的是NSRunLoop并不是线程安全的,而CFRunLoopRef是线程安全的。
官方文档原文是:
ThreadsafetyvariesdependingonwhichAPIyouareusingtomanipulateyourrunloop.
ThefunctionsinCoreFoundationaregenerallythread-safeandcanbecalledfromanythread.
Ifyouareperformingoperationsthataltertheconfigurationoftherunloop,however,
itisstillgoodpracticetodosofromthethreadthatownstherunloopwheneverpossible.
TheCocoaNSRunLoopclassisnotasinherentlythreadsafeasitsCoreFoundationcounterpart.
IfyouareusingtheNSRunLoopclasstomodifyyourrunloop,youshoulddosoonlyfromthesamethreadthatownsthatrunloop.
Addinganinputsourceortimertoarunloopbelongingtoadifferentthreadcouldcauseyourcodetocrashorbehaveinanunexpectedway.
接下来,看一下CFRunLoopRef里都保存了哪些数据?
可以从CF框架源码的CFRunLoop.h和CFRunLoop.c,看看苹果对CFRunLoopRef的定义。
CFRunLoopRef是结构体__CFRunLoop的重命名,由typedefstruct__CFRunLoopCFRunLoopRef;可知;
__CFRunLoop的定义:
struct__CFRunLoop{
CFRuntimeBase_base;
pthread_mutex_t_lock;/lockedforaccessingmodelist,每次读取modelist要加锁/
__CFPort_wakeUpPort;//usedforCFRunLoopWakeUp
Boolean_unused;
volatile_per_run_data_perRunData;//resetforrunsoftherunloop
pthread_t_pthread;//与该runLoop关联的线程
uint32_t_winthread;
CFMutableSetRef_commonModes;//set中保存的就是NSRunLoopCommonModes表示的mode,我们也可以将自定义的mode添加到这个set里。
CFMutableSetRef_commonModeItems;//添加到NSRunLoopCommonModes中的source/timer等item都会被添加到这个set里,这在应用场景一中有打印出来。
CFRunLoopModeRef_currentMode;//RunLoop当前执行的是哪个mode
CFMutableSetRef_modes;//该runLoop中所有的mode
struct_block_item_blocks_head;
struct_block_item_blocks_tail;
CFAbsoluteTime_runTime;
CFAbsoluteTime_sleepTime;
CFTypeRef_counterpart;
再来看一下RunLoopMode的结构,之前说过RunLoopMode中存放的是两种source/timer/observer,而CFRunLoopModeRef是struct__CFRunLoopMode重命名的(typedefstruct__CFRunLoopModeCFRunLoopModeRef;),看下定义就明白了:
struct__CFRunLoopMode{
CFRuntimeBase_base;
pthread_mutex_t_lock;/musthavetherunlooplockedbeforelockingthis/
CFStringRef_name;//mode的name
Boolean_stopped;
char_padding[3];
CFMutableSetRef_sources0;//保存所有source0的set
CFMutableSetRef_sources1;//保存所有source1的set
CFMutableArrayRef_observers;//保存所有observer的数组
CFMutableArrayRef_timers;//保存所有timer的数组
CFMutableDictionaryRef_portToV1SourceMap;
__CFPortSet_portSet;
CFIndex_observerMask;
#ifUSE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t_timerSource;
dispatch_queue_t_queue;
Boolean_timerFired;//settotruebythesourcewhenatimerhasfired
Boolean_dispatchTimerArmed;
#endif
#ifUSE_MK_TIMER_TOO
mach_port_t_timerPort;
Boolean_mkTimerArmed;
#endif
#ifDEPLOYMENT_TARGET_WINDOWS
DWORD_msgQMask;
void(_msgPump)(void);
#endif
uint64_t_timerSoftDeadline;/TSR/
uint64_t_timerHardDeadline;/TSR/
};
看完上面__CFRunLoopMode和__CFRunLoop的定义,关于RunLoop中保存的是RunLoopMode,而RunLoopMode中保存的才是实际的任务这点没有疑问了。
如何创建一个RunLoop?
包括MainRunLoop在内,每一个RunLoop都与一个线程关联着。确切的说,是先有线程,再有RunLoop。
关于线程与RunLoop的关系,在RunLoop官方文档的第一节讲的很清楚。
我们不用,也最好不要显示的创建RunLoop,苹果提供了两个API,便于我们来获取RunLoop。
CFRunLoopGetMain()和CFRunLoopGetCurrent(),分别用于获取MainRunLoop和当前线程的RunLoop(在主线程中调用CFRunLoopGetCurrent()与CFRunLoopGetMain()获取的其实都是MainRunLoop)。
先来看一下,这两个函数的源码实现:
CFRunLoopRefCFRunLoopGetMain(void){
CHECK_FOR_FORK();
staticCFRunLoopRef__main=NULL;//noretainneeded
//通过_CFRunLoopGet0这个关键函数,取出MainRunLoop。
if(!__main)__main=_CFRunLoopGet0(pthread_main_thread_np());//noCASneeded
return__main;
}
CFRunLoopRefCFRunLoopGetCurrent(void){
CHECK_FOR_FORK();
CFRunLoopRefrl=(CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if(rl)returnrl;
//通过_CFRunLoopGet0这个关键函数,取出当前RunLoop。
return_CFRunLoopGet0(pthread_self());
}
从以上源码,可以看出RunLoop是通过_CFRunLoopGet0函数来获取的,并且以线程作为参数。
这个函数的作用与通过key从NSDictionary获取Value极为相似。
接下来,看一下_CFRunLoopGet0的实现(太长不想看,可以看下面的伪代码):
staticCFMutableDictionaryRef__CFRunLoops=NULL;
staticCFLock_tloopsLock=CFLockInit;
//shouldonlybecalledbyFoundation
//t==0isasynonymfor"mainthread"thatalwaysworks
CF_EXPORTCFRunLoopRef_CFRunLoopGet0(pthread_tt){
if(pthread_equal(t,kNilPthreadT)){
t=pthread_main_thread_np();
}
__CFLock(&loopsLock);
if(!__CFRunLoops){
__CFUnlock(&loopsLock);
CFMutableDictionaryRefdict=CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0,NULL,&kCFTypeDictionaryValueCallBacks);
CFRunLoopRefmainLoop=__CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict,pthreadPointer(pthread_main_thread_np()),mainLoop);
if(!OSAtomicCompareAndSwapPtrBarrier(NULL,dict,(voidvolatile)&__CFRunLoops)){
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
CFRunLoopRefloop=(CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,pthreadPointer(t));
__CFUnlock(&loopsLock);
if(!loop){
CFRunLoopRefnewLoop=__CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop=(CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,pthreadPointer(t));
if(!loop){
CFDictionarySetValue(__CFRunLoops,pthreadPointer(t),newLoop);
loop=newLoop;
}
//don''treleaserunloopsinsidetheloopsLock,becauseCFRunLoopDeallocatemayenduptakingit
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if(pthread_equal(t,pthread_self())){
_CFSetTSD(__CFTSDKeyRunLoop,(void)loop,NULL);
if(0==_CFGetTSD(__CFTSDKeyRunLoopCntr)){
_CFSetTSD(__CFTSDKeywww.tt951.comRunLoopCntr,(void)(PTHREAD_DESTRUCTOR_ITERATIONS-1),(void()(void))__CFFinalizeRunLoop);
}
}
returnloop;
如果上面的源码看不懂,那就来看一下简化后的伪代码:
///全局的Dictionary,key是pthread_t,value是CFRunLoopRef
staticCFMutableDictionaryRefloopsDic;
///访问loopsDic时的锁
staticCFSpinLock_tloopsLock;
///获取一个pthread对应的RunLoop。
CFRunLoopRef_CFRunLoopGet(pthread_tthread){
OSSpinLockLock(&loopsLock);
if(!loopsDic){
//第一次进入时,初始化全局Dic,并先为主线程创建一个RunLoop。
loopsDic=CFDictionaryCreateMutable();
CFRunLoopRefmainLoop=_CFRunLoopCreate();
CFDictionarySetValue(loopsDic,pthread_main_thread_np(),mainLoop);
}
///直接从Dictionary里获取。
CFRunLoopRefloop=CFDictionaryGetValue(loopsDic,thread));
if(!loop){
///取不到时,创建一个,一定要传一个线程参数
loop=_CFRunLoopCreate(thread);
CFDictionarySetValue(loopsDic,thread,loop);
///注册一个回调,当线程销毁时,顺便也销毁其对应的RunLoop。
_CFSetTSD(...,thread,loop,__CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
returnloop;
大致过程,获取某个线程的RunLoop,首先以线程作为key,从全局字典中找,如果没找到,则新建一个,并以线程为key,RunLoop为Value存到全局字典中(如果全局字典不存在,就先初始化全局字典,并新建一个MainRunLoop保存到全局字典中)。
我们自己在使用RunLoop时,可能比较多的是用NSRunLoop,所以与此相关的API其实就两个:
//往RunLoop的Mode中添加一个timer
-(void)addTimer:(NSTimer)timerforMode:(NSRunLoopMode)mode;
//往RunLoop的Mode中添加一个source1任务
-(void)addPort:(NSPort)aPortforMode:(NSRunLoopMode)mode;
//从RunLoop的Mode里删除source1任务
-(void)removePort:(NSPort)aPortforMode:(NSRunLoopMode)mode;
如果使用CFRunLoopRef,那么常用的API也就多了几个而已:
CFRunLoopAddSource(CFRunLoopRefrl,CFRunLoopSourceRefsource,CFStringRefmodeName);
CFRunLoopAddObserver(CFRunLoopRefrl,CFRunLoopObserverRefobserver,CFStringRefmodeName);
CFRunLoopAddTimer(CFRunLoopRefrl,CFRunLoopTimerReftimer,CFStringRefmode);
CFRunLoopRemoveSource(CFRunLoopRefrl,CFRunLoopSourceRefsource,CFStringRefmodeName);
CFRunLoopRemoveObserver(CFRunLoopRefrl,CFRunLoopObserverRefobserver,CFStringRefmodeName);
CFRunLoopRemoveTimer(CFRunLoopRefrl,CFRunLoopTimerReftimer,CFStringRefmode);
苹果公开提供的Mode有两个:kCFRunLoopDefaultMode(NSDefaultRunLoopMode)和UITrackingRunLoopMode,你可以用这两个ModeName来操作其对应的Mode。
如果要操作多个Mode,我们可以使用kCFRunLoopCommonModes(NSRunLoopCommonModes),关于CommonModes中包含哪几个具体的mode,可以参考RunLoop官方文档的RunLoopModes一节。
当然我们可以把自定义的Mode添加都CommonModes中,可以使用如下的API来操作:
CFRunLoopAddCommonMode(CFRunLoopRefrunloop,CFStringRefmodeName);
接下来就是重点了,RunLoop是内部是如何来执行任务的?
CFRunLoopRun和CFRunLoopRunInMode内部都调用了CFRunLoopRunSpecific。而CFRunLoopRunSpecific内部又调用了__CFRunLoopRun,CFRunLoopRunSpecific和__CFRunLoopRun合起来就是RunLoop的完整实现了。
RunLoop官方文档的TheRunLoopSequenceofEvents一节介绍了RunLoop执行的10个步骤,用中文图示就是这样的:
来自ibireme的博客
我们可以对着伪代码和CF框架源码、RunLoop官方文档的TheRunLoopSequenceofEvents一节对比着看,RunLoop内部逻辑的伪代码如下:
///RunLoop的实现
intCFRunLoopRunSpecific(runloop,modeName,seconds,stopAfterHandle){
///首先根据modeName找到对应mode
CFRunLoopModeRefcurrentMode=__CFRunLoopFindMode(runloop,modeName,false);
///如果mode里没有source/timer/observer,直接返回。
if(__CFRunLoopModeIsEmpty(currentMode))return;
///1.通知Observers:RunLoop即将进入loop。
__CFRunLoopDoObservers(runloop,currentMode,kCFRunLoopEntry);
///内部函数,进入loop
__CFRunLoopRun(runloop,currentMode,seconds,returnAfterSourceHandled){
BooleansourceHandledThisLoop=NO;
intretVal=0;
do{
///2.通知Observers:RunLoop即将触发Timer回调。
__CFRunLoopDoObservers(runloop,currentMode,kCFRunLoopBeforeTimers);
///3.通知Observers:RunLoop即将触发Source0(非port)回调。
__CFRunLoopDoObservers(runloop,currentMode,kCFRunLoopBeforeSources);
///执行被加入的block
__CFRunLoopDoBlocks(runloop,currentMode);
///4.RunLoop触发Source0(非port)回调。
sourceHandledThisLoop=__CFRunLoopDoSources0(runloop,currentMode,stopAfwww.baiyuewang.netterHandle);
///执行被加入的block
__CFRunLoopDoBlocks(runloop,currentMode);
///5.如果有Source1(基于port)处于ready状态,直接处理这个Source1然后跳转去处理消息。
if(__Source0DidDispatchPortLastTime){
BooleanhasMsg=__CFRunLoopServiceMachPort(dispatchPort,&msg)
if(hasMsg)gotohandle_msg;
}
///通知Observers:RunLoop的线程即将进入休眠(sleep)。
if(!sourceHandledThisLoop){
__CFRunLoopDoObservers(runloop,currentMode,kCFRunLoopBeforeWaiting);
}
///7.调用mach_msg等待接受mach_port的消息。线程将进入休眠,直到被下面某一个事件唤醒。
///?一个基于port的Source的事件。
///?一个Timer到时间了
///?RunLoop自身的超时时间到了
///?被其他什么调用者手动唤醒
__CFRunLoopServiceMachPort(waitSet,&msg,sizeof(msg_buffer),&livePort){
mach_msg(msg,MACH_RCV_MSG,port);//threadwaitforreceivemsg
}
///8.通知Observers:RunLoop的线程刚刚被唤醒了。
__CFRunLoopDoObservers(runloop,currentMode,kCFRunLoopAfterWaiting);
///收到消息,处理消息。
handle_msg:
///9.1如果一个Timer到时间了,触发这个Timer的回调。
if(msg_is_timer){
__CFRunLoopDoTimers(runloop,currentMode,mach_absolute_time())
}
///9.2如果有dispatch到main_queue的block,执行block。
elseif(msg_is_dispatch){
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
///9.3如果一个Source1(基于port)发出事件了,处理这个事件
else{
CFRunLoopSourceRefsource1=__CFRunLoopModeFindSourceForMachPort(runloop,currentMode,livePort);
sourceHandledThisLoop=__CFRunLoopDoSource1(runloop,currentMode,source1,msg);
if(sourceHandledThisLoop){
mach_msg(reply,MACH_SEND_MSG,reply);
}
}
///执行加入到Loop的block
__CFRunLoopDoBlocks(runloop,currentMode);
if(sourceHandledThisLoop&&stopAfterHandle){
///进入loop时参数说处理完事件就返回。
retVal=kCFRunLoopRunHandledSource;
}elseif(timeout){
///超出传入参数标记的超时时间了
retVal=kCFRunLoopRunTimedOut;
}elseif(__CFRunLoopIsStopped(runloop)){
///被外部调用者强制停止了
retVal=kCFRunLoopRunStopped;
}elseif(__CFRunLoopModeIsEmpty(runloop,currentMode)){
///source/timer/observer一个都没有了
retVal=kCFRunLoopRunFinished;
}
///如果没超时,mode里没空,loop也没被停止,那继续loop。
}while(retVal==0);
}
///10.通知Observers:RunLoop即将退出。
__CFRunLoopDoObservers(rl,currentMode,kCFRunLoopExit);
}
上面的2、3、4、5、7,其实都是从__CFRunLoopRun中摘出来的。
|
|