重拾RunLoop之源码分析1

栏目: IOS · 发布时间: 5年前

内容简介:原文链接虽然自己很早前就看过RunLoop的源码,当时看得时候,有点地方还是比较生涩的。所有抽了个时间,重新整理了一下之前RunLoop的笔记。CoreFoundation源代码关于RunLoop的源码主要集中在苹果并不允许我们直接创建RunLoop,RunLoop的创建在第一次获取的时候,使用

原文链接 重拾RunLoop之源码分析1

虽然自己很早前就看过RunLoop的源码,当时看得时候,有点地方还是比较生涩的。所有抽了个时间,重新整理了一下之前RunLoop的笔记。CoreFoundation源代码关于RunLoop的源码主要集中在 CFRunLoop.c 文件中。

RunLoop的获取

苹果并不允许我们直接创建RunLoop,RunLoop的创建在第一次获取的时候,使用 [NSRunLoop mainRunLoop]CFRunLoopGetMain() 可以获取主线程的RunLoop;通过 [NSRunLoop currentRunLoop]CFRunLoopGetCurrent() 获取当前线程的RunLoop。

CFRunLoopGetMain()CFRunLoopGetCurrent() 的源码如下:

// 主线程的RunLoop
CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();//判断是否需要fork 进程
    static CFRunLoopRef __main = NULL; // no retain needed
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

// 当前线程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    //先从TSD中查找有没有相关的runloop信息,有则返回。
    //我们可以理解为runloop不光存在与全局字典中,也存在中TSD中。
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
复制代码

CHECK_FOR_FORK(); 用来判断是否需要fork进程,这里我们可以暂时不管。

在获取主线程RunLoop的时候,它使用了 static CFRunLoopRef __main 进行保存,当第二次调用 CFRunLoopGetMain()__main 是有值的,就不会再重新创建,否则就使用 _CFRunLoopGet0 进行创建,传入的是 pthread_main_thread_np() 即主线程。

在获取当前线程的RunLoop的时候,首页会通过 _CFGetTSD 获取RunLoop,如果没有再通过 _CFRunLoopGet0 ,传入的是当前的线程。

Thread-specific data

Thread-specific data 是线程私有数据就是上面的 TSD ,顾名思义就是存一些特定的数据的,RunLoop会保存在线程的私有数据里。

// __CFTSDTable
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;

// _CFGetTSD
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
    __CFTSDTable *table = __CFTSDGetTable();
    if (!table) { return NULL; }
    uintptr_t *slots = (uintptr_t *)(table->data);
    return (void *)slots[slot];
}

// _CFSetTSD
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) {
    __CFTSDTable *table = __CFTSDGetTable();
    if (!table) { return NULL; }

    void *oldVal = (void *)table->data[slot];
    table->data[slot] = (uintptr_t)newVal;
    table->destructors[slot] = destructor;
    
    return oldVal;
}

复制代码

__CFTSDTabledata 数组用来保存私有数据, destructors 数组用来保存释放函数(后面也会提到)。 destructorCount 记录 destructors 数组元素的个数。

_CFGetTSD 的作用就是获取 __CFTSDTabledata 数据,并返回 slot 的值。

_CFSetTSD 的作用就是给 __CFTSDTable 里设置 data[slot]destructors[slot] 位置的值。

_CFRunLoopGet0

// t==0 is a synonym for "main thread" that always works
// t==0是主线程的代名词
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    // 当前线程为0,则取主线程
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    // __CFRunLoops是一个全局的静态字典。
    // 如果该字典为空,就进行以下两步操作
    // 1.创建一个临时字典;
    // 2.创建主线程的RunLoop,并将它存到临时字典里
    // 3.OSAtomicCompareAndSwapPtrBarrier用来将这个临时字典复制到全局字典里;
    // 并且使用了锁机制确保安全。
    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的获取,获取不到就使用__CFRunLoopCreate创建一个RunLoop,并保存在全局字典里
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    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);
    }
    //t为当前线程的话,将loop保存在线程私有数据中
    if (pthread_equal(t, pthread_self())) {
        //
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        // __CFFinalizeRunLoop是RunLoop的析构函数,
        // PTHREAD_DESTRUCTOR_ITERATIONS 表示是线程退出时销毁线程私有数据的最大次数
        // 这也是RunLoop的释放时机--线程退出的时候
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop;
}
复制代码

