Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.
- 事件处理循环, 对将到来的事件做相应的调度工作
- 保证线程忙时工作, 闲时睡眠
Structure of a run loop and its sources
A run loop receives events from two different types of sources. Input sources deliver asynchronous events, usually messages from another thread or from a different application. Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval. Both types of source use an application-specific handler routine to process the event when it arrives.
- 从两种不同的源接收事件
- 输入源传递异步事件, 事件通常来自于其他线程/进程
- 定时源传递同步事件
Run Loop Modes
A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified. Each time you run your run loop, you specify (either explicitly or implicitly) a particular “mode” in which to run. During that pass of the run loop, only sources associated with that mode are monitored and allowed to deliver their events. (Similarly, only observers associated with that mode are notified of the run loop’s progress.) Sources associated with other modes hold on to any new events until subsequent passes through the loop in the appropriate mode.
- run loop mode 是一个集合(需要被监测的输入源,定时源以及要通知的观察者的集合)
- 每次run loop运行都要指定特定的mode
- 只有和mode相关的源,观察者才会被监测和通知
You use modes to filter out events from unwanted sources during a particular pass through your run loop.
Note: Modes discriminate based on the source of the event, not the type of the event. For example, you would not use modes to match only mouse-down events or only keyboard events. You could use modes to listen to a different set of ports, suspend timers temporarily, or otherwise change the sources and run loop observers currently being monitored.
- 通过指定mode可以过滤不想要的事件
- mode是用于区分来自于源的事件, 而不是事件的类型
- mode可以监听一组事件(基于端口,定时器...)
Predefined run loop modes
-
Default NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
- 用于大多数操作, 通常用来开始运行run loop 或是配置输入源
-
Connection NSConnectionReplyMode (Cocoa)
- 用于Cocoa中
NSConnection对象监测回复, 尽可能少使用
- 用于Cocoa中
-
Modal NSModalPanelRunLoopMode(Cocoa)
- Cocoa中用于标识 modal panels 事件
-
Event tracking NSEventTrackingRunLoopMode(Cocoa)
- 限定于鼠标拖动, 界面滚动的事件
-
Common modes NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)
- 这是一组可配置的常用模式,将输入源与此模式相关联还将其与组中的每个模式相关联,对于Cocoa应用程序,此集合默认包括默认模式和事件跟踪模式,最初Core Foundation仅包括默认模式,可以使用CFRunLoopAddCommonMode函数将自定义模式添加到集合中
Input Sources
Input sources deliver events asynchronously to your threads. The source of the event depends on the type of the input source, which is generally one of two categories. Port-based input sources monitor your application’s Mach ports. Custom input sources monitor custom sources of events. As far as your run loop is concerned, it should not matter whether an input source is port-based or custom. The system typically implements input sources of both types that you can use as is. The only difference between the two sources is how they are signaled. Port-based sources are signaled automatically by the kernel, and custom sources must be signaled manually from another thread.
When you create an input source, you assign it to one or more modes of your run loop. Modes affect which input sources are monitored at any given moment. Most of the time, you run the run loop in the default mode, but you can specify custom modes too. If an input source is not in the currently monitored mode, any events it generates are held until the run loop runs in the correct mode.
- 输入源向线程异步传递事件
- 输入源有两种, 基于端口的源, 自定义输入源
- 系统已实现这这两种输入源, 可直接使用
- 两种源的区别在于基于端口的源由内核发送, 自定义源要在其他线程手动发送
- 源需要指定在特定的mode中才会起作用, run loop是通过mode去监听特定的事件的
Port-Based Sources
Cocoa and Core Foundation provide built-in support for creating port-based input sources using port-related objects and functions. For example, in Cocoa, you never have to create an input source directly at all. You simply create a port object and use the methods of NSPort to add that port to the run loop. The port object handles the creation and configuration of the needed input source for you.
In Core Foundation, you must manually create both the port and its run loop source. In both cases, you use the functions associated with the port opaque type (CFMachPortRef, CFMessagePortRef, or CFSocketRef) to create the appropriate objects.
- Cocoa中端口源不需要手动创建, 创建一个
NSPort对象添加到run loop中即可,端口对象会自己处理创建和配置输入源
Cocoa Perform Selector Sources
In addition to port-based sources, Cocoa defines a custom input source that allows you to perform a selector on any thread. Like a port-based source, perform selector requests are serialized on the target thread, alleviating many of the synchronization problems that might occur with multiple methods being run on one thread. Unlike a port-based source, a perform selector source removes itself from the run loop after it performs its selector.
Note: Prior to OS X v10.5, perform selector sources were used mostly to send messages to the main thread, but in OS X v10.5 and later and in iOS, you can use them to send messages to any thread.
When performing a selector on another thread, the target thread must have an active run loop. For threads you create, this means waiting until your code explicitly starts the run loop. Because the main thread starts its own run loop, however, you can begin issuing calls on that thread as soon as the application calls the
applicationDidFinishLaunching:method of the application delegate. The run loop processes all queued perform selector calls each time through the loop, rather than processing one during each loop iteration.
- Cocoa定义了特殊的自定义源允许在任意线程执行
selector - OS X v10.5之前, 执行
selector通常是给主线程发送消息, v10.5之后和iOS中, 可向任意线程发送消息 - 在线程中执行
selector要确保所在线程run loop是激活的(run起来的) - run loop一次循环处理所有排队中的
selector, 而不是一次迭代处理一个selector
Timer Sources
Timer sources deliver events synchronously to your threads at a preset time in the future. Timers are a way for a thread to notify itself to do something. For example, a search field could use a timer to initiate an automatic search once a certain amount of time has passed between successive key strokes from the user. The use of this delay time gives the user a chance to type as much of the desired search string as possible before beginning the search.
Although it generates time-based notifications, a timer is not a real-time mechanism. Like input sources, timers are associated with specific modes of your run loop. If a timer is not in the mode currently being monitored by the run loop, it does not fire until you run the run loop in one of the timer’s supported modes. Similarly, if a timer fires when the run loop is in the middle of executing a handler routine, the timer waits until the next time through the run loop to invoke its handler routine. If the run loop is not running at all, the timer never fires.
You can configure timers to generate events only once or repeatedly. A repeating timer reschedules itself automatically based on the scheduled firing time, not the actual firing time. For example, if a timer is scheduled to fire at a particular time and every 5 seconds after that, the scheduled firing time will always fall on the original 5 second time intervals, even if the actual firing time gets delayed. If the firing time is delayed so much that it misses one or more of the scheduled firing times, the timer is fired only once for the missed time period. After firing for the missed period, the timer is rescheduled for the next scheduled firing time.
- 定时源向线程同步传递事件, 是线程通知自己做事的一种方式
- 定时源虽然产生基于时间的通知, 但是并不是一个实时的机制, timer也是只在包含有timer定时源的mode中才会起作用
- 定时器自动执行是基于调度的执行时间, 而不是实际时间, 例:时刻
00:00,在00:05处应该执行timer任务的调度, 因为runloop的某些原因, 调度发生在时刻00:07,调度后仍然会按5s后调度, 而不是原定的00:10时刻调度, 而是00:12执行下一次调度, 如果在时刻00:07调度后, 很长时间(超过多个调度时间)某时刻, 也只会调度一次, 后续还是会按5s间隔调度一次
Run Loop Observers
In contrast to sources, which fire when an appropriate asynchronous or synchronous event occurs, run loop observers fire at special locations during the execution of the run loop itself. You might use run loop observers to prepare your thread to process a given event or to prepare the thread before it goes to sleep. You can associate run loop observers with the following events in your run loop:
The entrance to the run loop.
When the run loop is about to process a timer.
When the run loop is about to process an input source.
When the run loop is about to go to sleep.
When the run loop has woken up, but before it has processed the event that woke it up.
The exit from the run loop.
Similar to timers, run-loop observers can be used once or repeatedly. A one-shot observer removes itself from the run loop after it fires, while a repeating observer remains attached. You specify whether an observer runs once or repeatedly when you create it.
- Observer可以一次观察, 和重复观察, 一次观察后run loop会自动移除Observer, 多次观察则会保留Observer
The Run Loop Sequence of Events
注意run loop的事件执行步骤
Each time you run it, your thread’s run loop processes pending events and generates notifications for any attached observers. The order in which it does this is very specific and is as follows:
1.Notify observers that the run loop has been entered.
2.Notify observers that any ready timers are about to fire.
3.Notify observers that any input sources that are not port based are about to fire.
4.Fire any non-port-based input sources that are ready to fire.
5.If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
6.Notify observers that the thread is about to sleep.
7.Put the thread to sleep until one of the following events occurs:
- An event arrives for a port-based input source.
- A timer fires.
- The timeout value set for the run loop expires.
- The run loop is explicitly woken up.
8.Notify observers that the thread just woke up.
9.Process the pending event.
- If a user-defined timer fired, process the timer event and restart the loop. Go to step 2.
- If an input source fired, deliver the event.
- If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
10.Notify observers that the run loop has exited.
- 1.通知观察者进入run loop
- 2.通知观察者timer将要执行
- 3.通知观察者非端口的输入源将要执行
- 4.执行准备好的非基于端口的源
- 5.基于端口的源准备就绪并且等待执行, 立即执行事件跳转9
- 6.通知观察者线程进入休眠
- 7.当下列事件发生唤醒线程
- 基于端口的事件源到达
- timer执行
- run loop时间过期
- run loop被显示唤醒
- 8.通知观察者run loop被唤醒
- 9.处理待处理事件
- 如果用户定义的定时器启动,处理定时器事件并重启run loop,进入步骤2
- 如果输入源启动,传递相应的消息
- 如果run loop被显式唤醒而且时间还没超时,重启run loop,进入步骤 2
- 10.通知观察者run loop退出
Because observer notifications for timer and input sources are delivered before those events actually occur, there may be a gap between the time of the notifications and the time of the actual events. If the timing between these events is critical, you can use the sleep and awake-from-sleep notifications to help you correlate the timing between the actual events.
Because timers and other periodic events are delivered when you run the run loop, circumventing that loop disrupts the delivery of those events. The typical example of this behavior occurs whenever you implement a mouse-tracking routine by entering a loop and repeatedly requesting events from the application. Because your code is grabbing events directly, rather than letting the application dispatch those events normally, active timers would be unable to fire until after your mouse-tracking routine exited and returned control to the application.
A run loop can be explicitly woken up using the run loop object. Other events may also cause the run loop to be woken up. For example, adding another non-port-based input source wakes up the run loop so that the input source can be processed immediately, rather than waiting until some other event occurs.
- 定时源和输入源的通知在他们相应事件之前之前, 所以通知和执行之间存在着误差(通知之后并不是立即执行),可以使用休眠和唤醒通知来帮助校对实际发生事件的时间
- run loop周期性的运行调度执行, 避免了破坏事件传递
- 可添加非基于端口的源立即唤醒run loop执行
When Would You Use a Run Loop?
注意什么时候应该使用run loop
The only time you need to run a run loop explicitly is when you create secondary threads for your application. The run loop for your application’s main thread is a crucial piece of infrastructure. As a result, the app frameworks provide the code for running the main application loop and start that loop automatically. The run method of UIApplication in iOS (or NSApplication in OS X) starts an application’s main loop as part of the normal startup sequence. If you use the Xcode template projects to create your application, you should never have to call these routines explicitly.
For secondary threads, you need to decide whether a run loop is necessary, and if it is, configure and start it yourself. You do not need to start a thread’s run loop in all cases. For example, if you use a thread to perform some long-running and predetermined task, you can probably avoid starting the run loop. Run loops are intended for situations where you want more interactivity with the thread. For example, you need to start a run loop if you plan to do any of the following:
Use ports or custom input sources to communicate with other threads.
Use timers on the thread.
Use any of the performSelector… methods in a Cocoa application.
Keep the thread around to perform periodic tasks.
If you do choose to use a run loop, the configuration and setup is straightforward. As with all threaded programming though, you should have a plan for exiting your secondary threads in appropriate situations. It is always better to end a thread cleanly by letting it exit than to force it to terminate. Information on how to configure and exit a run loop is described in Using Run Loop Objects.
- 需要自己执行run loop运行只有在为程序创建了辅助线程(次线程/子线程)
- 在辅助线程中, 并不是所有的情况都需要运行run loop, 和线程有更多交互时才使用run loop
run loop使用情形
- 基于端口/自定义源和其他线程通信
- 使用timer
- Cocoa中执行
performSelector - 让线程周期性的执行任务
未完待续...
理解如有错误 望指正 转载请说明出处