[OC RunLoop_翻译]一、介绍 & 二、剖析运行循环

[OC RunLoop_翻译]一、介绍 & 二、剖析运行循环
[OC RunLoop_翻译]三、 什么时候使用运行循环 & 四、使用运行循环对象
[OC RunLoop_翻译]五、配置运行循环源
注:pdf翻译文档百度云下载链接,密码:zcs2

注: _Run Loops _链接

一、介绍

运行循环是与线程相关联的基本基础结构的一部分。运行循环是事件处理循环,可用于安排工作并协调收到的事件的接收。运行循环的目的是在有工作要做时让线程忙,而在没有工作时让线程进入睡眠状态

运行循环管理不是完全自动的。您仍然必须设计线程的代码以在适当的时候启动运行循环并响应传入的事件。 CocoaCore Foundation提供运行循环对象,以帮助您配置和管理线程的运行循环。您的应用程序不需要显式创建这些对象。每个线程(包括应用程序的主线程)都有一个关联的运行循环对象。但是,只有辅助线程需要显式地运行其运行循环。在应用程序启动过程中,应用程序框架会自动在主线程上设置并运行运行循环。

以下各节提供有关运行循环以及如何为应用程序配置循环的更多信息。有关运行循环对象的其他信息,请参见 NSRunLoop Class ReferenceCFRunLoop Reference

二、剖析运行循环

运行循环如其名一样。它是线程进入并用于运行事件处理程序以响应传入事件的循环。您的代码提供了用于实现运行循环实际循环部分的控制语句,换句话说,您的代码提供了驱动运行循环的while或for循环。在循环中,您可以使用运行循环对象来“运行”事件处理代码,以接收事件并调用已安装的处理程序。

运行循环从两种不同类型的源接收事件。输入源传递异步事件,通常是来自另一个线程或其他应用程序的消息。计时器源传递同步事件,这些事件在计划的时间或重复的间隔发生。两种类型的源都使用特定于应用程序的处理程序例程来处理事件到达时的事件。

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

Figure 3-1 运行循环的结构及其源

[OC RunLoop_翻译]一、介绍 & 二、剖析运行循环_第1张图片
图片.png

除了处理输入源之外,运行循环还生成有关运行循环行为的通知。注册的runloop观察者可以接收这些通知,并使用它们在线程上进行其他处理。您可以使用Core Foundation在线程上安装runloop观察器。

以下章节提供了有关运行循环组件及其运行模式的更多信息。它们还描述了在处理事件期间的不同时间生成的通知。

2-1、运行循环模式

runloop模式要监视的输入源和计时器的集合,以及要通知的运行循环观察者的集合。每次运行runloop时,都可以(显式或隐式)指定运行的特定“模式”。在运行循环的整个过程中,仅监视与该模式关联的源,并允许其传递事件。 (类似地,只有与该模式相关联的观察者才被告知运行循环的进度。)与其他模式相关联的源将保留任何新事件,直到随后以适当的模式通过该循环。

在代码中,您可以通过名称标识模式。 Cocoa和Core Foundation都定义了默认模式和几种常用模式,以及用于在代码中指定这些模式的字符串。您可以通过简单地为模式名称指定自定义字符串来定义自定义模式。尽管您分配给自定义模式的名称是任意的,但这些模式的内容却不是。您必须确保将一个或多个输入源,计时器或运行循环观察器添加到您创建的任何模式中,以使其有用。

您可以使用模式从运行循环的特定遍历中过滤掉有害来源的事件。大多数时候,您将需要在系统定义的“默认”模式下运行runloop。但是,模式面板可以在“模式”模式下运行。在这种模式下,只有与模式面板相关的源才会将事件传递给线程。对于辅助线程,您可以使用自定义模式来防止低优先级源在时间紧迫的操作期间传递事件。

注:模式的区别取决于事件的来源,而不是事件的类型。例如,您不会使用模式来只匹配鼠标按下事件或键盘事件。您可以使用模式来监听不同的端口集,暂时挂起计时器,或者更改当前正在监视的源和运行循环观察器。

表3-1列出了Cocoa和Core Foundation定义的标准模式,以及何时使用该模式的说明。name列列出了用于在代码中指定模式的实际常量。

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