通过源代码我们可以知道:

  1. RunLoop和线程之间是一一对应的,它们之间的关系保存在一个全局字典以及线程私有数据中。
  2. 在线程创建的时候,是没有对应的RunLoop,它的创建是在第一次获取的时候,它的销毁则发生在线程销毁的时候。

之前在看源码的时候有两个地方不是很理解。第一个就是为什么上面的loop要再取一次,在《程序员的自我修养》第29页中得到启发。里面关于单例有这样一段代码:

volatile T* pInst = 0;
T* GetInstance()
{
    if(pInst == NULL)
    {
        lock();
        if(pInst == NULL)
            pInst = new T;
        unlock();
    }
    return pInst;
}
复制代码

书上只说明双重if在这里可以让lock的调用开销降到最低。为什么有这个效果,这里做一下说明。

在不考虑CPU乱序的情况下,假设有两个线程A、B同时访问 GetInstance() ,A和B同时执行第一个判断语句,结果一样,都进入了代码块。 lock() 的设定就是只允许一个线程进入,假设A先进入,B在等待。A进入后首先判断 pInstNULL ,那么new一个对象,然后解锁返回对象。唤醒B,这是B进入发现第二个判断通过不了(因为 pInst 已经有值了),这样的话B就直接解锁返回对象。假设只有最外层的判断的话,那么B也会创建一个对象。

我想这里应该也是类似的作用吧。

第二个就是RunLoop销毁的时机,这个会在RunLoop的释放说明。

RunLoop的创建

使用 __CFRunLoopCreate 返回一个 CFRunLoopRef 的实例,这个函数大致分为两步:

  1. 使用 _CFRuntimeCreateInstance 创建一个 CFRunLoopRef 实例,其实现为 CFRuntime.c 文件;
  2. CFRunLoopRef 进行初始化配置,包括调用 __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);

__CFRunLoopFindMode 里讲到了RunLoop的定时器,用宏进行了判断

#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
复制代码

MACOSX 下,同时还会有使用 GCD Timer 来做定时器,而 MK_TIMER 是两个平台下都有的。

RunLoop的释放

关于RunLoop的释放是发生在线程销毁的时候。为什么这么说呢? __CFTSDGetTable() 中有一个 __CFTSDFinalize 的析构函数,其实现如下:

static void __CFTSDFinalize(void *arg) {
    __CFTSDSetSpecific(arg);

    if (!arg || arg == CF_TSD_BAD_PTR) {
        return;
    }
    
    __CFTSDTable *table = (__CFTSDTable *)arg;
    table->destructorCount++;
        
    for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
        if (table->data[i] && table->destructors[i]) {
            uintptr_t old = table->data[i];
            table->data[i] = (uintptr_t)NULL;
            table->destructors[i]((void *)(old));
        }
    }
    
    if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) {    // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
        free(table);
        
        __CFTSDSetSpecific(CF_TSD_BAD_PTR);
        return;
    }
}
复制代码

我们可以看到, table 会循环遍历 datadestructors 的数据,并且把 old 变量作为 destructors 里函数的参数。所以当线程退出的时候,会调用到RunLoop的析构函数 __CFFinalizeRunLoop 释放RunLoop。

RunLoop运行

RunLoop通过 CFRunLoopRunCFRunLoopRunInMode 这两个函数运行。

CFRunLoopRun

void CFRunLoopRun(void) {
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
复制代码

函数默认在 kCFRunLoopDefaultMode 下运行RunLoop,并且一直运行在一个do-while的循环里。 另外函数不会主动调用 CFRunLoopStop 函数( kCFRunLoopRunStopped )或者将所有事件源移除( kCFRunLoopRunFinished )。 从这里我们也可以了解,如果RunLoop的 _currentMode 值变化,只能退出,然后重新指定一个Mode进入。

CFRunLoopRunInMode

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
复制代码

无论是 CFRunLoopRun 还是 CFRunLoopRunInMode 都是调用了 CFRunLoopRunSpecific

CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 首先根据modeName找到对应Mode,如果没有则创建一个新的Mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 如果mode为空或者mode中没有相关的source/timer/observer,则不进入循环
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);

    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    // 1.通知observer即将进入RunLoop
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop真正执行的方法:第2~9步
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 10.通知observer已退出RunLoop
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

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

