循环运行

运行循环是与线程相关联的基本基础设施的一部分。一个运行循环是用于调度工作,并协调接收传入事件的事件处理循环。一个运行循环的目的是让你的线程繁忙时,有工作要做,把你的线程时有没有睡觉。

循环运行管理不完全是自动的。你还必须设计线程的代码开始在适当的时候运行循环和响应传入的事件。无论可可和核心基础提供了运行循环对象,以帮助您配置和管理线程的运行循环。您的应用程序并不需要显式地创建这些对象; 每个线程,包括应用程序的主线程,都有一个相关的运行循环对象。只有辅助线程但需要明确地运行他们的运行循环。该应用程序框架自动设置和运行在主线程中运行循环的应用程序启动过程的一部分。

以下部分提供了有关循环运行以及如何将它们配置为您的应用程序的更多信息。有关运行循环对象的其他信息,请参阅NSRunLoop类参考CFRunLoop参考

一个运行循环的解剖

一个运行循环是非常喜欢它的名字听起来。是一个循环的线程进入并用来响应于输入事件执行的事件处理程序。您的代码提供了用于实现循环,换句话说,你的代码提供了运行的实际环部分的控制语句whilefor循环驱动的运行循环。在你的循环,你使用一个运行循环对象“运行”接收事件并调用处理程序安装在事件处理代码。

一个运行环接收来自两个不同类型的源事件。输入源传递异步事件,通常消息从另一个线程或从不同的应用程序。定时源提供的同步事件,在预定的时间发生的或重复间隔。这两种类型的源的使用应用特定处理例程,当它到达处理该事件。

图3-1显示了一个运行循环和各种来源的概念结构。输入源传递异步事件到相应的处理程序,并导致runUntilDate:方法(称为线程相关的NSRunLoop对象)来退出。定时源提供的事件他们的处理程序例程,但不会导致运行循环退出。

图3-1   运行循环及其来源结构一个运行循环及其来源结构

除了处理输入源,循环运行也产生对运行循环的行为的通知。注册运行循环观察者可以收到这些通知,并利用它们的线程上做额外的处理。您可以使用Core Foundation的在你的线程安装运行循环观察员。

以下各节提供有关运行循环的组成部分,并在其运行的模式的详细信息。他们还描述被事件的处理期间,在不同的时间产生的通知。

运行循环模式

一个运行的循环模式输入源和定时器的集合进行监测和运行循环观察者的集合的通知。每次运行您的运行循环时,可以指定(或隐或显),在其中运行一个特定的“模式”。在运行过程中循环的该通,只有与该模式相关联的源被监视并能读出它们的事件。(同样,只有与该模式相关观察员通知的运行循环的进度。)与其他模式相关的源持有任何新的事件,直到通过适当的模式循环随后的通行证。

在您的代码,按名称识别模式。无论可可和Core Foundation的定义默认的模式和一些常用的模式,用在你的代码中指定这些模式串一起。你可以简单的通过指定的模式名称自定义字符串定义自定义模式。虽然分配给自定义模式的名称是任意的,这些模式的内容都没有。你一定一定要添加一个或多个输入源,定时器,或运行循环观察员为其创建有用的模式。

您可以使用模式特定通过您运行的循环中过滤掉干扰源事件。大多数时候,你会想在运行系统中定义的“默认”模式,您的运行循环。模态面板,但是,可能会在“模式”模式下运行。在此模式下,唯一来源相关的模式面板将提供事件的线程。对于辅助线程,你可以使用自定义模式,以防止低优先级的来源,从在时间临界操作提供事件。

注意:  基于所述事件,而不是事件的类型的来源模式鉴别。例如,您不会使用模式匹配只鼠标按下事件或只有键盘事件。你可以使用模式来听一组不同的端口,暂停定时器或者改变当前监视的来源和运行循环观察员。

 

表3-1列出了可可和核心基础与当您使用模式的描述以及定义的标准模式。名称列列出你实际使用的常量来指定在你的代码模式。

表3-1   预定义的运行循环模式

模式

名称

描述

默认

NSDefaultRunLoopMode (可可)

kCFRunLoopDefaultMode (核心基金会)

默认模式是用于大多数操作之一。在大多数情况下,你应该使用此模式启动运行循环和配置您的输入源。

连接

NSConnectionReplyMode (可可)

可可使用此模式结合NSConnection对象监控的答复。你应该很少需要自己使用此模式。

语气

NSModalPanelRunLoopMode(可可)

可可使用此模式来确定用于模态面板的事件。

事件追踪

NSEventTrackingRunLoopMode(可可)

可可使用此模式限制在鼠标拖动循环和其他类型的用户界面跟踪环路的输入事件。

共模

NSRunLoopCommonModes (可可)

kCFRunLoopCommonModes (核心基金会)

这是常用的模式的可配置群组。相关联的输入源与该模式还它与每个组中的模式的关联。对于Cocoa应用程序,这一套包括默认,模态和事件跟踪默认模式。核心基金仅包括默认模式开始。您可以添加自定义模式,使用设定的CFRunLoopAddCommonMode功能。

输入源

输入源传递异步事件,你的线程。事件的源取决于输入源,它一般是两种类别之一的类型。基于端口的输入源监控应用程序的马赫端口。自定义输入源监控事件自定义来源。至于你的运行循环而言,它不应该的问题输入源是基于端口或自定义。该系统通常实现了两种类型,您可以使用为的就是输入源。两个源之间的唯一差别是它们是如何发出信号。基于端口的源内核发出信号,以及自定义来源必须手动从另一个线程来通知。

当你创建输入源,你把它分配给您的运行循环的一个或多个模式。的方式影响该输入源是在任何给定时刻进行监控。在大多数情况下,运行在默认模式下运行环路,但你可以指定得自定义模式。如果输入源是不是在当前监视模式下,它产生的任何事件都保持,直到运行循环在正确的模式下运行。

下面的部分描述了一些输入源。

