这是对苹果官方文档《Threading Program Guide:Run Loops》的翻译

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.

Run loops 是和线程相关的基础部分。一个run loop是一个用来调度工作和协调接受的事件的循环。一个run loop的目的是有任务的时候保持线程忙碌,没有任务的时候线程休眠。

Run loop management is not entirely automatic. You must still design your thread’s code to start the run loop at appropriate times and respond to incoming events. Both Cocoa and Core Foundation provide run loop objects to help you configure and manage your thread’s run loop. Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object. Only secondary threads need to run their run loop explicitly, however. The app frameworks automatically set up and run the run loop on the main thread as part of the application startup process.

Runloop的管理并不是完全自动的,你必须编写线程代码在恰当时间启动runloop,并且响应接收的事件。Cocoa和Core框架都提供了runloop对象供开发者配置和管理线程的runloop。然而你的应用显示不需要创建这些对象,app的框架在程序启动的过程中已经自动设置并且运行了在主线程的runloop。

The following sections provide more information about run loops and how you configure them for your application. For additional information about run loop objects, see NSRunLoop Class Reference and CFRunLoop Reference.

下面的章节提供了更多关于run loops和怎么在应用中配置run loops的信息,更多的关于runloop 对象的信息查看NSRunLoop Class Reference和CFRunLoop Reference

Anatomy of a Run Loop

Run Loop解析

A run loop is very much like its name sounds. It is a loop your thread enters and uses to run event handlers in response to incoming events. Your code provides the control statements used to implement the actual loop portion of the run loop—in other words, your code provides the while or for loop that drives the run loop. Within your loop, you use a run loop object to "run” the event-processing code that receives events and calls the installed handlers.

一个run loop和它的名字听起来非常相似,它是一个你的线程进入的循环,并且用户使用它运行事件处理程序来应答事件。 你的代码控制实现runloop的真正的循环部分。换句话说,你的代码提供了for或者while用来驱动run loop。在你的循环内,你使用一个run loop对象来启动事件处理代码----这些代码能够接收事件并且调用已安装的事件处理程序。

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.

runloop接收的事件来自两个不同类型的源,input source负责分发异步事件,消息通常来自其他的线程或者一个不同的应用程序。timer source 分发同步事件,这些事件发生在计划的时间点或者重复的时间间隔。两种类型的事件源都用一个应用程序特定的程序处理方式来处理到来的事件。

Figure 3-1 shows the conceptual structure of a run loop and a variety of sources. The input sources deliver asynchronous events to the corresponding handlers and cause the runUntilDate: method (called on the thread’s associated NSRunLoop object) to exit. Timer sources deliver events to their handler routines but do not cause the run loop to exit.

图标3-1展示了runloop和各种各样的事件源的概念结构,输入源异步地将事件发送给相应的处理程序,并且导致 runUntilDate:方法(调用NSRunLoop相关联的线程的对象)终止,定时器源会给把事件传递给处理程序,但是不会导致runloop的终止。

In addition to handling sources of input, run loops also generate notifications about the run loop’s behavior. Registered run-loop observers can receive these notifications and use them to do additional processing on the thread. You use Core Foundation to install run-loop observers on your threads.

除了处理输入源,run loops也会生成关于run loop的行为的通知,注册run-loop 观察者可以接收这些通知并且可以使用这些通知在线程上做额外的处理。你可以使用Core Foundation在线程上添加run loop观察者。

The following sections provide more information about the components of a run loop and the modes in which they operate. They also describe the notifications that are generated at different times during the handling of events.

下面的节提供了更多关于run loop组成和run loop处理模式的信息,同样描述了runloop在处理事件的不同时刻获取到的通知。

Run Loop Modes

Run Loop模式

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模式是一个将要被监听额输入源和定时器的集合,以及等待run loop通知的观察者集合。你每次启动run loop,你显式或者隐式的指定一个“模式”来运行,在run loop的运行过程中,只有和指定模式相关的源才会被监听和分发它们的事件(相似的,只有和指定模式关联的观察者才能获得run loop运行进度的通知),和其他模式相关的输入源会将任何接收到的事件保存起来,直到后来以合适的模式运行run loop。

In your code, you identify modes by name. Both Cocoa and Core Foundation define a default mode and several commonly used modes, along with strings for specifying those modes in your code. You can define custom modes by simply specifying a custom string for the mode name. Although the names you assign to custom modes are arbitrary, the contents of those modes are not. You must be sure to add one or more input sources, timers, or run-loop observers to any modes you create for them to be useful.

在你的代码中,你可以通过名字识别运行模式,Cocoa和Core Foundation都定义了一个默认的模式和其他几个通用的模式,可以通过字符串在代码中指定。你可以通过简单为自定义的模式指定字符串名的方式实现自定义模式。虽然你在自定义模式下赋值的名字是任意的,但是这些模式的内容却不是随意的,你必须确保为你创建的模式添加一个或多个输入源、定时器或者run loop观察者,这样自定义的模式才会可用。

You use modes to filter out events from unwanted sources during a particular pass through your run loop. Most of the time, you will want to run your run loop in the system-defined “default” mode. A modal panel, however, might run in the “modal” mode. While in this mode, only sources relevant to the modal panel would deliver events to the thread. For secondary threads, you might use custom modes to prevent low-priority sources from delivering events during time-critical operations.

你使用模式可以在特定run loop运行中过滤掉不想要的源的事件。大多数情况下,你会在“default”模式运行代码。一个模态的面板,然而可能运行在“modal”模式下,因为在这种模式下,只有和模态面板相关的源才能够把事件传递到线程上。(这里是Mac开发的吧,不理解) ,对于次要的线程,你可以使用自定义的模式阻止低优先级的输入源在时间要求比较严格的操作期间传递事件。

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.

注意:模式和事件的输入源要区别对待,模式不是事件的类型。比如:你不能使用模式去单独匹配鼠标按下事件或者单独匹配键盘事件。你可以使用模式来监听一组不同的端口(ports),暂时挂起定时器。也可以改变正在被监控的源和run loop的观察者。

Table 3-1 lists the standard modes defined by Cocoa and Core Foundation along with a description of when you use that mode. The name column lists the actual constants you use to specify the mode in your code.

表3-1列举了Cocoa和Core Foundationd的标准模式和使用的描述信息,name这一栏列举了在代码中指定模式所使用的常量。
Table 3-1 Predefined run loop modes

Default:大多数操作都使用的模式,大多数情况下你应该在这个模式下开启run loop,配置输入源。
Connection:Cocoa使用这个模式结合NSConnection对象来检查依赖。你自己几乎不会用到这种模式
Modal:Cocoa使用这个模式区分发送到模态面板的事件。
Event tracking:Cocoa用这个模式在鼠标拖拽和其他类型用户界面操作跟踪过程中限制输入的事件。
Common modes:这是一个可以通常使用的模式的课配置的组合,和这个模式相关的输入源同样会和组里面的任意一个模式关联。对于Cocoa application,这个组默认包含了default、modal、event tracking模式,Core Foundation初始化时仅仅包含了default模式,你可以使用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.

