ios RunLoop解析
本文转载自:http://www.dreamingwish.com/dream-2012/ios-multithread-program-runloop-the.html,仅供自己学习,不用做任何商业用途。尊重原创!这篇文章翻译的很好很详细,本文只转载了基本介绍。至于详细的数据源、观察者等都是怎么实现的,请参看原文。
Run loops是线程相关的的基础框架的一部分。一个run loop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。使用run loop的目的是让你的线程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
Run loop的管理并不完全自动的。你仍然需要设计你的线程代码在合适的时候启动run loop并正确响应输入事件。Cocoa和Core Fundation都提供了run loop objects来帮助配置和管理你线程的run loop。你的应用程序不需要显式的创建这些对象(run loop objects);每个线程,包括程序的主线程都有与之对应的run loop object。只有辅助线程才需要显式的运行它的run loop。在Carbon和Cocoa程序中,主线程会自动创建并运行它run loop,作为一般应用程序启动过程的一部分。
一、介绍
run loop,是线程进入和被线程用来响应事件以及调用事件处理函数的地方。需要在代码中使用控制语句实现run
loop的循环,也就是说,需要代码提供while 或者 for循环来驱动run loop。在这个循环中,使用一个runloop对象[NSRunloop currentRunloop]执行接收消息,调用对应的处理函数。
Run loop接收输入事件来自两种不同的来源:输入源(input source)和定时源(timer source)。输入源传递异步事件,通常消息来自于其他线程或程序。定时源则传递同步事件,发生在特定时间或者重复的时间间隔。两种源都使用程序的某一特定的处理例程来处理到达的事件。
输入源传递异步消息给相应的处理例程,并调用runUntilDate:方法来退出(在线程里面相关的NSRunLoop对象调用)。定时源则直接传递消息给处理例程,但并不会退出run loop。
除了处理输入源,run loops也会生成关于run loop行为的通知(notifications)。注册的run loop观察者(run-loop Observers)可以收到这些通知,并在线程上面使用它们来做额外的处理。你可以使用Core Foundation在你的线程注册run-loop观察者。
二、RunLoop Mode模式
一个runloop mode就是input sources、timer和observers的集合。每次执行runloop,都需要指定一个mode。在期间,只有与该mode关联的source才会被监管和传递他们的事件,同样只有相关的observer被通知。其他mode关联的源,只有再它的那种模式下,才会运行,否则处于暂停状态。
在代码中,mode的命名用string表示,有一些default mode和其他常用的modes。
可以使用字符串来标识一个自定义的mode。新建的自定义mode,至少需要一个inputsources或者timers或者 observers。
mode用来过滤掉你不想监听的sources,使得你想要的事件通过你代码中的循环。大数情况运行在系统的default
mode中。对于辅助线程,可以使用自定义mode来防止低优先级的sources传递事件,这样如果当前操作是time-critical,可以省下资源。
mode区分是基于事件的源,而不是基于事件的种类,例如,你不可以使用模式只选择处理鼠标按下或者键盘事件。
系统的mode有:Default、Connection、Modal、Event tracking、Common modes。
三、输入源
传递异步事件,事件来源取决于输入源的种类:基于端口的输入源和自定义输入源。基于端口的输入源监听程序相应的端口。自定义输入源则监听自定义的事件源。
run loop并不关心输入源的是哪种种类。系统会实现两种输入源供你使用。两类输入源的区别在于如何显示:基于端口的输入源由内核自动发送,而自定义的则需要人工从其他线程发送。当你创建输入源,你需要将其分配给run loop中的一个或多个模式。
1、基于端口的输入源
系统支持使用端口相关的对象和函数来创建的基于端口的源。
在Cocoa里,你不需要直接创建输入源。你只要简单的创建端口对象,并使用NSPort的方法把该端口添加到run
loop。端口对象会自己处理创建和配置输入源。
在Core Foundation,你必须人工创建端口和它的run loop源。
在两种情况下,你都可以使用端口相关的函数(CFMachPortRef,CFMessagePortRef,CFSocketRef)来创建合适的对象。
2、自定义输入源
必须使用Core Foundation里面的CFRunLoopSourceRef类型相关的函数来创建。你可以使用回调函数来配置自定义输入源。Core Fundation会在配置源的不同地方调用回调函数,处理输入事件,在源从run loop移除的时候清理它。
除了定义在事件到达时自定义输入源的行为,你也必须定义消息传递机制。源的这部分运行在单独的线程里面,并负责在数据等待处理的时候传递数据给源并通知它处理数据。消息传递机制的定义取决于你,但最好不要过于复杂。参阅CFRunLoopSource Reference。
3、Cocoa 执行 Selector 的源<为自定义输入源准备的>
Cocoa定义了自定义输入源,允许你在任何线程执行selector。和基于端口的源一样,执行selector请求会在目标线程上序列化,减缓许多在线程上允许多个方法容易引起的同步问题。不像基于端口的源,一个selector执行完后会自动从run loop里面移除。
系统提供的方法:
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:这两个和上面两个配套使用,用来取消发送消息。
四、定时源
定时源在预设的时间点同步方式传递消息。重复工作定时器会基于安排好的时间而非实际时间调度它自己运行。举个例子,如果定时器被设定在某一特定时间开始并5秒重复一次,那么定时器会在那个特定时间后5秒启动,即使在那个特定的触发时间延迟了。如果定时器被延迟以至于它错过了一个或多个触发时间,那么定时器会在下一个最近的触发事件启动,而后面会按照触发间隔正常执行。查看NSTimer Class Reference或CFRunLoopTimer Reference。
五、RunLoop观察者
为了创建一个run loop观察者,你可以创建一个CFRunLoopObserverRef类型的实例。它会追踪你自定义的回调函数以及其它你感兴趣的活动。
和定时器类似,run loop观察者可以只用一次或循环使用。若只用一次,那么在它启动后,会把它自己从run loop里面移除,而循环的观察者则不会。你在创建run loop观察者的时候需要指定它是运行一次还是多次。
你可以将run loop观察者和以下事件关联:
- Run loop入口
- Run loop何时处理一个定时器
- Run loop何时处理一个输入源
- Run loop何时进入睡眠状态
- Run loop何时被唤醒,但在唤醒之前要处理的事件
- Run loop终止
六、RunLoop的事件队列
每次运行run loop,你线程的run loop对会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:
- 通知观察者run loop已经启动
- 通知观察者任何即将要开始的定时器
- 通知观察者任何即将启动的非基于端口的源
- 启动任何准备好的非基于端口的源
- 如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9。
- 通知观察者线程进入休眠
- 将线程置于休眠直到任一下面的事件发生:
- 某一事件到达基于端口的源
- 定时器启动
- Run loop设置的时间已经超时
- run loop被显式唤醒
- 通知观察者线程将被唤醒。
- 处理未处理的事件
- 如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
- 如果输入源启动,传递相应的消息
- 如果run loop被显式唤醒而且时间还没超时,重启run loop。进入步骤2
- 通知观察者run loop结束。