Mode Name Description
Default NSDefaultRunLoopMode (Cocoa)、kCFRunLoopDefaultMode (Core Foundation) 默认模式是用于大多数操作的模式。大多数时候,您应该使用此模式来启动运行循环并配置输入源。
Connection NSConnectionReplyMode (Cocoa) Cocoa将此模式与NSConnection对象结合使用来监视响应。你应该很少需要自己使用这种模式。
Modal NSModalPanelRunLoopMode (Cocoa) Cocoa使用此模式来识别用于模式面板的事件
Event tracking NSEventTrackingRunLoopMode (Cocoa) Cocoa使用此模式来限制鼠标拖动循环和其他类型的用户界面跟踪循环期间的传入事件
Common modes NSRunLoopCommonModes (Cocoa)、kCFRunLoopCommonModes (Core Foundation) 这是一组常用模式的可配置组。将输入源与此模式相关联还将其与组中的每个模式相关联。对于Cocoa应用程序,此集合默认包括默认,模式和事件跟踪模式。最初,Core Foundation仅包括默认模式。您可以使用CFRunLoopAddCommonMode 函数将自定义模式添加到集合中

2-2、输入源

输入源以异步方式向线程传递事件。事件的源取决于输入源的类型,通常是两种类型之一。基于端口的输入源监视应用程序的Mach端口自定义输入源监视事件的自定义源。就运行循环而言,输入源是基于端口还是自定义的并不重要。系统通常实现两种类型的输入源,您可以按原样使用。这两个信号源之间的唯一区别是它们是如何发出信号的基于端口的源由内核自动发出信号自定义源必须从另一个线程手动发出信号

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

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

2-3、基于端口的源

Cocoa和corefoundation为使用端口相关的对象和函数创建基于端口的输入源提供了内置支持。例如,在Cocoa中,根本不必直接创建输入源。您只需创建一个port对象并使用 NSPort 方法将该端口添加到运行循环中。port对象为您处理所需输入源的创建和配置

CoreFoundation中,必须手动创建端口及其运行循环源。在这两种情况下,都可以使用与端口不透明类型(CFMachPortRef, CFMessagePortRef, 或 CFSocketRef)关联的函数来创建适当的对象。

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

2-4、自定义输入源

创建自定义输入源,必须使用与Core Foundation中的 CFRunLoopSourceRef不透明类型关联的函数。您可以使用几个回调函数配置自定义输入源。Core Foundation在不同的点调用这些函数来配置源代码,处理任何传入事件,并在源代码从运行循环中删除时将其销毁。

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

有关如何创建自定义输入源的示例,请参见 定义自定义输入源。有关自定义输入源的参考信息,请参见 CFRunLoopSource Reference

2-5、Cocoa执行选择器(selector)源

除了基于端口的源代码外,Cocoa还定义了一个自定义输入源允许您在任何线程上执行选择器。与基于端口的源一样,perform selector请求在目标线程上序列化,从而减轻了在一个线程上运行多个方法时可能出现的许多同步问题。与基于端口的源不同,执行选择器源在执行其选择器后将自身从运行循环中移除
注意:在OS X v10.5之前,执行选择器源主要用于向主线程发送消息,但在OS X v10.5及更高版本以及iOS中,您可以使用它们向任何线程发送消息。

在另一个线程上执行选择器时,目标线程必须具有活动的运行循环。对于您创建的线程,这意味着等待直到您的代码显式启动运行循环。但是,由于主线程启动其自己的运行循环,因此只要应用程序调用应用程序委托的applicationdiffinishlaunching:方法,就可以开始对该线程发出调用。运行循环每次通过循环处理所有排队的执行选择器调用,而不是在每次循环迭代期间处理一个

表3-2列出了NSObject上定义的方法,这些方法可用于在其他线程上执行选择器(perform selector)。由于这些方法是在NSObject上声明的,因此可以在任何有权访问Objective-C对象的线程中使用它们,包括POSIX线程。这些方法实际上并不创建新的线程来执行选择器。

Table 3-2 在其他线程上执行选择器

Methods Description
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 Class Reference.

2-6、定时器源(NSTimer)

计时器源在将来的预设时间将事件同步传递到线程。计时器是线程通知自身执行某项操作的一种方式。例如,搜索字段可以使用计时器在用户连续按键之间经过一定时间后启动自动搜索。使用这个延迟时间,用户就有机会在开始搜索之前键入尽可能多的所需搜索字符串。

