Run Loops

写在前面

阅读 Apple Deleloper Run Loops 的笔记。

简介

Run Loops 是和线程息息相关的基础组件。
顾名思义,它就是一个循环,目的是:当有工作时,让线程忙碌起来;当没有工作时,让线程休眠。

Run loop 的管理不是完全自动化的,开发者仍然必须设计线程相关代码,让 run loop 在合适时间开启。

Cocoa 和 Core Foundation 都提供了 run loop 对象辅助配置和管理。
开发者不需要再创建这些对象。每个线程(包括主线程)都有一个 run loop 对象与之关联。
App 在启动时,会在主线程上设置和启动 run loop。
只有次要线程需要手动启动 run loop。

Structure of a run loop and its sources

如图所示,run loop 接收2种类型数据:

  1. Input sources : 传递异步事件,经常是来自其他线程或不同应用的信息。
  2. Timer sources : 传递同步事件,定时或重复发生。

除了处理资源,run loops 也创建了关于 run loop 行为的通知。
注册一个 run loop observer 可以接受到这些通知,并且可以在线程上使用它们。
可以使用 Core Foundation 来安装 run loop observer 到线程上。

Run Loop Modes

一个 run loop mode 是 input sources 和 timer sources 的集合。
传输过程中,只有关联到 mode 的 source 才允许传递事件。
Cocoa 和 Core Foundation 都定义了默认 mode 和一些常用 mode。
开发者也可以自定义 mode。
使用 mode 可以过滤事件。

预置的 mode

  • Default
    NSConnectionReplyMode(Cocoa)
    kCFRunLoopDefaultMode(Core Foundation)
    默认 mode ,在大部分操作中使用,大多数时候,应该使用这个 mode 去开启 run loop 和配置 input source。

  • Connection
    NSConnectionReplyMode(Cocoa)
    Cocoa 使用这个 mode,结合 NSConnection 对象去管理回复,开发者很少需要使用它。

  • Modal
    NSModalPanelRunLoopMode(Cocoa)
    Cocoa 使用这个 mode 去标记模态面板。

  • Event tracking
    NSEventTrackingRunLoopMode(Cocoa)
    当用户接口追踪循环过程中,如鼠标拖拽,Cocoa 使用这个 mode 去限制即将到来的事件。

  • Common modes
    NSRunLoopCommonModes(Cocoa)
    kCFRunLoopCommonModes(Core Foundation)
    这是一个可以配置的组合,它已经包含了常用 mode ,开发者也可以自己添加自定义 mode 。

Input Sources

Input source 异步地传递事件给线程。
事件资源依赖于 input source 的类型,一般分为2类:

  1. port_based 资源:管理应用的 Mach ports。
  2. 自定义 input source:管理自定义事件资源。

无须关心 run loop 的类型,系统一般会实现2种类型。

这2种资源唯一的区别在于它们是如何被发信号的:
port_based 资源由内核自动发信号,而自定义资源必须手动由其他线程完成。

如果某个 input source 不是当前运行中的 mode ,它创建的任何事件都会被挂起,直到 run loop 运行到合适的 mode 。

Port_Based Sources

Cocoa 和 Core Foundation 内置创建这类 source 的支持。
比如说,开发者从不需要直接创建一个 input source ,只需要创建一个端口对象,然后使用 NSPort 的方法,添加端口到 run loop。端口对象会创建并配置好需要的 input source。

在 Core Foundation 中,你必须手动创建端口和它的 run loop 资源。
需要使用关联特定端口类型(CFMachPortRef, CFMessagePortRef, CFSocketRef)的方法来创建合适的对象。

Custom Input Sources

使用 Core Foundation 中关联了 CFRunLoopSourceRef 类型的方法,来创建 custom input source,并通过很多回调方法来配置它。

为了定义 custom input source 的行为,必须定制事件传递机制。

Cocoa Perform Selector Sources

除了 port_based sources,Cocoa 定义了能在任何线程上 perform selector 的 input source。
跟 port_based sources 一样,perform selector 请求会在目标线程上排好序。
不同的是,它在执行后,需要移除自身。

Timer Sources

Timer sources 在预设的时间点,同步传递事件给线程。

Timer 不是一个实时机制,跟 input sources 一样,它与 run loop 中特定的 modes 关联,如果不在 run loop 当前的 modes 中,那么它不会被执行。

Run Loop observers

Run Loop observers 事件类型:

  • The entrance to the run loop.
  • When the run loop is about to process a timer.
  • When the run loop is about to process an input source.
  • When the run loop is about to go to sleep.
  • When the run loop has woken up, but before it has processed the event that woke it up.
  • The exit from the run loop.

Run Loop 事件顺序:

  1. Notify observers that the run loop has been entered.
  2. Notify observers that any ready timers are about to fire.
  3. Notify observers that any input sources that are not port based are about to fire.
  4. Fire any non-port-based input sources that are ready to fire.
  5. If a port-based input source is ready and waiting to fire, process the event immediately. Go to step 9.
  6. Notify observers that the thread is about to sleep.
  7. Put the thread to sleep until one of the following events occurs:
    • An event arrives for a port-based input source.
    • A timer fires.
    • The timeout value set for the run loop expires.
    • The run loop is explicitly woken up.
  8. Notify observers that the thread just woke up.
  9. Process the pending event.
    • If a user-defined timer fired, process the timer event and restart the - loop. Go to step 2.
    • If an input source fired, deliver the event.
    • If the run loop was explicitly woken up but has not yet timed out, restart the loop. Go to step 2.
  10. Notify observers that the run loop has exited.

