编程最怕的就是有盲点,不肯定,而runloop官网对其说起的又不多;那么看完这篇应该使你有底气不少~编程
An event-processing loop, during which events are received and dispatched to appropriate handlers.数据结构
事件运行循环:就相似下面的while循环部分,固然要复杂不少,能够把它抽象成以下代码:app
main() { initialize(); do { message = get_next_message(); process_message(message); } while (message != quit); }
“消息”循环,等待消息(会休眠)->接收消息->处理消息。经过上面的代码,runloop本质就是提供了一种消息处理模式,只不过它封装抽象的太好了(通常开发的时候根本就感受不到,或者说不用关心)。异步
runloop至关于帮咱们打包了各类消息,并将消息发送给指定的接受者。async
能够将runloop理解为一个函数,功能是一个消息循环,有消息则处理,没有消息则休眠。(注意:runloop实质是一个对象,可是不影响以上的假设)函数
简单使用:新建一个线程,添加一个定时器,而后运行便可oop
- (void)timerFire { NSLog(@"mode:%@",[[NSRunLoop currentRunLoop] currentMode]); } - (void)runLoopTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimer *tickTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:2 target:self selector:@selector(modeTestTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timerFire forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; }); }
若是你接触过嵌入式操做系统(纯内核)开发,那么对下面代码确定很熟悉测试
void ledTask (void *p_arg) { initialize(); while (1) { LED_ON(); delay_ms(500); LED_OFF(); delay_ms(500); }; }
LED闪烁线程,让一个LED灯1HZ的频率闪烁,功能很简单:首先初始化,而后进入while(1)死循环,延迟函数会使线程进入休眠(节省CPU)。直到程序死掉线程结束。是否和runloop很类似?ui
一句话归纳:很复杂,各类各样 :)spa
不过,根据上图咱们能够将消息分为二种类型,第一种类型又能够细分为三种,此三种共同点就是它们都是异步执行的
监听程序的Mach ports,Mach ports是一个比较底层的东西,能够简单的理解为:内核经过port这种方式将信息发送,而mach则监听内核发来的port信息,而后将其整理,打包发给runloop。
很明显,由开发人员本身发送。不只仅是发送,过程的话至关复杂,苹果也提供了一个CFRunLoopSource来帮助处理。因为不多用到,能够简单说下核心,可是对帮助咱们理解runloop却颇有帮助:
NSObject类提供了不少方法供咱们使用,这些方法是添加到runloop的,因此若是没有开启runloop的话,不会运行(不过有个坑,请看下面介绍)。
/// 主线程 performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: /// 指定线程 performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes: /// 针对当前线程 performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes: /// 取消,在当前线程,和上面两个方法对应 cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:
下面提供的方法是在指定的线程运行aSelector
,通常状况下aSelector
会添加到指定线程的runloop。但,若是调用线程和指定线程为同一线程,且wait
参数设为YES,那么aSelector
会直接在指定线程运行,再也不添加到runloop。
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes: performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes:
其实这也很好理解,假设这种状况也添加到指定线程的runloop,咱们能够这样反向理解:1,当前线程runloop尚未开启,那么aSelector
就不会被执行,然而你却一直在等待,形成线程卡死。2,当前线程runloop已经开启,那么调用performSelector
这个方法的位置确定是处于runloop的callout方法里面,在这里等待runloop再callout从而调用aSelector
方法完成,显然也是死等待,线程卡死。。。
还有一些performSelector
方法,是不会添加到runloop的,而是直接执行,能够按照上面的特殊状况进行理解。方法列举以下:
- (id)performSelector:(SEL)aSelector; - (id)performSelector:(SEL)aSelector withObject:(id)object; - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
看到这里,是否感受有些乱???只要记住没有延迟或者等待的都不会添加到runloop,有延迟或者等待的还有排除上面提到的特殊状况。
Timer Sources:它的事件发送是同步的,这个用的比较多,会在下一篇专门介绍
下面举例,监听全部状态,在非主线程(能够看到一个完整的周期):
+ (void)observerTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ /** param1: 给observer分配存储空间 param2: 须要监听的状态类型:kCFRunLoopAllActivities监听全部状态 param3: 是否每次都须要监听,若是NO则一次以后就被销毁,再也不监听,相似定时器的是否重复 param4: 监听的优先级,通常传0 param5: 监听到的状态改变以后的回调 return: 观察对象 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { switch (activity) { case kCFRunLoopEntry: NSLog(@"即将进入runloop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即将处理timer"); break; case kCFRunLoopBeforeSources: NSLog(@"即将处理input Sources"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即将睡眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"从睡眠中唤醒,处理完唤醒源以前"); break; case kCFRunLoopExit: NSLog(@"退出"); break; default: break; } }); // 没有任何事件源则不会进入runloop [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:@selector(doFireTimer) userInfo:nil repeats:NO]; CFRunLoopAddObserver([[NSRunLoop currentRunLoop] getCFRunLoop], observer, kCFRunLoopDefaultMode); [[NSRunLoop currentRunLoop] run]; }); } + (void)doFireTimer { NSLog(@"---fire---"); }
打印结果:一个完整的周期
runloop的模式,使得runloop显得更加灵活,适应更多的应用场景。
上面提到的事件源,都是处于特定的模式下的,若是和当前runloop的模式不一致则不会获得响应,举个例子:
若是定时器处于mode1,而runloop运行在mode2,则定时器不会触发,只有runloop运行在mode1时,定时器才会触发。
系统为咱们提供了多种模式,下面列一些比较常遇到的:
除了系统给咱们的模式,咱们本身也能够自定义。
NSRunLoopMode
的类型为字符串类型,定义:typedef NSString * NSRunLoopMode
,自定义类型就很简单了,示例代码以下:直接调用runLoopModeTest方法便可测试
- (void)modeTestTimer { NSLog(@"mode:%@",[[NSRunLoop currentRunLoop] currentMode]); } /// 这里使用非主线程,主要考虑若是一直处于customMode模式,则主线瘫痪 - (void)runLoopModeTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimer *tickTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:2 target:self selector:@selector(modeTestTimer) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:tickTimer forMode:@"customMode"]; [[NSRunLoop currentRunLoop] runMode:@"customMode" beforeDate:[NSDate distantFuture]]; }); }
runloop模式的切换
主线程没有使用runloop嵌套是根据个人测试得出,没办法,官方文档太太太少,也没有更底层源码,只有CFRunLoop的源码:http://opensource.apple.com/tarballs/CF/CF-855.17.tar.gz。
最后总结下,thread--runloop--mode--event sources,关系能够表示以下:
能够分为三步:建立->运行(开启,内部循环)->退出
苹果是不容许开发人员手动建立runloop,runloop是伴随着线程的建立而建立,线程与runloop是一一对应的,具备惟一性,另外建立还区分是否为主线程
主线程:系统会自动建立
非主线程:系统不会自动建立,开发人员必须显示的调用[NSRunLoop currentRunLoop]
方法来获取runloop的时候,系统才会建立,相似懒加载
系统只提供了两种方法获取runloop,currentRunLoop
和mainRunLoop
,能够看出非主线程只有在本身的线程内才能得到runloop。
NSRunLoop提供的方法: - (void)run; // 默认模式 - (void)runUntilDate:(NSDate *)limitDate; - (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
CFRunLoop提供的函数: /// 默认模式 void CFRunLoopRun(void); /// 在指定模式,指定时间,运行 CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
当执行了上面的运行方法后,若是runloop所在的模式没有对应的事件源,即上面图中提到的input sources、timer sources,会直接退出当前runloop(注意:是当前)。另外注意的是,input sources里面的Selector Sources,它有一些特殊状况,上面也提到了。这些状况下runloop仍是会直接退出。
网上有不少说到事件源包括了observe,实际上是不包含的,即runloop是否退出与observe没有关系,observe只是监听runloop自己的状态而已。
这样看起来仍是比较清晰的。
关于自动释放池提一下(下一篇会作详细说明):
上面提到的自动释放池的处理固然是系统帮咱们处理的,非主线程和主线程系统都帮咱们作了处理。官方说到,若是你使用POSIX thread APIs建立线程,那就是另一套内存回收系统了,是不会用autoreleasePool,系统固然也不会建立。
能够用如下方式退出runloop
- (void)run; // 默认模式 - (void)runUntilDate:(NSDate *)limitDate;
当执行NSRunLoop的run方法,一旦成功(默认模式下有事件源),那么run会不停的调用runMode:beforeDate:来运行runloop,那么即使CFRunLoopStop退出了一个runloop,很快会有另外一个runloop执行。即:若是你想退出一个runloop,那么你就不应调用run方法来开启runloop
runUntilDate:与run同样不停的执行runMode:beforeDate:方法,CFRunLoopStop也是退不出来的,不一样的是runUntilDate:本身有个期限,超过这个期限会自动退出
很明显,你会想到利用事件源为空来退出,这种方法上面已经说了,不推荐。。。
一个不想回答的问题:runloop自己的释放。有人会纠结这个问题,通过多方查问、资料、源码、测试加自身理解,得出:runloop退出后,是不会被释放的(或者说当即),它大概极可能是伴随着线程的释放而释放。。。。。。欢迎补充
嵌套,刚接触时感受很神奇,然而一入嵌套深似海。。。特别是约瑟夫环的问题(http://www.jianshu.com/p/3c62ac7d9285)。。。
在当前runloop的callout函数里面执行上runloop,例程代码以下:
/** runloop嵌套测试, */ + (void)nestTest { dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSTimer *tickTimer = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timerHandle1) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:tickTimer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:2]]; NSLog(@"-end-"); }); } /** 不停的运行与退出最内层runloop */ + (void)timerHandle1 { NSLog(@"timer111-%@",[[NSRunLoop currentRunLoop] currentMode]); // 防止屡次添加timer,开发中应特别注意 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSTimer *tickTimer2 = [[NSTimer alloc] initWithFireDate:[NSDate date] interval:1 target:self selector:@selector(timerHandle2) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:tickTimer2 forMode:UITrackingRunLoopMode]; }); [[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate distantFuture]]; } + (void)timerHandle2 { NSLog(@"timer222-%@",[[NSRunLoop currentRunLoop] currentMode]); CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]); }
打印结果
例程中外层runloop运行在NSDefaultRunLoopMode
模式下,而后在它的callout函数(定时器1)又执行runloop,运行在UITrackingRunLoopMode
模式下,实现嵌套,而后在内层runloop的callout(timerHandle2),中止运行当前runloop,即中止内层runloop,这时又回到外层循环。外层runloop只运行2秒到期。-end-
上面嵌套是运行在不一样模式下,当同一模式下的runloop出现嵌套时,苹果依然处理的很好。举例:
NSDefaultRunLoopMode
模式下运行NSDefaultRunLoopMode
模式下运行可能你会以为很诧异,t2怎么也会运行呢????其实这很符合逻辑:
假设在第2步骤中,咱们没有执行r2,即没有r2,那么t2仍是加到了r1上。既然是加到了r1那执行就不难理解了。(是否感受苹果很强大?)
注意:r1与r2表明的是同一runloop,只是调用栈不一样,或者说嵌套层。若是把runloop理解为一个函数,那么就能够理解为函数r1调用了自身,那个"自身"称为r2。