RunLoop官方文档翻译

Run Loops

Run loops是与线程相关的基础框架的一部分。一个run loop是一个循环,在这个循环中你可以分配任务,并协调安排接收到的各种事件。run loop存在的目的是使线程在有任务的时候处于工作状态,并且当没有任务的时候使线程处于休眠状态.
Run loop的管理不完全是自动的,你仍然需要设计跟线程有关的代码,以便在适当的时候启动运行循环,并响应传入的事件。
Cocoa和Core Foundation都提供一些与run loop相关的对象来帮助你配置和管理线程的run loop。你的应用程序不需要显式的创建这些对象;每一个线程,包括主线程都有一个相关联的run loop对象。子线程的run loop对象需要手动开启,而系统的app framework会在app启动时候 自动设置并开启主线程的run loop。

Run Loop剖析

run loop就像它的名字含义一样。它是一个线程可以进入其中并用来运行事件处理程序以响应传入事件的循环。我们可以通过代码控制循环,换句话说,我们可以在代码中用for循环或者while循环控制run loop。在循环内部,我们可以用run loop对象去运行事件处理代码,这些代码可以接收外部事件,并且调用已经绑定到run loop对象的回调函数。
run loop接收来自两种不同类型的源的事件。输入源提供异步事件,通常是来自另一个线程或不同应用程序的消息。定时器源提供同步事件,发生在预定时间或重复间隔。这两种类型的源都使用应用程序特别指定的处理程序来处理到达的事件。
下图显示了run loop和各种源的结构。输入源将异步事件传递给相应的处理程序,并导致runUntilDate:方法(在与线程关联的NSRunLoop对象上调用)退出。定时器源将事件传递到其处理程序例程,但不会导致run loop退出。


图片.png

除了处理输入源之外,run loop同时也生成了有关run loop各种状态的通知。注册run loop的observers可以收到这些通知,并且可以在线程上做一些其他操作。通过Core Foundation框架可以在线程上添加observer

Run Loop Modes

Run Loop Modes是要监视的输入源和定时器的集合,以及要通知的运行循环observer的集合。每次运行run loop时,都需要(显式或隐式)指定特定的“模式”来运行。在这次run loop过程中,只有与该模式相关的源会被监听,并允许其发送事件。 (类似地,只有与该模式相关的observer才会被通知run loop的状态。)与其他模式相关的源会保留发送给它们事件,直到随后以适当的模式运行。
在我们的代码中,我们可以通过名字来区分模式。 Cocoa和Core Foundation都定义了一个默认模式和若干个常用模式,以及用于在代码中标志这些模式的字符串。我们可以通过自定义一个字符串作为名字来定义模式。虽然这个字符串可以是任意的,但是模式的内容却不是任意的。为了确保创建的模式(models)可用,必须向其中添加至少一个或多个输入源(input sources),定时器(timers),或者run loop观察者(run loop observer)。
我们可以使用模式在特定的运行循环模式下过滤掉不需要的源事件。在大多数情况下,我们是在系统已经定义好的"default"模式下。然而,模态面板可能以"模态"模式运行(注:这段不是很懂,不了解模态面板和模态模式)。在这种模式下,只有与该模式相关的源才会将事件传递给线程。对于其他次级线程,我们可以通过自定义models用来阻值低优先级的源在限时操作中传递事件。

注意:模式基于事件的来源而不是事件的类型进行区分。 例如,你不会使用模式来匹配手指触摸事件或键盘输入事件。 你可以使用模式来监听一组不同的端口,计时器临时的暂停,或者更改当前正在监视的源(source)以及run loop observer。

输入源(input source)

输入源异步发送事件到线程中。输入源的类型决定了事件的类型,通常有两种事件。Port-based输入源监听应用的Mach端口。自定义输入源监听自定义事件。就run loop而言,输入源是基于Mach端口或者其他端口并不重要。
当创建一个输入源后,可以将它添加run loop的一个或多个model中。在任何时刻,models决定了哪些输入源处于被监听状态。大多数时间,run loop处于default model下,但是可以切换到custom model下。如果一个输入源没有被添加到当前正在运行的模式下,它的所有事件都会被阻塞,直到run loop切换到该模式下。
下面几部分介绍了其中一些输入源(input source)

Port-Based Sources

Cocoa框架和Core Foundation框架内部都提供了与port相关的对象和方法,可以用来创建输入源。在Cocoa框架中,不用直接创建输入源,只需要创建一个port对象,并使用NSPort提供的方法将这个对象加入到run loop即可。port对象会为你管理输入源的创建和配置。
在Core Foundation中,你必须手动创建port和它的输入源。在这两种情况下,你可以使用与port类型(CFMachPortRef, CFMessagePortRef, or CFSocketRef)相关的函数去创建合适的对象。

Custom Input Sources

为了创建一个自定义输入源,你必须使用Core Foundation中与CFRunLoopSourceRef相关的函数。你使用几个回调函数来配置一个输入源。Core Foundation框架会在不同时刻调用这些函数以配置源、处理任何输入事件,并且当source被移除run loop时候会被自动释放掉。
除了需要定义回调函数之外,还必须定义事件传递机制。这部分的源代码在单独的线程上运行,负责为输入源提供数据,并在数据准备好处理时用信号通知它。 事件传递机制取决于你,但不必过于复杂。

Cocoa Perform Selector Sources

除了基于端口的源之外,Cocoa还定义了一个自定义输入源,允许你在任何线程上执行选择器。 像基于端口的源一样,执行选择器请求在目标线程上被序列化,减轻了在一个线程上运行多个方法时可能发生的许多同步问题。 与基于端口的源不同,执行选择器源在执行选择器之后将自身从运行循环中移除。