__CFRunLoopRun

__CFRunLoopRun 可以说是RunLoop运行的核心方法。由于代码过长,这里对代码进行了删减,简化后的代码如下:

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    // 获取CPU运行时间,用于控制超时
    uint64_t startTSR = mach_absolute_time();
    
    // 如果RunLoop或mode是stop状态,则直接return kCFRunLoopRunStopped,不进入循环
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    
    // 初始化mach端口为0
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    // 如果是主线程 && 传入的RunLoop是主线程的RunLoop && 传入的mode是commonMode,则给mach端口赋值为主线程收发消息的端口
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    // USE_DISPATCH_SOURCE_FOR_TIMERS为1表示在MACOSX下,iOS不会调用这段代码
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    ...
#endif
    
    // GCD定时器,用于实现runloop超时机制
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    }
    // seconds为超时时间,超时时执行__CFRunLoopTimeout函数
    else if (seconds <= TIMER_INTERVAL_LIMIT) {
        ...
    }
    // 永不超时
    else {
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
    // 标志位默认为true
    Boolean didDispatchPortLastTime = true;
    // 记录最后RunLoop的状态
    int32_t retVal = 0;
    do {
        ...
        
        // 需要监听的端口
        __CFPortSet waitSet = rlm->_portSet;
        
        // 设置RunLoop为可以被唤醒状态
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        
        // 2.通知observer,即将处理Timer事件
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3.通知observer,即将触发Source0回调
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        
        // 执行加入当前runloop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 4.处理source0事件,有事件处理返回true,没有事件返回false
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // 如果没有Sources0事件处理并且没有超时,poll为false
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg = (mach_msg_header_t *)msg_buffer;
            // 5.接收dispatchPort端口的消息,(接收source1事件)直接跳到第9步
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }
        
        didDispatchPortLastTime = false;
        
        // 6.通知observer,即将进入休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 设置RunLoop为休眠状态
        __CFRunLoopSetSleeping(rl);
        
        ...
        
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        ...
#else
        if (kCFUseCollectableAllocator) {
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        // 7.接收waitSet端口的消息,这些消息可能是
        // 一个基于 port 的Source 的事件。
        // 一个 Timer 到时间了
        // RunLoop 自身的超时时间到了
        // 被其他什么调用者手动唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
        ...
        
        // user callouts now OK again
        //取消runloop的休眠状态
        __CFRunLoopUnsetSleeping(rl);
        // 8.通知observer,线程刚被唤醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        //9.处理收到的消息,之后重新进入第2步
    handle_msg:;
        ...
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // 进入第2步重新循环
            // do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
            // Always reset the wake up port, or risk spinning forever
            ResetEvent(rl->_wakeUpPort);
#endif
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        ...
        // 这里是GCD相关的定时器,可以忽略
#endif
#if USE_MK_TIMER_TOO
        // 如果是定时器事件
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 9.1处理timer事件
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        ...
        
        CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        // 有source1事件
        if (rls) {
            mach_msg_header_t *reply = NULL;
            // 9.2 处理source1事件
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }
        
        ...
            
        if (sourceHandledThisLoop && stopAfterHandle) {
            // 处理完事件就返回
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            // 超时
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            // RunLoop终止
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // mode终止
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        ...

    } while (0 == retVal);
    
    ...
    
    return retVal;
}
复制代码

这里盗一张RunLoop运行流程的图:

重拾RunLoop之源码分析1

参考

程序员的自我修养深入理解RunLoop 苹果文档--RunLoop


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

旷世之战――IBM深蓝夺冠之路

旷世之战――IBM深蓝夺冠之路

纽伯 / 邵谦谦 / 清华大学出版社 / 2004-5 / 35.0

本书作者Monty Neworn是国际计算机象棋协公的主席,作者是用生动活泼的笔触描写了深蓝与卡斯帕罗夫之战这一引起全世界关注的历史事件的前前后后。由于作者的特殊身份和多年来对计算机象棋的关心,使他掌握了许多局外人不能得到的资料,记叙了很多鲜为人知的故事。全书行文流畅、文笔优美,对于棋局的描述更是跌宕起伏、险象环生,让读者好像又一次亲身经历了那场流动人心的战争。 本书作为一本科普读物......一起来看看 《旷世之战――IBM深蓝夺冠之路》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具