码迷,mamicode.com
首页 > 移动开发 > 详细

IOS NSRunLoop

时间:2015-03-05 14:53:22      阅读:268      评论:0      收藏:0      [点我收藏+]

标签:应用   ios   object   nsrunloop   

原英文网址是:https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

运行循环

运行的循环是与线程相关联的基本基础结构的一部分。运行循环是您使用调度工作和协调收到传入的事件的事件处理循环。运行循环的目的是保持您的线程忙工作、 并把您的线程睡眠时没有的时候。

运行的循环管理不是完全自动的。您仍然必须设计您的线程代码以在适当的时候启动运行的循环和响应传入的事件。可可和核心基础提供运行循环对象以帮助您配置和管理您的线程运行的循环。您的应用程序不会创建这些对象明确 ;每个线程,包括应用程序的主线程有一个关联的运行的循环对象。只有第二个线程需要明确、 但是运行其运行的循环。应用程序框架自动设置,并在主线程上运行运行的循环作为应用程序启动过程的一部分。

以下各节提供有关运行的循环和你如何将它们配置为您的应用程序的详细信息。有关运行的循环对象的其他信息请参阅NSRunLoop 类引用CFRunLoop 参考

一个运行的循环的解剖

一个运行的循环是很像它的名字听起来。这是一个循环、 您的线程进入和使用来运行以响应传入事件的事件处理程序。您的代码提供了用于执行运行回路的实际循环部分的控制语句 — — — — 换句话说,您的代码提供驱动器运行的循环的你努力放松一下循环。在你的循环中、 您使用运行的循环对象"运行"接收事件并调用安装处理程序的事件处理代码。

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

图 3-1显示了一个运行的循环和各种来源的概念结构。输入的源提供异步事件到对应的处理程序并导致runUntilDate: (在该线程关联的NSRunLoop对象上调用) 方法来退出。计时器来源到其处理程序例程传递事件,但不是会导致循环的运行退出。

图 3-1一个运行的循环和其来源结构技术分享

除了处理输入源、 运行的循环也产生运行的循环行为有关的通知。注册的运行循环观察员可以接收这些通知、 并用于执行附加处理的线程上。您可以使用核心基础在您的线程上安装运行循环观察员。

以下各节提供有关组件运行的循环和它们在其中运作的模式的详细信息。他们还描述了在不同的时间、 期间处理的事件生成的通知。

运行循环模式