基于端口的源

可可和核心基础提供内置的创建使用端口相关的对象和函数基于端口的输入源的支持。例如,在可可,你从来没有直接在各创建一个输入源。您只需创建端口对象,并使用方法NSPort到该端口添加到运行循环。端口对象为您处理所需要的输入源的创建和配置。

在核心基础,你必须手动创建这两个港口,它的运行循环源。在这两种情况下,您使用的端口类型不透明(相关的功能CFMachPortRefCFMessagePortRefCFSocketRef)创建合适的对象。

有关如何设置和配置定制的基于端口的源示例,请参阅配置基于端口的输入源

自定义输入源

要创建自定义输入源,必须使用与相关联的功能CFRunLoopSourceRef在核心基础不透明的类型。您可以配置使用多个回调函数的自定义输入源。核心基础调用在不同的点这些功能配置源,处理任何输入事件,并且当它被从运行循环除去推倒源。

除了定义在事件到达自定义源的行为,你还必须定义事件传递机制。源的这部分在一个单独的线程运行,并负责提供其数据输入源和信令它时,数据已准备好进行处理。该事件传递机制是由你,但不必过于复杂。

有关如何创建自定义输入源的例子,请参阅定义自定义输入源。对于自定义输入源的参考信息,也见CFRunLoopSource参考

可可进行选择来源

除了基于端口的源,可可定义了一个自定义输入源,允许你在任何线程执行的选择。像基于端口的源,执行选择请求被序列化的目标线程上,缓解了可能与被在一个线程中运行多个方法同步发生的问题。不像基于端口的源,进行选择源从运行循环中删除它本身执行其选择了。

注:  在此之前OS X v10.5上,进行选择的来源大多用于将消息发送到主线程,但在OS X v10.5及其更高版本以及iOS中,你可以使用它们将消息发送到任何线程。

 

当另一个线程进行选择,目标线程必须有一个有效运行循环。对于创建线程,这意味着等到你的代码明确开始运行循环。由于主线程启动它自己的运行循环,但是,您可以立即开始在该线程调用发布的应用程序调用 applicationDidFinishLaunching:的应用程序委托的方法。运行循环处理所有排队通过循环进行选择每次通话时间,而不是每次循环迭代的过程中处理的。

表3-2列出了定义的方法NSObject,可用于在其它线程执行选择器。因为这些方法都宣布对NSObject,你可以从那里你可以使用Objective-C的对象的线程,包括POSIX线程使用它们。这些方法实际上并没有创建新的线程来执行选择。

表3-2   其他线程上执行选择

方法

描述

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

执行该线程的下一次运行循环周期中的应用程序的主线程上的指定选择。这些方法给你直到执行选择阻塞当前线程的选择。

performSelector:onThread:withObject:waitUntilDone:

performSelector:onThread:withObject:waitUntilDone:modes:

执行对您有任何线程指定的选择NSThread对象。这些方法给你直到执行选择阻塞当前线程的选择。

performSelector:withObject:afterDelay:

performSelector:withObject:afterDelay:inModes:

在接下来的运行循环周期和一个可选的延迟时间后执行当前线程上的指定选择。因为它等待,直到下一次运行循环周期进行选择,这些方法提供从当前执行的代码自动迷你延迟。多个排队选择执行一个接一个,他们排队的顺序。

cancelPreviousPerformRequestsWithTarget:

cancelPreviousPerformRequestsWithTarget:selector:object:

让您取消使用发送到当前线程的消息performSelector:withObject:afterDelay:performSelector:withObject:afterDelay:inModes:方法。

有关每种方法的详细信息,请参阅NSObject类参考

定时源

定时器源在未来预设的时间交付事件同步到你的线程。定时器是线程通知自己做一些事情的方法。例如,一个搜索字段可以用一个计时器一旦一定的时间量已经从用户连续击键之间传递以引发自动搜索。利用这一延迟时间为用户提供了一个机会,在开始搜索之前尽可能多所需的搜索字符串尽可能类型。

虽然它产生基于时间的通知,计时器不是实时机制。像输入源,定时器与您的运行循环的特定模式相关。如果一个定时器不是目前正在运行的循环监控的模式下,它不火,直到您在计时器的支持的模式之一运行运行循环。同样,如果在运行循环中执行处理程序的中间有一个定时器触发,定时器等待通过运行循环的下一次调用它的处理程序,直到。如果运行循环没有在所有运行,计时器永远不会触发。

您可以配置定时器产生的事件只有一次或多次。重复计时器重新安排本身自动根据预定的发射时间,而不是实际发射时间。例如,如果一个计时器预定触发在特定的时间,之后每5秒钟,预定烧制时间将总是落在原来的5第二时间间隔,即使实际的烧成时间被延迟。如果烧成时间被延迟,以至于它忽略一个或多个预定的烧成时间,定时器为错过时间段烧制一次。烧成错过时间后,定时器重新安排下一个计划的烧制时间。

有关配置定时源的详细信息,请参阅配置定时器源。有关参考信息,请参阅的NSTimer类参考CFRunLoopTimer参考

运行循环观察员

相反,消息人士透露,该灭火时适当的同步或异步事件发生时,运行循环观察员运行循环本身的执行过程中开火特殊的位置。您可以使用运行循环观察员准备你的线程来处理某一特定事件或睡觉之前准备的线程。您可以在您的运行循环下运行的事件循环观察家关联:

  • 入口处的运行循环。

  • 当运行的循环将要处理一个计时器。

  • 当运行的循环将要处理的输入源。

  • 当运行循环即将进入睡眠状态。

  • 当运行循环已经醒来,但在此之前它已处理了醒起来的事件。

  • 从运行循环的退出。

您可以添加运行的循环利用观察者核心基金会应用程序。要创建一个运行循环的观察者,你创建的新实例CFRunLoopObserverRef不透明的类型。这种类型的跟踪您的自定义的回调函数,并在它感兴趣的活动。

