深入理解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的基础上可以设置超时时间外,其他都是一样的。

你可能感兴趣的:(深入理解RunLoop)