Runloop笔记(一)

来源:互联网 发布:计算机应用与软件期刊 编辑:程序博客网 时间:2024/05/21 10:22

Runloop笔记(一)

Run Loops

Run Loops 是线程的基础。一个run loop(运行循环)就是一个事件处理循环,你用来安排工作,协调即将到来的事件。run loop的目的是当有事情需要做的时候保持你的线程繁忙,当没有事情做的时候,保持你的线程休眠。

Run loop的管理并不是完全自动的。你仍然必须设计你的代码在适宜的时间开启run loop并相应即将到来的事件。Cocoa和Core Foundation框架都提供了run loop对象来帮助你配置、管理你的线程的运行循环。你的应用不需要明确地创建这些对象;包括应用的主线程,每一个线程都有一个关联的run loop对象。然而只有非主线程需要明确运行它们的run loop。APP框架自动地在主线程建立并运行run loop作为APP启动过程的一部分。

下面的章节提供更多关于run loops和如何为你的应用配置它们的信息。更多关于run loop对象的信息,查看NSRunLoop Class Reference和CFRunLoop Reference。

剖析Run Loop

run loop非常像它的名字听起来那样,它是一个线程加入的循环,用来运行事件处理器来响应即将到来的事件。你的代码提供控制语句用来实现运行时循环的实际循环部分——换一种说法,你的代码提供while或for循环来驱动run loop。在你的循环中,你使用一个run loop对象来运行接收事件并调用安装处理程序的事件处理代码。

run loop从两个不同类型的源接收事件。输入源传递异步事件,通常是从另一个线程或者另一个应用传递过来的消息。定时器源传递同步事件,发生在预定时间或者有重复的间隔。两种类型的源使用应用程序特定的处理器程序来处理到达的事件。

下图展示了run loop和各种源的概念上的结构。输入源传递异步事件给响应的处理器并导致runUntilDate:(在线程关联的NSRunloop对象上调用)方法退出。定时器源传递时间到它们的处理器程序但是并不会导致run loop退出。

除了处理输入源,run loops也生成关于run loop行为的通知。注册run-loop观察者可以接收到这些通知并使用它们在线程上做额外的处理。使用Core Foundation框架在线程上设置run-loop观察者。

下面章节提供更多关于run loop组件和操作模式的信息。同时描述了在处理事件的不同时刻生成的通知。

Run Loop 模式

run loop 模式是要监视的输入源和定时器源的集合,以及要通知的run loop观察者集合。每次你运行你的run loop,你可以显式或隐式地指定特定的运行模式。在运行循环过程中,只有与该模式相关联的源被监视并被允许处理它们的事件。(同样地,只有与该模式相关联的观察被通知run loop的进度。)与其他模式相关联的源会保持所有的新事件直到在适宜的模式通过循环。

在你的代码中,你通过名字识别模式。Cocoa和Core Foundation定义一个默认的模式和几个常用的模式,以及在代码中指定这些模式的字符串。你可以通过简单的声明一个自定义的字符串作为模式名称来自定义一个模式。虽然你分配给自定义模式的名称是任意的,但是这些模式的内容却不是。你必须确认添加一个或者更多的输入源,定时器或者run loop观察者到你创建的任何模式中使它们有用。

你可以使用模式来过滤一个运行循环中你不想要的源的事件。大多数时间,你想在系统定义的默认模式下运行run loop。然而,模态面板可能在模态模式下运行。当在这个模式下,只有与模态面板有关的源才会传递事件到线程。对于非主线程,你可能会使用自定义模式阻止低优先级源再关键时间操作期间传递事件。

注意:模式的区分基于事件的源,而不是事件的类型。例如,你不会使用模式来匹配仅仅是鼠标向下或者仅仅是键盘有关的事件。你可以使用模式来监听不同的端口集,暂时暂停定时器或者除此之外你可以更改当前正在监视的源和观察者。

下表列出了Cocoa和Core Foundation定义的标准模式以及你何时使用该模式的描述。名称一列列出了在你的代码中指定模式的实际字符串常量。

模式名称描述DefaultNSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)默认模式适用于大多数操作。大部分时间,你应该使用这种模式开始你的run loop并配置你的输入源ConnectionNSConnectionReplyMode (Cocoa)Cocoa使用此模式与NSConnection对象一起来监视响应。你很少需要自己使用这种模式。ModalNSModalPanelRunLoopMode (Cocoa)Cocoa使用此模式用于标识模态面板的事件。Event trackingNSEventTrackingRunLoopMode (Cocoa)Cocoa使用此模式在鼠标拖动循环和其他类型的用户界面跟踪循环期间限制传入事件。Common modesNSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)这是一组可配置的常用模式。将输入源与此模式相关联还将其与组中的每个模式相关联。对于Cocoa应用,这个设置默认包括default, modal, and event tracking模式 。Core Foundation框架下最初只包含default模式。你可以使用CFRunLoopAddCommonMode函数将自定义模式添加到集合中