输入源异步的向你的线程分发事件,事件的来源取决于输入源的类型,通常是两种类型的一种,基于端口的输入源监控你的应用程序的Mach端口,自定义的输入源监控自定义事件源。就你的run loop而言,它不会关心一个输入源是自定义还是基于端口的。系统通常会实现两种输入源,你只管使用就可以了。两种输入源的唯一区别是他们的信号是怎么获得的。基于端口的源由内核发送信号,自定义的源必须手动的在其他线程发信号。

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.

当你创建了一个输入源,你给它指定一种或者多种运行模式,模式决定了那些输入源在任意给定的时刻会被监视。大多数时间你在default模式下运行,但是也可以指定自定义的模式。如果一个输入源并不在当前模式的监视范围,它产生的任意事件都会被保存直到run loop运行在正确的模式。

The following sections describe some of the input sources.

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.

Cocoa和Core Foundation为使用端口相关对象和功能创建基于端口的输入源提供内置支持,比如在Cocoa里面,你从来不需要直接创建输入源,你只需要创建一个端口对象,调用NSPort的方法在run loop上添加端口,端口对象处理需要的输入源的创建和配置。

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.

在Core Foundation,你必须手动的创建端口和run loop输入源。在创建端口和输入源的情况下,需要使用和对外不透透明的(开发文档没有描述)的类型(CFMachPortRef, CFMessagePortRef, or CFSocketRef)相关的函数创建合适的对象。

For examples of how to set up and configure custom port-based sources, see Configuring a Port-Based Input Source.

比如怎么创建一个和配置一个定制的基于端口的输入源,参考 7.7 配置基于端口的输入源

Custom Input Sources

自定义输入源

To create a custom input source, you must use the functions associated with the CFRunLoopSourceRef opaque type in Core Foundation. You configure a custom input source using several callback functions. Core Foundation calls these functions at different points to configure the source, handle any incoming events, and tear down the source when it is removed from the run loop.

创建一个定制的输入源,必须使用在Core Foundation中不透明类CFRunLoopSourceRef相关的函数,配置定制的输入源用到几个回调函数。Core Foundation会在不同的点调用这些函数配置源、处理到来的事件、在源从run loop移除的时候销毁源。

In addition to defining the behavior of the custom source when an event arrives, you must also define the event delivery mechanism. This part of the source runs on a separate thread and is responsible for providing the input source with its data and for signaling it when that data is ready for processing. The event delivery mechanism is up to you but need not be overly complex.

除了定义自定输入源在事件到来时的行为,你必须也定义事件的传递机制,输入源的这部分运行在一个单独的线程上,并且负责提供输入源的数据、在数据准备处理的时候发信号给输入源。事件的传递机制取决于你,但是不需要过于复杂。

For an example of how to create a custom input source, see Defining a Custom Input Source. For reference information for custom input sources, see also CFRunLoopSource Reference.
有关如何创建自定义输入源的示例,请7.1 定义一个自定义的输入源。有关自定义输入源的参考信息,请参阅“CFRunLoopSource”。

Cocoa Perform Selector Sources

Cocoa执行消息选择器源

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.

除了基于端口的输入源,Cocoa定义了一个自定义的输入源允许你在任意线程上执行selector的,就像基于端口的输入源,在目标线程上执行selector的请求被序列化了,减少了许多在多个方法同时执行在一个线程的情况下发生的同步问题。和基于端口不同的是,一个perform selector输入源在执行完selector后会自动把自己从run loop移除。


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.


在10.5 之前的OS X上,perform selector 输入源主要给主线程发信息,在OS X10.5之后,可以给任意线程发消息。

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.

当在线程上执行一个selector的时候,该线程必须有一个活跃的run loop,对于你创建的线程,这意味着一直等待到你的代码显示的开启run loop。因为主线程已经开启它的run loop了,所以程序一调用applicationDidFinishLaunching:就向该线程发出调用,run loop每进行一次循环就会处理队列化的perform selector的调用,而不是每次run loop循环处理队列中的选一个处理。

Table 3-2 lists the methods defined on NSObject that can be used to perform selectors on other threads. Because these methods are declared on NSObject, you can use them from any threads where you have access to Objective-C objects, including POSIX threads. These methods do not actually create a new thread to perform the selector.

表3-2列举了定义在NSObject可以在其他线程上执行selecors的方法,因为这些方法定义在NSObject类里面,你可以在任何你可以访问到Objective-C对象的线程中使用,包括POSIX线程。这些方法实际上并不创建新的线程去执行selector。

For detailed information about each of these methods, see NSObject Class Reference.

这里是表格说明

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.

虽然定时器产生了基于时间的通知,但是一个定时器并不是真正实时机制。就像输入源一样,定时器关联了你的run loop里的特定的模式。如果一个timer并不是处于run loop当前监控的模式,定时器在你以定时器支持的模式运行run loop之前就不会启动。相似的,一个定时器如果在run loop执行处理代码的过程中开启了,定时器会等到下一次run loop调用它的处理程序。如果run loop没有运行,定时器永远不会启动。

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.

你可以配置定时器一次或者重复的产生事件,一个重复的定时器自动的在一个预定的启动(fire)时间开始重复调度自己,并不是从真正的定时器fire的时间开始算。比如,一个定时器被设定在特定的时间点启动而且从那以后5秒钟一次。预定的fire时间将永远会落在于原来5s的时间间隔,如果真正的启动时间延迟。如果启动的时间延迟非常多以至于定时器错过了一次或多次预定的fire时间点,定时器只会在错过的时间片段内启动一次,在错过的时间段fire后,定时器会重新设定下次预设的fire时间。

For more information on configuring timer sources, see Configuring Timer Sources. For reference information, see NSTimer Class Reference or CFRunLoopTimer Reference.

配置定时器更多参考 7.6 配置定时器, NSTimer Class Reference or CFRunLoopTimer Reference.

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:

与输入源相反,当一个合适的同步或者异步事件发生时输入源会fire.而run loop观察者在run loop本身自己执行的过程中会在一个特殊的地方fire。你可以用run loop观察者让你的线程去处理一个给定的事件或者为run loop将要进入睡眠准备线程。你同样可以将run loop观察者和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.
  • run loop的入口
  • run loop将要处理一个定时器
  • run loop 将要处理一个输入源
  • run loop 将要进入睡眠
  • run loop 已经唤醒,但是还没有处理唤醒run loop的事件
  • 退出run loop

You can add run loop observers to apps using Core Foundation. To create a run loop observer, you create a new instance of the CFRunLoopObserverRef opaque type. This type keeps track of your custom callback function and the activities in which it is interested.

你可以给app用 Core Foundation 添加run loop观察者,创建一个run loop观察者,你创建了一个CFRunLoopObserverRef的类型的对象,这个类型持续跟踪你自定义的回调和它关心的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.

和定时器相似,run loop观察者可以重复或者单次使用,一个单次使用的观察者会在它fire后在run loop中移除,一个重复的观察者依然依附在run loop上。单次还是重复可以在创建的时候指定。

For an example of how to create a run-loop observer, see Configuring the Run Loop. For reference information, see CFRunLoopObserver Reference.

有关如何创建run loop 观察者的示例,请参阅6.2 配置run loop。有关参考信息,请参阅CFRunLoopObserver

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:

每次你运行run loop,你的线程的run loop会处理挂起的事件,并且会给它的观察者发送通知。处理的顺序是非常特别的,就是下面顺序:

  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.
  1. Notify observers that the thread just woke up.
  2. 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.
  1. Notify observers that the run loop has exited.

