Runloop
一个 run loop 是用来在线程上管理事件异步到达的基础设施。一个 run loop 为 线程监测一个或多个事件源。当事件到达的时候,系统唤醒线程并调度事件到 run loop,然后分配给指定程序。如果没有事件出现和准备处理,run loop 把线程置于休 眠状态。
你创建线程的时候不需要使用一个 run loop,但是如果你这么做的话可以给用户 带来更好的体验。Run Loops 可以让你使用最小的资源来创建长时间运行线程。因为 run loop 在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗 CPU 周期轮询,并防止处理器本身进入休眠状态并节省电源。
为了配置 run loop,你所需要做的是启动你的线程,获取 run loop 的对象引用, 设置你的事件处理程序,并告诉 run loop 运行。Cocoa 和 Carbon 供的基础设施会 自动为你的主线程配置相应的 run loop。如果你打算创建长时间运行的辅助线程, 那么你必须为你的线程配置相应的 run loop。
Run loops 是线程相关的的基础框架的一部分。一个 run loop 就是一个事件处理 的循环,用来不停的调度工作以及处理输入事件。使用 run loop 的目的是让你的线 程在有工作的时候忙于工作,而没工作的时候处于休眠状态。
Run loop 接收输入事件来自两种不同的来源:输入源(input source)和定时源 (timer source)。输入源传递异步事件,通常消息来自于其他线程或程序。定时源 则传递同步事件,发生在特定时间或者重复的时间间隔。两种源都使用程序的某一特 定的处理例程来处理到达的事件。Runloop模式
Run loop 模式是所有要监视的输入源和定时源以及要通知的 run loop 注册观察 者的集合。每次运行你的 run loop,你都要指定(无论显示还是隐式)其运行个模 式。在 run loop 运行过程中,只有和模式相关的源才会被监视并允许他们传递事件 消息。(类似的,只有和模式相关的观察者会通知 run loop 的进程)。和其他模式关 联的源只有在 run loop 运行在其模式下才会运行,否则处于暂停状态。
通常在你的代码中,你可以通过指定名字来标识模式。Cocoa 和 Core foundation 定义了一个默认的和一些常用的模式,在你的代码中都是用字符串来标识这些模式。 当然你也可以给模式名称指定一个字符串来自定义模式。虽然你可以给模式指定任意 名字,但是模式的内容则不能是任意的。你必须添加一个或多个输入源,定时源或者 run loop 的观察者到你新建的模式中让他们有价值。
通过指定模式可以使得 run loop 在某一阶段过滤来源于源的事件。大多数时候run loop 都是运行在系统定义的默认模式上。但是模态面板(modal panel)可以运 行在 “modal”模式下。在这种模式下,只有和模式面板相关的源才可以传递消息给 线程。对于辅助线程,你可以使用自定义模式在一个时间周期操作上屏蔽优先级低的 源传递消息。输入源
输入源异步的发送消息给你的线程。事件来源取决于输入源的种类:基于端口的 输入源和自定义输入源。基于端口的输入源监听程序相应的端口。自定义输入源则监 听自定义的事件源。至于 run loop,它不关心输入源的是基于端口的输入源还是自 定义的输入源。系统会实现两种输入源供你使用。两类输入源的区别在于如何显示: 基于端口的输入源由内核自动发送,而自定义的则需要人工从其他线程发送。
当你创建输入源,你需要将其分配给 run loop 中的一个或多个模式。模式只会 在特定事件影响监听的源。大多数情况下,run loop 运行在默认模式下,但是你也 可以使其运行在自定义模式。若某一源在当前模式下不被监听,那么任何其生成的消 息只在 run loop 运行在其关联的模式下才会被传递。
基于端口的输入源 Cocoa 和 Core Foundation 内置支持使用端口相关的对象和函数来创建的基于端
口的源。例如,在 Cocoa 里面你从来不需要直接创建输入源。你只要简单的创建端口 对象,并使用 NSPort 的方法把该端口添加到 run loop。端口对象会自己处理创建和 配置输入源。
在 Core Foundation,你必须人工创建端口和它的 run loop 源.在两种情况下, 你都可以使用端口相关的函数(CFMachPortRef,CFMessagePortRef,CFSocketRef) 来创建合适的对象。
自定义输入源 为了创建自定义输入源,必须使用 Core Foundation 里面的 CFRunLoopSourceRef
类型相关的函数来创建。你可以使用回调函数来配置自定义输入源。Core Fundation 会在配置源的不同地方调用回调函数,处理输入事件,在源从 run loop 移除的时候 清理它。
除了定义在事件到达时自定义输入源的行为,你也必须定义消息传递机制。源的 这部分运行在单独的线程里面,并负责在数据等待处理的时候传递数据给源并通知它 处理数据。消息传递机制的定义取决于你,但最好不要过于复杂。
Cocoa 执行 Selector 的源 除了基于端口的源,Cocoa 定义了自定义输入源,允许你在任何线程执行selector。和基于端口的源一样,执行 selector 请求会在目标线程上序列化,减缓 许多在线程上允许多个方法容易引起的同步问题。不像基于端口的源,一个 selector 执行完后会自动从 run loop 里面移除。
当在其他线程上面执行 selector 时,目标线程须有一个活动的 run loop。对于 你创建的线程,这意味着线程在你显式的启动 run loop 之前处于等待状态。由于主 线程自己启动它的 run loop,那么在程序通过委托调用 applicationDidFinishlaunching:的时候你会遇到线程调用的问题。因为 Run loop 通过每次循环来处理所有队列的 selector 的调用,而不是通过 loop 的迭代来处理 selector。定时源
定时源在预设的时间点同步方式传递消息。定时器是线程通知自己做某事的一种
方法。例如,搜索控件可以使用定时器,当用户连续输入的时间超过一定时间时,就 开始一次搜索。这样使用延迟时间,就可以让用户在搜索前有足够的时间来输何时使用 Run Loop
仅当在为你的程序创建辅助线程的时候,你才需要显式运行一个 run loop。Run loop 是程序主线程基础设施的关键部分。所以,Cocoa 和 Carbon 程序 供了代码运 行主程序的循环并自动启动 run loop。IOS 程序中 UIApplication 的 run 方法
对于辅助线程,你需要判断一个 run loop 是否是必须的。如果是必须的,那么 你要自己配置并启动它。你不需要在任何情况下都去启动一个线程的 run loop。比 如,你使用线程来处理一个预先定义的长时间运行的任务时,你应该避免启动 run loop。Run loop 在你要和线程有更多的交互时才需要,比如以下情况:
1、使用端口或自定义输入源来和其他线程通信
2、使用线程的定时器 3、Cocoa中使用任何performSelector…的方法
4、使线程周期性工作启动 Run Loop
启动 run loop 只对程序的辅助线程有意义。一个 run loop 通常必须包含一个输 入源或定时器来监听事件。如果一个都没有,run loop 启动后立即退出。
有几种方式可以启动 run loop,包括以下这些:
1、无条件的
2、设置超时时间
3、特定的模式
无条件的进入 run loop 是最简单的方法,但也最不推荐使用的。因为这样会使 你的线程处在一个永久的循环中,这会让你对 run loop 本身的控制很少
你可以添 加或删除输入源和定时器,但是退出 run loop 的唯一方法是杀死它。没有任何办法 可以让这 run loop 运行在自定义模式下。- 替代无条件进入 run loop 更好的办法是用预设超时时间来运行 run loop,这样 run loop 运作直到某一事件到达或者规定的时间已经到期。如果是事件到达,消息 会被传递给相应的处理程序来处理,然后 run loop 退出。你可以重新启动 run loop 来等待下一事件。如果是规定时间到期了,你只需简单的重启 run loop 或使用此段 时间来做任何的其他工作。
- 除了超时机制,你也可以使用特定的模式来运行你的 run loop。模式和超时不是 互斥的,他们可以在启动 run loop 的时候同时使用。模式限制了可以传递事件给 run loop 的输入源的类型,这在”Run Loop 模式”部分介绍。
可以递归的运行 run loop。换句话说你可以使用 CFRunLoopRun, CFRunLoopRunInMode 或者任一 NSRunLoop 的方法在输入源或定时器的处理程序里面 启动 run loop。这样做的话,你可以使用任何模式启动嵌套的 run loop,包括被外 层 run loop 使用的模式。
退出 Run Loop
1、给run loop设置超时时间
2、通知run loop停止
如果可以配置的话,推荐使用第一种方法。指定一个超时时间可以使 run loop 退出前完成所有正常操作,包括发送消息给 run loop 观察者。
使用 CFRunLoopStop 来显式的停止 run loop 和使用超时时间产生的结果相似。 Run loop 把所有剩余的通知发送出去再退出。与设置超时的不同的是你可以在无条 件启动的 run loop 里面使用该技术。
尽管移除 run loop 的输入源和定时器也可能导致 run loop 退出,但这并不是可 靠的退出 run loop 的方法。一些系统例程会添加输入源到 run loop 里面来处理所需 事件。因为你的代码未必会考虑到这些输入源,这样可能导致你无法没从系统例程中 移除它们,从而导致退出 run loop。配置 Run loop 的源
- 定义自定义输入源
创建自定义的输入源包括定义以下内容
1、输入源要处理的信息
2、使感兴趣的客户端(可理解为其他线程)知道如何和输入源交互的调度例程。
3、处理其他任何客户端(可理解为其他线程)发送请求的例程。
4、使输入源失效的取消例程。
图 3-2 显示了一个自定义输入源的配置的例子。在该例中,程序的主线程维护了 输入源的引用,输入源所需的自定义命令缓冲区和输入源所在的 run loop。当主线 程有任务需要分发给工作线程时,主线程会给命令缓冲区发送命令和必须的信息来通 知工作线程开始执行任务。(因为主线程和输入源所在工作线程都可以访问命令缓冲 区,因此这些访问必须是同步的)一旦命令传送出去,主线程会通知输入源并且唤醒 工作线程的 run loop。而一收到唤醒命令,run loop 会调用输入源的处理程序,由 它来执行命令缓冲区中相应的命令。
为了让添加的输入源有用,你需要维护它并从其他线程给它发送信号。输入源的
主要工作就是将与输入源相关的线程置于休眠状态直到有事件发生。这就意味着程序 中的要有其他线程知道该输入源信息并有办法与之通信。
配置定时源
为了创建一个定时源,你所需要做只是创建一个定时器对象并把它调度到你的 run loop。Cocoa 程序中使用 NSTimer 类来创建一个新的定时器对象,而 Core Foundation 中使用 CFRunLoopTimerRef 不透明类型。本质上,NSTimer 类是 Core Foundation 的简单扩展,它 供了便利的特征,例如能使用相同的方法创建和调配 定时器。
Cocoa 中可以使用以下 NSTimer 类方法来创建并调配一个定时器: scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
scheduledTimerWithTimeInterval:invocation:repeats:
上述方法创建了定时器并以默认模式把它们添加到当前线程的 run loop。你可以 手工的创建 NSTimer 对象,并通过 NSRunLoop 的 addTimer:forMode:把它添加到 run loop。两种方法都做了相同的事,区别在于你对定时器配置的控制权。例如,如果你 手工创建定时器并把它添加到 run loop,你可以选择要添加的模式而不使用默认模 式。列表 3-10 显示了如何使用这这两种方法创建定时器。第一个定时器在初始化后 1 秒开始运行,此后每隔 0.1 秒运行。第二个定时器则在初始化后 0.2 秒开始运行, 此后每隔 0.2 秒运行。配置基于端口的输入源
配置 NSMachPort 对象
为了和 NSMachPort 对象建立稳定的本地连接,你需要创建端口对象并将之加入 相应的线程的 run loop。当运行辅助线程的时候,你传递端口对象到线程的主体入 口点。辅助线程可以使用相同的端口对象将消息返回给原线程。
当使用 NSMachPort 时候,本地和远程线程可以使用相同的端口对象在线程间进 行单边通信。换句话说,一个线程创建的本地端口对象成为另一个线程的远程端口对 象。