一. 什么是 Runloop

顾明思议, Runloop便是运转循环.依照正常逻辑一个程序在return 0 回来时分, 这个程序就会退出. 关于 app 来说, 咱们希望 app 能够一向运转下去, 等待用户交互, 并做出相应, 那么它就需求能够不断的运转下去, 相当于在它的内部不断地做 do while 循环.

二. Runloop 的根本效果

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

03.Runloop源码分析
关于 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 * 结构体指针

03.Runloop源码分析

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滑动失效的问题

03.Runloop源码分析

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;
        }
    });
}