1.通知观察者run loop已经进入了循环。
2.通知观察者所有准备就绪的定时器将要 fire
3.通知观察者所有非基于端口的输入源将要 fire
4.fire所有非基于端口的准备fire的输入源
5.如果一个基于端口的输入源准备好了并且等待fire。立刻fire。到第9部。
6.通知观察者线程将要睡眠
7.将线程睡眠直到下面任意一个事件发生:

  • 一个事件到达了基于端口的源
  • 定时器fire
  • run loop设置了到期的超时事件
  • 显示的指定run loop唤醒

8.通知观察者线程已经唤醒。
9.处理挂起的事件。

  • 如果一个用户定义的定时器fire。处理定时器事件并且重新启动run loop。到步骤2.
  • 如果一个输入源fire,传递事件。
  • 如果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.

因为定时器和其他的周期性的事件会在你运行run loop的时候传递,所以要避免run loop对事件传递的打断。一个经典行为:每当你通过一个循环不断的从应用程序请求事件来实现一个鼠标的跟踪程序的时候。因为你的代码是直接捕获的事件,而不是让应用程序正常的分发这些事件,活跃的定时器将在你的鼠标跟踪程序退出并将控制权返回给应用程序后失效。

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对象显示的唤醒,其他的事件同样可以使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.

唯一需要显示的运行一个run loop的场景是在应用程序中创建了辅助线程。应用程序主线程的run loop是基础设施的关键部分。所以app的框架提供了运行主线程run loop的代码并且自动开启。iOS的UIAppliaction的run方法(或者OS X 的NSApplication)开启一个应用程序的main loop作为一些列程序启动流程的一部分。如果你使用xcode模板工程创建应用,你应该从来不显示的调用这些例程。

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:

对于辅助线程,你需要决定一个run loop是不是必要的,如果是,就配置并开启它。你并不需要在任意情况下都开启一个线程的run loop。比如:如果你使用一个线程执行某些长时间运行并且是事先确定的任务,你可以避免开启run loop。run loops的目的是为了应用在你想和线程有更多的交互的场合上的。比如:如果你想做下面的任何事情你就需要开启run loop。

  • 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.
  • 使用端口或者自定义的输入源和其他线程通信
  • 在线程上使用定时器
  • 在cocoa应用中使用任意一个performSelector…方法
  • 使得线程不被杀死去做周期性任务

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,配置和创建是非常简单的。和所有的线程编程一样,你为在合适的场合下结束你的辅助线程指定计划。通常来说让线程以结束的退出(exit)的方式要比强制让线程终止的办法好。怎么配置和退出run loop的描述信息在 6. 使用run loop对象.

Using Run Loop Objects

使用run loop对象

A run loop object provides the main interface for adding input sources, timers, and run-loop observers to your run loop and then running it. Every thread has a single run loop object associated with it. In Cocoa, this object is an instance of the NSRunLoop class. In a low-level application, it is a pointer to a CFRunLoopRef opaque type.

一个run loop对象提供了添加输入源,定时器,观察者和运行run loop的主要接口,每一个线程都单独有一个run loop对象和它关联。在 Cocoa中这个对象是NSRunLoop类的一个实例,在低层次的应用中,是一个CFRunLoopRef类型的指针。

Getting a Run Loop Object

获取一个run loop对象

To get the run loop for the current thread, you use one of the following:

获取当前线程的run loop只需要用下面的一种方法:

  • In a Cocoa application, use the currentRunLoop class method of NSRunLoop to retrieve an NSRunLoop object.
  • Use the CFRunLoopGetCurrent function.
  • 在Cocoa应用,使用NSRunLoop类的类方法currentRunLoop返回一个NSRunLoop对象
  • 使用CFRunLoopGetCurrent函数

Although they are not toll-free bridged types, you can get a CFRunLoopRef opaque type from an NSRunLoop object when needed. The NSRunLoop class defines a getCFRunLoop method that returns a CFRunLoopRef type that you can pass to Core Foundation routines. Because both objects refer to the same run loop, you can intermix calls to the NSRunLoop object and CFRunLoopRef opaque type as needed.

虽然这两个并不是可以自由的桥接类型,但是你在必要的时候可以从一个NSRunLoop对象中获取一个CFRunLoop类型。 通过NSRunLoop的getCFRunLoop方法获得,然后传递给Core Foundation的代码。因为两个对象引用了相同的run loop,你可以根据需要随意调用。

Configuring the Run Loop

配置run loop

Before you run a run loop on a secondary thread, you must add at least one input source or timer to it. If a run loop does not have any sources to monitor, it exits immediately when you try to run it. For examples of how to add sources to a run loop, see Configuring Run Loop Sources.

当你在一个辅助线程上开启run loop之前,必须给run loop添加至少一个输入源或者一个定时器。如果一个run loop没有任何源来监控,就会立刻退出。参考Configuring Run Loop Sources

In addition to installing sources, you can also install run loop observers and use them to detect different execution stages of the run loop. To install a run loop observer, you create a CFRunLoopObserverRef opaque type and use the CFRunLoopAddObserver function to add it to your run loop. Run loop observers must be created using Core Foundation, even for Cocoa applications.

除了添加输入源,你可以添加run loop观察者,并且使用他们监测run loop不同阶段的操作,添加观察者要创建一个 CFRunLoopObserverRef 类型的对象,用CFRunLoopAddObserve函数添加到run loop上。观察者必须用Core Foundation创建,即使在Cocoa应用中。

Listing 3-1 shows the main routine for a thread that attaches a run loop observer to its run loop. The purpose of the example is to show you how to create a run loop observer, so the code simply sets up a run loop observer to monitor all run loop activities. The basic handler routine (not shown) simply logs the run loop activity as it processes the timer requests.

3-1是一个绑定了观察者的线程开启它的run loop的代码。这个案例主要展示怎么创建run loop观察者,所以代码只是简单的创建了一个观察者来监控run loop的所有的活动。基本的处理程序(没有展示)简单地在处理定时器请求的时候记录了run loop的活动。
Listing 3-1 Creating a run loop observer

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

When configuring the run loop for a long-lived thread, it is better to add at least one input source to receive messages. Although you can enter the run loop with only a timer attached, once the timer fires, it is typically invalidated, which would then cause the run loop to exit. Attaching a repeating timer could keep the run loop running over a longer period of time, but would involve firing the timer periodically to wake your thread, which is effectively another form of polling. By contrast, an input source waits for an event to happen, keeping your thread asleep until it does.

当给长时间存在的线程配置run loop时,最好添加一个输入源来接收消息。即使你可以进入一个只有一个定时器的run loop,一旦定时器fire,就无效了。会导致run loop退出。绑定一个重复的定时器可以使得run loop在一个长的时间段运行。但是需要定期的触发定时器唤醒你的线程。这实际上是另一种形式的轮询。相反,一个输入源会等待事件的发生,在次之前会保持线程的休眠。

Starting the Run Loop

开启run loop

Starting the run loop is necessary only for the secondary threads in your application. A run loop must have at least one input source or timer to monitor. If one is not attached, the run loop exits immediately.

