从源码看Runloop-内部逻辑

从源码看Runloop-内部逻辑

RunLoop的入口

NSRunLoop有三个启动runloop的方法,CFRunLoop有两个启动方法。代码如下

// NSRunLoop 3个入口方法
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;

由于NSRunLoop看不到其实现,我们以CF代码分析。

// CFRunLoop 2个入口方法
// 方法1实现
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
                                      kCFRunLoopDefaultMode, 
                                      1.0e10, 
                                      false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

// 方法2实现
SInt32 CFRunLoopRunInMode(CFStringRef modeName,
                          CFTimeInterval seconds, 
                          Boolean returnAfterSourceHandled) {
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), 
                                modeName, 
                                seconds, 
                                returnAfterSourceHandled);
}

可以看到CFRunLoop的两个启动方法其真正干活的是CFRunLoopRunSpecific方法。而该方法实现如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, 
                            CFStringRef modeName, 
                            CFTimeInterval seconds,
                            Boolean returnAfterSourceHandled) {
    // // 省略一堆准备工作...
    if (currentMode->_observerMask & kCFRunLoopEntry ){
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    }
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) {
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    }
    // 省略一堆接收工作...
    return result;
}

在这个方法中才到了CFRunLoop的真正入口:__CFRunLoopRun。注意参数returnAfterSourceHandled,它为YES时,表示Runloop每处理完一个事件后都会返回。
runloop中一共有7个通知事件,如下代码所示。kCFRunLoopAllActivities是所有事件的集合,不是一个真正的事件。还剩6个事件,其中进入和退出是CFRunLoopRunSpecific方法中发送的,如上代码所示每次进入runloop前和结束runloop后都会会通知当前mode对象中的observer。其他4个是在__CFRunLoopRun方法中发送的。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

如上面提到的returnAfterSourceHandled,在NSRunLoop的三个入口方法中,经验证得到,该参数是YES。因为采用NSRunLoop的方法每处理完一个事件runloop都会返回。

runloop内部处理逻辑

runloop内部逻辑如下图所示:


从源码看Runloop-内部逻辑_第1张图片
RunLoop-内部逻辑.png

先连发两个通知,告诉observer要处理timer和source0了。
这里不清楚的是为什么要把source0和source1分开?难道是source0不能唤醒runloop?
经过试验得到performSelector是在source0中执行的。通常情况下我们唤醒runloop的方式就只要timer和performSeletor和GCD。创建source0的情况我们能用到的就是socket。
runloop会在一次循环中把积压的所有事件都做完。

AutoReleasePool

App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

你可能感兴趣的:(从源码看Runloop-内部逻辑)