一、概述
1.Run loops是线程的基础架构部分。一个run loop就是一个事件处理循环,用来不停的调配工作以及处理输入事件。使用run loop的目的是使你的线程在有工作的时候工作,没有的时候休眠。
2.Run loop的管理并不完全是自动的。你仍必须设计你的线程代码以在适当的时候启动run loop并正确响应输入事件。Cocoa和CoreFundation都提供了run loop对象方便配置和管理线程的run loop。你创建的程序不需要显示的创建run loop;任何线程,包含程序的主线程(main thread)都有与之相应的run loop对象。但是,自己创建的次线程是需要手动运行run loop的。在carbon和cocoa程序中,程序启动时,主线程会自行创建并运行run loop。
3.Run loop处理的输入事件有两种不同的来源:输入源(input source)和定时源(timer source)。输入源传递异步消息,通常来自于其他线程或者程序。定时源则传递同步消息,在特定时间或者一定的时间间隔发生。
除了处理输入源,run loop也会生成关于run loop行为的notification。注册的run-loop 观察者可以收到这些notification,并做相应的处理。可以使用Core Foundation在你的线程注册run-loop观察者。
二、Run Loop Modes
1.Run loop模式是所有要监视的输入源和定时源以及要通知的注册观察者的集合。每次运行run loop都会指定其运行在哪个模式下。以后,只有相应的源会被监视并允许接收他们传递的消息。(类似的,只有相应的观察者会收到通知)。其他模式关联的源只有在run loop运行在其模式下才会运行,否则处于暂停状态。
2.通常代码中通过指定名字来确定模式。Cocoa和core foundation定义了默认的以及一系列常用的模式,都是用字符串来标识。当然你也可以指定字符串来自定义模式。虽然你可以给模式指定任何名字,但是所有的模式内容都是相同的。你必须添加输入源,定时器或者run loop观察者到你定义的模式中。
3.通过指定模式可以使得run loop在某一阶段只关注感兴趣的源。大多数时候,run loop都是运行在系统定义的默认模式。但是模态面板(modal panel)可以运行在 “模态”模式下。在这种模式下,只有和模态面板相关的源可以传递消息给线程。对于次线程,可以使用自定义模式处理时间优先的操作,即屏蔽优先级低的源传递消息。
4.Note:模式区分基于事件的源而非事件的种类。例如,你不可以使用模式只选择处理鼠标按下或者键盘事件。你可以使用模式监听端口, 暂停定时器或者其他对源或者run loop观察者的处理,只要他们在当前模式下处于监听状态。
三、Run Loop Modes列表(由苹果官方文档翻译而来)
1.NSDefaultRunLoopMode:大多数操作使用的默认模式,多数情况下你需要使用这个模式来开启你的run loop以及配置输入源。 NSTimer默认是使用这个模式部署的。
2.NSConnectionReplyMode:Cocoa使用这个模式来关联NSConnection去监听返回,你应该很少会使用这个模式。
3.NSModalPanelRunLoopMode:Cocoa使用这个模式来鉴定模态面板的时间目的。很少使用。
4.NSEventTrackingRunLoopMode:当有鼠标操作或者其它用户跟踪交互时,Cocoa使用这个模式去限制其它输入事件。
5.NSRunLoopCommonModes:这是一个可配置的通用的模式组合,当将一个输入源跟这个模式关联,输入源将会和组合里面的所有模式关联。在Cocoa applications(主线程)中,这个组合默认包含Default,Modal,以及eventTrack。在Core Foundation(子线程)中,默认只包含default。可以通过CFRunLoopAddCommonMode来配置这个组合。
四、使用中遇到的问题:
1.问题一:
(1)场景说明:在一个ios应用中,使用NSTimer的默认方式(NSDefaultRunLoopMode模式下)初始化了一个定时器,或者以NSDefaultRunLoopMode方式在RunLoop上部署了网络请求(没有使用[connection scheduleInRunLoopXXXX]方法,默认都是Default),而此时发生了用户交互,例如发生了ScrollView滚动,此时主线程的RunLoop处在NSEventTrackingRunLoopMode模式下,其它的输入事件将会被屏蔽掉,导致定时器任务没有执行或者收不到网络请求回调。
(2)具体测试情况:(当用手一直滚动ScrollView或者手指不起来)
<1>Timer:Timer的触法会被直接过滤掉,调用不会堆积。
<2>Connection:请求回调会收不到,短时间内停止交互,数据不会丢失,数据会累积等回调发生时一起返回,但是如果长时间不停止交互,请求会超时或者链接丢失。非用户主动发起的交互不会阻塞回调,例如通过代码自动发起的UI动画。
(3)解决办法:在此种情况下,需要将定时器或者网络请求以NSRunLoopCommonModes模式部署到RunLoop上,这样用户交互事件和其它输入事件就可以同时接收了。例如:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
2.问题二:
(1)场景说明:在子线程中去发送异步请求,默认情况下收不到请求回调。
(2)解决办法:子线程有自动创建RunLoop,但是默认是没有运行的,在开始发送网络请求后,需要手动启动子线程的RunLoop,才能接收输入事件,并受到反馈。代码如下:
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];