在应用中开启run loop仅仅对于辅助线程是必要的,run loop必须有至少一个输入源或者定时器去监控,如果一个都没有就会立刻结束。
There are several ways to start the run loop, including the following:
下面是几种开启run loop的方法:

  • Unconditionally
  • With a set time limit
  • In a particular mode
  • 无条件的(Unconditionally)
  • 带有时间限制设置的(With a set time limit)
  • 在特定的模式下(In a particular mode)

Entering your run loop unconditionally is the simplest option, but it is also the least desirable. Running your run loop unconditionally puts the thread into a permanent loop, which gives you very little control over the run loop itself. You can add and remove input sources and timers, but the only way to stop the run loop is to kill it. There is also no way to run the run loop in a custom mode.

无条件的进入run loop是最简单的选项,但是也是最不需要的。无条件的运行run loop将线程放在一个永久的循环中,对run loop本身的控制就非常少。你可以添加或者移除输入源或者定时器,但是唯一使得run loop停止的方式是杀死它,而且没有办法在定制的模式下运行run loop。

Instead of running a run loop unconditionally, it is better to run the run loop with a timeout value. When you use a timeout value, the run loop runs until an event arrives or the allotted time expires. If an event arrives, that event is dispatched to a handler for processing and then the run loop exits. Your code can then restart the run loop to handle the next event. If the allotted time expires instead, you can simply restart the run loop or use the time to do any needed housekeeping.

与其无条件启动run loop,不如给run loop设置一个超时时间运行反而更好。当你用一个超时时间值时,run loop会一直运行直到事件的到来或者分配的时间用完。如果一个事件到来了,事件就会被分发给处理程序去处理,然后run loop退出。如果分配的时间过期了,你可以简单的重启run loop或者花时间处理任何需要的事物。

In addition to a timeout value, you can also run your run loop using a specific mode. Modes and timeout values are not mutually exclusive and can both be used when starting a run loop. Modes limit the types of sources that deliver events to the run loop and are described in more detail in Run Loop Modes.

除了设置超时事件值外,你也可以给run loop以指定的模式运行run loop,模式和超时时间值并不互斥,可以同时添加。模式限制了传递给run loop事件的输入源的类型。(详细信息1. Run Loop模式.)

Listing 3-2 shows a skeleton version of a thread’s main entry routine. The key portion of this example shows the basic structure of a run loop. In essence, you add your input sources and timers to the run loop and then repeatedly call one of the routines to start the run loop. Each time the run loop routine returns, you check to see if any conditions have arisen that might warrant exiting the thread. The example uses the Core Foundation run loop routines so that it can check the return result and determine why the run loop exited. You could also use the methods of the NSRunLoop class to run the run loop in a similar manner if you are using Cocoa and do not need to check the return value. (For an example of a run loop that calls methods of the NSRunLoop class, see Listing 3-14.)

3-2 是一个线程的主要代码结构,关键部分是这个案例展示了run loop的基本结构,实际上你可以给run loop添加自己的输入源和定时器然后重复的从多个程序例程中调用一个来启动run loop。每次run loop例程程序返回,你检查看看是否有任何可能导致线程结束的条件出现了。这个例子用了Core Foundation run loop程序,所以它可以检查返回结果并且知道为什么run loop退出了,如果你用Cocoa,同样可以用 NSRunLoop的方法以一个相似的方式运行run loop而且不用检查返回值,在3-14.
Listing 3-2 Running a run loop

- (void)skeletonThreadMain
{
    // Set up an autorelease pool here if not using garbage collection.
    BOOL done = NO;
 
    // Add your sources or timers to the run loop and do any other setup.
 
    do
    {
        // Start the run loop but return after each source is handled.
        SInt32    result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
 
        // If a source explicitly stopped the run loop, or if there are no
        // sources or timers, go ahead and exit.
        if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
            done = YES;
 
        // Check for any other exit conditions here and set the
        // done variable as needed.
    }
    while (!done);
 
    // Clean up code here. Be sure to release any allocated autorelease pools.
}

It is possible to run a run loop recursively. In other words, you can call CFRunLoopRun, CFRunLoopRunInMode, or any of the NSRunLoop methods for starting the run loop from within the handler routine of an input source or timer. When doing so, you can use any mode you want to run the nested run loop, including the mode in use by the outer run loop.

递归的运行一个run loop是有可能的,换句话说,你可以在输入源或者定时器的处理程序中调用CFRunLoopRun,CFRunLoopRunInMode,或者其他的任意的NSRunLoop方法。当这样做的时候,你可以使用任何你想要的模式运行嵌套的run loop,包括外层的run loop使用的模式。

Exiting the Run Loop

退出 Run Loop

There are two ways to make a run loop exit before it has processed an event:

在使得一个run loop处理事件之前有两种办法结束run loop:

  • Configure the run loop to run with a timeout value.
  • Tell the run loop to stop.
  • 给run loop配置一个超时时间。
  • 告诉run loop停止

Using a timeout value is certainly preferred, if you can manage it. Specifying a timeout value lets the run loop finish all of its normal processing, including delivering notifications to run loop observers, before exiting.

使用超时时间当然是最好的,你可以管理它。指定超时时间让run loop结束它所有的在退出之前通常进行的操作,包括给观察者发通知。

Stopping the run loop explicitly with the CFRunLoopStop function produces a result similar to a timeout. The run loop sends out any remaining run-loop notifications and then exits. The difference is that you can use this technique on run loops you started unconditionally.

用CFRunLoopStop函数显示的让run loop和通过设置超时时间产生的效果是相似的。run loop会发出所有剩下run loop相关的通知然后退出,区别在于你可以在无条件启动的run loop上使用这个技术

Although removing a run loop’s input sources and timers may also cause the run loop to exit, this is not a reliable way to stop a run loop. Some system routines add input sources to a run loop to handle needed events. Because your code might not be aware of these input sources, it would be unable to remove them, which would prevent the run loop from exiting.

虽然移除run loop的输入源和定时器同样会导致run loop退出,但是这并不是一个可靠的停止run loop的方式。有些系统程序给run loop增加输入源处理必要的事件。因为你代码可能没有意识到这些输入源的存在,它不能移除掉这些输入源,这会阻止run loop的退出。

Thread Safety and Run Loop Objects

线程安全和Run Loop对象

Thread safety varies depending on which API you are using to manipulate your run loop. The functions in Core Foundation are generally thread-safe and can be called from any thread. If you are performing operations that alter the configuration of the run loop, however, it is still good practice to do so from the thread that owns the run loop whenever possible.

线程安全的差异取决于你操作run loop所使用的API,在Core Foundation的函数通常是线程安全的,而且可以被任何线程调用。然而如果你在执行run loop配置的操作,尽可能的从该run loop对应的线程上操作依然是一个好的做法。

The Cocoa NSRunLoop class is not as inherently thread safe as its Core Foundation counterpart. If you are using the NSRunLoop class to modify your run loop, you should do so only from the same thread that owns that run loop. Adding an input source or timer to a run loop belonging to a different thread could cause your code to crash or behave in an unexpected way.

Cocoa的NSRunLoop类并不是像在Core Foundation中那样线程安全的,如果你使用NSRunLoop来修改你的run loop,你应该仅仅在run loop对应的那个线程上操作。添加一个输入源或者定时器给非当前线程的run loop会导致你的代码崩溃或者产生不可预测的行为。