类似计时器,运行循环观察者可以一次或多次重复使用。一个单次观测从运行循环中删除它本身火灾后,虽然重复观测保持连接。您可以指定,当您创建一个观察者是否运行一次或多次。

有关如何创建一个运行循环的观察者示例,请参阅配置运行循环。有关参考信息,请参见CFRunLoopObserver参考

事件的运行循环序列

每次运行它的时候,你线程的run循环处理未完成的事件,并生成任何附加的观察者通知。在它这是非常具体的,是如下顺序:

  1. 通知运行循环已进入观察员。

  2. 通知任何现成计时器即将开火观察员。

  3. 通知未基于端口的任何输入源即将开火观察员。

  4. 大火非基于端口的输入源是准备开火。

  5. 如果基于端口的输入源已准备就绪,等待开火,立即处理该事件。转至步骤9。

  6. 通知观察者该线程将要睡觉。

  7. 把线程睡眠,直到以下事件之一发生:

    • 事件到达一个基于端口的输入源。

    • 计时器火灾。

    • 对于运行循环设置超时值到期。

    • 运行循环明确唤醒。

  8. 通知观察者该线程刚刚醒来。

  9. 处理挂起的事件。

    • 如果用户定义的计时器所触发,过程计时器事件并重新启动循环。转到步骤2。

    • 如果输入源发射,提供的事件。

    • 如果运行循环被明确唤醒但还没有超时,请重新启动循环。转到步骤2。

  10. 通知运行退出循环观察员。

因为定时器和输入源观察者通知这些事件实际发生之前被传递,可能存在的通知的时间和实际事件的时间之间的间隙。如果这些事件之间的时间是至关重要的,你可以使用睡眠和清醒 - 从睡眠的通知,以帮助您关联的实际事件之间的时间。

因为当你运行运行的循环定时器等周期性事件传递,绕过该循环破坏这些事件的交付。当你输入一个循环反复请求从应用程序的事件实现鼠标跟踪程序发生这种行为的典型例子。因为你的代码直接抢夺事件,而不是让应用程序调度这些事件通常,活动的定时器将无法触发,直到后您的鼠标跟踪程序退出并返回到应用程序控件。

一个运行循环可以明确地唤醒使用运行循环对象。其他事件也可能导致运行循环唤醒。例如,添加另一非基于端口输入源唤醒运行循环,使得输入源可以被立即处理,而不是等待直到其他的事件发生。

当你使用一个运行循环?

你需要跑一跑环的唯一的时候,你为应用程序创建辅助线程明确的。为您的应用程序的主线程运行循环是基础设施关键部分。其结果是,该应用框架上运行的主应用程序循环提供代码并自动启动该循环。该run方法UIApplication在iOS的(或NSApplication在OS X)启动一个应用程序的主回路的正常启动序列的一部分。如果您使用的Xcode项目模板创建应用程序,你永远不应该显式调用这些例程。

对于辅助线程,你需要决定一个运行循环是否是必要的,如果是,配置和自行启动。你并不需要在所有情况下启动一个线程的运行循环。例如,如果你使用一个线程来执行一些长时间运行和预定的任务,你可能避免启动运行循环。运行回路供您希望与线程更多的交互情况。例如,你需要的,如果你打算做任何下列开始运行循环:

  • 使用端口或自定义输入源与其他线程沟通。

  • 使用线程计时器。

  • 使用任何performSelector......方法Cocoa应用程序。

  • 保持线程各地定期进行的任务。

如果你选择使用一个运行循环,配置和设置非常简单。如同所有的线程编程虽然,你应该在适当的场合退出您的辅助线程的计划。它始终是更好地通过让它退出,而不是迫使它终止完全结束线程。关于如何配置和退出运行循环的信息中说明使用运行循环对象

使用运行循环对象

一个运行循环对象提供的主界面添加输入源,定时器和运行环观察员的运行循环,然后运行它。每个线程都有与之关联的单一运行循环对象。在可可,此对象的实例NSRunLoop类。在低级别的应用程序,它是一个指向CFRunLoopRef不透明型。

获得一个运行循环对象

为了获得当前线程的运行循环,使用下列之一:

虽然他们不是免费桥接的类型,你可以得到一个CFRunLoopRef从不透明的类型NSRunLoop需要时对象。本NSRunLoop类定义了一个getCFRunLoop返回的方法CFRunLoopRef类型,你可以传递给Core Foundation的例程。因为这两个对象指的是相同的运行循环,可以混合使用,在两个电话NSRunLoop对象和CFRunLoopRef根据需要不透明型。

配置运行循环

在运行的辅助线程运行循环,你必须添加至少一个输入源或定时器给它。如果运行循环没有任何来源进行监控,当你尝试运行它立即退出。有关如何源添加到一个运行循环示例,请参阅配置运行循环源

除了安装源,还可以安装运行循环观察,并用它们来检测运行循环的不同的执行阶段。要安装运行循环的观察者,您创建一个CFRunLoopObserverRef不透明的类型和使用CFRunLoopAddObserver功能将它添加到您的运行循环。运行循环观察者必须使用核心基础,即使是Cocoa应用程序创建。

清单3-1显示了附加一个run loop的观察者到它的运行循环线程的主程序。这个例子的目的是向您展示如何创建一个运行循环的观察者,所以该代码只是设置一个运行循环的观察者来监控所有运行的循环活动。基本处理程序(未示出)简单地记录,因为它处理定时器的请求的运行循环活性。

清单3-1   创建一个运行循环的观察者

 - (无效)threadMain
{
    //应用程序使用垃圾收集,因此不需要自动释放池。
    NSRunLoop * myRunLoop = [NSRunLoop currentRunLoop]
 
    //创建一个运行循环的观察者并将其连接到运行循环。
    CFRunLoopObserverContext背景= {0,自我,NULL,NULL,NULL};
    CFRunLoopObserverRef观察者= CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities,YES,0,和myRunLoopObserver,与背景);
 
    如果(观察员)
    {
        CFRunLoopRef CFLOOP = [myRunLoop getCFRunLoop]
        CFRunLoopAddObserver(CFLOOP,观察者,kCFRunLoopDefaultMode);
    }
 
    //创建并安排计时器。
    [的NSTimer scheduledTimerWithTimeInterval:0.1目标:自我
                选择:@选择(doFireTimer :)用户信息:无重复:YES];
 
    NSInteger的loopCount = 10;
    {
        //运行运行循环10次,让计时器火灾。
        [myRunLoop runUntilDate:[的NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    而(loopCount);
}

当配置运行循环的长寿命线程,最好是添加至少一个输入源到接收消息。虽然你可以只连接定时器输入运行循环,一旦定时器触发,它通常是无效的,那么这将导致运行循环退出。附加重复计时器能保持运行在一个较长的时间周期的运行循环,但将涉及周期性击发定时器唤醒线程,这实际上是轮询的另一种形式。与此相反,输入源等待一个事件的发生,保持你的线程睡着了,直到它。

开始运行循环

开始运行循环只为您的应用程序中的辅助线程是必要的。一个运行循环必须至少有一个输入源或定时器来监控。如果一个人还没有安装,运行循环立即退出。

有几种方法来启动运行循环,包括以下情况:

  • 无条件

  • 有一组时间限制

  • 在一个特定的模式

无条件地输入您的运行循环是最简单的选择,但也是最不理想的。运行您的运行循环无条件放线变为永久循环,它使您可以在运行循环本身很难控制。您可以添加和删除输入源和定时器,但是停止运行循环的唯一方法是杀死它。也没有办法运行在自定义模式下运行循环。

相反,无条件地运行运行循环的,这是更好地运行具有超时值的运行循环。当您使用超时值,运行循环运行,直到事件到达或在指定的时间。如果一个事件到达,该事件被调度到进行处理的处理程序,然后运行退出循环。然后,您的代码可以重新启动运行循环来处理下一个事件。如果在指定的时间,而不是,你可以简单地重新开始运行循环,或者使用时间做任何需要的家政服务。

除了超时值,还可以使用特定的模式下运行您的运行循环。模式和超时值并不相互排斥,并开始运行循环时都可以使用。模式限制类型的事件传递到运行循环,并在更详细的描述来源运行循环模式

清单3-2显示了一个线程的主入口程序的框架版本。本实施例的关键部分示出一个运行循环的基本结构。从本质上讲,你添加输入源和定时器的运行循环,然后反复调用程序的一个开始运行循环。每次运行循环例程返回,检查时间,看看是否有任何的条件已经出现,可能值得退出线程。该示例使用Core Foundation的运行循环程序,以便它可以检查返回结果,并确定为什么运行循环退出。你也可以使用的方法NSRunLoop类,如果您使用的是可可以类似的方式来运行的运行循环,不需要检查返回值。(对于一个运行循环调用该方法的一个实例NSRunLoop类,请参阅清单3-14)。

清单3-2   运行运行循环

 - (无效)skeletonThreadMain
{
    //如果不使用垃圾收集设置自动释放池在这里。
    BOOL完成= NO;
 
    //你的源代码或计时器添加到运行循环,并做其他任何设置。
 
    {
        //开始运行循环,但每个源处理后返回。
        SINT32结果= CFRunLoopRunInMode(kCFRunLoopDefaultMode,10,YES);
 
        //如果源明确地停止了运行的循环,或如果没有
        //来源或计时器,继续和退出。
        如果((结果== kCFRunLoopRunStopped)||(结果== kCFRunLoopRunFinished))
            做= YES;
 
        //检查任何其他退出条件在这里和设置
        //根据需要进行变量。
    }
    而(做!);
 
    //这里清理代码。一定要释放任何分配的自动释放池。
}

有可能递归地运行一个运行循环。换句话说,你可以打电话CFRunLoopRunCFRunLoopRunInMode或任何的NSRunLoop方法从输入源或定时器的处理程序内开始运行循环。这样做的时候,你可以用你想要运行的嵌套运行循环,包括使用的模式由外循环运行任意模式。

退出运行循环

有两种方法可以让运行循环的退出已处理的事件之前:

  • 配置运行循环用的超时值运行。

  • 告诉运行循环停止。

使用超时值肯定是首选,如果你能管理它。指定超时值允许运行的循环完成所有的正常处理,包括发送通知运行循环观察员飞去。

在明确停止运行循环CFRunLoopStop功能产生类似于超时的结果。运行循环发送任何剩余的运行循环的通知,然后退出。所不同的是,在运行循环你开始无条件您可以使用这种技术。

虽然删除运行循环的输入源和定时器也可能导致运行循环退出,这不是一个可靠的方式来停止运行循环。一些系统程序输入源添加到一个运行循环来处理所需的事件。因为你的代码可能不知道这些输入源,这将是无法删除它们,这将防止运行循环退出。

线程安全和运行循环对象

线程安全取决于你使用的是操纵你的运行循环的API变化。在核心基础的功能一般是线程安全的,可以从任何线程调用。如果您正在执行改变运行循环的配置操作,但是,它仍然是很好的做法,从拥有运行循环尽可能线程这样做。

可可NSRunLoop类不是天生便线程安全作为其核心基础对口。如果您使用的是NSRunLoop类来修改你的run loop,你应该让只拥有该循环运行在同一个线程做。添加输入源或定时器到属于不同的线程运行的循环可能会导致您的代码以一种意想不到的方式崩溃或行为。

配置运行循环来源

以下部分显示了如何设置不同类型的两个可可和核心基础输入源的例子。

定义自定义输入源

创建自定义输入源包括定义如下:

  • 你想要的信息输入源来处理。

  • 的调度程序,让有兴趣的客户知道如何联系您的输入源。

  • 一个处理程序,以执行任何客户端发送的请求。

  • 一个消除例行程序无效输入源。

因为你创建自定义输入源来处理自定义信息,实际配置设计为灵活。调度,处理和取消程序是你几乎总是需要为您的自定义输入源的关键程序。大部分的输入源的行为的其余部分,但是,会发生这些处理程序例程的外部。例如,它是由你来定义的机制将数据传递到你的输入源以及输入源的存在传达给其他线程。

图3-2显示了自定义输入源的示例配置。在本实施例中,应用程序的主线程保持到输入源,该输入源的自定义命令缓冲器,并在其上安装输入源的运行循环引用。当主线程就是了移交给工作线程任务,它与该工作线程启动任务所需的任何信息一起发布到命令缓冲区的命令。(因为无论是主线程和工作线程的输入源可以访问命令缓冲区,该访问必须同步。)一旦命令发布,主线程信号的输入源和唤醒工作线程的运行循环。在接收到该唤醒命令,运行循环调用的输入源,其处理在命令缓冲器中的命令的处理程序。

图3-2   运行自定义输入源运行一个自定义输入源

以下部分说明了从上图自定义输入源的落实,并显示关键代码,你需要实现。

定义输入源

定义自定义输入源需要使用Core Foundation的例程来配置你的run loop源并将其连接到一个运行循环。虽然基本处理程序是基于C语言的功能,即不从写为这些功能包装和使用Objective-C或C ++来实现你代码的身体阻止你。

在引入的输入源图3-2使用了Objective-C的对象来管理命令缓冲区和运行循环坐标。清单3-3显示了这个对象的定义。在RunLoopSource对象管理命令缓冲区,并使用该缓冲器以接收来自其他线程的消息。这个清单也显示了定义RunLoopContext对象,这是真的只是用来传递一个容器对象RunLoopSource对象和运行循环参考应用程序的主线程。

清单3-3   自定义输入源对象定义

@interface RunLoopSource:NSObject的
{
    CFRunLoopSourceRef runLoopSource;
    NSMutableArray里*命令;
}
 
 - (id)的初始化;
 - (无效)addToCurrentRunLoop;
 - (无效)无效;
 
//处理方法
 - (无效)sourceFired;
 
//登记命令来处理客户端界面
 - (无效)addCommand:(NSInteger的)命令withData:(ID)的数据;
 - (无效)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
 
@结束
 
//这些是CFRunLoopSourceRef回调函数。
无效RunLoopSourceScheduleRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式);
无效RunLoopSourcePerformRoutine(void *的信息);
无效RunLoopSourceCancelRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式);
 