输入源

输入源异步传递事件给你的线程。事件的源取决于输入源的类型,通常是两个类别之一。Port-based 输入源监听你的应用的Mach端口。Custom输入源监听自定义事件源。就你的run loop而言,你不必在意输入源是port-based 还是 custom。系统通常都会实现两张输入源。两个源之间的区别仅仅是它们发送信号通知的方式。Port-based源通过内核自动发送,自定义源必须从另一个线程手动发送。

当你创建一个输入源,分配它到run loop的一个或多个模式。模式影响在任何给定时间内来监听那个输入源。大部分时间,你在default模式下运行 run loop,但你也可以指定自定义模式。如果输入源不在当前监听模式下,该输入源生成的任何事件都会被挂起直到run loop进入正确的模式。

下面章节描述了一些类型的输入源

Port-Based源

Cocoa和Core Foundation框架对使用端口相关的对象和函数创建port-based输入源提供了支持。例如,在Cocoa中,你根本不需要直接创建一个输入源。你只需要创建一个端口对象并且使用NSPort的方法添加这个端口到run loop。端口对象会为你处理输入源需要的创建和配置。

在Core Foundation中,你必须手动创建所有的端口和他们的run loop源。
在这两种情况下,你使用与端口不透明类型(CFMachPortRef, CFMessagePortRef, CFSocketRef)相关的函数来创建合适的对象。

自定义输入源

要创建一个自定义输入源,你必须使用Core Foundation框架下CFRunLoopSourceRef不透明类型相关联的函数。使用几个回调函数来配置自定义输入源。Core Foundation在不同的点调用这些函数来配置源,处理一些即将到来的事件,并在源代码从运行循环中移除时删除源。

除了定义当一个事件到达时自定义源的行为以外,你也要定义事件的传递机制。源的这部分在单独的线程上运行,并且负责向输入源提供其数据,并且当该数据准备好进行处理时用于发信号通知。事件的传递机制由你定义,但是不要过于复杂。

如何创建一个自定义输入源的例子,查看Defining a Custom Input Source.对于自定义源的参考信息,也可以查看CFRunLoopSource Reference.

Cocoa执行选择器源

除了port-based源,Cocoa定义了一个自定义输入源允许你在另一个线程执行方法。像port-based源一样,执行方法选择器的请求在目标线程上是序列化的,减轻了多任务在同一线程上可能发生的同步问题。不像port-based源,执行选择器源在它们执行选择器之后会将自己从run loop移除。

注意:在OS X 10.5之前,执行选择器源大部分被用来向主线程发生消息,但是在OS X 10.5以及以后和iOS平台,你可以使用它们给任意线程发送消息。

当在另一个线程执行方法选择器,目标线程必须有一个激活的run loop。对于你创建的线程,这意味着等待知道你的代码明确启动了run loop。因为主线程自己启动run loop,然而,一旦应用程序调用了代理的applicationDidFinishLaunching:方法你就可以对该线程发出调用。run loop通过循环处理所有排队的执行消息选择器的调用,而不是每一次循环迭代处理一个。

下表列出了定义在NSObject中的方法可以被用来在其它线程执行方法。因为这些方法被声明在NSObject,你可以任何可以访问OC对象的线程使用它们,包括POSIX线程。这些方法实际上不创建新的线程来执行选择器。

方法描述performSelectorOnMainThread: withObject:waitUntilDone: performSelectorOnMainThread: withObject:waitUntilDone:modes:在应用的主线程的下一个run loop循环期间执行指定的选择器。这些方法使你可以选择阻塞当前线程知道选择器被执行。performSelector:onThread: withObject:waitUntilDone: performSelector:onThread: withObject:waitUntilDone:modes:在你拥有NSThread对象的任意线程执行指定的选择器performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:在下一个run loop循环开始的指定时间后在当前线程执行指定的选择器。因为它等待直到下个run loop循环开始执行选择器,这些方法提供了来自当前代码的自动微延迟。多个排队的选择器按照他们排队的顺序一个接一个的执行。cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget: selector:object:让你来取消一个使用performSelector:withObject:afterDelay:或者performSelector:withObject:afterDelay:inModes:方法的消息发送

定时器源

