一、* what is run loop *
1、A run loop is an abstraction that (among other things) provides a mechanism to handle system input sources (sockets, ports, files, keyboard, mouse, timers, etc).
Each NSThread has its own run loop, which can be accessed via the currentRunLoop method.
In general, you do not need to access the run loop directly, though there are some (networking) components that may allow you to specify which run loop they will use for I/O processing.
A run loop for a given thread will wait until one or more of its input sources has some data or event, then fire the appropriate input handler(s) to process each input source that is “ready.”.
After doing so, it will then return to its loop, processing input from various sources, and “sleeping” if there is no work to do.
2、首先考虑这个问题:你的Cocoa程序大部分的时间什么都没做,更具体点,是在等待输入。然而,一旦你触摸屏幕,相应的事件被触发,就可能会执行你的一段事件处理代码。同理,socket中返回一些数据,或者计时器触发等也是一样的情况。而且更重要的是,一旦触发事件的代码执行完,程序就会回到等待状态。在很多情况下,代码执行的时间要远小于程序等待输入的时间。
我认为run loop就是较好的利用了这个事实的一种机制。一个run loop就是跑在单个线程上进行事件处理的循环。你在run loop上注册输入源,并指定当这些源有输入时应该执行的代码。当特定的源上有输入时,run loop就会执行对应的代码,然后继续等待下一个输入事件。如果在run loop正在执行处理代码时,另外一个源的输入到了,run loop会在执行完正当前的处理后处理这个输入事件。好处是虽然你不知道具体的输入顺序,但你知道它们最终会一个接一个地被串行处理。这就是说你不会遇到多线程的问题,这也是run loop非常有用的原因。
RunLoop这个东西,其实我们一直在用,但一直没有很好地理解它,或者甚至没有知道它的存在。RunLoop可以说是每个线程都有的一个对象,是用来接受事件和分配任务的loop。永远不要手动创建一个runloop,它是跟随着每个线程的。一个RunLoop接收两种source的事件:input source和timer source。同时必须知道的是,input source,runloop是异步交付的,而timer source是同步交付的。每个runloop都有一个RunLoop Modes,代表它以何种方式执行。
我们为什么从来没有感觉到runloop的存在呢,是因为当程序启动,系统默认帮我们启动了一个主线程的runloop,并且一直在运行,直到程序退出。而用户创建的子线程,runloop是需要手动启动的,所以在线程里启动timer或者调用performSelector: withObject:afterDelay:inModes: 是需要启动runloop的。
RunLoop从字面上看是运行循环的意思,这一点也不错,它确实就是一个循环的概念,或者准确的说是线程中的循环。 本文一开始就提到有些程序是一个圈,这个圈本质上就是这里的所谓的RunLoop,就是一个循环,只是这个循环里加入很多特性。
首先循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省CPU时间。 所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,一种是输入源,一种是定时器,定时器好理解就是那些需要定时执行的操作,输 入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。编程的时候可以添加自己的源。
二、* Run loop的使用 *
默认情况下,iPhone上的所有触摸事件都会被main run loop放在队列里等待处理,所以你不需要对UI组件做额外的事情,而其他输入源需要一些额外的编码。比如在run loop上schedule一个NSInputStream,你需要像下面这样:
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
在上面的代码中,一旦iStream有输入数据,就会执行self的stream:handleEvent的方法。而且这个stream可以是任意类型的输入源,包括socket.
另外,timer对象也可以被schedule在run loop上,比如:
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doStuff) userInfo: nil repeats:YES];
上面的代码把计时器schedule到当前的run loop上,每2秒就会调用self的doStuff方法。
三、* 不适用run loop的情况 *
那什么时候不适合使用run loop呢?根据run loop的特点,输入事件会一个接一个的被串行处理,那么如果一个事件的处理需要的时间特别长的话,就会导致在这个事件处理完之前,app无法响应别的输入事件。在这种情况下,新开一个线程处理更合适。 然而,大部分情况下,我们的代码处理屏幕、socket或者计时器事件都非常快,这时使用main run loop处理起来更简单,也更安全。
参考链接:
- Threading Programming Guide
- Threading Programming Guide翻译
- Objective-C之run loop详解
- iOS中的RunLoop
- Understanding NSRunLoop
- Run loop和Thread
- iOS关于RunLoop和Timer
- iOS多线程的初步研究(三)– NSRunLoop