注意:在OS X v10.5之前,执行选择器源主要用于向主线程发送消息,但在OS X v10.5及更高版本和iOS中,可以使用它们向任何线程发送消息。

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

timers

定时器源在未来的预设时间将事件同步传递给你的线程。定时器是线程通知自己做某事的一种方式。例如,search field可以使用定时器来在用户的连续输入之间经过一定的时间后启动自动搜索。这个延迟时间的使用使用户有机会在开始搜索之前尽可能多地输入想要的搜索字符串
虽然它会生成基于时间的通知,但计时器不是实时机制。像输入源一样,定时器与run loop的特定模式相关联。如果一个定时器不在当前被运行循环监视的模式下,它不会被触发,直到处于定时器支持的一种模式下运行时才会被触发。同样的,如果一个定时器在run loop处于执行处理程序的过程中触发,定时器会等待下一次通过run loop来调用它的处理程序。如果run loop根本没有运行,定时器不会启动。
你可以将定时器配置为仅生成一次或重复生成事件,重复的计时器会根据预订的触发时间而不是实际的触发时间自动重新安排时间。例如,如果计时器在某个时间以及之后的每5秒钟触发一次,则即使实际的触发被延迟,计划的触发时间将总是以原来的5秒的时间间隔下降。如果触发时间延迟过多,以至于错过了一个或多个预定的触发时间,则计时器仅在错过的时间段内触发一次。在错过的时间触发之后,定时器重新计划下一个预定的触发时间。

Run Loop Observers

与源不同,run loop observers是当某些同步事件或者异步事件发生的时候被触发。run loop observers在run loop中的一个特定状态被触发。你可以通过run loop observer去让线程在run loop的开始状态或者休眠状态执行特定的动作。你可以将run loop observer与run loop中的以下几种状态关联:
1.即将进入run loop
2.run loop即将处理timer
3.run loop即将处理输入源
4.run loop即将进入休眠状态
5.run loop已经被唤醒,但还没处理唤醒它的事件
6.run loop退出循环
你可以通过Core Foundation去添加run loop observer。如果要创建run loop observer,首先创建CFRunLoopObserverRef的实例。这个实例会将你自定义的回调函数与它关心的run loop状态关联。
同timer类似,run loop observer可以被一次或重复使用。一个一次性的run loop observer会在它触发后将自己从run loop移除。但是重复使用的observer不会这样。你可以在创建observer的时候指定它是否重复。

run loop事件队列

每次运行run loop时候,线程的run loop会处理即将发生的事件,并且会为所有observer生成通知。它会以特定的顺序执行这些操作,顺序如下:
1.通知观察者run loop已经进入循环
2.通知观察者任何准备好的timer即将被触发
3.通知观察者任何port端口之外的输入源将要触发
4.触发任何非基于端口(source0)的输入源事件
5.如果有基于端口的输入源事件(source1),立刻处理这些事件,并跳转到第9步
6.通知观察者,线程将要休眠
7.将线程置于休眠状态,直到下列事件发生:

  • port端口事件到达
  • timer 触发
  • 到达run loop超时时间
  • run loop被显示唤醒

8.通知观察者线程刚被唤醒
9.处理唤醒run loop的事件

  • 如果是用户定义的timer触发时间到了,处理timer事件,并重新开启run loop。跳转到第二步
  • 如果是输入源事件,处理这个事件
  • 如果run loop是被显式调用,并且还未到run loop的超时时间,重新开启runloop 跳转到第二步

10.通知observer,run loop已经退出循环。


图片.png

由于timer和输入源的观察者是先收到通知,在触发事件。所以通知的时间和实际事件触发的时间之间可能存在差距。如果这些事件之间的时间很关键,则可以通过使用休眠和从休眠中醒来的通知来获得实际事件发生之前的时间。
由于timer和其他重复事件是通过run loop触发的, 越过run loop会打断这些事件的传递机制。典型的例子是你通过进入run loop并不断重复向应用请求run loop的事件从事从而实现了一个手势跟踪效果。因为你的代码在主动去直接抓取run loop事件,而不是让应用去自然地派发这些事件。 因为,已经被激活的timer不会被触发,直到你抓取用户手势的事件结束,将对run loop的控制权交给系统后,才可以再次被触发。
一个run loop 可以通过run loop 对象显式唤醒。其他事件也可以唤醒run loop。例如:向run loop中添加一个非基于端口的输入源会唤醒run loop以立刻处理这个输入事件,而不是等其他事件发生。

什么时候需要使用Run Loop

只有需要为应用开辟子线程的时候才需要显式使用run loop。因为主线程的run loop是重要的根基,所以app 框架会自动创建主 run loop,并主动开启它。iOS中UIApplication框架(或者OS X中的NSApplication)的run 方法会将开启应用的main run loop作为启动的一个步骤。如果你是使用xocde模板去创建工程,你不需要显式调用这个方法。
对于子线程来说,你需要考虑下run loop是否是必须的,如果是必要的,配置并手动开启它。不是所有的子线程都需要开启run loop。例如,如果 使用子线程去执行一些长时间运行或者已经定义好的任务,你很大可能上可以避免开启run loop。run loop适用于你想与线程有更多交互的场景。例如,在下面这些情况下,你很可能需要开启一个run loop:

  • 使用端口和自定义输入源与其他线程通信
  • 在线程中使用timer
  • 在cocoa应用中使用任何performSelector...系列方法
  • 使线程重复周期性事件
    如果你决定使用run loop,那么run loop的适配和开启是很简单的。同其他多线程编程不同的是,你需要考虑好在适当的时机退出子线程。让run loop自动退出始终比强制终止它要好。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容