定时器源在将来的一个预设时间同步地传递事件。定时器是线程提醒自己做某些事情的途径。例如,在来自用户的连续键盘敲击之间如果过去了一段时间,搜索区域可以使用定时器自动发起搜索,使用该延迟时间给用户在开始搜索前尽可能多的机会来输入想要搜索的字符串。

即使它生成了基于时间的通知,定时器也不是一个真正的时间机制。像输入源,定时器与你的run loop的特定模式相关联。如果一个定时器不是在当前run loop监听的模式下,它不会触发,直到你在一个定时器支持的模式下运行run loop。同样的,如果run loop在一个正在执行的处理程序中定时器出发了,定时器会等到下一次通过run loop来调用它的处理程序。如果run loop没有运行,定时器不会触发。

你可以配置定时器来仅仅生成一次时间或是重复生成事件。一个重复的定时器基于预定的触发时间自动地重新预定(一下触发时间),不是当前的触发时间。例如,一个定时器预定在一个特定的时间触发并且每5秒触发一次,预定的触发时间会永远的在原来的5秒钟之后,即使实际触发时间有延迟。如果触发时间延迟很多,错过了一个或者多个预定的触发间隔,定时器仅仅会对错过的时间段做一次触发。再对错过的时间段触发之后,定时器会重新预定下一次的触发时间。

更多关于配置定时器源的信息,查看Configuring Timer Sources。关于参考信息,查看NSTimer Class Reference或者CFRunLoopTimer Reference。

Run Loop 观察者

与在你相应的异步或同步事件发生时触发的源相比,run loop观察者在执行循环本身期间在特定的位置触发。你可以使用run loop观察者准备线程来处理给定的时间或者在线程休眠前准备线程。你可以在你的run loop中将run loop观察者与下列事件相关联:

  • run loop 的入口
  • 当run loop要处理一个定时器
  • 当run loop要处理一个输入源
  • 当run loop要休眠
    • 当run loop被唤醒,但在它处理事件之前唤醒它
  • 从run loop退出

你可以使用Core Foundation来给APP添加run loop观察者。为了创建一个run loop观察者,你创建一个新的CFRunLoopObserverRef不透明类型的实例。这个类型会保持追踪你的自定义回调函数和其感兴趣的活动。

类似于定时器,run loop观察者可以使用一次或多次。一个一次性的观察者会在它触发后从run loop移除,而一个可重复的观察者会保持附着在run loop中。当你创建它的时候你可以指定观察者是一次性的还是可重复的。

关于如何创建一个run loop观察者的例子可以查看Configuring the Run Loop。关于参考信息查看CFRunLoopObserver Reference。

Run Loop 事件队列

每次你运行它,你的线程的run loop处理挂起的事件并对任何附着的观察者生成通知,其执行此项操作的顺序非常具体,如下所示:

  1. 通知观察者已经进入run loop循环
  2. 通知观察者任何准备就绪的定时器即将触发
  3. 通知观察者任何不属于port based的输入源将要触发
  4. 触发准备触发的任何非基于端口的输入源
  5. 如果一个基于端口的输入源准备好等待触发,立即处理时间,去往步骤9
  6. 通知观察者线程即将休眠
  7. 使线程休眠知道下列事件发生:
    • 事件到达基于端口的输入源
    • 定时器触发
    • 设置run loop的有效期为一个超时的值
    • run loop被显式地唤醒
  8. 通知观察者线程刚刚唤醒
  9. 处理挂起的事件
    • 如果用户定义的定时器触发,处理定时器事件并重启循环。到步骤2
    • 如果输入源被触发,传递事件
    • 如果run loop被显式地唤醒但还没有超时,重启loop,到步骤2
  10. 通知观察者run loop已经退出

因为定时器和输入源的观察者通知在这些事件发生前传递,通知时间和实际事件的时间之间存在间隙。如果这些事件之间的间隔是有严重影响的,你可以使用休眠和从休眠唤醒的通知来帮助你关联实际事件之间的时间。

因为当你运行run loop时,定时器和其他周期性的事件都被传递,要避免循环终止那些事件的传递。这一行为存在的典型的例子是你从应用进入一个循环并重复请求事件来实现一个鼠标跟踪程序,因为你的代码直接抓取事件,而不是让你的应用正常地调度那些事件,激活的定时器可能不会触发直到你的鼠标跟踪程序退出并将控制权返回给你的应用之后。

run loop可以使用run loop对象显式地唤醒,其他事件同样可能造成run loop被唤醒,添加另一个非基于端口的输入源唤醒run loop,输入源会被立即处理,而不是等待到事件发生。