// RunLoopContext是输入源的注册过程中使用的容器对象。
@interface RunLoopContext:NSObject的
{
    CFRunLoopRef runLoop;
    RunLoopSource *源;
}
@属性(只读)CFRunLoopRef runLoop;
@属性(只读)RunLoopSource *源;
 
 - (ID)initWithSource:(RunLoopSource *)SRC andLoop:(CFRunLoopRef)循环;
@结束

虽然Objective-C代码管理输入源的自定义数据,连接输入源到运行循环,需要基于C的回调函数。当你真正重视的运行循环源的运行循环的第一个函数被调用,并在所示列表3-4。因为该输入源中只有一个客户机(主线程),它使用调度程序功能将消息发送到其自身与该线程上应用程序的代理注册。当代表要与输入源进行通信,它使用的信息RunLoopContext的对象这样做。

清单3-4   调度运行循环源

无效RunLoopSourceScheduleRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    的AppDelegate *德尔= [AppDelegate中sharedAppDelegate]
    RunLoopContext * theContext = [[RunLoopContext页头] initWithSource:OBJ andLoop:RL];
 
    [德尔performSelectorOnMainThread:@selector(registerSource :)
                                withObject:theContext waitUntilDone:NO];
}

其中最重要的回调例程的是当用于输入源发信号处理自数据之一。清单3-5显示了与相关的执行回调例程RunLoopSource对象。这个函数简单转发给做的工作的请求sourceFired方法,然后处理存在的命令缓冲区的任何命令。

清单3-5   中的输入源执行工作

无效RunLoopSourcePerformRoutine(void *的信息)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    [OBJ sourceFired]
}

如果你从它的运行循环使用删除输入源CFRunLoopSourceInvalidate的功能,系统会调用你输入源的取消例程。你可以使用这个程序来通知客户端输入信号源不再有效,他们应该删除的任何引用。 清单3-6显示了注册的取消回调例程RunLoopSource对象。该函数将另一个RunLoopContext对象的应用程序委托,但这次要求委托删除运行循环源引用。

清单3-6   作废输入源

无效RunLoopSourceCancelRoutine(void *的信息,CFRunLoopRef RL,CFStringRef模式)
{
    RunLoopSource * OBJ =(RunLoopSource *)信息;
    的AppDelegate *德尔= [AppDelegate中sharedAppDelegate]
    RunLoopContext * theContext = [[RunLoopContext页头] initWithSource:OBJ andLoop:RL];
 
    [德尔performSelectorOnMainThread:@selector(removeSource :)
                                withObject:theContext waitUntilDone:YES];
}

注:  为应用程序委托的代码registerSource:removeSource:方法中显示与输入源的客户协调

 

安装在运行循环的输入源

清单3-7显示了initaddToCurrentRunLoop该方法的RunLoopSource类。该init方法创建CFRunLoopSourceRef了必须实际连接到运行循环不透明的类型。它通过RunLoopSource使回调例程有一个指针的对象物体本身作为上下文信息。直到工作线程调用不会发生输入源的安装addToCurrentRunLoop方法,在这一点的RunLoopSourceScheduleRoutine回调函数被调用。一旦输入源被添加到运行循环,线程可以运行它的运行循环等待就可以了。