运行循环模式是输入的源和要监视的计时器的集合和集合运行的循环观察员通知。每次您运行的循环运行时、 您指定 (显式或隐式) 要在其中运行一个特定的"模式"。在这阶段运行循环中、 与该模式相关联的唯一来源监察及允许提供他们的活动。(同样与该模式相关联的唯一观察员的通知运行的循环进展。与其他模式关联的源直到后来经过适当的模式中的循环都坚持到任何新的事件。

在您的代码中、 您按名称标识模式。可可和核心基础定义默认模式和几种常用的模式、 还会在您的代码中指定这些模式的字符串。只需指定一个自定义的字符串的模式名称、 您可以定义自定义模式。虽然您将分配给自定义的模式的名称是任意的这些模式的内容并不。你一定要添加一个或多个输入源、 计时器或为他们创建的任何模式运行循环观察员要有用。

您可以使用模式特定传递期间通过您运行的循环过滤掉来自意外源的事件。大多数时候、 你会想要在系统定义"默认值"模式下运行运行的循环。模态的面板中,但是,可能在"模式"模式下运行。而在此模式下、 只有源有关的模式面板将事件线程。对于第二个线程、 您可以使用自定义的模式以防止低优先级的内容源将事件传递在时间关键型操作期间。

注:模式歧视事件、 不是类型的事件的源上的基础。不会使用模式来匹配只有鼠标按下事件或只有键盘事件。 后点查询即可或否则为更改源和运行当前正在监视的循环观察员。 您可以使用模式来听一组不同的端口、 暂停计时器

文本 3 1列出了当您使用该模式时、 由可可和核心基础以及的描述定义的标准模式。名称列中列出了实际的常量您使用您的代码中指定的模式。

文本 3 1预定义运行的循环模式

模式

名称

可选项

默认

NSDefaultRunLoopMode (可可)

kCFRunLoopDefaultMode () 核心基金会

默认模式是用于大多数操作。大多数情况下、 应使用此模式来开始您运行的循环和配置输入的源。

连接

NSConnectionReplyMode(可可)

可可将与结合使用这种模式NSConnection要监视的答复的对象。你应该很少需要使用这种模式下你自己。

模态

NSModalPanelRunLoopMode(可可)

可可使用此模式来识别活动、 目的是为模态的面板。

事件跟踪

NSEventTrackingRunLoopMode(可可)

可可使用此模式来限制传入的事件期间鼠标拖动的循环和其他各种用户界面跟踪环。

常见模式

NSRunLoopCommonModes (可可)

kCFRunLoopCommonModes () 核心基金会

这是常用的模式可配置组。将输入的源与此模式相关联、 与每个组中的模式也关联。对于可可应用程序、 这套包括违约、 莫代尔、 和事件默认跟踪模式。核心基础包括最初只是默认的模式。您可以使用CFRunLoopAddCommonMode函数集添加自定义模式。

输入的源

输入的源到您的线程异步发送事件。事件源取决于输入的源、 通常是两个类别之一的类型。基于端口的输入的源监视您的应用程序的马赫端口。自定义输入的源监视事件的自定义源。您运行的循环而言它应该不管输入的源是基于端口或自定义。该系统通常实现是您可以使用这两种类型的输入的的源。这两个来源之间的唯一区别是如何、 他们都已终止。基于端口的源都由内核,自动终止和自定义源必须用手动信号通知从另一个线程。

当你创建一个输入的源时、 您可以将其分配给一个或多个您运行的循环模式。模式影响的输入的源监测在任何给定的时刻。大多数情况下、 在默认模式下、 运行在运行的循环、 但您也可以指定自定义模式。如果输入的源不在当前被监视模式下,它会生成任何事件举行直到运行的循环运行在正确的模式。

以下各节描述一些输入源。

基于端口的源

可可和核心基础用于创建基于端口的输入的源使用端口相关的对象和函数提供内置支持。后点查询即可,可可您不需要直接在所有创建的输入的源。你只需创建端口对象、 并使用NSPort方法将该端口添加到运行循环。端口对象处理的创建和配置所需的输入源中。

在核心基础、 您必须手动创建端口和其运行的循环源。在这两种情况下、 您使用与端口不透明类型 (CFMachPortRef、 CFMessagePortRefCFSocketRef) 来创建相应的对象相关联的功能。

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

自定义输入的源

若要创建自定义的输入的源、 您必须使用与核心基础中的CFRunLoopSourceRef不透明类型相关联的函数。您配置自定义的输入的源、 使用几个回调函数。核心基础在不同的点来配置源、 处理任何传入的事件,以及拆除源它从运行循环中移除时调用这些函数。

除了定义的行为的自定义源,当事件到达时您还必须定义事件交付机制。这部分源在一个单独的线程上运行,并负责提供其数据的输入的源并准备好处理这些数据时向其发出信号。事件传递机制是取决于你、 但不是需要过于复杂。

有关如何创建自定义的输入的源的示例、 请参阅定义自定义的输入源自定义输入源的参考信息、 请参阅CFRunLoopSource 参考

可可执行选择器来源

除了基于端口的来源,可可定义自定义的输入的源使您可以在任何线程上执行一个选择器。像一个基于端口的源,在目标线程减轻许多可能出现在一个线程上运行的多个方法的同步问题上执行请求序列化的选择器。不同于基于端口的源、 执行器源从中移除本身运行循环后它执行其选择器。

注:在 OS X v10.5 之前执行选择器源主要是用于将消息发送到主线程,但在 OS X v10.5 和以后和在 iOS 中,你可以使用它们来将消息发送到任何线程。

当在另一个线程上执行一个选择器、 目标线程必须有一个积极的运行的循环。对于您创建的线程,这意味着等待直到您的代码显式启动运行的循环。因为主线程启动其自身运行的循环,然而你可以开始发布在该线程上的调用应用程序调用applicationDidFinishLaunching:应用程序委托的方法。所有已排队的运行的循环过程执行选择器调用每次通过循环、 而不是在每个循环迭代期间处理一个。

文本 3 2列出了NSObject可用于在其他线程上执行选择器上定义的方法。因为这些方法在NSObject上的声明、 您可以使用它们从任何线程在那里你有目标 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 参考

运行的循环序列的事件

每次运行它时,您的线程运行的循环处理挂起的事件,并生成通知的任何附加的观察员。这一点单是非常特定的并且是,如下所示:

  1. 通知观测者已经进入了运行的循环。

  2. 通知观测者任何准备就绪的计时器、 就要开火。

  3. 通知观测者并非基于端口的任何输入的源将要火。

  4. 火是准备射击任何非基于端口的输入的源。

  5. 如果基于端口的输入的源是准备好了,等待到火灾立即处理该事件。请转到步骤 9。

  6. 通知观察器线程即将入睡。

  7. 使线程进入沉睡,直到发生下列事件之一:

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

    • 计时器将激发。

    • 为运行回路设置的超时值将到期。

    • 运行的循环显式地吵醒了。

  8. 通知观察器线程就醒了。

  9. 处理挂起的事件。

    • 如果一个用户定义的计时器被解雇,处理计时器事件并重新启动循环。请转到步骤 2。

    • 如果一个输入的源发射、 将事件传送。

    • 如果运行的循环被显式地吵醒了,但还不超时请重新启动循环。请转到步骤 2。

  10. 通知观察器运行的循环已退出。

因为定时器和输入的源的观察员通知被交付之前其实发生这些事件时、 可能会的通知时间与实际事件的时间之间的差距。如果这些事件之间的间隔时间是至关重要的可以使用的睡眠和清醒的睡眠通知以帮助您关联的实际事件之间的间隔时间。

因为定时器和其他周期性的事件被交付运行的循环运行时、 绕过该循环扰乱了这些事件的交货。只要您执行鼠标跟踪例程通过进入循环并一再请求从应用程序中的事件、 就会发生这种行为的典型例子。因为您的代码直接抓的事件,而不是让应用程序发送这些事件通常情况下主动计时器将无法消防直到后你的鼠标跟踪程序退出并返回到应用程序的控制。

一个运行的循环可以被显式地唤醒使用运行的循环对象。其他事件也可能导致循环的运行、 吵醒了。这样可以立即、 处理的输入的源、 而不是等到一些其他事件发生时、 后点查询即可、 添加另一个非基于端口的输入的源醒来运行循环。

何时您将使用一个运行的循环吗?

您需要显式运行一个运行的循环的唯一时间是当您为您的应用程序创建第二个线程。为您的应用程序的主线程运行的循环是一块关键的基础设施。其结果是,应用程序框架提供运行主应用程序循环的代码并且自动启动该循环。UIApplication在 iOS 中的run方法NSApplication在 OS X) 中启动应用程序的主循环作为正常启动序列的一部分。如果您使用 Xcode 模板项目来创建您的应用程序、 您应该不需要显式调用这些例程。

