ios run loop(一) 基本概念

Run loop是线程基本组成的一部分,是一个用来调度工作和协调到来事件的事件处理循环。主要作用是当有任务到来的时候保持线程的繁忙,当没有任务可做的时候,将线程置为sleep状态。

 run loop的管理不是完全自动的,有时你必须让你的代码来决定在某个适当的事件启动run loop 和对到来的事件做出相应。Cocoa和Core Foundation提供了run loop 对象来帮助你配置管理线程的循环。你的应用不必显式的创建run loop对象。包含主线程在内,每个线程都有一个相关联的run loop对象。只有二级线程需要显式调用runloop的run 方法,应用的库会在你的进程启动时,自动配置和启动主线程的runloop。

一、run loop 详情

run loop正如它的名字一样,就是你线程进入的循环,用来对到来的事件作出处理。runloop接收两种不同来源的事件。输入源(input sources)传递异步事件,通常是来自另一个线程或进程的事件。定时器源(Timer sources)传递在某个特定时间或者特定间隔的同步事件。这两类源都使用应用指定的处理规则(application-specific handler routine)处理到来的事件。

图3-1显示了runloop概念上的结构和不同的源,输入源传递异步事件到相关的处理函数,并调用runUtileDate来退出。定时器源,传递处理事件,不会引起runloop退出。

Figure 3-1  Structure of a run loop and its sources Structure of a run loop and its sources

 除了处理输入源,runloops也会对runloop的行为产生通知,注册run-loop观察者可以收到这些通知,并用来在线程中做额外的处理。你可以用Core Foundation来在你的线程中添加观察者。

下面一节提供更多关于runloop组成的信息和它运行的模式,以及关于在处理事件的不同事件产生的通知的描述。

二、Run Loop Modes

 run loop mode 就是被监听的输入源、定时器和观察者的集合。每次你运行runloop,都需要指定一个mode。只有和mode相关联的源才会被监听并允许传递他们的事件(对于观察者同样)。与其他模式相关的源会被保存下来,直到进入相关的mode下才会被处理。

 你使用名字来指定模式,Cocoa和Core Foundation定义了一个默认的模式和几个其他通用的模式。你也可以定义自己的模式,只需指定模式的名字。尽管模式的名字是任意的,但是模式的内容并不是,你必须指定一个或几个输入源或者定时器或者观察者才能使它生效。

  你使用mode来过滤掉runloop来自不需要的源的信息,大部分时间,runloop运行在default模式下。模态窗口,运行在modal模式下,在此模式下,只有与模态窗口有关的事件才会被传递到线程。在二级线程,你也可以定义自己的模式来在时间敏感的操作下阻止第优先级事件的处理。

模式是基于事件的源来区别的,而不是事件的类型。例如,你不能使用模式来只匹配鼠标点击或者键盘输入事件,你可以使用模式来监听不同的端口集合,短暂的暂停定时器,或者改变源或者run loop 观察者。

Table 3-1  Predefined run loop modes

Mode

Name

Description

Default

NSDefaultRunLoopMode(Cocoa)

kCFRunLoopDefaultMode (Core Foundation)

The default mode is the one used for most operations. Most of the time, you should use this mode to start your run loop and configure your input sources. 默认模式是大部分操作使用的,大部分时间你使用此模式来开启你的runloop和配置你的输入源

Connection

NSConnectionReplyMode(Cocoa)

Cocoa uses this mode in conjunction with NSConnection objects to monitor replies. You should rarely need to use this mode yourself.(Cocoa使用此模式来与NSConnection关联来监听应答,你很少需要用到此模式)

Modal

NSModalPanelRunLoopMode(Cocoa)

Cocoa uses this mode to identify events intended for modal panels. (CoCoa使用此模式来区别来自模态窗口的事件)

Event tracking

NSEventTrackingRunLoopMode(Cocoa)