清单3-7   安装运行循环源

 - (ID)的init
{
    CFRunLoopSourceContext背景= {0,自我,NULL,NULL,NULL,NULL,NULL,
                                        &RunLoopSourceScheduleRoutine,
                                        RunLoopSourceCancelRoutine,
                                        RunLoopSourcePerformRoutine};
 
    runLoopSource = CFRunLoopSourceCreate(NULL,0,和背景);
    命令= [[NSMutableArray里的alloc]初始化];
 
    返回自我;
}
 
 - (无效)addToCurrentRunLoop
{
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    CFRunLoopAddSource(runLoop,runLoopSource,kCFRunLoopDefaultMode);
}

与输入源的客户协调

为了您的输入源是有用的,你需要处理它,并从另一个线程表示它。输入源的全部要点是将其相关的线程休眠,直到有事可做。这一事实就必须在你的应用程序的其他线程知道该输入源,并有办法与之通信。

通知客户关于输入源的方法之一是发出注册请求时,第一次安装在其运行的循环输入源。您可以根据需要输入信号源,与尽可能多的客户登记,或者你可以简单地用一些中央机构,然后VENDS输入源为有兴趣的客户注册。清单3-8显示了应用委托定义,当调用注册方法RunLoopSource对象的调度函数被调用。此方法接收RunLoopContext由提供的对象RunLoopSource对象并将其添加到它的源列表。这个清单也显示了使用时,从它的运行循环中删除注销输入源的程序。

清单3-8   注册和删除与应用程序委托的输入源

 - (无效)registerSource:(RunLoopContext *)sourceInfo;
{
    [sourcesToPing ADDOBJECT:sourceInfo];
}
 
 - (无效)removeSource:(RunLoopContext *)sourceInfo
{
    ID objToRemove =零;
 
    对于(RunLoopContext *在sourcesToPing上下文)
    {
        如果([背景下,isEqual:方法sourceInfo])
        {
            objToRemove =背景;
            打破;
        }
    }
 
    如果(objToRemove)
        [sourcesToPing的removeObject:objToRemove];
}

注:  调用在前面的上市方法的回调函数在显示列表3-4列表3-6

 

信号输入源

它的数据后双手关闭输入源,客户端必须信号源和唤醒它的运行循环。信号源让运行循环知道源已准备好进行处理。而且因为当信号发生线程可能是睡着了,你应该总是醒来运行循环明确。如果不这样做可能会导致在处理输入源的延迟。

清单3-9显示了fireCommandsOnRunLoop该方法的RunLoopSource对象。当他们准备好源来处理它们添加到缓冲区中的命令的客户端调用此方法。

清单3-9   醒来的运行循环

 - (无效)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
    CFRunLoopSourceSignal(runLoopSource);
    CFRunLoopWakeUp(runloop);
}

注意:  你不应该试图处理SIGHUP通过短信定制输入源或其他类型的进程级的信号。核心基础功能唤醒运行循环不是信号安全的,不应该应用程序的信号处理程序内部使用。有关信号处理例程的详细信息,请参阅sigaction手册页。

 

配置定时器源

要创建一个定时器源,所有你所要做的就是创建一个定时器对象,并安排它在你的运行循环。在可可,你可以使用NSTimer类来创建新的Timer对象,并在核心基金使用CFRunLoopTimerRef不透明的类型。在内部,NSTimer该类是核心基础的扩展,提供了一些方便的功能,如创建和使用相同的方法安排一个计时器的能力。

在可可,你可以创建并安排一次全部使用以下任一类方法的计时器:

这些方法创建定时器,它在默认模式下添加到当前线程的运行循环(NSDefaultRunLoopMode)。您也可以手动,如果你想通过创建调度计时器NSTimer对象,然后用它添加到运行循环addTimer:forMode:的方法NSRunLoop。这两种技术基本上做同样的事情,但给你不同级别的定时器的配置控制。例如,如果你创建定时器,并手动添加到运行循环,你可以这样做使用默认模式以外的模式。清单3-10显示了如何创建使用两种技术定时器。第一定时器具有1秒的初始延迟,但在此之后,每0.1秒定期触发。第二个定时器开始初始0.2秒延迟后烧制,再经过每0.2秒闪光。

清单3-10   使用的NSTimer创建和调度计时器

NSRunLoop * myRunLoop = [NSRunLoop currentRunLoop]
 
//创建并安排第一个定时器。
*的NSDate = futureDate [NSDate的dateWithTimeIntervalSinceNow:1.0];
*的NSTimer = myTimer [的NSTimer页头] initWithFireDate:futureDate
                        区间:0.1
                        目标:自我
                        选择:@选择(myDoFireTimer1 :)
                        用户信息:无
                        重复:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
//创建并安排第二计时器。
[的NSTimer scheduledTimerWithTimeInterval:0.2
                        目标:自我
                        选择:@选择(myDoFireTimer2 :)
                        用户信息:无
                        重复:YES];

清单3-11显示了配置使用的Core Foundation功能的计时器所需的代码。虽然这个例子没有通过在上下文结构中的任何用户定义的信息,你可以使用这个结构来绕过你需要为您的计时器任何自定义的数据。有关此结构的内容的更多信息,请参阅其在描述CFRunLoopTimer参考

清单3-11   创建和调度使用Core Foundation的一个计时器

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext上下文= {0,NULL,NULL,NULL,NULL};
CFRunLoopTimerRef计时器= CFRunLoopTimerCreate(kCFAllocatorDefault,0.1,0.3,0,0,
                                        &myCFTimerCallback,与背景);
 
CFRunLoopAddTimer(runLoop,计时器,kCFRunLoopCommonModes);

配置基于端口的输入源

无论可可和核心基金会线程之间或进程之间的通信提供了基于端口的对象。以下部分显示您如何设置使用几种不同类型的端口的端口进行通信。

配置NSMachPort对象

要建立与本地连接NSMachPort对象,创建端口对象并将其添加到您的主线程的运行循环。当您启动辅助线程,传递同一个对象的线程的入口点函数。辅助线程可以使用相同的对象发送消息回主线程。

实现主线程代码

