RunLoop(官方文档翻译)

循环运行

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

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

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

一个运行循环的解剖

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

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

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

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

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

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

运行循环模式

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

在您的代码,按名称识别模式。不管可可和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功能。该功能的实现依赖于工做线程作,而不是这里显示的类型。

相关文章
相关标签/搜索