Configuring Run Loop Sources

配置 Run Loop 资源

The following sections show examples of how to set up different types of input sources in both Cocoa and Core Foundation.

下面章节的代码是一些如何设置不同类型输入源的案例(Cocoa和Foundation)

Defining a Custom Input Source

定义一个自定义的输入源
Creating a custom input source involves defining the following:
创建一个自定义的输入源包含如下定义选项

  • The information you want your input source to process.
  • A scheduler routine to let interested clients know how to contact your input source.
  • A handler routine to perform requests sent by any clients.
  • A cancellation routine to invalidate your input source.
  • 输入源希望处理的信息
  • 一个调度程序让感兴趣的客户(client)知道怎么和你的输入源取得联系
  • 一个处理程序负责执行客户(client)发来的请求
  • 一个取消程序让你输入源无效

Because you create a custom input source to process custom information, the actual configuration is designed to be flexible. The scheduler, handler, and cancellation routines are the key routines you almost always need for your custom input source. Most of the rest of the input source behavior, however, happens outside of those handler routines. For example, it is up to you to define the mechanism for passing data to your input source and for communicating the presence of your input source to other threads.

因为你自己创建一个自定义的输入源来处理自定义的信息,实际的配置的设计是灵活的。调度程序和取消程序是关键程序,你的自定义输入源几乎总是需要的,剩下的大部分输入源行为发生在这些程序之外。比如你可以定义传递数据给你的输入源的机制和将输入源的存在传递给其他线程。

Figure 3-2 shows a sample configuration of a custom input source. In this example, the application’s main thread maintains references to the input source, the custom command buffer for that input source, and the run loop on which the input source is installed. When the main thread has a task it wants to hand off to the worker thread, it posts a command to the command buffer along with any information needed by the worker thread to start the task. (Because both the main thread and the input source of the worker thread have access to the command buffer, that access must be synchronized.) Once the command is posted, the main thread signals the input source and wakes up the worker thread’s run loop. Upon receiving the wake up command, the run loop calls the handler for the input source, which processes the commands found in the command buffer.

图3-2是一个自定义输入源配置的案例。这案例中程序的主线程维护对输入源、自定义输入源的自定义命令缓冲区、输入源所在的run loop的引用。当主线程有一个任务要交给工作线程的时候,它会向命令缓冲区发送一个命令和工作线程需要的所有开始任务所需要的信息。(因为主线程和工作线程都有访问命令缓冲区的权限,访问必须是同步的)一旦受到唤醒的命令,run loop调用输入源的处理程序来处理在命令缓冲区的命令。

The following sections explain the implementation of the custom input source from the preceding figure and show the key code you would need to implement.

下面的章节解释了上面图标自定义输入源的实现,和关键要实现的代码

Defining the Input Source

定义输入源

Defining a custom input source requires the use of Core Foundation routines to configure your run loop source and attach it to a run loop. Although the basic handlers are C-based functions, that does not preclude you from writing wrappers for those functions and using Objective-C or C++ to implement the body of your code.

自定义一个输入源需要用Core Foundation的代码来配置run loop资源,并且将它和run loop依附在一起。虽然基础的处理程序是C函数,但是并不排除你需要用OC或者C++来封装这些函数来实现你的代码主体。

The input source introduced in Figure 3-2 uses an Objective-C object to manage a command buffer and coordinate with the run loop. Listing 3-3 shows the definition of this object. The RunLoopSource object manages a command buffer and uses that buffer to receive messages from other threads. This listing also shows the definition of the RunLoopContext object, which is really just a container object used to pass a RunLoopSource object and a run loop reference to the application’s main thread.

图3-2中介绍的输入源使用了OC对象来管理一个命令行缓冲区,协调run loop。3-3展示的是这个对象的定义,RunLoopSource对象管理一个命令行缓冲区,用缓冲区接收其他线程的消息。3-3同样展示了RunLoopContext对象的定义,这是一个真正的用来传递一个RunLoopSource对象和run loop的引用到应用程序主线程的容器对象。
Listing 3-3 The custom input source object definition

@interface RunLoopSource : NSObject
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray* commands;
}
 
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
 
// Handler method
- (void)sourceFired;
 
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@end
 
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
 
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
    CFRunLoopRef        runLoop;
    RunLoopSource*        source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
 
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end

Although the Objective-C code manages the custom data of the input source, attaching the input source to a run loop requires C-based callback functions. The first of these functions is called when you actually attach the run loop source to your run loop, and is shown in Listing 3-4. Because this input source has only one client (the main thread), it uses the scheduler function to send a message to register itself with the application delegate on that thread. When the delegate wants to communicate with the input source, it uses the information in RunLoopContext object to do so.

虽然输入源的自定义的数据是OC代码管理的,但是将输入源和run loop关联在一起的代码需要基于C的回调函数,这些函数的第一个会在你真正将run loop源和run loop绑定的时候调用,在3-4,因为输入源只有一个客户(主线程)它使用调度程序中的函数发送一个信息来将自己在那个线程的应用代理上注册自己。当代理想和输入源取得联系的时候,就会使用RunLoopContext对象来实现。
Listing 3-4 Scheduling a run loop source

void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate*   del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(registerSource:)
                                withObject:theContext waitUntilDone:NO];
}

One of the most important callback routines is the one used to process custom data when your input source is signaled. Listing 3-5 shows the perform callback routine associated with the RunLoopSource object. This function simply forwards the request to do the work to the sourceFired method, which then processes any commands present in the command buffer.

最重要的回调程序之一是用来在输入源收到到信号时处理自定义数据的,3-5展示了执行和RunLoopSource对象相关的回调代码。这个函数简单的转发了工作请求给sourceFired方法,这个方法会在以后处理命令缓冲区内出现的任何命令。

Listing 3-5  Performing work in the input source
void RunLoopSourcePerformRoutine (void *info)
{
    RunLoopSource*  obj = (RunLoopSource*)info;
    [obj sourceFired];
}

If you ever remove your input source from its run loop using the CFRunLoopSourceInvalidate function, the system calls your input source’s cancellation routine. You can use this routine to notify clients that your input source is no longer valid and that they should remove any references to it. Listing 3-6 shows the cancellation callback routine registered with the RunLoopSource object. This function sends another RunLoopContext object to the application delegate, but this time asks the delegate to remove references to the run loop source.

如果你使用CFRunLoopSourceInvalidate函数将输入源移除,系统会调用输入源的取消代码。你可以用这个代码通知客户们你的输入源已经不再有效了,他们应该移除和它的所有的关联。3-6是RunLoopSource对象注册的取消回调代码。这个函数发送另一个RunLoopContext对象给应用代理,但是这次是请求代理移除run loop源的关联。
Listing 3-6 Invalidating an input source

void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
    RunLoopSource* obj = (RunLoopSource*)info;
    AppDelegate* del = [AppDelegate sharedAppDelegate];
    RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
 
    [del performSelectorOnMainThread:@selector(removeSource:)
                                withObject:theContext waitUntilDone:YES];
}
Note: The code for the application delegate’s registerSource: and removeSource: methods is shown in Coordinating with Clients of the Input Source.
备注:应用程序代理的registerSource: and removeSource:方法在Coordinating with
Clients of the Input Source

