runloop 核心点

2018-04-08  本文已影响19人  George_Luofz
概述
1. 原理
1.1 runloop与线程的关系

要想理解这个还是得参考源码,创建runloop时,内部调用的其实是_CFRunLoopGet0这个方法

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
   // 1.如果__CFRunLoops这个全局字典不存在,就创建一个
    if (!__CFRunLoops) {
    // 1.1 创建全局字典
    CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
    // 1.2创建主线程的runloop,保存在全局字典中
    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);
    }
    // 2. 创建一个runloop,先从全局字典中找,没有就创建并加入字典中
    CFRunLoopRef newLoop = NULL;
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
    newLoop = __CFRunLoopCreate(t);
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        loop = newLoop;
    }
    __CFUnlock(&loopsLock);
    // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
    if (newLoop) { CFRelease(newLoop); }
    // 3.检查如果是当前线程(就是说在当前线程创建了其runloop),则做额外处理
    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;
}

理解:

  1. 线程与runloop是一对一的,主线程的runloop默认会创建,其他线程的runloop默认不创建;同一个线程的runloop是唯一的,是说已经创建了就不会再创建
  2. 创建时机:当线程第一次获取其runloop时,才会创建对应的runloop(我原来的理解有误)
  3. 销毁时机:可以肯定线程结束,就没有runloop了;具体销毁时机,暂时不明
1.2 runloop的mode作用是什么

两方面理解:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
   // 1.如果modeName不合法,直接return
    if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
            _CFRunLoopError_RunCalledWithInvalidMode();
        });
        return kCFRunLoopRunFinished;
    }
   // 2.如果runloop正在释放,return finish
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
   // 3.找到了对应的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 3.1 合理性判断
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
   // 3.2 初始配置
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
        // 3.3 runloop的状态是entry(就是进入),然后运行该runloop,核心逻辑就是调用__CFRunLoopRun()方法
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_START, rl, currentMode, seconds, previousMode);
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        cf_trace(KDEBUG_EVENT_CFRL_RUN | DBG_FUNC_END, rl, currentMode, seconds, previousMode);
       // 3.4 如果状态是exit,就销毁其中的perData等数据(可以理解是退出时释放其持有的数据)
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

理解:

  1. runloop每次都是在对应的mode下运行,至于在该mode下如何运行,则要继续参考函数static int32_t __CFRunLoopRun()的实现
1.2.1 runloop与mode的关系
1.3 runloop的实现原理

__CFRunLoopRun()函数太长,具体解释参考Run Loop记录与源码注释,用伪代码(参考:深入理解Runloop)表示:

    // 0. 先判断mode里有没有source/timer/observer,如果没有,就直接返回。
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
    // 1. 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
   // 2. 进入loop
 do{
      // 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
      __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
      // 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
      __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
     // 4. 执行被加入的block
     __CFRunLoopDoBlocks(runloop, currentMode);
    // 5. 如果有source1,直接处理source1
   __CFRunLoopServiceMachPort(dispatchPort, &msg)
    // 6. 通知即将进入休眠,其实如果没有timer、mainQueue的block等任务,本次循环就算执行完了
   __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
   // 7. 等待mach_port消息
   __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort)        {
                mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
    }
    // 8. 通知线程被唤醒了
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
     // 9.1 timer时间到,执行timer
     if (msg_is_timer) {
                __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
            } 
    // 9.2.执行mainQueue中的block
    else if (msg_is_dispatch) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } 
    // 9.3 执行source1的事件
    else {
                CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                if (sourceHandledThisLoop) {
                    mach_msg(reply, MACH_SEND_MSG, reply);
                }
            }
    // 10.执行加入到其中的block
    __CFRunLoopDoBlocks(runloop, currentMode);
   // 11.都执行完毕,修改状态为stopped/finished/超时等
   if (sourceHandledThisLoop && stopAfterHandle) {
                /// 进入loop时参数说处理完事件就返回。
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout) {
                /// 超出传入参数标记的超时时间了
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(runloop)) { 
                /// 被外部调用者强制停止了
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                /// source/timer/observer一个都没有了
                retVal = kCFRunLoopRunFinished;
            }
 }while(retVal == 0);

从上边runloop的内部逻辑可以看出,runloop其实就是一个死循环,让线程一直运行或者处于休眠状态;直到超时或者停止才会结束循环

1.4 runloop的数据结构
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * 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; //线程,runloop整个是基于pthread_t的
    uint32_t _winthread;
    CFMutableSetRef _commonModes; //加入该runloop的commonModes
    CFMutableSetRef _commonModeItems; // 所有commonModes里的items
    CFRunLoopModeRef _currentMode; //当前的mode
    CFMutableSetRef _modes; //加入该runloop的所有modes
    struct _block_item *_blocks_head; //存放 CFRunLoopPerformBlock 函数添加的 block 的双向链表的头指针
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

参考:Run Loop记录与源码注释

2. 应用

2.1. 解决NSTimer不执行

2.2. tableView优化

2.3. 线程保活

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}
AFN该线程是一个单例,所以不需要考虑停止runloop,也就使用[[NSRunLoop currentRunLoop] run]这个就可以满足要求了

2.4. 代码监测卡顿
这个类似于tableView的优化,思路就是在子线程监听runloop的休眠和唤醒周期,计算两个时间间隔,如果间隔连续多次(比如5次)超过50ms,就认为出现了卡顿,然后获取当前runloop线程的堆栈信息,上报给监控平台
代码参考:iOS实时卡顿监控

上一篇 下一篇

猜你喜欢

热点阅读