RunLoop的原理探索-基本认识

/ 0

概述

RunLoop 顾名思义,就是运行循环,在程序运行过程中循环做一些事情。

RunLoop的基本作用

应用范畴

如果没有 RunLoop,那么程序执行完就退出了,比如下面代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}

如果有了 RunLoop,程序并不会马上退出,而是保持运行状态。比如类似下面伪代码:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int retVal = 0;
        do {
            // 睡眠中等待消息
            int message = sleep_and_wait();
            // 处理消息
            retVal = process_message(message);
        } while (0 == retVal);
    }
    return 0;
}

RunLoop对象

iOS 中有 2 套 API 来访问和使用 RunLoop

NSRunLoop 是基于 CFRunLoopRef 的一层 OC 包装,并且 CFRunLoopRef 是开源的。源码地址:https://opensource.apple.com/tarballs/CF/

获取 RunLoop 对象:

// 获取当前线程RunLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
CFRunLoopRef currentRunLoopRef = CFRunLoopGetCurrent();

// 获取主线程RunLoop
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
CFRunLoopRef mainRunLoopRef = CFRunLoopGetMain();

通过 lldb 指令,也可以验证 NSRunLoop 是对 CFRunLoopRef 的包装:

(lldb) p/x currentRunLoop->_rl
(__NSCFType *) $1 = 0x0000600003b7c500
(lldb) p/x currentRunLoopRef
(CFRunLoopRef) $2 = 0x0000600003b7c500

RunLoop与线程

每条线程都有唯一的一个与之对应的 RunLoop 对象。

RunLoop 保存在一个全局的 Dictionary 里,线程作为 keyRunLoop 对象作为 value

创建

子线程 刚创建时并没有 RunLoop 对象,RunLoop 会在第一次获取它使创建。

主线程RunLoop 是在 main 函数中调用的 UIApplicationMain 函数中创建的。

销毁

RunLoop 会在线程结束时销毁。

RunLoop创建和获取的源码

// 获取RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    // 根据线程获取RunLoop
    return _CFRunLoopGet0(pthread_self());
}

static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

// 根据线程获取RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // 如果字典为空,则创建存储RunLoop的全局字典
    if (!__CFRunLoops) {
        __CFUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // 从字典中根据线程获取RunLoop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    // 如果从字典中根据线程获取RunLoop失败,则根据线程创建RunLoop并存储到字典中
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}

RunLoop相关的类

Core Foundation 中关于 RunLoop5 个类:

CFRunLoopModeRef 代表 RunLoop 的运行模式。

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source0/Source1/Timer/Observer

RunLoop 启动时只能选择其中一个 Mode,作为 currentMode

如果需要切换 Mode,只能退出当前 Loop,再重新选择一个 Mode 进入。不同组的 Source0/Source1/Timer/Observer 能分隔开来,互不影响。

如果 Mode 里没有任何 Source0/Source1/Timer/ObserverRunLoop 会立马退出。

CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; // 当前的模式
    CFMutableSetRef _modes; // 所有的模式,CFRunLoopModeRef
    //...
};

CFRunLoopModeRef

常见的 Mode

kCFRunLoopDefaultModeNSDefaultRunLoopMode):App 的默认 Mode,通常主线程是在这个 Mode 下运行。

UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。

kCFRunLoopCommonModesNSRunLoopCommonModes):它并不是一种模式,而是一种标记。表示可以在 kCFRunLoopDefaultModeUITrackingRunLoopMode 模式中运行。

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0; // CFRunLoopSourceRef
    CFMutableSetRef _sources1; // CFRunLoopSourceRef
    CFMutableArrayRef _observers; // CFRunLoopObserverRef
    CFMutableArrayRef _timers; // CFRunLoopTimerRef
};

CFRunLoopSourceRef

source事件源 不是 事件

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;
    CFMutableBagRef _runLoops;
    union {
        CFRunLoopSourceContext version0;
        CFRunLoopSourceContext1 version1;
    } _context;
};

CFRunLoopObserverRef

/* 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
};

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities; // 状态
    CFIndex _order;
    CFRunLoopObserverCallBack _callout;
    CFRunLoopObserverContext _context;
};

监听RunLoo状态

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
        default:
            NSLog(@"default");
            break;
    }
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
CFRelease(observer);

CFRunLoopTimerRef

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;
    CFTimeInterval _tolerance;
    uint64_t _fireTSR;
    CFIndex _order;
    CFRunLoopTimerCallBack _callout;
    CFRunLoopTimerContext _context;
};

RunLoop图示

RunLoop的运行逻辑

Source0

Source1

Timers

Observers

执行流程

01、通知 Observers:进入 Loop
02、通知 Observers:即将处理 Timers
03、通知 Observers:即将处理 Sources
04、处理 Blocks
05、处理 Source0(可能会再次处理 Blocks)
06、如果存在 Source1,就跳转到第8步
07、通知 Observers:开始休眠(等待消息唤醒)
08、通知 Observers:结束休眠(被某个消息唤醒)
    01> 处理 Timer
    02> 处理 GCD Async To Main Queue
    03> 处理 Source1
09、处理 Blocks
10、根据前面的执行结果,决定如何操作
    01> 回到第02步
    02> 退出 Loop
11、通知 Observers:退出 Loop

执行流程图示

RunLoop休眠原理

下面代码也是 阻塞线程,但是会一直持续占用 CPU 资源:

while (1) ;

RunLoop阻塞线程 能实现真正的睡眠,不会持续占用 CPU 资源。想要实现这种效果,只有 内核层面 的 API 才能做到。

Core Foundation 框架的 CFRunLoop.c 文件的 __CFRunLoopServiceMachPort 函数中,通过 mach_msg() 函数向内核层发送线程休眠的消息,等待其他消息来唤醒休眠的线程。