Installing the Input Source on the Run Loop

在run loop上添加输入源

Listing 3-7 shows the init and addToCurrentRunLoop methods of the RunLoopSource class. The init method creates the CFRunLoopSourceRef opaque type that must actually be attached to the run loop. It passes the RunLoopSource object itself as the contextual information so that the callback routines have a pointer to the object. Installation of the input source does not occur until the worker thread invokes the addToCurrentRunLoop method, at which point the RunLoopSourceScheduleRoutine callback function is called. Once the input source is added to the run loop, the thread can run its run loop to wait on it.

3-7展示了RunLoopSource的init和addToCurrentRunLoop方法。init方法创建了必须依附到RunLoop上的CFRunLoopSourceRef非透明类型对象,它通过传递RunLoopSource对象本身作为上下文信息,所以回调程序会有指向该对象的指针。输入源的安装工作不会在工作线程调用addToCurrentRunLoop方法前进行,addToCurrentRunLoop调用时RunLoopSourceScheduleRoutine的回调函数就会被调用,一旦输入源添加到run loop,线程就可以运行它的run loop来等待事件。
Listing 3-7 Installing the run loop source

- (id)init
{
    CFRunLoopSourceContext    context = {0, self, NULL, NULL, NULL, NULL, NULL,
                                        &RunLoopSourceScheduleRoutine,
                                        RunLoopSourceCancelRoutine,
                                        RunLoopSourcePerformRoutine};
 
    runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
    commands = [[NSMutableArray alloc] init];
 
    return self;
}
 
- (void)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}

Coordinating with Clients of the Input Source

协调输入源的客户

For your input source to be useful, you need to manipulate it and signal it from another thread. The whole point of an input source is to put its associated thread to sleep until there is something to do. That fact necessitates having other threads in your application know about the input source and have a way to communicate with it.

为了输入输入源起作用,你应该巧妙控制它并且在另一个线程给它发信号。输入源的要点让和它关联的线程睡眠直到有事可做。所以让其他的线程能够获得输入源的信息和并且和输入源进行通信是现实的需求。

One way to notify clients about your input source is to send out registration requests when your input source is first installed on its run loop. You can register your input source with as many clients as you want, or you can simply register it with some central agency that then vends your input source to interested clients. Listing 3-8 shows the registration method defined by the application delegate and invoked when the RunLoopSource object’s scheduler function is called. This method receives the RunLoopContext object provided by the RunLoopSource object and adds it to its list of sources. This listing also shows the routine used to unregister the input source when it is removed from its run loop.

一个通知输入源的客户的方式是当输入源第一次安装在run loop上的时候发送注册请求。可以为一个输入源注册多个客户,也可以简单的注册到一些中心机构,然后在把输入源给感兴趣的客户。3-8展示了应用程序代理的注册并在RunLoopSource对象的调度函数被调用时执行的注册方法,这个方法接收RunLoopSource对象提供的RunLoopContext对象,并且把它添加到源列表上,下面的代码也包含了在从run loop移除的时候如何反注册输入源。
Listing 3-8 Registering and removing an input source with the application delegate

- (void)registerSource:(RunLoopContext*)sourceInfo;
{
    [sourcesToPing addObject:sourceInfo];
}
 
- (void)removeSource:(RunLoopContext*)sourceInfo
{
    id    objToRemove = nil;
 
    for (RunLoopContext* context in sourcesToPing)
    {
        if ([context isEqual:sourceInfo])
        {
            objToRemove = context;
            break;
        }
    }
 
    if (objToRemove)
        [sourcesToPing removeObject:objToRemove];
}
Note: The callback functions that call the methods in the preceding listing are shown in Listing 3-4 and Listing 3-6.
回调函数调用的方法在上面的3-4和3-6

Signaling the Input Source

给输入源发信号

After it hands off its data to the input source, a client must signal the source and wake up its run loop. Signaling the source lets the run loop know that the source is ready to be processed. And because the thread might be asleep when the signal occurs, you should always wake up the run loop explicitly. Failing to do so might result in a delay in processing the input source.

当一个客户把它的数据传递给输入源后,必须给输入源发信号唤醒它的run loop,给输入源发信号让run loop知道输入源已经准备好,等待处理。因为一个信号发生的时候线程可能正在休眠,你应该总是显示的唤醒run loop。如果不这样做可能会导致处理输入源的数据上产生延迟。

Listing 3-9 shows the fireCommandsOnRunLoop method of the RunLoopSource object. Clients invoke this method when they are ready for the source to process the commands they added to the buffer.

3-9展示了RunLoopSource 对象的fireCommandsOnRunLoop方法,客户在他们为输入源做好处理缓冲区数据的准备时调用这个方法。
Listing 3-9 Waking up the run loop

- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(runloop);
}
Note: You should never try to handle a SIGHUP or other type of process-level signal by messaging a custom input source. The Core Foundation functions for waking up the run loop are not signal safe and should not be used inside your application’s signal handler routines. For more information about signal handler routines, see the sigaction man page.
备注:你不应该尝试通过发送自定义输入源来处理SIGHUP或其他类型的进程级信号,用于唤醒Run Loop的Core Foundation功能不是信号安全的,不应该在应用程序的信号处理程序中使用。 有关信号处理程序例程的更多信息,请参阅sigaction手册页。

Configuring Timer Sources

配置定时器

To create a timer source, all you have to do is create a timer object and schedule it on your run loop. In Cocoa, you use the NSTimer class to create new timer objects, and in Core Foundation you use the CFRunLoopTimerRef opaque type. Internally, the NSTimer class is simply an extension of Core Foundation that provides some convenience features, like the ability to create and schedule a timer using the same method.

要创建定时器源,你只需创建一个定时器对象并在Run Loop中调度。 在Cocoa中,您可以使用NSTimer类来创建新的定时器对象,而在Core Foundation中,您可以使用CFRunLoopTimerRef类型。 在内部,NSTimer类只是Core Foundation的扩展,它提供了一些方便的功能,例如使用相同方法创建和计划定时器的能力。

In Cocoa, you can create and schedule a timer all at once using either of these class methods:

在Cocoa中,您可以使用以下任一类方法一次创建和调度定时器器:

scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:

These methods create the timer and add it to the current thread’s run loop in the default mode (NSDefaultRunLoopMode). You can also schedule a timer manually if you want by creating your NSTimer object and then adding it to the run loop using the addTimer:forMode: method of NSRunLoop. Both techniques do basically the same thing but give you different levels of control over the timer’s configuration. For example, if you create the timer and add it to the run loop manually, you can do so using a mode other than the default mode. Listing 3-10 shows how to create timers using both techniques. The first timer has an initial delay of 1 second but then fires regularly every 0.1 seconds after that. The second timer begins firing after an initial 0.2 second delay and then fires every 0.2 seconds after that.

这些方法创建定时器,并以默认模式(NSDefaultRunLoopMode)将其添加到当前线程的Run Loop中。 如果您想通过创建NSTimer对象然后使用NSRunLoop的addTimer:forMode:方法将其添加到运行循环中,也可以手动调度计时器。这两种技术基本上都是一样的,但是给你不同级别的控制定时器配置。 例如,如果创建定时器并手动将其添加到运行循环中,则可以使用除默认模式之外的模式来执行此操作。 清单3-10显示了如何使用这两种技术创建定时器。 第一个定时器的初始延迟为1秒,但随后每0.1秒钟定时fire。 第二个定时器在初始0.2秒延迟后开始首次fire,然后每0.2秒fire一次。
Listing 3-10 Creating and scheduling timers using NSTimer

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