清单3-12显示了启动辅助工作线程的主线程代码。因为Cocoa框架执行许多配置端口和运行循环的中间步骤,该launchThread方法是明显比它的核心基础当量(短清单3-17); 但是,两者的行为几乎是相同的。一个区别是,而不是发送本地端口的名称工作线程的,这种方法发送NSPort直接对象。

清单3-12   主线程启动方法

 - (无效)launchThread
{
    * NSPORT MyPort上= [NSMachPort端口]
    如果(MyPort上)
    {
        //这个类处理传入端口的消息。
        [MyPort上setDelegate:个体经营];
 
        //安装端口作为当前的运行循环的输入源。
        [NSRunLoop currentRunLoop] addPort:MyPort上forMode:NSDefaultRunLoopMode];
 
        //分离线程。让员工释放的端口。
        [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort :)
               toTarget:[MyWorkerClass类] withObject:MyPort上]。
    }
}

为了建立你的线程之间的双向通信通道,您可能想工作者线程发送自己的本地端口到主线程在办理入住手续的消息。接到办理入住手续的消息让你的主线程知道所有在启动第二个线程顺利,也给你一个方法来发送更多的信息给该线程。

清单3-13显示了handlePortMessage:对主线程的方法。当数据到达该线程自身的本地端口上调用此方法。当入住消息到达时,该方法直接从端口信息检索的端口辅助线程并将其保存以备后用。

清单3-13   处理Mach端口的消息

#定义kCheckinMessage 100
 
//从工作者线程处理响应。
 - (无效)handlePortMessage:(NSPortMessage *)portMessage
{
    无符号整型消息= [portMessage MSGID]
    NSPORT * distantPort =零;
 
    如果(消息== kCheckinMessage)
    {
        //获取工作线程的通信端口。
        distantPort = [portMessage发送端口]。
 
        //保留并保存工人端口供以后使用。
        [个体经营storeDistantPort:distantPort];
    }
    其他
    {
        //处理其他消息。
    }
}

实施辅助线程代码

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

清单3-14示出了用于设置辅助线程的代码。为线程创建自动释放池后,该方法将创建一个工人对象驱动线程执行。工人对象的sendCheckinMessage:方法(在显示清单3-15)创建工作线程的本地端口并发送入住消息回主线程。

清单3-14   启动使用马赫端口的工作线程

+(无效)LaunchThreadWithPort:(ID)INDATA
{
    NSAutoreleasePool *池= [[NSAutoreleasePool的alloc]初始化];
 
    //设置该线程和主线程之间的连接。
    NSPORT * distantPort =(NSPORT *)INDATA;
 
    MyWorkerClass * workerObj = [[自我的alloc]初始化];
    [workerObj sendCheckinMessage:distantPort];
    [distantPort发布]
 
    //让运行循环过程的东西。
    {
        [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
                            beforeDate:[的NSDate distantFuture]];
    }
    而([workerObj shouldExit]!);
 
    [workerObj发布]
    [池释放];
}

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

清单3-15显示了辅助线程的入住程序。此方法设置为未来通信自身的本地端口,然后发送办理入住手续的消息回主线程。该方法使用在接收到的端口对象LaunchThreadWithPort:方法作为消息的目标。

清单3-15   发送使用马赫端口的入住信息

//工作线程办理入住手续的方法
 - (无效)sendCheckinMessage:(NSPORT *)外港
{
    //保留并保存远程端口以便将来使用。
    [个体经营setRemotePort:外港];
 
    //创建并配置工作线程端口。
    * NSPORT MyPort上= [NSMachPort端口]
    [MyPort上setDelegate:个体经营];
    [NSRunLoop currentRunLoop] addPort:MyPort上forMode:NSDefaultRunLoopMode];
 
    //创建办理入住手续的消息。
    NSPortMessage * messageObj = [[NSPortMessage页头] initWithSendPort:外港
                                         receivePort:MyPort上的组件:无];
 
    如果(messageObj)
    {
        //完成配置信息,并立即将其发送。
        [messageObj setMsgId:setMsgid:kCheckinMessage];
        [messageObj sendBeforeDate:[NSDate的日期]];
    }
}

配置NSMessagePort对象

要建立与本地连接NSMessagePort对象,你不能简单地在线程之间传递端口对象。远程消息端口必须通过名字来获得。在可可使这可能需要具有特定名称注册的本地端口,然后通过该名称远程线程使其能够获得适当的端口对象进行通信。清单3-16显示了要使用消息的端口情况下,端口创建和注册过程。

清单3-16   注册信息的端口

NSPORT *将localPort = [[NSMessagePort的alloc]初始化];
 
//配置对象,并将其添加到当前运行的循环。
[将localPort setDelegate:个体经营];
[NSRunLoop currentRunLoop] addPort:将localPort forMode:NSDefaultRunLoopMode];
 
//注册使用特定名称的端口。该名称必须是唯一的。
* NSString的localPortName = [的NSString stringWithFormat:@“MyPortName”];
[NSMessagePortNameServer sharedInstance] registerPort:将localPort
                     名称:localPortName];

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

本节将展示如何设置使用Core Foundation的应用程序的主线程和辅助线程之间的双向通信通道

清单3-17显示了应用程序的主线程调用,以启动工作线程的代码。代码做的第一件事就是建立一个CFMessagePortRef不透明类型来监听来自工作线程的消息。工作线程需要端口的名称来进行连接,以使字符串值被输送到工作线程的入口点的功能。端口名称一般应为当前用户上下文中是唯一的; 否则,你可能会遇到冲突。

清单3-17   附加一个核心基础信息端口一个新的线程

#定义kThreadStackSize(8 * 4096)
 
