不知道大家有没有想过这个问题,一个应用开始运行以后放在那里,如果不对他进行任何操作,这个应用就像静止了一样,不会自发的有任何动作发生,但是如果我们点击界面上的一个按钮,这个时候就会有对应的按钮响应事件发生。给我们的感觉就像应用一直处于随时待命的状态,在没人操作的时候它一直在休息,在让他干活的时候,它就能立刻响应。其实,这就是runloop的功劳。
一、线程与runloop
1.1 线程任务的类型
有些线程执行的任务是一条直线,起点到终点;而另一些线程要干的活则是一个圆,不断循环,直到通过某种方式将它终止。直线线程如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花一现那样;圆形的如操作系统,一直运行直到你关机。在iOS中,圆型的线程就是通过runloop不停的循环实现的。
1.2 线程与runloop的关系
RunLoop,正如其名,loop表示某种循环,和run放在一起就表示一直在运行着的循环。实际上,runloop和线程是紧密相连的,可以这样说runloop是为了线程而生,没有线程,它就没有存在的必要。runloop是线程的基础架构部分,Cocoa和CoreFundation都提供了runloop对象方便配置和管理线程的runloop。每个线程,包括程序的主线程(main thread)都有与之相应的runloop对象。
1.21 主线程的run loop默认是启动的。
iOS的应用程序里面,程序启动后会有一个如下的main() 函数:
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
}
}
重点是UIApplicationMain() 函数,这个方法会为main thread 设置一个NSRunLoop 对象,这就解释了本文开始说的为什么我们的应用可以在无人操作的时候休息,需要让它干活的时候又能立马响应。
1.2.2 对其它线程来说,run loop默认是没有启动的,如果你需要更多的线程交互则可以手动配置和启动,如果线程只是去执行一个长时间的已确定的任务则不需要。
1.2.3 在任何一个Cocoa程序的线程中,都可以通过:
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
来获取到当前线程的run loop。
1.3 关于run loop的几点说明
1.3.1 Cocoa中的NSRunLoop类并不是线程安全的
我们不能再一个线程中去操作另外一个线程的run
loop对象,那很可能会造成意想不到的后果。不过幸运的是CoreFundation中的不透明类CFRunLoopRef是线程安全的,而且两种类型的run loop完全可以混合使用。Cocoa中的NSRunLoop类可以通过实例方法:
- (CFRunLoopRef)getCFRunLoop;
获取对应的CFRunLoopRef类,来达到线程安全的目的。
1.3.2 Run loop的管理并不完全是自动的。
我们仍必须设计线程代码以在适当的时候启动run loop并正确响应输入事件,当然前提是线程中需要用到run loop。而且,我们还需要使用while/for语句来驱动run loop能够循环运行,下面的代码就成功驱动了一个run loop:
BOOL isRunning = NO;
do {
isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);
1.3.3 Run loop同时也负责autorelease pool的创建和释放
在使用手动的内存管理方式的项目中,会经常用到很多自动释放的对象,如果这些对象不能够被即时释放掉,会造成内存占用量急剧增大。Run loop就为我们做了这样的工作,每当一个运行循环结束的时候,它都会释放一次autorelease
pool,同时pool中的所有自动释放类型变量都会被释放掉。
1.3.4 Run loop的优点
一个run loop就是一个事件处理循环,用来不停的监听和处理输入事件并将其分配到对应的目标上进行处理。如果仅仅是想实现这个功能,你可能会想一个简单的while循环不就可以实现了吗,用得着费老大劲来做个那么复杂的机制?显然,苹果的架构设计师不是吃干饭的,你想到的他们早就想过了。
首先,NSRunLoop是一种更加高明的消息处理模式,他就高明在对消息处理过程进行了更好的抽象和封装,这样才能是的你不用处理一些很琐碎很低层次的具体消息的处理,在NSRunLoop中每一个消息就被打包在input source或者是timer source(见后文)中了。其次,也是很重要的一点,使用run loop可以使你的线程在有工作的时候工作,没有工作的时候休眠,这可以大大节省系统资源。
四、RunLoop的应用
正如前面所说,我们一直在使用他,却很少见到他。并且,我们在大多数情况下,都不需要显式的创建或者启动RunLoop,有两种情况,我们却必须手动设置它:
1、在分线程中使用定时器
定时器的实现便是基于runloop的,平时我们使用定时器你或许并没有对runloop做什么操作,那是因为主线程的runloop默认是开启运行的,如果我们在分线程中也需要重复执行某一动作,如下:
你会发现,程序运行后并没有打印任何信息,方法并没有被调用,我们必须在线程中手动的执行如下代码:
[[NSRunLoop currentRunLoop] run];
定时器才能正常工作。
2.在子线程中进行NSTimer的操作,再在主线程中修改UI界面。
(1)
(2)使用GCD,同样也是多线程
声明全局成员变量 dispatch_source_t _timers;
2.仍然在主线程中进行NSTimer操作,但是将NSimer实例加到mainRunLoop的特定mode中,避免被复杂的操作或者UI界面的刷新所干扰。
总结:
每一个线程都有一个实际已经存在的runloop。比如我们的主线程,在主函数的UIApplication中
系统就为我们将主线程的main runloop隐式的启动了。runloop顾名思义就是一个循环,他不停的运行,从程勋开始到程序退出。正是由于这个“循环”在不停的监听各种事件,程序才能有能力检测到用户的各种触摸交互、网络返回的数据才会诶检测到、定时器才会在预定的事件触发操作。
runloop只接受两种任务:输入源和定时源。文中说的是定时源。在默认的状态下,子线程的runloop中没有加入我们自己的源,那么我们在子线程中使用自己的定时器时,就需要自己加到runloop中,并启动该子线程的runloop,这样才能正确的运行定时器。
文/cherish_our(简书作者)
原文链接:http://www.jianshu.com/p/36ba6299268f
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。