配色: 字号:
RunLoop总结:RunLoop基础知识
2016-12-21 | 阅:  转:  |  分享 
  
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中摘出来的。

献花(0)
+1
(本文系thedust79首藏)