对于第二个线程,您需要决定是否运行的循环是必要的和如果是配置并启动它自己。你不需要在所有情况下启动一个线程运行的循环。后点查询即可,如果您使用一个线程来执行一些长时间运行和预定的任务您可能可以避免开始运行的循环。运行的循环供在哪里你想要更多的交互性与线程的情况。后点查询即可,您需要启动一个运行的循环,如果您计划执行以下任一操作:

  • 使用端口或自定义的输入的源、 与其他线程进行通信。

  • 使用计时器的线程上。

  • 使用任何performSelector......可可应用中的方法。

  • 保持大约要执行定期任务的线程。

如果您选择使用一个运行的循环、 配置和安装程序是简单的。作为与所有线程编程虽然、 你应该退出你第二个线程在适当情况下的预案。它总是不如让它退出、 比来迫使它终止干净利落地结束一个线程。有关如何配置并退出运行的循环信息描述在使用运行循环对象

使用运行的循环对象

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

获取运行的循环对象

若要获取当前线程运行的循环,您使用下列操作之一:

虽然它们不是免费的桥接的类型,你可以从NSRunLoop对象时所需的CFRunLoopRef不透明类型。NSRunLoop类定义一个getCFRunLoop方法,返回一个CFRunLoopRef类型,您可以将传递给核心基础例程。因为这两个对象引用相同的运行循环,则可以根据需要混合使用对NSRunLoop对象和CFRunLoopRef不透明类型的调用。