Cocoa uses this mode to restrict incoming events during mouse-dragging loops and other sorts of user interface tracking loops. (Cocoa使用此模式来限制当拖曳鼠标的时候到来的事件一级其他用户界面轨迹循环)

Common modes

NSRunLoopCommonModes(Cocoa)

kCFRunLoopCommonModes (Core Foundation)

This is a configurable group of commonly used modes. Associating an input source with this mode also associates it with each of the modes in the group. For Cocoa applications, this set includes the default, modal, and event tracking modes by default. Core Foundation includes just the default mode initially. You can add custom modes to the set using the CFRunLoopAddCommonMode function.(这是可配置的通用模式组,将一个输入源与此模式关联会使它与这个组中的每一个模式关联,对于Cocoa应用,这个集合包含default,modal和event tracking三个模式,Core Foundation在初始时只包含default模式,可以使用CFRunLoopAddCommonMode来添加自定义模式到这个集合中)

三、输入源(Input Soures)

输入源异步传递事件到线程。事件的来源取决于输入源的类型,通常是两类中的之一。基于端口的输入源和自定义输入源。

基于端口的输入源监控你应用的Mach 端口。自定义输入源监控自定义的事件来源。这两类源的唯一不同就是他们被通知的方式,基于端口的源由内核来通知,自定义源需要另一个线程中手动来通知。当你创建了一个输入源,你将它赋给runloop的一个或多个模式。模式影响在每个给定时刻是哪个输入源正在被监听。如果一个输入源不在当前监控的模式下,那么它产生的事件都会等待直到runloop运行在正确的模式下。

基于端口的源:Cocoa和Core Foundation 对于创建基于端口的源提供内建的函数和端口相关的对象。例如在Cocoa中,完全不需要自己来直接创建输入源,只需要创建一个端口对象,并使用NSPort的方法来将端口加入到runloop。端口对象会替你创建和配置需要的输入源。在Core Foundation中,必须手动创建端口和run loop 源。

自定义输入源:在Core Foundation中,创建自定义输入源,需要用到与CFRunLoopSourceRef相关的函数,使用几个回调函数来配置自定义输入源。Core Foundation在不同点来调用这些函数来配置源,处理到来的事件,当源从runloop中移除的时候来卸载源。除了在事件到来时定义源的行为,还必须定义事件传递机制。不同的源在运行在不同的线程下,需要向输入源提供他所需的数据,并且在数据准备完毕时通知他来处理。事件传递机制有你来确定,但是不应该太过复杂。

Cocoa执行选择器源:除了基于端口的源,Cocoa定义了自定义输入源,可以用来在任何线程上执行选择器(selector)。和基于端口的源类似,在目标线程上运行选择器是同步的。减少了许多同步问题。与基于端口的源不同的是,执行selector的源会执行完selector之后自动从runloop中移除。

当在另一个线程中执行selector的时候,目标线程必须有一个活跃的runloop。对于你创建的线程来说,必须明确的启动runloop。由于主线程默认启动了它的runloop,因此当applicationDidFinishLauching:方法调用完毕后, 你就可以开始向主线程发送调用,runloop会从头到尾处理所有队列中的selector。

Table 3-2  Performing selectors on other threads

Methods

Description

performSelectorOnMainThread:withObject:waitUntilDone:

performSelectorOnMainThread:withObject:waitUntilDone:modes:

Performs the specified selector on the application’s main thread during that thread’s next run loop cycle. These methods give you the option of blocking the current thread until the selector is performed.

performSelector:onThread:withObject:waitUntilDone:

performSelector:onThread:withObject:waitUntilDone:modes:

Performs the specified selector on any thread for which you have an NSThread object. These methods give you the option of blocking the current thread until the selector is performed.

performSelector:withObject:afterDelay:

performSelector:withObject:afterDelay:inModes:

Performs the specified selector on the current thread during the next run loop cycle and after an optional delay period. Because it waits until the next run loop cycle to perform the selector, these methods provide an automatic mini delay from the currently executing code. Multiple queued selectors are performed one after another in the order they were queued.

