OC底层原理探索之runloop

1,490 阅读3分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

在苹果的官方文档对Thread的介绍中有提及到runloop,这个图也是官方提供的image.png 这些事务Items所对应的回调分别是:

  • block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
  • 调用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
  • 响应source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
  • 响应source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
  • GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
  • observer源:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

runloop的作用

  • 保持程序的持续运行
  • 处理App中的各种事件(触摸、定时器、performSelector)
  • 节省cpu资源、提高程序的性能,有事的时候就唤醒,没事的时候就休息。

runloop源码查看

runloop和普通的死循环是不一样的。它可以保持程序的持续运行,处理APP中的各种事件(触摸、定时器、performSelector),节省cpu资源、提供程序的性能:该做事就做事,该休息就休息。 runloop的底层是CFRunLoop,我们找到底层源码查看

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

1.0e10 = 10 ^10 ,这里是对runloop的超时设置,设置一个非常大的值,所以给人的错觉就是一直在运行。 CFRunLoopGetCurrent -> _CFRunLoopGet0 runloop和线程绑定,不论是不是主线程,线程和runloop都是key-value绑定的

// 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

在看下它的数据结构CFRunLoopRef

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;            /* locked for accessing mode list */
    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

_commonModes有多个,每一个modes下面可以有多个 _commonModeItem事务。

再来看下CFRunLoopModeRef的结构

struct __CFRunLoopMode {
   // ...
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
	// ...
};

刚好可以总结为下面这一张图 image.png 那么这些items(事务)是如何依赖于Mode在runloop中run起来的呢?

runloop如何处理事务

我们就Timer的流程来了解下,我们知道给runloop添加一个Timer事务OC是这么写的

{
    CFRunLoopTimerContext context = {
        0,
        ((__bridge void *)self),
        NULL,
        NULL,
        NULL
    };
    CFRunLoopRef rlp = CFRunLoopGetCurrent();
    /**
     参数一:用于分配对象的内存
     参数二:在什么是触发 (距离现在)
     参数三:每隔多少时间触发一次
     参数四:未来参数
     参数五:CFRunLoopObserver的优先级 当在Runloop同一运行阶段中有多个CFRunLoopObserver 正常情况下使用0
     参数六:回调,比如触发事件,我就会来到这里
     参数七:上下文记录信息
     */
    CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context);
    CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode);
}

打开CFRunLoop的源码

static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;
    CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
    CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
    if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
        CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
        CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
    } else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
        CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
    }
}

进入到CFRunLoopAddTimer我们看到首先匹配当前的模式modeName == kCFRunLoopCommonModes,找到rl->_commonModes的集合,非空的话就把当前的Timer事务和对应的回调函数__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__加进去, 那么这个回调函数什么时候调用呢?来到了上面提到的CFRunLoopRun这个函数,接着顺着方法调用顺序来到了CFRunLoopRunSpecific函数,这个函数在即将进入runLoop和即将退出runLoop有一步操作

image.png

__CFRunLoopRun中找到和Timer事务相关的代码

image.png

__CFRunLoopDoTimers中可以看到遍历所有模式下的timers然后执行__CFRunLoopDoTimer

image.png __CFRunLoopDoTimer方法中,找到对应的rlm也就是CFRunLoopModeRef,然后解锁runloop执行回调

image.png

所以整体的流程就是CFRunLoopRunSpecific-> __CFRunLoopRun -> __CFRunLoopDoTimers -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ 其他的事务大致流程都是一致的

runloop原理

在上面分析Timer的时候,提到了

image.png

/* Run Loop Observer Activities */
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
};

其实最主要的处理逻辑都在**__CFRunLoopRun**里面,在一个do-while循环里面

  • 通知Observers:即将处理timer事件
  • 通知Observers:即将处理Source事件
  • 处理blocks
  • 处理source0: 如果source0=true,处理blocks
  • 判断有无端口消息(source1)有的话处理,然后唤醒runloop, source1具备runloop的唤醒功能
  • 通知Observers:线程即将休眠

总结起来就是下面这张图,相信我们都不陌生。 image.png 那么runloop在实际的项目开发中有什么用处呢,我们下一篇“界面优化”见。