配置运行的循环

在辅助线程上运行一个运行的循环之前,您必须向其添加至少一个输入的源或计时器。如果运行的循环并没有任何来源监测,它退出立即当您尝试运行它。如何将源添加到一个运行循环示例,请参见配置运行循环来源.

除了安装源,您也可以安装运行的循环观察员,并用于检测不同的执行阶段的运行循环。若要安装一个运行的循环的观察者,你创建一个CFRunLoopObserverRef的不透明类型,并使用CFRunLoopAddObserver函数来将其添加到您运行的循环。运行的循环使用核心基础,即使对于可可应用程序必须创建的观察员。

清单 3-1显示某个线程的运行的循环观察员重视其运行的循环的主的例程。该示例的目的是向您展示如何创建一个运行的循环的观察者,所以代码简单地设置运行的循环观察员来监测所有运行循环活动。(未显示) 的基本的处理程序例程只是记录运行的循环活动,因为它处理计时器请求。

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

- (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);
}

配置长寿线程运行的循环时,最好添加至少一个输入的源接收消息。虽然只用了附加,一旦计时器触发一个定时器可以输入运行的循环,它是通常将失效,这然后将导致循环的运行,退出。附加一个重复的计时器可以保持运行的循环运行较长的时间,但会涉及定期来唤醒你的生命线,是有效地另一种形式的轮询计时器的触发频率。与此相反的是,一个输入的源等待事件发生,保持您的线程睡着了,直到它。

开始运行的循环

开始运行的循环是只需在您的应用程序的辅助线程。一个运行的循环必须有至少一个输入的源或要监视计时器。如果一个未连接,运行的循环会立即退出。

有几种方法来启动运行的循环,包括以下内容:

  • 无条件地

  • 与设置的时间限制

  • 在一个特定的模式

无条件地进入你运行的循环是最简单的选项,但它也是最不可取。无条件地运行您运行的循环的线程放入一个永久的循环,使您运行循环本身很少控制。您可以添加和删除输入的源和计时器,但停止运行的循环的必由之路是杀了它。也是没有办法在自定义模式下运行运行的循环。

而不是无条件地运行一个运行的循环、 它是更好地运行的循环运行有一个超时值。当您使用一个超时值时,运行的循环运行直到事件到达或所分配的时间过期。如果事件发生时,该事件派往处理的处理程序然后运行的循环退出。您的代码、 然后可以重新启动运行的循环来处理下一个事件。如果所分配的时间过期相反、 你可以简单地重新启动运行的循环或利用这段时间做任何必要的家务。

除了一个超时值,还可以运行您运行的循环使用一种特定的模式。模式和超时值并不相互排斥、 都可以用当启动一个运行的循环。模式限制的来源、 向运行循环发送事件和在环路模式下运行的更多详细介绍的类型.

清单 3-2显示线程主入口例程的主干的版本。在此示例中的关键部分显示一个运行循环的基本结构。从本质上说,你添加您的输入的源和定时器运行循环然后反复调用例程来启动运行的循环之一。每次运行的循环例程返回、 你看看如果我们任何条件会出现可能需要退出线程。该示例使用运行循环例程、 以便它可以检查返回的结果并且确定为什么运行的循环退出的核心基础。让运行的循环运行以类似的方式如果您使用的可可并不需要检查返回值。 您也可以使用NSRunLoop类的方法(运行循环调用NSRunLoop类的方法的示例请参阅清单 3 14 .) 

清单 3 2运行运行的循环

- (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.
}

很可能要运行运行的循环递归。换句话说,CFRunLoopRun、 CFRunLoopRunInMode、 从内部处理程序例程的输入的源或计时器。 你可以调用或NSRunLoop方法中的任何启动运行的循环当这样做的过程中、 您可以使用任何你想要运行嵌套的循环、 包括正在使用的模式、 由外部运行循环运行的模式。