cancelPreviousPerformRequestsWithTarget:

cancelPreviousPerformRequestsWithTarget:selector:object:

Lets you cancel a message sent to the current thread using the performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method.

定时器源: 定时器源在某个预定的时间来传递同步事件。定时器是线程用来通知自己处理事情的一种方式,例如,一个搜索域用定时器来设置当用户敲击建之间的时间到达一定值后就自动搜索一次,这就给用户足够的时间来输入需要搜索的关键字。

尽管它产生了基于时间的通知,定时器并不是一个实时的机制。和输入源类似,定时器和runloop的特定模式相关联。如果定时器不在runloop当前运行的模式下,那么他会等到runloop运行在它需要的模式下才会启动。同样如果定时器在runloop执行处理中启动,那么会等到下次runloop轮寻的时候才会被启动。如果runloop不运行了,那么定时器永远不会启动了。

你可以设置定时器每隔一段时间来调用一次,一个重复的定时器调度基于调度启动时间而不是实际的启动时间。例如,一个定时器设置在某个时间启动,并每隔5秒调用一次,调度的时间总是基于最初调度的5秒间隔,即使实际调度时间被推迟了。如果启动时间被推迟了很久,以至于丢失了一次或几次调度,那么当定时器启动时,只会对于丢失时间段内的调度一次,之后重新根据调度时间调度。

四、run loop 观察者

与当某一个同步或异步事件发生时启动的源不同,runloop观察者在runloop执行的特定位置启动。可以使用runloop 观察者来使线程对于处理一个事件或在线程进入睡眠态之前做好处理。可以将runloop观察者关联到如下的事件。

1.run loop的入口2.当runloop要处理一个定时器的时候3.当runloop要处理一个输入源时4.当runloop要进入睡眠的时候,5.当runloop醒来时,还没有来得及处理事件之前。6.runloop退出时。

你可以使用Core Foundation 来向App中添加runloop 观察者。为了创建一个runloop观察者,需要创建一个新的CFRunLoopObserverRef的实例,这个类型会追踪自定义回调函数及它感兴趣的活动。与定时器类似,run-loop观察者可以被用一次或重复使用。一次观察者会在被runloop 触发后移除自己,而重复调用的不会。

五、runloop 事件处理顺序如下

1.通知观察者run loop启动

2.通知观察者任何将要启动的定时器

3.通知观察者任何非基于端口的输入源将被触发

4.触发任何准备好的所有的非基于端口的输入源

5.如果一个基于端口的输入源准备就绪,立刻处理这个事件,转到步骤9

6.通知观察者线程将进入睡眠

7.使线程进入睡眠,除非以下事件发生:(1.一个基于端口输入源事件的到来(2.定时器启动(3.为runloop设置的超时时间到了(4.runloop被明确的唤醒

8.通知观察者线程醒了

9.处理等待时间 (1)如果用户定义的定时器启动了,处理定时器事件重启runloop 回到步骤2.(2)如果输入源触发了,传递这个事件(3)如果runloop被明确的唤醒,但是还没有到达超时时间,重启runloop回到步骤2

10.通知观察者runloop退出了

由于定时器和输入源的通知在他们实际发生之前发生,那么在通知和实际事件发生之间有一段时间。如果这段时间很重要,你可以使用sleep 和awake-from-sleep通知来帮助你关联这些事件之间的时间。

由于定时器和其他间隔事件是在runloop运行的时候传递的,如果有事件一直占用runloop会打断这些事件的传递。典型的例子就是当你实现鼠标轨迹跟踪的时候会不断地向应用请求事件。由于你的代码直接能抓取事件,而不是通过应用程序传递这些事件。定时器将不会被启动直到,退出鼠标轨迹跟踪,并将控制权返回到应用。

一个runloop可以被明确的唤醒,其它事件也可以唤醒runloop.例如添加非基于端口的输入源会唤醒runloop,如此输入源事件才会被立刻处理,而不是等待其它事件发生。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值