iOS中的RunLoop(基本概念)

251 阅读2分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

什么是RunLoop

从字面上理解就是一个运行循环,一般程序就是执行一个线程,是一条直线,有起点和终点。而RunLoop就是一直在线程上面画圆圈,一直在跑圈,在不断跑圈中,一直在检测一些点击事件、定时器等等,一旦检测到就开始执行,执行结束后再睡眠,睡眠中再检测,除非切断否则一直在运行,否则就一直在循环。其内部的结构是一个do-while循环,在这个循环内部不断处理各种任务(比如timersourceobserver


RunLoop基本作用

  • 保持程序的持续运行,App持续运行就是因为有RunLoop
  • 处理App中的各种事件(比如触摸事件、定时事件、Selector事件)
  • 节省CPU资源,提高程序性能,该做事时做事,该休息时休息

RunLoop对象

NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象,NSRunLoop 是基于 CFRunLoopRef 的一层 OC 的包装

  • Foundation : NSRunLoop
  • CoreFoundation : CFRunLoopRef

RunLoop与线程

  • 每条线程都有唯一的一个与之相对应的 RunLoop 对象
  • 主线程的 RunLoop 系统已经创建好了,子线程的 RunLoop 需要自己创建
  • RunLoop 在第一次获取时创建,在线程结束时销毁
//Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop];    // 获得主线程的RunLoop对象
  
//Core Foundation
CFRunLoopGetCurrent();      // 获得当前线程的RunLoop对象
CFRunLoopGetMain();         // 获得主线程的RunLoop对象

注:
NSRunLoop 初始化不需要 alloc ,只需要在该线程里调用[NSRunLoop currentRunLoop]


苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain()CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样:

// 全局的Dictionary
//key 是 pthread_t, 
//value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;

// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
    
// 获取一个 pthread 对应的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);

    if (!loopsDic) {
        // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic,pthread_main_thread_np(), mainLoop);
    }
        
    // 直接从 Dictionary 里获取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
        
    if (!loop) {
        // 取不到时,创建一个
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
    
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
    
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

从上面的代码可以看出,线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的 Dictionary 里。线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。你只能在一个线程的内部获取其 RunLoop(主线程除外)。