RunLoop实际上是一个事件处理循环,被用做工做调度而且协调传入事件的接收。通常状况下,单条线程一次只能执行一个任务,执行完成以后线程就会退出,若是咱们但愿线程可以随时的处理事件而且不会退出,那么就在线程中开启一个RunLoop,RunLoop其实就是一个运行循环,它的主要目的是可以让线程在有工做的时候保持忙碌,在没有工做的时候进入休眠,这样作的好处就是让线程进入休眠以后避免资源占用。html
RunLoop的结构以下安全
RunLoop在循环过程当中处理定时器事件、performSelector事件、自定义源以及端口事件等等。bash
RunLoop的整个运行逻辑其实能够概括为如下代码markdown
function loop() { initialize(); do { //休眠中等待消息 var message = get_next_message(); //处理消息 process_message(message); } while (message != quit); } 复制代码
因而可知,其实RunLoop就是一个对象,它管理了须要进行处理的事件和消息,而且为线程提供了一个如上的入口函数,当线程执行了这个入口函数以后,就会进入一个“接收消息->休眠等待->处理消息”的循环之中,一直到循环结束。app
Cocoa和Core Foundation框架都提供了运行循环对象来帮助咱们配置和管理线程的运行循环:NSRunLoop和CFRunLoopRef框架
RunLoop的主要做用能够总结为如下几点:异步
运行循环其实不是彻底自动的,当咱们建立一个线程的时候须要在适当的时间启动运行循环而且响应传入的事件,上文中说到Cocoa和Core Foundation框架都提供了运行循环对象来帮助咱们配置和管理线程的运行循环,所以咱们不须要显式的建立运行循环对象,程序中的每一个线程,包括应用程序的主线程都有一个关联的RunLoop对象。可是,只有子线程须要显式的开启它们的运行循环。而主线程做为应用程序启动过程的一部分,应用程序框架会在主线程上自动设置而且运行RunLoop。函数
苹果的API为咱们提供了两种方法获取RunLoop对象oop
阅读CF源码,咱们能够获得获取RunLoop函数的内部逻辑以下性能
//获取线程对应的RunLoop,若是t=0表示获取主线程的RunLoop CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //若是当前线程为nil if (pthread_equal(t, kNilPthreadT)) { //获取主线程 t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (!__CFRunLoops) { __CFUnlock(&loopsLock); //第一次进入时,全局字典__CFRunLoops为nil,因此须要初始化全局dic CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); //首先为主线程建立一个RunLoop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); //将主线程RunLoop存入全局字典中 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } //根据线程t到全局字典__CFRunLoops中获取对应的RunLoop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { //若是不存在对应RunLoop,则为当前线程t新建一个RunLoop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { //而且将新建的RunLoop保存到全局字典中去 CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。 _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 复制代码
因而可知线程和RunLoop的关系以下
在Core Foundation框架中提供了关于RunLoop的5个类
它们之间的关系以下:
在一个RunLoop对象当中包含了多个Mode,而每一个Mode又包含了若干个Source0/Source1/Timer/Observer,RunLoop启动的时候只能指定其中的一个Mode,这个Mode被称为CurrentMode。若是须要切换Mode,只能先退出当前Loop,而后从新指定Mode再进入Loop。这样作的主要目的就是为了分割不一样组的Source0/Source1/Timer/Observer,让它们互不影响。
若是Mode中没有任何的Source0/Source1/Timer/Observer,那么RunLoop会当即退出。这里的Source0/Source1/Timer/Observer统称为一个Mode item。
CFRunLoopRef其实就是Core Foundation框架提供的RunLoop对象。
CFRunLoopModeRef其实就是就是多个Source0/Source1/Timer/Observer的集合。每次运行RunLoop循环时,都要指定特定的模式,在RunLoop循环过程中,只监视与该模式相关联的源,而且容许它们进行事件交付。相似的,也只有关联了该模式的观察者才能监听到RunLoop的状态变化。CFRunLoopModeRef的大体结构以下
struct __CFRunLoopMode { CFStringRef _name; //Mode名称 Boolean _stopped; char _padding[3]; CFMutableSetRef _sources0; //Source0 CFMutableSetRef _sources1; //Source1 CFMutableArrayRef _observers; //观察者集合 CFMutableArrayRef _timers; //定时器集合 ...... } struct __CFRunLoop { __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop CFMutableSetRef _commonModes; // 全部标记为Common的Mode的name集合 CFMutableSetRef _commonModeItems; // 全部被标记为Common的Source/Observer/Timer CFRunLoopModeRef _currentMode; // 当前RunLoop指定的Mode CFMutableSetRef _modes; // 全部Mode集合 ...... }; 复制代码
Mode中主要包含如下几种元素:
苹果提供了两种公开的Mode,kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和UITrackingRunLoopMode
而在RunLoop对象中,集合_modes包含了全部RunLoop支持的Mode。同时,RunLoop还支持一种“CommonModes”的概念,每一个Mode都能将其标记为“Common”属性(具体是将Mode的name添加到RunLoop的_commonModes集合当中),主线程的RunLoop两个预置的Mode:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和UITrackingRunLoopMode都已经被标记为了“Common”。
RunLoop中的_commonModeItems集合就是用来存放被标记为common的Source/Observer/Timer,当RunLoop状态发生变化时,会将_commonModeItems中的全部的Source/Observer/Timer同步到具备common标识的全部Mode中。
以定时器举例,咱们在主线程中添加一个定时器,而且添加到NSDefaultRunLoopMode当中,定时器会正常回调。此时若是界面上存在ScrollView,而且滑动ScrollView,RunLoop就会切换到UITrackingRunLoopMode模式下,此时,因为定时器只存在于NSDefaultRunLoopMode模式中,一旦切换到UITrackingRunLoopMode模式,定时器便会中止,等待RunLoop从新切换到DefaultMode时恢复运行。若是想要定时器在两种模式下都可以正常运行,能够将定时器同时添加到两种Mode中,还有一种方式,就是将定时器标记为“common”,其实也就是将定时器加入到RunLoop对象的_commonModeItems中去。此时RunLoop会自动将定时器同步到具备Common标记的Mode中去。代码以下:
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"定时器任务"); }]; //方法一 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; //方法二 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 复制代码
除了使用苹果公开的Mode外,咱们还能够建立自定义Mode,具体接口以下
//指定RunLoop建立Mode,主要是经过mode name来建立,若是RunLoop内部根据mode name没有发现对应的mode,RunLoop则会自动建立CFRunLoopModeRef
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
复制代码
同时,Mode中也提供了对Mode Item的操做函数,以下
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); 复制代码
CFRunLoopSourceRef又称之为输入源,它是事件产生的地方。输入源是将事件异步传递到线程中。事件的源则取决于输入源的类型,而输入源通常分为两类:第一类是自定义输入源,监视自定义事件源,又称为Source0。第二类是基于端口的输入源,监视应用程序的Mach端口,又称为Source1
举一个简单的例子:当App在前台静止时,若是咱们点击App的页面,此时咱们首先接触的是手机屏幕,此时,触摸屏幕的事件会被包装成Event传递给source1,而后source1主动唤醒RunLoop,以后将事件传递给Source0来进行处理。
CFRunLoopTimerRef是定时器源,它会在未来某个预设的时间点将事件同步发送到线程。定时器是线程通知本身作某件事的一种方式。定时器生成基于时间的通知,可是它并非实时的。与输入源同样,计时器和运行循环的特定mode相关联。
咱们能够配置定时器来生成一次或者屡次事件,重复定时器会根据预约的触发时间(而不是实际的触发时间)自动从新调度本身。例如一个定时器在一个特定的时间触发,而且在以后每5s触发一次,那么计划的触发时间将始终落在最初的5s间隔上。若是此时RunLoop正在处理一个耗时的任务,那么定时器的触发时间会被延时。假设RunLoop执行的耗时任务为12s,那么在RunLoop执行完完耗时任务以后,定时器会当即触发一次,而后定时器会从新安排下一次预约的触发时间。也就是说在耗时的12s内,只会触发一次定时器。
CFRunLoopObserverRef是观察者,每个Observer都包含一个回调,当RunLoop状态发生变化时,观察者就可以经过回调接收到变化状态。RunLoop有如下几种状态能被观测
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
复制代码
在代码中,咱们可使用如下方式来监听RunLoop的状态变化
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry:{ NSLog(@"即将进入loop"); break; } case kCFRunLoopBeforeTimers:{ NSLog(@"即将处理Timer"); break; } case kCFRunLoopBeforeSources:{ NSLog(@"即将处理Source"); break; } case kCFRunLoopBeforeWaiting:{ NSLog(@"即将进入休眠"); break; } case kCFRunLoopAfterWaiting:{ NSLog(@"从休眠中唤醒"); break; } case kCFRunLoopExit:{ NSLog(@"即将退出loop"); break; } default: break; } }); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes); CFRelease(observer); 复制代码
RunLoop运行逻辑流程图以下:
此处是对照官方文档的流程,其中忽略了对block的处理
想要查看RunLoop源码,首先须要知道RunLoop的入口函数,方法很简单,新建项目,在项目启动后在任意处断点,而后经过LLDB指令bt能够获得调用栈以下:
其中CFRunLoopRunSpecific函数就是RunLoop的入口函数,以下(此处只保留部分主要代码):
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ //第一步、通知Observers:即将进入loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); //内部函数,进入loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //第十步、通知Observers:退出loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; } 复制代码
loop的真正核心就是__CFRunLoopRun函数,源码以下:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { //第二步、通知Observers:即将处理Timers __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //第三步、通知Observers:即将处理Source0(非port) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //执行被加入的block __CFRunLoopDoBlocks(rl, rlm); //第四步、处理Source0,触发Source0回调 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { //再次执行执行被加入的block __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); //第五步、判断是否有Source1须要进行处理,若是有,跳转到handle_msg标记处执行 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; } } didDispatchPortLastTime = false; //第六步、通知Observers,RunLoop所在线程即将进入休眠 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); //第七步、调用内核函数mach_msg,让线程进入休眠,直到被如下事件唤醒 //一、接收到Source1事件 //二、启动Timer //三、为运行循环设置超时值过时 //四、被外部手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); //第八步、通知Observers:RunLoop线程刚被唤醒 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //第九步、接收到消息开始进行处理 handle_msg:; if (被Timer唤醒) { //9-一、若是用户自定义的计时器触发,则处理计时器事件并从新启动循环 __CFRunLoopDoTimers(rl, rlm, mach_absolute_time() } else if (被GCD唤醒) { //9-二、若是GCD子线程中有回到主线程的操做,那么唤醒线程,执行主线程的block __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } else { //9-三、若是有基于port的Source1事件传入,则处理Source1事件 CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); if (rls) { mach_msg_header_t *reply = NULL; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; if (NULL != reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); } } } //再次执行被加入的block __CFRunLoopDoBlocks(rl, rlm); if (sourceHandledThisLoop && stopAfterHandle) { //若是当前事件处理完成就直接返回 retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { //RunLoop到超时时间了 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { //RunLoop被强制中止了 retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { //若是RunLoop当前指定的Mode中Source/Timer/Observer都为空 retVal = kCFRunLoopRunFinished; } //若是RunLoop没有超时,没有被中止,指定的Mode中存在ModeItem,则一直运行RunLoop } while (0 == retVal); return retVal; } 复制代码
RunLoop在进行回调时,都会调用一个特别长的函数,例如调用__CFRunLoopDoObservers通知Observers即将进入RunLoop时,内部会调用CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION函数。如下将源码中对应的函数转换成了实际调用的函数,以下
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ //第一步、通知Observers:即将进入loop __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry); //内部函数,进入loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //第十步、通知Observers:退出loop __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit); return result; } static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { //第二步、通知Observers:即将处理Timers __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers); //第三步、通知Observers:即将处理Source0(非port) __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources); //执行被加入的block __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); //第四步、处理Source0,触发Source0回调 Boolean sourceHandledThisLoop = __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0); if (sourceHandledThisLoop) { //再次执行执行被加入的block __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); //第五步、判断是否有Source1须要进行处理,若是有,跳转到handle_msg标记处执行 if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) { goto handle_msg; } } didDispatchPortLastTime = false; //第六步、通知Observers,RunLoop所在线程即将进入休眠 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting); //第七步、调用内核函数mach_msg,让线程进入休眠,直到被如下事件唤醒 //一、接收到Source1事件 //二、启动Timer //三、为运行循环设置超时值过时 //四、被外部手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); //第八步、通知Observers:RunLoop线程刚被唤醒 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting); //第九步、接收到消息开始进行处理 handle_msg:; if (被Timer唤醒) { //9-一、若是用户自定义的计时器触发,则处理计时器事件并从新启动循环 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer); } else if (被GCD唤醒) { //9-二、若是GCD子线程中有回到主线程的操做,那么唤醒线程,执行主线程的block __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block); } else { //9-三、若是有基于port的Source1事件传入,则处理Source1事件 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1); } //再次执行被加入的block __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); //若是RunLoop没有超时,没有被中止,指定的Mode中存在ModeItem,则一直运行RunLoop } while (0 == retVal); return retVal; } 复制代码
具体流程图总结以下:
流程中穿插着对block的处理,其中的block能够经过CFRunLoopPerformBlock函数来添加。
RunLoop一个很重要的做用就是能用来控制线程的生命周期,也就是线程包活。主线程的RunLoop默认开启,所以主线程一直处于活跃状态,可是子线程默认没有开启RunLoop,因此子线程执行完任务以后就会被销毁。早期AFNetworking 2.x版本就使用了RunLoop来保活线程。具体实现以下:
XLThread.h源码以下
#import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN typedef void(^XLThreadTask)(void); @interface XLThread : NSObject /** 开始任务 */ - (void)executeTask:(XLThreadTask)task; /** 结束线程 */ - (void)stop; @end NS_ASSUME_NONNULL_END 复制代码
XLThread.m以下
#import "XLThread.h" #import <objc/runtime.h> @interface XLThread () @property(nonatomic, strong)NSThread *innerThread; @end @implementation XLThread #pragma mark - Public - (instancetype)init { self = [super init]; if (self) { self.innerThread = [[NSThread alloc] initWithTarget:self selector:@selector(__initThread) object:nil]; [self.innerThread start]; } return self; } - (void)executeTask:(XLThreadTask)task{ if (!self.innerThread || !task) { return; } [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO]; } - (void)stop { if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES]; } - (void)dealloc { SBPLog(@"%s", __func__); [self stop]; } #pragma mark - Private - (void)__stop{ CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil; } - (void)__executeTask:(XLThreadTask)task{ task(); } - (void)__initThread{ //建立上下文 CFRunLoopSourceContext context = {0}; //建立source CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context); //向runloop中添加source CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode); //销毁source CFRelease(source); //启动runLoop CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false); } @end 复制代码
在iOS中,AutoreleasePool其实也是基于RunLoop来实现的,App启动时会在主线程的RunLoop中注册两个Observer。
关于AutoreleasePool的具体实现会在后面的章节详细介绍。
前面说到的CFRunLoopTimerRef其实就是NSTimer,NSTimer的触发必须基于RunLoop,而且RunLoop须要处于开启状态。一般咱们会在主线程中使用定时器,由于主线程的RunLoop默认开启,若是想要在子线程中使用定时器,就须要手动开启子线程RunLoop。
当咱们建立NSTimer,而后将其注册到RunLoop后,RunLoop会根据预设的触发时间在重复的时间点注册好事件,例如设置定时器在5:10分开始触发,而且每隔5m触发一次,定时器的触发时间就是固定的5:十、5:1五、5:20、5:25等等,可是RunLoop为了节省资源,并不会在很是准确的时间点触发定时器。若是在触发定时器以前RunLoop执行了一个很长的任务,以致于错过了预设的时间点,那么在长时间任务执行完成以后会当即触发一次定时器,而后等到下一个预设的时间点再次触发。
假设原定于5:10分触发定时器,可是RunLoop因为执行耗时任务到5:22,那么此时,在耗时任务执行完成以后会当即触发一次定时器任务,而后等待到5:25时再次触发定时器。
上文提到,RunLoop中输入源分为两种,一种是基于port的输入源,还有一种是自定义输入源。
Cocoa框架为咱们提供了一种自定义源(Perform Selector Source),可让咱们在任何线程上执行Selector,而且在执行完Selector以后,该源会自动从RunLoop中移除。当咱们使用performSelector在另外一个线程中执行Selector时,必需要保证目标线程中有开启RunLoop,所以就须要咱们显式的启动目标线程的RunLoop。
如下就是在NSObject中声明的performSelector方法
方法 | 描述 |
---|---|
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: |
在应用程序主线程的RunLoop的下一次循环周期内执行指定的Selector。 而且该方法能够阻塞当前线程,直到执行Selector为止。 |
performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: |
在任何线程上指向指定的Selector。 而且该方法能够阻塞当前线程,直到执行Selector为止。 |
performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes: |
在下一个运行循环周期中以及可选的延迟时间以后,在当前线程上执行指定的selector。 由于是等到下一个运行循环周期执行selector,因此这些方法提供了当前执行代码的最小自动延迟。 多个排队的选择器按照排队的顺序依次执行 |
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object: |
取消发送到当前线程的消息 |
举一个简单的例子,在touchesBegan:方法中添加如下示例代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ NSLog(@"任务1"); [self performSelector:@selector(test) withObject:nil afterDelay:.0]; NSLog(@"任务3"); } - (void)test{ NSLog(@"任务2"); } 复制代码
当点击页面时,执行顺序依次是任务一、任务3和任务2。缘由很简单,由于performSelector:withObject:afterDelay方法本质实际上是建立一个定时器NSTimer,而且将定时器添加到RunLoop中进行处理。整个流程大体以下:
以上内容纯属我的理解,若是有什么不对的地方欢迎留言指正。
一块儿学习,一块儿进步~~~