03.Runloop源码分析

2,476 阅读4分钟

一. 什么是 Runloop

顾明思议, Runloop就是运行循环.按照正常逻辑一个程序在return 0 返回时候, 这个程序就会退出. 对于 app 来说, 我们希望 app 可以一直运行下去, 等待用户交互, 并做出相应, 那么它就需要能够不停的运行下去, 相当于在它的内部不停地做 do while 循环.

二. Runloop 的基本作用

  • 保持程序的持续运行
  • 处理APP中的各种事件(触摸、定时器、performSelector)
  • 节省cpu资源、提供程序的性能:该做事就做事,该休息就休 息

image.png 关于 Runloop 有两套 API 去使用它, 分别是 CoreFoundation框架 的 CFRunloopFoundation框架的 的 NSRunLoop

// CoreFoundation
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopRun();
// Foundation
[[NSRunLoop currentRunLoop] run];

我们在开发中大多使用NSRunLoop的 API, 由于 Foundation 并不是开源的, 因此如果我们想要学习 Runloop 相关知识, 弄清楚其内部实现原理, 就需要翻看CFRunLoop的源码来了解其背后的实现原理.

三. Runloop 的数据结构

CoreFoundation关于 RunLoop的 5 个类

CFRunLoopRef #Runloop对象 
CFRunLoopModeRef #代表RunLoop的运行模式 __CFRunLoopMode *类型结构体指针
CFRunLoopSourceRef # __CFRunLoopSource * 结构体指针
CFRunLoopTimerRef # __CFRunLoopTimer * 结构体指针
CFRunLoopObserverRef # __CFRunLoopObserver * 结构体指针

image.png

1. __CFRunLoop数据结构如下

CFRunLoopRef是一个 Runloop 对象

struct __CFRunLoop {
    pthread_t _pthread; # runloop 所在线程
    CFMutableSetRef _commonModes; # runloop 的模式, runloop 同时只能运行到一种模式下.
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; # runloop 当前运行模式
    CFMutableSetRef _modes; #runloop 所有模式
};
2. __CFRunLoopMode 数据结构如下
  • CFRunLoopModeRef代表RunLoop的运行模式
  • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
  • RunLoop启动时只能选择其中一个Mode,作为currentMode
  • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入,不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
  • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name; # 模式的名词 比如 kCFRunLoopDefaultMode, kCFRunLoopCommonModes,
    CFMutableSetRef _sources0; # source0
    CFMutableSetRef _sources1; # soures1
    CFMutableArrayRef _observers; # observers
    CFMutableArrayRef _timers; #timers
};
3. __CFRunLoopSource 数据结构如下
struct __CFRunLoopSource {
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};
4. __CFRunLoopTimer 数据结构如下
struct __CFRunLoopTimer {
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
};
5. __CFRunLoopObserver数据结构如下
struct __CFRunLoopObserver {
    CFRunLoopRef _runLoop;
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

/* Run Loop Observer Activities */

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

四.Runloop 执行流程

在控制台输入lldb指令 bt 可以看到 runloop 执行的堆栈信息

    frame #7: 0x00007fff2513fccd UIKitCore`__eventFetcherSourceCallback + 232

    frame #8: 0x00007fff20373833 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17

    frame #9: 0x00007fff2037372b CoreFoundation`__CFRunLoopDoSource0 + 180

    frame #10: 0x00007fff20372bf8 CoreFoundation`__CFRunLoopDoSources0 + 242

    frame #11: 0x00007fff2036d2f4 CoreFoundation`__CFRunLoopRun + 871

