iOS RunLoop的一些理解,以及RunLoop的实际应用

说到RunLoop无论你是iOS开发还是C++ ,java等技术背景,我们曾经都知道一个概念那就是事件循环,事件队列,系统CPU时间片轮转,不停的去获取任务执行,如果没有任务那就休眠当前的线程,不占用系统资源,可以腾出更多的资源,时间给需要的其他线程。

是的,最早的时候,大学的计算机老师就给我如此启蒙。这种言简意赅,浅尝辄止的说教方式效果也不错,最起码能让我们学习操作系统的时候有一个宏观的认识。

然而,真的做项目的时候只是理解原理甚至还不够,要理解原理中的原理才是解决问题的手段。今天我就谈谈Runloop的概念原理细节,以及应用场景

第一:Runloop的概念

英文好的码友们可以直接戳:apple develop document >>>>>

Runloop是附属在线程上的最基本的服务机制,能够让线程长久运行,不断的从事件队列中获取任务去执行,直到没有事件的时候被挂起休眠,释放CPU资源。一旦新的事件到来,又会被唤醒继续执行。最终,runloop可以被释放,这个线程才终结。

作用

  • 保持程序持续运行
  • 处理app中的各种事件(如触摸事件、定时器事件【NSTimer】、selector事件【选择器·performSelector···】)
  • 节省CPU资源,提高程序性能

说明

  • 没有RunLoop,那么程序一启动就会退出
  • 有了Runoop,那么相当于在内部有一个死循环,能够保证程序的持续运行
  • main函数中的RunLoop
  • 在UIApplication函数内部就启动了一个RunLoop
    该函数返回一个int类型的值
  • 这个默认启动的RunLoop是跟主线程相关联的

对象

  • 在iOS开发中有两套api来访问RunLoop
  • Foundation框架【NSRunLoop】
  • CoreFoundation框架【CFRunLoopRef】
  • NSRunLoop和CFRunLoopRef都代表着RunLoop对象,它们是等价的,可以互相转换
  • NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)

第二:Runloop原理

所有的线程都可以无限执行下去,只是系统给我们默认的情况是主线程是自动开启Runloop的,而我们手动创建的线程是需要自定义是否使用Runloop的。

NSRunLoop * runloop1 = [NSRunLoop currentRunLoop];才会获取并且创建自定义线程的Runloop。

如果查看Runloop的源码我们就会发现:app默认会创建第一个主线程,这个时候在这个函数里创建了第一个Runloop,也就是主线程的Runloop,并放入到全局字典中。

  1. // 全局字典,key是pthread_t 代表线程,value是CFRunLoopRef 代表事件循环对象
  2. static CFMutableDictionaryRef __CFRunLoops = NULL;
  3. // 访问__CFRunLoops时的锁
  4. static CFLock_t loopsLock = CFLockInit;
  5. // should only be called by Foundation
  6. // t==0 is a synonym for "main thread" that always works
  7. CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
  8.     // 如果传进来的线程为空,默认为主线程
  9.     if (pthread_equal(t, kNilPthreadT)) {
  10.         t = pthread_main_thread_np();
  11.     }
  12.     __CFLock(&loopsLock);
  13.     // 第一次进来全局字典里面没有任何东西
  14.     if (!__CFRunLoops) {
  15.         __CFUnlock(&loopsLock);
  16.         // 初始化全局字典
  17.         CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
  18.         // 根据主线程创建主线程RunLoop
  19.         CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
  20.         // 加入到全局字典 key main_thread value mainLoop
  21.         CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
  22.         // 和全局__CFRunLoops关联上,然后把临时创建的杀死
  23.         if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
  24.             CFRelease(dict);
  25.         }
  26.         CFRelease(mainLoop);
  27.         __CFLock(&loopsLock);
  28.     }
  29.     // 如果全局字典存在,根据线程取RunLoop
  30.     CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  31.     __CFUnlock(&loopsLock);
  32.     // 取不到在创建一个
  33.     if (!loop) {
  34.         // 创建
  35.         CFRunLoopRef newLoop = __CFRunLoopCreate(t);
  36.         __CFLock(&loopsLock);
  37.         // 再取一次
  38.         loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
  39.         // 还是没有
  40.         if (!loop) {
  41.             // 根据线程key把刚创建的关联上
  42.             CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
  43.             loop = newLoop;
  44.         }
  45.         // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  46.         __CFUnlock(&loopsLock);
  47.         CFRelease(newLoop);
  48.     }
  49.     // 注册一个回调,当线程销毁时,通知销毁RunLoop
  50.     if (pthread_equal(t, pthread_self())) {
  51.         _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
  52.         if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
  53.             _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
  54.         }
  55.     }
  56.     return loop;
  57. }

RunLoop 与线程的关系

首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。

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

其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:

  1. /// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
  2. static CFMutableDictionaryRef loopsDic;
  3. /// 访问 loopsDic 时的锁
  4. static CFSpinLock_t loopsLock;
  5. /// 获取一个 pthread 对应的 RunLoop。
  6. CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
  7.     OSSpinLockLock(&loopsLock);
  8.     if (!loopsDic) {
  9.         // 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
  10.         loopsDic = CFDictionaryCreateMutable();
  11.         CFRunLoopRef mainLoop = _CFRunLoopCreate();
  12.         CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
  13.     }
  14.     /// 直接从 Dictionary 里获取。
  15.     CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
  16.     if (!loop) {
  17.         /// 取不到时,创建一个
  18.         loop = _CFRunLoopCreate();
  19.         CFDictionarySetValue(loopsDic, thread, loop);
  20.         /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
  21.         _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
  22.     }
  23.     OSSpinLockUnLock(&loopsLock);
  24.     return loop;
  25. }
  26. CFRunLoopRef CFRunLoopGetMain() {
  27.     return _CFRunLoopGet(pthread_main_thread_np());
  28. }
  29. CFRunLoopRef CFRunLoopGetCurrent() {
  30.     return _CFRunLoopGet(pthread_self());
  31. }

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

RunLoop 对外的接口

在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef,CFRunLoopModeRef,CFRunLoopSourceRef,CFRunLoopTimerRef,CFRunLoopObserverRef。

其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:

iOS RunLoop的一些理解,以及RunLoop的实际应用

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: