RunLoop最细详解

Runloop的实现机制

RunLoop 通过mach_msg()函数接收、发送消息。它的本质是调用函数 mach_msg_trap(),相当于是一 个系统调用,会触发内核状态切换。在用户态调用时会切换到内核态; 而内核态中内核实现了mach_msg()函数会完成实际的工作。

RunLoop基本作用

  • 保持程序的不断运行
  • 处理App中的各种事件,触摸事件,定时器事件
  • 节省CPU的资源,提高程序性能,该做事的时候做事,不做事的时候休眠

RunLoop详解

  • 一个 Runloop包含若干个Mode,每个Mode又包含若干个 Source0/ Source1/ Timer/ Observer,切换mode不会导致程序退出。
  • Runloop启动时只能选择其中一个Mode,作为 currentMode。
  • 如果需要切换Mode,只能退出当前Mode,再重新选择一个Mode进入,不同组的 Source0/ Source1/ Timer/ Observer能分隔开来,互不影响。
  • 如果Mode里没有任何 Source0/ Source/ Timer/ Observer,Runloop会立马退出。

Mode详解

Mode内部Source0/ Source1/ Timer/ Observer解析

  • Source0: 触摸事件处理
  • Source1:基于Port的线程间通信,系统事件捕捉,点击事件由Source1捕捉,分发给Source0处理
  • Timers:定时器NSTimer,延时操作performselector: withobject: afterdelay:
  • Observers:用于监听 Runloop的状态,UI刷新,自动释放池Autorelease pool

RunLoop运行逻辑

内部不断的循环处理block,source,Timer等,处理完成就休眠,被消息唤醒就继续处理。

Runloop 数据结构

  • CFRunLoop:Runloop对象 (由 pthread(线程对象,说明 和线程是一一对应的)、currentMode(当前所处的运行模式)、 modes(多个运行模式的集合)、 (模式名称字符串集合)、,Timer,Source 集合)构成)
  • CFRunLoopMode:运行模式(由 name、source0、source1、observers、timers 构成)
  • CFRunLoopSource:输入源/事件源 (分为 source0 和 source1 两种)
  • CFRunLoopTimer:定时源(基于时间的触发器,基本上说的就是 NStimer。在预设的时间点唤醒 执行回调。因为它是基于 RunLoop 的,因此它不是实时的(就是 NStimer 是不准确的。 因为只负责分发源的消息。如果 线程当前正在处理繁重的任务,就有可能导致 Timer 本次延时,或者少执行一次))
  • CFRunLoopObserver:观察者(对相关事件runloop的状态进行监听)

RunLoop与线程的关系

  • 每条线程都有唯一的一个RunLoop对象。
  • RunLoop保存在一个全局的 Dictionary里,线程作为key, RunLoop作为 value。
  • 线程刚创建时并没有 RunLoop对象, RunLoop会在第一次获取它时创建。
  • RunLoop会在线程结束时销毁。
  • 主线程的 RunLoop已经自动获取,子线程默认没有开启 Runloop。

NSTimer与RunLoop的关系

CADisplayLink 是一个和屏幕刷新率一致的定时器,在快速滑动 TableView 时,即使一帧的卡顿也会 让用户有所察觉。 FaceBook开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用 到了 RunLoop。

NSTimer其实就是 CFRunLoopTimerRef(基于时间的触发器) ,他们之间是tool-free bridged 的。一个 NSTimer 注册到RunLoop后, RunLoop会为其重复的时间点注册好事件。例如 10:00, 10:10, 10:20 这几个时间点。 RunLoop为了节省资源,并不会在非常准确的时间点回调这个 Timer。Timer 有个属性叫做Tolerance(宽容度),标示了当时间点到后,容许有多少最大误差。
如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延后执行。就比如等公交,如果 10:10 时我忙着玩手机错过了那个点的公交,那我只能等 10:20 这一趟了。

__block NSInteger count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%ld",(long)++count);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

RunLoop应用场景

  • 控制线程生命周期
  • 解决定时器在滑动时停止工作的问题
  • 监控应用卡顿
  • 性能优化等

RunLoop对象及创建

1.获取当前的RunLoop

NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 
CFRunLoopRef runloop = CFRunLoopGetCurrent();

2.主线程RunLoop对象

NSRunLoop *runloop = [NSRunLoop mainRunLoop];
CFRunLoopRef runloop = CFRunLoopGetMain();

怎么创建一个常驻线程?

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runloop run];

怎样保证子线程数据回来更新 UI 的时候不打断用户的滑动操作?

当子线程请求数据的同时滑动当前页面,如果数据请求成功要切回主线程更新 UI,那么就会影响当 前正在滑动的体验。此时我们将更新UI放到主线程中执行即可,等滑动停止由子线程切换到主线程更新UI。

[self performSelectorOnMainThread: @selector(readLoad) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];

观察者observer怎么监听Runloop状态

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    // 即将进入runloop
    kCFRunLoopEntry = (1UL << 0),
    // 即将处理timer
    kCFRunLoopBeforeTimers = (1UL << 1),
    // 即将处理source
    kCFRunLoopBeforeSources = (1UL << 2),
    // 即将进入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    // 休眠后唤醒
    kCFRunLoopAfterWaiting = (1UL << 6),
    // 退出runloop
    kCFRunLoopExit = (1UL << 7),
    // runloop所有活动
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

扩展知识

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

推荐阅读更多精彩内容