因为 timer 和 input sources 的监听通知会在这些事件实际发生前被传递。
所以,如果时间要求比较苛刻的话,可使用 sleepawake-from-sleep 通知来确认时间。

何时需要使用 Run Loop?

只有创建次要线程时,才需要显式运行一个 run loop。
主线程上的 run loop 是至关重要的。
因此,app frameworks 提供了运行 main application loop 的代码,并且自动启动 run loop。

在以下情况,你需要去启动一个 run loop

  • Use ports or custom input sources to communicate with other threads.
  • Use timers on the thread.
  • Use any of the performSelector… methods in a Cocoa application.
  • Keep the thread around to perform periodic tasks.

使用 Run Loop 对象

一个 run loop 对象提供添加 input sources, timers, run-loop observers,并运行它们的主要接口,每个线程都有一个关联的 run loop 对象。Cocoa 中,它是 NSRunLoop 的实例,更底层的,它是一个指向 CFRunLoopRef opaque type 的指针。

获取 Run Loop 对象

方法:

  1. Cocoa 应用,使用 NSRunLoop 的 currentRunLoop
  2. 使用 CFRunLoopGetCurrent 方法。

虽然它们不是无缝连接的类型,但可以通过 NSRunLoop 里的 getCFRunLoop 方法来获得一个 CFRunLoopRef opaque type,因为它们都是引用同一 run loop,所以可以随意使用其中之一。

配置 Run Loop

运行一个 run loop 前,它必须至少添加了一个 input source 或 timer,否则无法运行它。

除了 sources,还可以安装 run loop observers,然后使用它们来判断 run loop 的运行状态。

当配置一个长时间存活的 run loop,最好得添加至少一个 input source 来接收信息。因为即使添加一个 timer 也可以运行 run loop,但一旦 timer 结束,这种方式也就失效了,这样就会造成 run loop 退出。

启动 Run Loop

只需要在次要线程中启动 Run Loop,有以下方式启动 run loop

  • Unconditionally
    最简单方式,可以添加或移除 input sources, timers,但只能通过 kill 来停止它,而且也无法运行在“自定义 mode”。

  • With a set time limit
    比 Unconditionally 更好的方式是,运行一个带有 timeout 变量的 run loop。

  • In a particular mode
    除了 timeout 变量,可以使用特定 Mode 运行。

退出 Run Loop

方式

  • Configure the run loop to run with a timeout value.
    比较推荐的方式,在退出之前,run loop 处理完该处理的。
  • Tell the run loop to stop.
    调用 CFRunLoopStop 方法也能起到使用 timeout 变量同样的效果,不同之处在于,可以在 Unconditionally 启动的 run loops 上使用这项技术。

虽然移除 input sources 和 timers 也有可能会造成 run loop 退出,但这不是停止 run loop 的可靠方式。系统在需要处理事件时,会添加 input sources 到 run loop 中,因为开发者无法知道这些 input sources,所以无法移除它们,这就无法使 run loop 退出。

线程安全和 Run Loop 对象

是否安全取决于使用什么 API 来操作 run loop。

Core Foundation 中的方法一般是线程安全的,可在任何线程中调用,但最好还是在持有 run loop 的线程上调用。
Cocoa NSRunLoop 不是线程安全的,使用时,要尽量在同一持有 run loop 的线程中使用。

总结

Run Loop 是一个特殊的循环:有工作时,让线程忙碌;当没有工作时,让线程休眠。

它接收的数据类型:

  1. Input Sources
  • Port-Based Input Sources
  • Custom Input Sources
  • Cocoa Perform Selector Sources
  1. Timer Sources

不同的 Input Sources 和 Timer Sources 的集合就是 Run Loop Modes。

除了 Sources 外,还能注册 Run Loop Observer 来接收 Run Loop 行为的通知。

只有在次级线程上,才需要使用 Run Loop 对象来管理 Run Loop。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 1. 查看服务器最后重启时间 2. 根据文件名查找文件 3. 查看进程信息 4. 使用scp复制本地文件到远程服务...
    任无名F阅读 213评论 0 0
  • 原文地址:客户端生成七牛上传token 在使用七牛iOS SDK上传图片时需要用到上传的token,虽然七牛建议t...
    宫城_阅读 3,951评论 1 8
  • 涵宝和云宝都有喜欢的小伙伴,涵宝喜欢他们班的峻瑜同学,云宝喜欢他们班的晨轩同学。 有一天云宝自顾自得说:等我长大了...
    pan02阅读 225评论 0 0
  • 01. 大致算了下,来到这学校已经567天了,2016年7月4日来到学校,2016年的180天学校生活和2017年...
    路卡利欧Mega阅读 77评论 0 0