OSStatus MySpawnThread()
{
    //用于接收响应创建一个本地端口。
    CFStringRef myPortName;
    CFMessagePortRef MyPort上;
    CFRunLoopSourceRef rlSource;
    CFMessagePortContext上下文= {0,NULL,NULL,NULL,NULL};
    布尔shouldFreeInfo;
 
    //创建与港口名称的字符串。
    myPortName = CFStringCreateWithFormat(NULL,NULL,CFSTR(“com.myapp.MainThread”));
 
    //创建的端口。
    MyPort上= CFMessagePortCreateLocal(NULL,
                myPortName,
                &MainThreadResponseHandler,
                和背景,
                &shouldFreeInfo);
 
    如果(MyPort上!= NULL)
    {
        //端口创建成功。
        //现在为它创建一个运行循环源。
        rlSource = CFMessagePortCreateRunLoopSource(NULL,MyPort上,0);
 
        如果(rlSource)
        {
            //源添加到当前的运行循环。
            CFRunLoopAddSource(CFRunLoopGetCurrent(),rlSource,kCFRunLoopDefaultMode);
 
            //一旦安装完毕,这些可以被释放。
            CFRelease(MyPort上);
            CFRelease(rlSource);
        }
    }
 
    //创建线程并继续处理。
    MPTaskID TASKID;
    回报(MPCreateTask(ServerThreadEntryPoint,
                    (无效*)myPortName,
                    kThreadStackSize,
                    空值,
                    空值,
                    空值,
                    0,
                    &的TaskID));
}

如果安装了端口并启动线程,在等待的线程来检查主线程可以继续其正常运行。当入住消息到达时,它被分派到主线程的MainThreadResponseHandler功能,显示清单3-18。这个函数提取端口名工作线程,并为未来通信的管道。

清单3-18   接收签入的消息

#定义kCheckinMessage 100
 
//主线程端口消息处理程序
CFDataRef MainThreadResponseHandler(CFMessagePortRef地方,
                    SINT32 MSGID,
                    CFDataRef数据,
                    void *的信息)
{
    如果(MSGID == kCheckinMessage)
    {
        CFMessagePortRef messagePort;
        CFStringRef threadPortName;
        CFIndex BufferLength中= CFDataGetLength(数据);
        UINT8 *缓冲区= CFAllocatorAllocate(NULL,BufferLength中,0);
 
        CFDataGetBytes(数据,CFRangeMake(0,BufferLength中),缓冲液);
        threadPortName = CFStringCreateWithBytes(NULL,缓冲,BufferLength中,kCFStringEncodingASCII,FALSE);
 
        //你必须按名称获得远程消息端口。
        messagePort = CFMessagePortCreateRemote(NULL,(CFStringRef)threadPortName);
 
        如果(messagePort)
        {
            //保留并保存线程的通信端口以备将来参考。
            AddPortToListOfActiveThreads(messagePort);
 
            //由于港口是由先前的功能,保留的释放
            // 在这里。
            CFRelease(messagePort);
        }
 
        // 清理。
        CFRelease(threadPortName);
        CFAllocatorDeallocate(NULL,缓冲区);
    }
    其他
    {
        //处理其他消息。
    }
 
    返回NULL;
}

随着配置的主线,剩下的唯一事情是新创建的工作线程创建自己的端口,并办理入住手续。清单3-19显示了工作线程的入口点函数。该函数提取主线程的端口名称,并用它来创建一个远程连接回主线程。该函数然后为自己创建一个本地端口,安装在线程的运行循环的端口,并发送办理入住手续的信息,即包括本地端口名称的主线。

清单3-19   设置线程结构

OSStatus ServerThreadEntryPoint(void *的参数)
{
    //创建远程端口到主线程。
    CFMessagePortRef mainThreadPort;
    CFStringRef PORTNAME =(CFStringRef)PARAM;
 
    mainThreadPort = CFMessagePortCreateRemote(NULL,PORTNAME);
 
    //不含在参数传递的字符串。
    CFRelease(PORTNAME);
 
    //工作线程创建的端口。
    CFStringRef myPortName = CFStringCreateWithFormat(NULL,NULL,CFSTR(“com.MyApp.Thread-%D”),MPCurrentTaskID());
 
    //存储端口,供日后参考此线程的上下文信息。
    CFMessagePortContext背景= {0,mainThreadPort,NULL,NULL,NULL};
    布尔shouldFreeInfo;
    布尔shouldAbort = TRUE;
 
    CFMessagePortRef MyPort上= CFMessagePortCreateLocal(NULL,
                myPortName,
                &ProcessClientRequest,
                和背景,
                &shouldFreeInfo);
 
    如果(shouldFreeInfo)
    {
        //无法创建本地端口,所以杀死线程。
        MPExit(0);
    }
 
    CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL,MyPort上,0);
    如果(!rlSource)
    {
        //无法创建本地端口,所以杀死线程。
        MPExit(0);
    }
 
    //源添加到当前的运行循环。
    CFRunLoopAddSource(CFRunLoopGetCurrent(),rlSource,kCFRunLoopDefaultMode);
 
    //一旦安装完毕,这些可以被释放。
    CFRelease(MyPort上);
    CFRelease(rlSource);
 
    //打包端口名称和发送校验的消息。
    CFDataRef returnData =零;
    CFDataRef outData;
    CFIndex stringLength = CFStringGetLength(myPortName);
    UINT8 *缓冲液= CFAllocatorAllocate(NULL,stringLength,0);
 
    CFStringGetBytes(myPortName,
                CFRangeMake(0,stringLength),
                kCFStringEncodingASCII,
                0,
                假,
                缓冲,
                stringLength,
                空值);
 
    outData = CFDataCreate(NULL,缓冲器,stringLength);
 
    CFMessagePortSendRequest(mainThreadPort,kCheckinMessage,outData,0.1,0.0,NULL,NULL);
 
    //清理线程的数据结构。
    CFRelease(outData)以;
    CFAllocatorDeallocate(NULL,缓冲区);
 
    //输入运行循环。
    CFRunLoopRun();
}

一旦进入它的运行循环,发送到线程的端口所有未来事件被处理ProcessClientRequest功能。该功能的实现依赖于工作线程做,而不是这里显示的类型。