尽管计时器生成基于时间的通知,但它不是一种实时机制。与输入源一样,计时器与运行循环的特定模式相关联如果计时器未处于运行循环当前监视的模式,则在以计时器支持的模式之一运行运行循环之前,它不会触发。类似地,如果在运行循环正在执行处理程序例程的过程中触发计时器,则计时器将等到下一次通过运行循环调用其处理程序例程如果运行循环根本没有运行,计时器就不会触发

您可以将计时器配置为仅生成一次事件重复生成事件重复计时器根据预定的触发时间而不是实际触发时间自动重新调度自己。例如,如果一个计时器被安排在某个特定时间触发,并且此后每隔5秒触发一次,那么即使实际触发时间被延迟,计划的触发时间也将始终落在原来的5秒时间间隔上。如果触发时间延迟太久,以致错过了一个或多个预定的触发时间,则对于错过的时间段,计时器只触发一次。在为错过的时间段触发后,计时器将重新安排为下一个预定的触发时间

有关配置计时器源的更多信息,请参见配置计时器源。有关参考信息,请参见NSTimer Class ReferenceCFRunLoopTimer Reference

2-7、运行循环观察者(RunLoop Observer)

与在发生适当的异步或同步事件时触发的源不同。运行循环观察器在运行循环本身执行期间的特殊位置触发。您可以使用运行循环观察器来准备线程以处理给定事件,或者在线程进入睡眠状态之前准备线程。您可以将运行循环观察者与运行循环中的以下事件相关联

  • 运行循环的入口
  • 当运行循环将要处理计时器时
  • 当运行循环将要处理输入源时
  • 当运行循环即将进入睡眠状态时
  • 当运行循环已唤醒,但需要在处理该事件之前将其唤醒。
  • 从运行循环中退出

您可以使用Core Foundation将运行循环观察器添加到应用程序。要创建运行循环观察器,请创建 CFRunLoopObserverRef透明类型的新实例。此类型跟踪自定义回调函数及其感兴趣的活动。

与计时器类似,运行循环观察器可以使用一次或重复使用。一次触发的观察者在触发后将自己从运行循环中删除,而重复的观察者仍保持连接。您可以指定观察者在创建时是运行一次还是重复运行。

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

2-8、事件的运行循环序列(重要!!!)

每次运行它时,线程的运行循环都会处理未决事件,并为所有附加的观察者生成通知。它的执行顺序非常明确,如下所示:

    1. 通知观察者已进入运行循环。
    1. 通知观察者准备就绪的计时器即将触发。.
    1. 通知观察者任何不基于端口的输入源都将被触发。
    1. 触发所有准备触发的非基于端口的输入源。
    1. 如果基于端口的输入源已准备好并等待启动,请立即处理事件。转到步骤9。
    1. 通知观察者线程即将休眠。.
    1. 使线程进入睡眠状态,直到发生以下事件之一:
    • 基于端口的输入源的事件到达.
    • 计时器触发.
    • 运行循环设置的超时值已过期.
    • 运行循环被显式唤醒
    1. 通知观察者线程刚刚醒来。
    1. 处理挂起的事件。.
    • 如果触发了用户定义的计时器,请处理计时器事件并重新启动循环。转到步骤2。.
    • 如果触发了输入源,则传递事件
    • 如果运行循环已显式唤醒但尚未超时,请重新启动循环。转到步骤2.
    1. 通知观察者运行循环已退出.

由于计时器和输入源的观察者通知是在这些事件实际发生之前传递的,因此通知时间和实际事件时间之间可能会有差距。如果这些事件之间的时间很关键,则可以使用sleep和asleep-from-sleep通知来帮助您关联实际事件之间的时间。

因为计时器和其他周期性事件是在运行runloop时传递的,因此规避该循环会中断这些事件的传递。这种行为的典型示例是,每当您通过输入循环并重复从应用程序请求事件来实现鼠标跟踪例程时,都会发生这种行为。因为您的代码直接捕获事件,而不是让应用程序正常分配事件,所以在您的滑鼠追踪程式退出并将控制权传回应用程式之前,活动计时器将无法启动。

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

注:大家可以关-注 Wei-Xin 公-众-号 :Style月月专栏

你可能感兴趣的:([OC RunLoop_翻译]一、介绍 & 二、剖析运行循环)