壹:RunLoop和线程的联系

这儿的RunLoop的源码参阅了两个地址的源码,一个是GitHub上的github.com/apple/swift…中的runloop源码,一个是opensource.apple.com/source/CF/中的runloop源码,主要是以后者为准,因为后者猜想更挨近为iOS上的版别,前者应该为其他平台上的实现版别。

RunLoop

RunLoop是和线程相关的基础架构的一部分。一般来说,一个线程执行完使命之后就会退出,但是这在Cocoa Touch中是行不通的,比方main thread,需要不停地去处理点击事情等等。而Runloop的意图就在于此,它让线程在有使命时工作,在无使命时歇息。这种模型一般称为**Event Loop,**即事情循环,一般逻辑如下:

void CFRunLoopRun(void) {
    int result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
                                      KCFRunLoopRunLoopDefaultMode, ...);
    } while (KCFRunLoopRunStopped != result && KCFRunLoopRunFinished != result);
}

Event Loop是在程序中等候和派发事情或许音讯的一种规划形式。当Event Loop构成一个程序的中心控制流时,一般情况下,称之为主循环。Event Loop在很多程序中都有实际的运用,比方Windows程序中的音讯循环,MacOS中的事情循环等等。

而在iOS中RunLoop是一个对象,这个对象办理了需要处理的事情,而且供给了一个进口函数来处理。

let runloop = RunLoop.current()
runloop.add(Port.init(), forMode: .common)
runloop.run()

线程在执行了这个函数之后,就会一向处于“接受音讯 -> 等候 -> 处理”的循环中,直到循环结束,函数返回。

在iOS中有两个类来办理RunLoop,一个是RunLoop类,一个是CFRunLoopRef。CFRunLoopRef是CoreFoundation框架封装了,供给了面向对象的API,一切这些API都是线程安全的。RunLoop是根据CFRunLoop的封装,供给了面向对象的API,但是这些API并不是线程安全的。

同时要注意RunLoop实例调用的**run()**办法并不是直接调用的该办法,而是封装的以下办法:

extension RunLoop {
	public func run() {
        while run(mode: .default, before: Date.distantFuture) { }
    }
    public func run(until limitDate: Date) {
        while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
    }
    ......
}

RunLoop和线程

iOS中的线程,我们一般是运用Thread以及pthread_t 来办理的。通过开源的源码能够看到,**Thread是封装了pthread_t的,**而pthread_t是直接包装最底层的mach thread。同时Thread和pthread_t是逐个相关的。

Swift的Runloop中,Runloop对象是对CFRunLoop的封装,而CFRunLoop是根据pthread_t来进行办理的。

以下是RunLoop的部分源码:

typedef pthread_mutex_t CFLock_t;
/// 大局的dictionary:key是pthread_t, value是CFRunLoopRef
static CFMutableDictionaryRef loopsDic = NULL;
// 拜访loopsDic的锁
static CFLock_t loopLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;
// ** 这个办法只能通过Foundation来调用
// 比方Swift的Runloop封装CFRunloop,调用了_CFRunLoopGet2这个API,这个办法内部调用了_CFRunloopGet0
// 获取一个pthread对应的Runloop
CFRunLoopRef _CFRunloopGet0(pthread_t thread) {
    // 1、首要增加互斥锁
    pthread_mutex_lock(&loopLock);
    //2、第一次进入,初始化大局Dic,并先为主线程创立一个RunLoop
    if (!loopDic) {
        loopsDic = CFDictionaryCreateMutable();
        CFRunloopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_up());
        CFDictionarySetValue(loopsDic, pthread_main_thread_up(), mainLoop);
    }
    //3、直接从大局Dic中获取
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(loopsDic, thread);
    //4、假如取不到,那就创立一个
    if (!loop) {
        loop = __CFRunLoopCreate(thread);
        CFDictionarySetValue(loopDic, thread, loop);
     }
    //5、确保线程安全
    // 注册回调相关:当thread被毁掉时,这个runloop也会被毁掉!
    if (pthread_equal(t, thread_self())) {
        //TSD:Thread-share data 线程共享数据
        //这儿当时thread会持有tsdTable,同时tsdtable->data[__CFTSDKeyRunLoop]的值设为loop
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunloopCntr)) {
            _CFSetTSD(_CFTSDKeyRunLoopCntr, __CFFinalizeRunLoop);
        }
    }
    return loop;
}

线程和RunLoop之间是逐个对应的,保存在了一个大局的Dictionary中。线程创立的时候是没有RunLoop的,假如不自动获取,那它一向都不会有。RunLoop的创立是产生在第一次获取时,RunLoop的毁掉是产生线程结束时。线程结束时会调用 __CFFinalizeRunLoop 办法,这个办法中会毁掉大局的Dictionary中的Key-Value对。

除了大局的Dictionary之外,两者之间还有相互持有的联系,其一是CFRunLoop的结构体中持有pthread_t, 其二是pthread_t的TSD数据中也持有runloop。

// runLoop中持有pthread
struct __CFRunLoop {
    ...
    _CFThreadRef _pthread;
    CFMutableSetRef _modes;
    ...
}
// pthread中持有runloop
static void __CFSDSetSpecific(void *arg) {
	pthread_setspecific(_CFTSDIndexKey, arg);
}
static __CFTSDTable *__CFTSDGetTable(const Boolean create) {
    ...
    __CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
    __CFTSDSetSpecific(table);
    ...
	return table;
}
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);

TSD

什么是pthread_t的TSD数据呢?

TSD的全称为:Thread-Specific Data即线程特有数据。TSD由具体的每一个线程来保护。TSD采用了一键多值的技术,即一个键对应多个不同的值,每个线程拜访数据时通过拜访该键来得到对应的数据

线程特有数据能够看作是一个二维的数组,key作为行的索引,而线程id作为列的索引。一个线程特有数据的key是一个不透明的**pthread_key_t**数据类型。在一个进程中的一切线程都能够运用这个key。即使一切的线程运用了一样的key,它们通过这个key拜访到的或许修正的线程特有数据也是不同的。

Keys T1 Thread T2 Thread T3 Thread T4 Thread
K1(__CFTSDKeyRunLoop) 6 56 4 3
K2 87 21 0 9
K3 23 12 61 2
K4 11 76 47 88

以上表为例,线程T2运用的K3对应的数据是12,而线程T3运用的K3对应的数据是61。

对应到我们的代码中上述代码中的最后一行:_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); key便是_CFTSDKeyRunLoop,而value便是当时的loop。也便是说线程都会将当时的loop存储在对应的线程特有数据中。

总结

以上介绍了RunLoop和线程的联系,严格来讲它们除了相互持有之外,还有一个大局的哈希表来存储它们的对应联系,这个哈希表的Key是线程,Value是RunLoop对象。

  • 我正在参与技术社区创作者签约方案招募活动,点击链接报名投稿。