退出运行的循环

有两种方法可以使一个运行的循环退出之前它已经处理的事件:

  • 配置循环的运行、 运行有一个超时值。

  • 告诉运行的循环停止。

使用超时值是当然首选、 如果你能做到。指定一个超时值,让运行的循环完成所有其正常的处理包括传递通知运行循环观察员之前退出运行。

停止使用CFRunLoopStop函数的显式运行的循环产生的结果类似于超时。运行的循环发出任何剩余的运行循环通知、 然后退出。区别是您可以使用这种技术、 你开始无条件的运行循环。

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

线程安全和运行的循环对象

线程安全而异、 具体取决于哪个 API 使用来操纵你运行的循环。核心基础中的函数都是线程安全的一般、 可以从任何线程调用。然而,它是循环的仍然良好的做法,这样做从拥有尽可能运行的循环的线程。 如果您执行的操作来修改该配置运行

可可NSRunLoop类本质上不是线程安全作为其核心基础相对物。如果你在使用NSRunLoop类修改您运行的循环、 你应该做所以只能从同一个线程拥有运行循环。添加输入的源或计时器到属于一个不同的线程运行循环可能导致您的代码与崩溃或以意想不到的方式行事。

配置运行的循环源

以下部分说明如何设置不同类型的输入源可可和核心基础中的示例。

定义自定义的输入的源

创建一个自定义的输入的源包括定义以下内容:

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

  • 调度程序例程、 让有兴趣的客户知道如何联络你的输入的源。

  • 要执行任何客户端发送的请求的处理程序例程。

  • 取消例程、 以使你的输入的源无效。

由于您创建自定义的输入的源,来处理自定义信息实际配置旨在采取灵活的态度。调度程序,处理程序和取消例程是关键的例程你几乎总是需要您自定义的输入源。大多数其余的输入的源的行为,然而发生在那些处理程序例程之外。它是由您自己定义的机制传递到你的输入源的数据和交流你给其他线程的输入源的存在。 后点查询即可

图 3-2显示了一个示例配置自定义的输入源。在此示例中、 应用程序的主线程维护输入源、 自定义命令缓冲区的输入源、 并在其安装的输入的源运行的循环的引用。当主线程的任务,它要上交掉到辅助线程时它会发送到命令缓冲区以及任何所需要的辅助线程开始执行该任务的信息的命令。(因为主线程和辅助线程的输入的源到命令缓冲区的访问访问必须同步。一旦发布了命令、 主线程发出信号的输入的源和唤醒工作线程运行循环。接到命令醒来,运行的循环调用处理程序的输入源处理命令在命令缓冲区中发现。

图 3-2运行自定义的输入的源技术分享

以下各节从前面的图解释执行自定义的输入源,并显示关键代码您将需要实现。

定义输入的源

定义自定义的输入的源要求核心基础例程、 可以配置您运行的循环源并将其附加到运行的循环的使用。虽然基本的处理程序是基于 c 语言的函数但这并不排除你从写作为这些职能的包装和使用目标 C 或 c + + 来实现您的代码的身体。

图 3-2中介绍的输入的源使用目标 C 对象来管理命令缓冲区和配合运行循环。清单 3-3显示此对象的定义。并使用该缓冲区接收来自其他线程的消息。 RunLoopSource对象管理命令缓冲区此列表还显示的RunLoopContext交互、 是真的只是一个用来传递一个RunLoopSource对象和应用程序的主线程运行的循环引用的容器对象的定义。

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

@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

虽然目标 C 代码管理的输入源的自定义数据、 附加的输入的源到一个运行的循环需要基于 C 的回调函数。你实际上将运行的循环源附加到您运行的循环并且清单 3-4中所示时、 将调用这些函数的第一个。因为此输入的源具有只有一个客户端 (主线程)、 它使用调度功能发送一条消息、 自身注册应用程序委托在该线程上。当委托想要沟通的输入源时、 它使用的信息在RunLoopContext对象中这样做。

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

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];
}

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

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

void RunLoopSourcePerformRoutine (void *info)
{
    RunLoopSource*  obj = (RunLoopSource*)info;
    [obj sourceFired];
}

如果你曾经从其运行的循环使用CFRunLoopSourceInvalidate函数中删除你的输入的源、 系统调用你的输入的源取消例程。您可以使用这个例程来通知客户端,你的输入的源已不再有效他们应该删除任何对它的引用。清单 3-6显示了RunLoopSource对象注册取消回调例程。此函数将另一个RunLoopContext对象发送到应用程序委托、 但是这一次问要移除对运行的循环源的引用的委托。

清单 3-6无效的输入的源

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];
}

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

在运行回路上安装的输入的源

清单 3-7显示了RunLoopSource类的initaddToCurrentRunLoop方法。init方法创建的CFRunLoopSourceRef不透明类型实际上必须附加到运行的循环。这样的回调例程有指向对象的指针、 它将作为上下文信息传递RunLoopSource对象本身。此时调用RunLoopSourceScheduleRoutine回调函数就不会发生输入源的安装。 直到辅助线程调用addToCurrentRunLoop方法一旦输入的源添加到运行循环时,该线程可以运行其运行的循环以在其上等待。

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

- (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);
}

协调与客户的输入源

对你有用的输入源、 您需要对它进行操作和信号从另一个线程。输入源的整点是要其关联的线程休眠直到有事做。这一事实,就必须有您的应用程序中的其他线程的输入源的了解并有某种与它交流。

一种方法通知客户关于你的输入源是发送注册请求时你的输入的源第一次安装在其运行的环路上。当多个客户端你想要或者您可以简单地注册它与一些然后供应你感兴趣的客户端的输入的源的中央机构、 您可以注册与您输入的源。清单 3 8显示由应用程序委托定义和调用时调用RunLoopSource对象调度函数的注册方法。此方法接收RunLoopSource对象所提供的RunLoopContext交互并将其添加到自己的源列表。此列表还显示用于注销的输入的源、 当它从它运行的循环中移除该例程。

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

- (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];
}

注:清单 3 4清单 3 6所示的调用的方法、 在前面的清单中的回调函数

信号的输入的源

它不插手干预其数据的输入源后,客户端必须信号源其运行的循环中醒来。信号源让运行的循环知道的来源是准备进行处理。因为信号发生时,该线程可能是睡着了,醒来你应该总是会运行循环显式。如果不这样做可能会导致延迟处理的输入的源。

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

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

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

注:你应该永远不会尝试处理SIGHUP或其他类型的进程级信号通过消息传递自定义的输入的源。醒来运行循环功能是核心基础不信号安全、 不应在您的应用程序信号处理程序例程内部使用。信号处理程序例程的更多信息、 请参见sigaction手册页。

配置定时源

若要创建一个计时器来源、 所有你必须做是创建计时器对象并安排它在您运行的循环。可可、 您使用NSTimer类来创建新的计时器对象而在核心基础你使用CFRunLoopTimerRef不透明类型。在内部NSTimer类是简单地扩展提供了一些方便的功能如创建和安排一个计时器使用相同的方法的能力的核心基础。

可可,您可以创建和安排一个计时器,一次全部使用这些类的方法之一:

这些方法创建计时器、 并将其添加到当前线程运行循环中的默认模式 (NSDefaultRunLoopMode)。您还可以安排一个计时器手动,NSTimer交互,然后将它添加到运行的循环使用如果你想通过创建您的addTimer:forMode: NSRunLoop的方法。这两种技术基本上做同样的事情、 但是给你不同级别的控制计时器的配置。后点查询即可、 如果您创建的计时器、 并手动将它添加到运行的循环、 你可以使用非默认模式的模式。清单 3-10显示了如何创建计时器使用这两种技术。第一个计时器有 1 秒但然后火灾初始延迟定期每 0.1 秒后显示。第二个计时器开始射击后最初的 0.2 秒的延迟、 然后触发后、 每 0.2 秒。

清单 3-10创建和计划使用 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];

清单 3 11显示配置计时器使用核心基础功能所需的代码。尽管本示例没有通过上下文结构中的任何用户定义的信息,您可以使用这种结构传递任何自定义的数据你需要为你的计时器。这种结构的内容的更多信息、 请参见CFRunLoopTimer 参考中的说明

