iOS底层原理 - 探寻RunLoop本质(一)

面试题引发的思考:

Q: RunLoop内部实现逻辑?

RunLoop运行逻辑

Q: RunLoop和线程的关系?

  • 每条线程都有唯一的一个与之对应的RunLoop对象;
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 主线程的RunLoop自动创建,子线程的RunLoop需要手动创建;
  • RunLoop在第一次获取线程时创建,在线程结束时销毁。

Q: timer与RunLoop的关系?

  • timer运行在RunLoop里,相当于RunLoop来控制timer何时执行。

Q: RunLoop 是怎么响应用户操作的, 具体流程是什么样的?

  • 先由Source1捕捉触摸事件,然后由Source0去处理触摸事件。

Q: RunLoop有哪几种状态?

RunLoop状态

Q: RunLoop的mode作用是什么?

  • 把不同模式下的Source0/Source1/Timer/Observer隔离开来,互不影响,会使程序在当前模式下流畅运行。

1. RunLoop简介

(1) RunLoop概念

RunLoop顾名思义,运行循环,在程序运行过程中循环做一些事情:

  • 如果没有RunLoop程序执行完毕就会立即退出;
  • 如果有RunLoop程序并不会马上退出,而是保持运行状态,等待处理程序的各种事件;
  • RunLoop可以保持程序的持续运行,在没有事件处理的时候使程序进入休眠模式,从而节省CPU资源,提高程序性能。
没有RunLoop

代码如上:没有RunLoop,执行完第14行代码后,会立即退出程序。

有RunLoop

代码如上:有RunLoop,程序并不会马上退出,而是保持运行状态。

其伪代码如下:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int result = 0;
        do {
            // 睡眠中等待消息
            int message = sleep_and_wait();
            // 处理消息
            result = process_message(message);
        } while (result == 0);
        return 0;
    }
}

RunLoop确实是do while通过判断result的值实现的。因此,我们可以把RunLoop看成一个死循环。

(2) RunLoop应用

RunLoop应用范畴:

  • 定时器(Timer)、PerformSelector
  • GCD Async Main Queue
  • 事件响应、手势识别、界面刷新
  • 网络请求
  • AutoreleasePool

2. RunLoop相关概念

(1) RunLoop对象

  • iOS中有2套API来访问和使用RunLoop
  • FoundationNSRunLoop
  • Core FoundationCFRunLoopRef
  • NSRunLoopCFRunLoopRef都代表着RunLoop对象
  • NSRunLoop是基于CFRunLoopRef的一层OC包装
  • CFRunLoopRef 源码是开源的
- (void)viewDidLoad {
    [super viewDidLoad];

    // Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

    // Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
}

(2) RunLoop与线程

  • 每条线程都有唯一的一个与之对应的RunLoop对象;
  • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
  • 主线程的RunLoop自动创建,子线程的Runloop需要手动创建;
  • RunLoop在第一次获取线程时创建,在线程结束时销毁。

以上结论都可以由以下相关源码总结得出:

CFRunLoopGetCurrent函数

CFRunLoopGetCurrent调用_CFRunLoopGet0

_CFRunLoopGet0函数

(3) RunLoop相关的类

Core Foundation中关于RunLoop的5个类:

  • CFRunLoopRef:获得当前RunLoop和主RunLoop
  • CFRunLoopModeRef:运行模式
  • CFRunLoopSourceRef:事件源,输入源
  • CFRunLoopTimerRef:定时器时间
  • CFRunLoopObserverRef:观察者

相关源码可知:

CFRunLoop结构体
CFRunLoopMode结构体

由以上代码可以得出RunLoop五个类直接的对应关系:

对应关系
  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
  • RunLoop启动时只能选择其中一个Mode,作为_currentMode
  • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入;
    不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响;
  • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。

  • Source0 触摸事件处理、performSelector:onThread:
  • Source1 基于Port的线程间通信、系统事件捕捉
  • Timers NSTimerperformSelector:withObject:afterDelay:
  • Observers 用于监听RunLoop的状态、UI刷新(BeforeWaiting)、Autorelease poolBeforeWaiting
1> CFRunLoopModeRef

常见的2种Mode

  • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):
    App的默认Mode,通常主线程是在这个Mode下运行;
  • UITrackingRunLoopMode
    界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
2> CFRunLoopObserverRef

RunLoop的Observer状态类型如下:

RunLoop状态

监听RunLoop状态:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 创建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }
            default:
                break;
        }
    });
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer);
}

ViewController放一个UITextView控件,运行程序之后,滑动控件,查看打印结果:

打印结果

由打印结果可知:

  • 控件开始滚动时,先退出kCFRunLoopDefaultMode,然后进入UITrackingRunLoopMode
  • 控件停止滚动时,先退出UITrackingRunLoopMode,然后进入kCFRunLoopDefaultMode

如此便产生了模式的切换。


3. RunLoop运行逻辑

(1) 官方文档解读

苹果官方文档可知RunLoop的运行逻辑如下:

RunLoop运行逻辑

由以上图例可知:
RunLoop在运行循环过程中,接收到Input sources或者Timer sources时会通过对应处理方式处理;没有事件消息传入的时候,就会使程序处于休眠状态。

(2) 源码解读

下面通过源码解读RunLoop底层实现。

RunLoop入口函数

首先通过运行程序断点可以知道RunLoop入口函数为CFRunLoopRunSpecific函数

相关源码进行简化,以便解读RunLoop底层实现:

RunLoop底层实现

RunLoop底层实现代码流程图如下:

RunLoop运行逻辑

(3) 特殊要点 GCD相关

一般GCD是有自己的处理逻辑,不依赖RunLoop实现;
GCD有一种特殊情况,需要交给RunLoop进行处理:

GCD特殊情况依赖RunLoop

(4) RunLoop休眠的实现原理

RunLoop底层实现可知:

RunLoop休眠

RunLoop是通过__CFRunLoopServiceMachPort函数来休眠的:

__CFRunLoopServiceMachPort函数

__CFRunLoopServiceMachPort函数主要方法为mach_msg函数,mach_msg是内核层面的API,这是程序真正达到休眠的方式。

那么RunLoop休眠的实现原理如下:

RunLoop休眠的实现原理

RunLoop在用户态调用mach_msg()时,会自动转到内核态,调用内核态的mach_msg(),达到真正休眠的目的:
没有消息就让线程休眠;有消息就唤醒线程,回到用户态处理消息。

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

推荐阅读更多精彩内容