RunLoop 之IOS

一,什么是RunLoop?

要说RunLoop就必须说道线程.一般情况下,一个线程被开启,它去执行自己的任务,当任务完成后,线程就会退出.如果我们需要某个线程在没有任务的时候,不退出,我们就引入了RunLoop.所以RunLoop得存在,就是为了让一个线程在无任务的时候,不退出.

一般情况下,当一个线程在执行完线程中的任务后,线程会自动退出.如果我们需要你个线程处理一个周期性任务,不想让他退出,那么我们就需要一个机制,让线程在执行完任务,或者暂时无任务的状态下不退出.在IOS和MacOS中,这个东西就是RunLoop.RunLoop可以让线程在有任务的时候执行,没有任务的时候处于休眠状态,从而节省资源.

RunLoop其实就是一个对象,这个对象管理了其所需要处理的事件和消息.

二,RunLoop是和线程的关系.

RunLoop,翻译过来就是跑圈的意思.苹果向我们提供了两个获取RunLoop的函数,MainRunLoop和currentRunLoop.

当然一个是主线程对应的RunLoop,一个是当前线程的RunLoop.我们可以在这里找到CFRunLoopRef的源码.其中有一段能看出RunLoop的到底是干什么的:


/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef

static CFMutableDictionaryRef loopsDic;

/// 访问 loopsDic 时的锁

static CFSpinLock_t loopsLock;

/// 获取一个 pthread 对应的 RunLoop。

CFRunLoopRef _CFRunLoopGet(pthread_t thread) {

OSSpinLockLock(&loopsLock);

if (!loopsDic) {

// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。

loopsDic = CFDictionaryCreateMutable();

CFRunLoopRef mainLoop = _CFRunLoopCreate();

CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);

}

/// 直接从 Dictionary 里获取。

CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));

if (!loop) {

/// 取不到时,创建一个

loop = _CFRunLoopCreate();

CFDictionarySetValue(loopsDic, thread, loop);

/// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。

_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);

}

OSSpinLockUnLock(&loopsLock);

return loop;

}

CFRunLoopRef CFRunLoopGetMain() {

return _CFRunLoopGet(pthread_main_thread_np());

}

CFRunLoopRef CFRunLoopGetCurrent() {

return _CFRunLoopGet(pthread_self());

}

从上面的代码我们可以看出RunLoop和线程被放在了一个全局的字典中,并且以键值对的形式存放.所以线程和RunLoop肯定是一一对应的.(RunLoop可以嵌套,但是主RunLoop只有一个).当一个线程被创建出来后,RunLoop是不存在的,当你第一次调用CurrentRunLoop时,才会创建对应的RunLoop.主线程的RunLoop在最开始就生成了.RunLoop的销毁发生在线程结束时.除了主线程的RunLoop外,子线程的RunLoop你只能在子线程的内部通过currentRunLoop来获取.

三,RunLoop的组成

RunLoop必定对应一个线程.但是要想开启一个RunLoop.必须给RunLoop一个Mode.其中Mode中又包含了Source,Timer,Observer.Source,Timer,Observer都是集合.一个Mode必须含有Source,Timer,Observer.的其中之一.

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

• Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

• Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

kCFRunLoopEntry        = (1UL << 0), // 即将进入Loop

kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer

kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source

kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒

kCFRunLoopExit          = (1UL << 7), // 即将退出Loop

};

上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

四,RunLoop和NSTimer,performSelector以及CADisplayLink的关系.

(1),NSTimer

NSTimer的执行必须依赖RunLoop.因为NSTimer对象必须被放在一个RunLoop中,定时器才会运行.其实NSTimer就是CFRunLoopTimerRef.在定时器被加入到RunLoop中后,RunLoop会在定时器指定的时间点注册好事件.例如,5s,10s,15s这几个时间点.RunLoop为了节省资源,可能并不会在准确的时间点调用之前注册好的事件.NSTimer类中有一个属性tolerance.这个是定时器的允许误差.如果当期现场堵塞,一直到允许误差结束后,还没有调用定时器事件,那么这次事件就会被跳过.所以往RunLoop中添加定时器,其实就是往RunLoop中添加了一个CFRunLoopTimerRef对象.这里需要注意的是[timer fire]是source0事件,不是CFRunLoopTimerRef.你可以打断点后,在Xcode的栈中看到

(2)performSelector

当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

(3)CADisplayLink

CADisplayLink 是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和 NSTimer 并不一样,其内部实际是操作了一个 Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去(和 NSTimer 相似),造成界面卡顿的感觉。在快速滑动TableView时,即使一帧的卡顿也会让用户有所察觉。Facebook 开源的 AsyncDisplayLink 就是为了解决界面卡顿的问题,其内部也用到了 RunLoop.

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

推荐阅读更多精彩内容