清单 3 11创建和计划使用核心基础计时器

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);

配置基于端口的输入的源

可可和核心基础之间的线程或进程之间传达提供基于端口的对象。以下各节展示了如何使用几种不同类型的端口的端口通信设置。

一个 NSMachPort 交互

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

执行主线程代码

清单 3-12显示启动辅助工作线程的主线程代码。由于可可框架执行很多干预的步骤配置端口和运行的循环launchThread方法是明显短于其核心基础等效 (清单 3-17) ;然而这两个行为是几乎完全相同的。一个区别是而不是发送到辅助线程的本地端口的名称、 此方法将发送NSPort对象直接。

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

- (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];
    }
}

要设置您的线程之间的双向通信信道、 你可能想要向你主线程检查在消息中发送其自己的本地端口的工作线程。接受检查的消息让你知道一切都很好在发射第二个线程的主线程、 也给你的方法来将进一步消息发送到该线程。

清单 3 13显示handlePortMessage:方法的主线程。当数据到达线程的本地端口时、 将调用此方法。当检查在消息到达时,该方法直接从端口消息检索辅助线程的端口并将其保存以备后用。

清单 3 13处理马赫端口消息

#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.
    }
}
执行的辅助线程代码

对于辅助工作线程,你必须配置线程并使用指定的端口进行通信信息反馈给主线程。

清单 3 14显示了用于设置工作线程的代码。后创建的线程 autorelease 池、 该方法将创建工人对象开车线程执行。工人交互sendCheckinMessage:方法 (清单 3 15并将检查在消息发送回主线程。 所示) 创建工作线程的本地端口

清单 3 14启动辅助线程使用马赫端口

+(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];
}

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

清单 3 15显示签入常规的辅助线程。此方法设置其自身未来通信的本地端口、 然后将检查在消息发送回主线程。该方法使用端口对象在收到此方法设置其自身未来通信的本地端口、 然后将检查在消息发送回主线程。方法作为消息的目标。

清单 3 15发送使用马赫端口的检查消息

// 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]];
    }
}

一个 NSMessagePort 交互

要建立与你不能简单地通过端口对象线程之间。要建立与NSMessagePort对象的本地连接对象的本地连接、 你不能简单地通过端口对象线程之间。远程消息端口必须按名称获取。制作可可中的这可能需要具有特定名称注册你的本地端口、 然后顺带一提、 名称到远程线程、 以便它可以获取适当的端口对象进行通信。远程消息端口必须按名称获取。显示端口创建和注册过程中要使用消息端口的情况。

清单 3 16注册消息端口

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];

在核心基础中配置基于端口的输入的源

本节演示如何设置您的应用程序的主线程和辅助线程使用核心基础之间的双向通信通道.

清单 3 17显示了应用程序的主线程启动辅助线程所调用的代码。代码做的第一件事设置CFMessagePortRef 不透明类型以侦听消息从工作线程。辅助线程需要要使该连接的端口的名称、 以便字符串值传递给工作线程的入口点函数。端口名称一般应在当前用户上下文内唯一;否则您可能会遇到冲突。

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

#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));
}

随着港口安装和启动线程、 主线程可以继续其定期执行等待线程来签到时。当检查在消息到达时、 它被调度到主线程MainThreadResponseHandler区域、清单 3 18中所示。此函数提取工作线程的端口名称、 并创建未来的通信渠道。

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

#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;
}

与主线程配置、 剩下的唯一的事是为新创建的辅助线程以创建它自己的端口、 并检查清单 3 19显示了工作线程的入口点函数的英寸。函数提取主线程端口名称、 并使用它来创建一个远程连接回主线程。并检查在消息发送到主线程包含本地端口的名称。该函数然后为自己创建一个本地端口、 安装端口的线程运行循环中

清单 3 19设置线程结构

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();
}

一旦进入其运行的循环由ProcessClientRequest函数处理发送到线程的端口的所有未来事件。该函数的实现取决于类型的工作线程并不会显示在这里。

IOS NSRunLoop

标签:应用   ios   object   nsrunloop   

原文地址:http://blog.csdn.net/zhaoguodongios/article/details/44081323

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!