深入理解RunLoop

不知道大家有没有跟我一样的困惑?看过很多关于RunLoop的博客,本来觉得已经理解了runloop的运行原理,但是一写代码就发现运行结果和自己预想的不一致。我认为有两个原因:第一是没有去认真看runloop的源码,第二是iOS封装的NSRunLoop的三个接口,run:/runUntilDate:/runMode:beforeDate:隐藏了一些细节,迷惑了大家。

关于RunLoop的运行原理,有很多博客写的都很详细,我这里就不在重复了。我这篇文章从爬坑的角度来探究一下RunLoop. 大家都知道,NSRunLoop是对CFRunLoopRef的面向对象的封装。那么到底封装了什么呢?

  1. CFRunLoopRef源码探究
/* Reasons for CFRunLoopRunInMode() to Return */
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

上面是CFRunLoop最常用过的两个函数,这两个函数最后都是调用CFRunLoopRunSpecific(),CFRunLoopRunSpecific()函数返回的是runloop结束的原因,这个原因就是下面的枚举,

enum {
    kCFRunLoopRunFinished = 1,  //runloop执行完成,通常是source都被移除了
    kCFRunLoopRunStopped = 2,  //runloop被人为停止了
    kCFRunLoopRunTimedOut = 3, //runloop超时了
    kCFRunLoopRunHandledSource = 4 //runloop处理完source handle返回了
};

可以就看到一次runloop结束会又四种原因,kCFRunLoopRunFinished通常是runloop被释放,或者source都被移除了;kCFRunLoopRunStopped是调用了CFRunLoopStop(); kCFRunLoopRunTimedOut是超过了设置的seconds,kCFRunLoopRunHandledSource取决于returnAfterSourceHandled参数,如果returnAfterSourceHandled=true,那么runloop在被source触发并执行完了source 回调的函数后就会结束(如果是timer source且是repeats的,那么只有timer被invalidate才会算结束),这时结束原因就是kCFRunLoopRunHandledSource。

理解了这些,我们再来看NSRunLoop就简单多了!!

  1. [NSRunLoop run] 是不是就是CFRunLoopRun()?
    [NSRunLoop run] 这个函数大家都很熟悉了,意思就是让loop跑起来,说更详细点,就是在NSDefaultRunLoopMode下,超时时间是[NSDate distantFuture]的条件下运行runloop; 那么好像和CFRunLoopRun()是相同的操作, 我们来看看CFRunLoopRun()的源码
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

从源代码可以看到CFRunLoopRun()是在NSDefaultRunLoopMode下,超时时间是1.0e10, 而且returnAfterSourceHandled=false 的条件下运行runloop. 看似和[NSRunLoop run]是一样的。

但是在写代码过程中我们发现,用[NSRunLoop run]运行起来的runloop永远不会停止(除非我们移除了所有的timer source和input source),即使调用了CFRunLoopStop()也不会停止,而CFRunLoopRun()的runloop在调用了CFRunLoopStop()后会立马停止。根据observer看到runloop虽然不会退出,但是每次都会重新起一个runloop,说明returnAfterSourceHandled=true这说明[NSRunLoop run]并不是直接调用的CFRunLoopRun(),我们可以猜想,他的实现代码应该是这样的:

-(void) run(void) { /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, true);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunFinished != result);
}

也就是去掉了kCFRunLoopRunStopped != result这个条件。即使我们CFRunLoopStop()当前runloop,do-while循环不会退出,又会重新开始一个runloop。只有当result==kCFRunLoopRunFinished的时候才会退出,从源码中能看到,只有source为空了,或者runloop被dealloc的时候result才会等于kCFRunLoopRunFinished。那么可以得知,要想结束这种runloop只有移除所有的input source和timer source。这就和我们的实验结果是一致的了,那么[NSRunLoop run]就理解清楚了。同理[NSRunLoop runUntilDate]就是在run的基础上可以设置超时时间外,其他都是一样的。

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