Listing 3-11 shows the code needed to configure a timer using Core Foundation functions. Although this example does not pass any user-defined information in the context structure, you could use this structure to pass around any custom data you needed for your timer. For more information about the contents of this structure, see its description in CFRunLoopTimer Reference.

3-11显示了使用Core Foundation函数配置定时器所需的代码。 虽然此示例不会在上下文结构中传递任何用户定义的信息,但您可以使用此结构传递定时器所需的任何自定义数据。 有关此结构的内容的更多信息,请参阅CFRunLoopTimer参考中的描述。
Listing 3-11 Creating and scheduling a timer using Core Foundation

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

Configuring a Port-Based Input Source

配置基于端口的输入源

Both Cocoa and Core Foundation provide port-based objects for communicating between threads or between processes. The following sections show you how to set up port communication using several different types of ports.

Cocoa和Core Foundation都提供基于端口的对象,用于线程之间或进程之间的通信。 以下部分将介绍如何使用几种不同类型的端口设置端口通信。

Configuring an NSMachPort Object

配置NSMachPort对象

To establish a local connection with an NSMachPort object, you create the port object and add it to your primary thread's run loop. When launching your secondary thread, you pass the same object to your thread's entry-point function. The secondary thread can use the same object to send messages back to your primary thread.

要建立与NSMachPort对象的本地连接,你将创建端口对象并将其添加到主线程的Run Loop中。 启动辅助线程时,将相同的对象传递给线程的入口点函数。 辅助线程可以使用相同的对象将消息发送回主线程。

Implementing the Main Thread Code

实现主线程代码

Listing 3-12 shows the primary thread code for launching a secondary worker thread. Because the Cocoa framework performs many of the intervening steps for configuring the port and run loop, the launchThread method is noticeably shorter than its Core Foundation equivalent (Listing 3-17); however, the behavior of the two is nearly identical. One difference is that instead of sending the name of the local port to the worker thread, this method sends the NSPort object directly.

3-12显示了启动辅助工作线程的主线程代码。 因为Cocoa框架执行了许多用于配置端口和run loop的介入步骤,所以launchThread方法明显短于其Core Foundation中等效的配置(清单3-17);然而,两者的行为几乎相同。 一个区别是,该方法不是将本地端口的名称发送给工作线程,而是直接发送NSPort对象。
Listing 3-12 Main thread launch method

- (void)launchThread
{
    NSPort* myPort = [NSMachPort port];
    if (myPort)
    {
        // This class handles incoming port messages.
        [myPort setDelegate:self];
 
        // Install the port as an input source on the current run loop.
        [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
        // Detach the thread. Let the worker release the port.
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
               toTarget:[MyWorkerClass class] withObject:myPort];
    }
}

In order to set up a two-way communications channel between your threads, you might want to have the worker thread send its own local port to your main thread in a check-in message. Receiving the check-in message lets your main thread know that all went well in launching the second thread and also gives you a way to send further messages to that thread.

为了在线程之间建立一个双向通信通道,你可能希望工作线程在登录消息中将自己的本地端口发送到主线程。 接收签入消息让你的主线程知道在启动第二个线程时一切顺利,并且还可以向你发送更多消息到该线程。

Listing 3-13 shows the handlePortMessage: method for the primary thread. This method is called when data arrives on the thread's own local port. When a check-in message arrives, the method retrieves the port for the secondary thread directly from the port message and saves it for later use.

3-13显示了主线程的handlePortMessage:方法。 当数据到达线程自己的本地端口时调用此方法。 当一个签到消息到达时,该方法直接从端口消息中检索次要线程的端口,并保存以备以后使用。
Listing 3-13 Handling Mach port messages

#define kCheckinMessage 100
 
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
    unsigned int message = [portMessage msgid];
    NSPort* distantPort = nil;
 
    if (message == kCheckinMessage)
    {
        // Get the worker thread’s communications port.
        distantPort = [portMessage sendPort];
 
        // Retain and save the worker port for later use.
        [self storeDistantPort:distantPort];
    }
    else
    {
        // Handle other messages.
    }
}

Implementing the Secondary Thread Code

实现次要线程代码

For the secondary worker thread, you must configure the thread and use the specified port to communicate information back to the primary thread.

对于辅助工作线程,你必须配置线程并使用指定的端口将信息传回主线程。

Listing 3-14 shows the code for setting up the worker thread. After creating an autorelease pool for the thread, the method creates a worker object to drive the thread execution. The worker object’s sendCheckinMessage: method (shown in Listing 3-15) creates a local port for the worker thread and sends a check-in message back to the main thread.

清单3-14显示了设置工作线程的代码。 为线程创建自动释放池后,该方法将创建一个工作对象来驱动线程执行。 工作对象的sendCheckinMessage:方法(如清单3-15所示)为工作线程创建一个本地端口,并将一个签入消息发送回主线程。
Listing 3-14 Launching the worker thread using Mach ports

+(void)LaunchThreadWithPort:(id)inData
{
    NSAutoreleasePool*  pool = [[NSAutoreleasePool alloc] init];
 
    // Set up the connection between this thread and the main thread.
    NSPort* distantPort = (NSPort*)inData;
 
    MyWorkerClass*  workerObj = [[self alloc] init];
    [workerObj sendCheckinMessage:distantPort];
    [distantPort release];
 
    // Let the run loop process things.
    do
    {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                            beforeDate:[NSDate distantFuture]];
    }
    while (![workerObj shouldExit]);
 
    [workerObj release];
    [pool release];
}

When using NSMachPort, local and remote threads can use the same port object for one-way communication between the threads. In other words, the local port object created by one thread becomes the remote port object for the other thread.

当使用NSMachPort时,本地和远程线程可以使用相同的端口对象进行线程之间的单向通信。 换句话说,由一个线程创建的本地端口对象将成为另一个线程的远程端口对象。

Listing 3-15 shows the check-in routine of the secondary thread. This method sets up its own local port for future communication and then sends a check-in message back to the main thread. The method uses the port object received in the LaunchThreadWithPort: method as the target of the message.

清单3-15显示了次要线程的签入例程。 该方法设置自己的本地端口用于将来的通信,然后发送一个检入消息回主线程。 该方法使用在LaunchThreadWithPort:方法中接收的端口对象作为消息的目标。
Listing 3-15 Sending the check-in message using Mach ports

// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
    // Retain and save the remote port for future use.
    [self setRemotePort:outPort];
 
    // Create and configure the worker thread port.
    NSPort* myPort = [NSMachPort port];
    [myPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
 
    // Create the check-in message.
    NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
                                         receivePort:myPort components:nil];
 
    if (messageObj)
    {
        // Finish configuring the message and send it immediately.
        [messageObj setMsgId:setMsgid:kCheckinMessage];
        [messageObj sendBeforeDate:[NSDate date]];
    }
}

Configuring an NSMessagePort Object

配置一个NSMessagePort对象