    frame #12: 0x00007fff2036ca90 CoreFoundation`CFRunLoopRunSpecific + 562
  • CFRunLoopRun函数
void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    //可以看出来 runloop 是一个 do while 循环 只要条件成立 会一直执行 while 循环里边的代码, 一直到返回结果是 kCFRunLoopRunStopped 或者 kCFRunLoopRunFinished 才会退出while循环
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
- CFRunLoopRunSpecific函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    if (currentMode->_observerMask & kCFRunLoopEntry ) { // 通知observes: 即将进入 runloop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    }
    // 通过返回值 反推这里 就是 runloop 要做的事情 最终返回一个 result 结果
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    if (currentMode->_observerMask & kCFRunLoopExit ) { // 通知 observers: 即将退出 runloop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    }
    // 根据 SInt32 返回值 result 向上反推导 result 赋值地方就是核心的代码
    return result;
}
- __CFRunLoopRun函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
        // 通知 observer 即将处理 timer
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 通知 observer 即将处理 sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 通知 observer 即将处理 block
        __CFRunLoopDoBlocks(rl, rlm);
        
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) { // 处理 source0 完毕后 如果返回 YES 将会处理 blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // 判断有无 source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 如果有 source1 跳转到 handle_msg
            goto handle_msg;
        }
        
        didDispatchPortLastTime = false;
        
        // 通知 observer 即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 开始休眠
        __CFRunLoopSetSleeping(rl);
        
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
        do {
            // 等待别的消息来唤醒当前线程 如果被唤醒 继续往下走
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

        } while (1);
        
        // 结束休眠
        __CFRunLoopUnsetSleeping(rl);
        // 通知 observer 结束休眠
       __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:;
        // 被 timer 唤醒
        if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            // 处理 timer
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
        else if (livePort == dispatchPort) { // GCD 相关
            // 处理 gcd 相关的事情
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { //  被source1唤醒
            // 处理 source1
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
        // 继续处理 block
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 设置函数返回值
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource; // 处理 source
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut; // 超时
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped; //停止
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped; //停止
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished; //结束
        }
        
    } while (0 == retVal);
    
    return retVal;
}

五. Runloop 应用

1. 线程保活
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.

    self.thread = [[MJThread alloc] initWithBlock:^{
     NSLog(@"%@",[NSThread currentThread]);
        // 创建上下文
        CFRunLoopSourceContext context = {0};
        // 创建source
        CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
        // 向Runloop中添加source
        CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
        // 销毁source
        CFRelease(source);
        // 第三个参数returnAfterSourceHandled: 设置为true时,代表执行完任务之后就会退出当前loop
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
        NSLog(@"---- runloop 停止成功");
    }];
    [self.thread start];
}
// 退出 runloop
- (void) stopRunloop {
   CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)dealloc {
    [self performSelector:@selector(stopRunloop) onThread:self.thread withObject:nil waitUntilDone:NO];
    NSLog(@"MJSecondViewController dealloc");
}
2. timer滑动失效的问题

image.png

3. 监听主线程卡顿
static void CallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    MJBlockMonitor *monitor = (__bridge MJBlockMonitor *)info;
    monitor->activity = activity;
    // 发送信号
    dispatch_semaphore_t semaphore = monitor->_semaphore;
    dispatch_semaphore_signal(semaphore);
}

- (void)registerObserver{
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    //NSIntegerMax : 优先级最小
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            NSIntegerMax,
                                                            &CallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
}

- (void)startMonitor{
    // 创建信号
    _semaphore = dispatch_semaphore_create(0);
    // 在子线程监控时长
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES)
        {
            // 超时时间是 1 秒,没有等到信号量,st 就不等于 0, RunLoop 所有的任务
            long st = dispatch_semaphore_wait(self->_semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            if (st != 0) {
                if (self->activity == kCFRunLoopBeforeSources || self->activity == kCFRunLoopAfterWaiting) {
                    if (++self->_timeoutCount < 2){
                        NSLog(@"timeoutCount==%lu",(unsigned long)self->_timeoutCount);
                        continue;
                    }
                    // 一秒左右的衡量尺度 很大可能性连续来 避免大规模打印!
                    NSLog(@"检测到超过两次连续卡顿");
                }
            }
            self->_timeoutCount = 0;
        }
    });
}