To establish a local connection with an NSMessagePort object, you cannot simply pass port objects between threads. Remote message ports must be acquired by name. Making this possible in Cocoa requires registering your local port with a specific name and then passing that name to the remote thread so that it can obtain an appropriate port object for communication. Listing 3-16 shows the port creation and registration process in cases where you want to use message ports.

要建立与NSMessagePort对象的本地连接,您不能简单地在线程之间传递端口对象。 远程消息端口必须以名称获取。 在Cocoa中可能需要使用特定的名称注册本地端口,然后将该名称传递给远程线程,以便它可以获取适当的端口对象进行通信。 清单3-16显示了要使用消息端口的端口创建和注册过程。
Listing 3-16 Registering a message port

NSPort* localPort = [[NSMessagePort alloc] init];
 
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
 
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
                     name:localPortName];

Configuring a Port-Based Input Source in Core Foundation

Core Foundation中配置基于端口的输入源

This section shows how to set up a two-way communications channel between your application’s main thread and a worker thread using Core Foundation.

本节介绍如何使用Core Foundation在应用程序的主线程和工作线程之间设置双向通信通道。

Listing 3-17 shows the code called by the application’s main thread to launch the worker thread. The first thing the code does is set up a CFMessagePortRef opaque type to listen for messages from worker threads. The worker thread needs the name of the port to make the connection, so that string value is delivered to the entry point function of the worker thread. Port names should generally be unique within the current user context; otherwise, you might run into conflicts.

清单3-17显示了应用程序主线程调用的代码,以启动工作线程。 代码的第一件事是设置一个CFMessagePortRef opaque类型来监听来自工作线程的消息。 工作线程需要进行连接的端口名称,以便将字符串值传递给工作线程的入口点函数。 端口名称通常在当前用户上下文中是唯一的; 否则,您可能会遇到冲突。
Listing 3-17 Attaching a Core Foundation message port to a new thread
Listing 3-17 :将Core Foundation消息端口附加到新线程

#define kThreadStackSize        (8 *4096)
 
OSStatus MySpawnThread()
{
    // Create a local port for receiving responses.
    CFStringRef myPortName;
    CFMessagePortRef myPort;
    CFRunLoopSourceRef rlSource;
    CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
    Boolean shouldFreeInfo;
 
    // Create a string with the port name.
    myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
 
    // Create the port.
    myPort = CFMessagePortCreateLocal(NULL,
                myPortName,
                &MainThreadResponseHandler,
                &context,
                &shouldFreeInfo);
 
    if (myPort != NULL)
    {
        // The port was successfully created.
        // Now create a run loop source for it.
        rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
 
        if (rlSource)
        {
            // Add the source to the current run loop.
            CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
            // Once installed, these can be freed.
            CFRelease(myPort);
            CFRelease(rlSource);
        }
    }
 
    // Create the thread and continue processing.
    MPTaskID        taskID;
    return(MPCreateTask(&ServerThreadEntryPoint,
                    (void*)myPortName,
                    kThreadStackSize,
                    NULL,
                    NULL,
                    NULL,
                    0,
                    &taskID));
}

With the port installed and the thread launched, the main thread can continue its regular execution while it waits for the thread to check in. When the check-in message arrives, it is dispatched to the main thread’s MainThreadResponseHandler function, shown in Listing 3-18. This function extracts the port name for the worker thread and creates a conduit for future communication.

在安装端口并启动线程的情况下,主线程可以在等待线程检入时继续其正常执行。当检入消息到达时,它将被分派到主线程的MainThreadResponseHandler函数,如清单3- 18。 此函数提取工作线程的端口名称,并创建未来通信的管道。
Listing 3-18 Receiving the checkin message

#define kCheckinMessage 100
 
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
                    SInt32 msgid,
                    CFDataRef data,
                    void* info)
{
    if (msgid == kCheckinMessage)
    {
        CFMessagePortRef messagePort;
        CFStringRef threadPortName;
        CFIndex bufferLength = CFDataGetLength(data);
        UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
 
        CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
        threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
 
        // You must obtain a remote message port by name.
        messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
 
        if (messagePort)
        {
            // Retain and save the thread’s comm port for future reference.
            AddPortToListOfActiveThreads(messagePort);
 
            // Since the port is retained by the previous function, release
            // it here.
            CFRelease(messagePort);
        }
 
        // Clean up.
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL, buffer);
    }
    else
    {
        // Process other messages.
    }
 
    return NULL;
}

With the main thread configured, the only thing remaining is for the newly created worker thread to create its own port and check in. Listing 3-19 shows the entry point function for the worker thread. The function extracts the main thread’s port name and uses it to create a remote connection back to the main thread. The function then creates a local port for itself, installs the port on the thread’s run loop, and sends a check-in message to the main thread that includes the local port name.

在配置主线程之后,唯一剩下的就是新创建的工作线程创建自己的端口并签入。清单3-19显示了工作线程的入口点函数。 该函数提取主线程的端口名称,并使用它来创建一个远程连接回主线程。 该函数然后为其自身创建本地端口,将端口安装在线程的运行循环上,并向包含本地端口名称的主线程发送检入消息。
Listing 3-19 Setting up the thread structures

OSStatus ServerThreadEntryPoint(void* param)
{
    // Create the remote port to the main thread.
    CFMessagePortRef mainThreadPort;
    CFStringRef portName = (CFStringRef)param;
 
    mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
 
    // Free the string that was passed in param.
    CFRelease(portName);
 
    // Create a port for the worker thread.
    CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
 
    // Store the port in this thread’s context info for later reference.
    CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
    Boolean shouldFreeInfo;
    Boolean shouldAbort = TRUE;
 
    CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
                myPortName,
                &ProcessClientRequest,
                &context,
                &shouldFreeInfo);
 
    if (shouldFreeInfo)
    {
        // Couldn't create a local port, so kill the thread.
        MPExit(0);
    }
 
    CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
    if (!rlSource)
    {
        // Couldn't create a local port, so kill the thread.
        MPExit(0);
    }
 
    // Add the source to the current run loop.
    CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
 
    // Once installed, these can be freed.
    CFRelease(myPort);
    CFRelease(rlSource);
 
    // Package up the port name and send the check-in message.
    CFDataRef returnData = nil;
    CFDataRef outData;
    CFIndex stringLength = CFStringGetLength(myPortName);
    UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
 
    CFStringGetBytes(myPortName,
                CFRangeMake(0,stringLength),
                kCFStringEncodingASCII,
                0,
                FALSE,
                buffer,
                stringLength,
                NULL);
 
    outData = CFDataCreate(NULL, buffer, stringLength);
 
    CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL);
 
    // Clean up thread data structures.
    CFRelease(outData);
    CFAllocatorDeallocate(NULL, buffer);
 
    // Enter the run loop.
    CFRunLoopRun();
}

Once it enters its run loop, all future events sent to the thread’s port are handled by the ProcessClientRequest function. The implementation of that function depends on the type of work the thread does and is not shown here.

一旦进入其run loop,发送到线程端口的所有未来事件都将由ProcessClientRequest函数处理。 该功能的实现取决于线程工作的类型,此处未显示。

posted on 2018-10-08 15:54  广坤山货  阅读(173)  评论(0编辑  收藏  举报