iOS开发-RunLoop总结

序言

       在《iOS之应用程序启动过程及原理总结》一篇中介绍了iOS应用的启动原理。我们知道当应用启动后,系统会自动创建一个线程来执行任务,该线程被称为主线程或者UI线程。其实在主线程创建的时候,系统还会为主线程创建并启动一种机制(其实就是一个对象,该对象和应用的生命周期有关),叫做RunLoop,被称为运行循环机制。本文主要将介绍iOS应用中的RunLoop机制。

RunLoop简介

RunLoop概念

       提到RunLoop,我们一般都会提到线程,这是为什么呢?先来看下 官方对 RunLoop 的定义 RunLoop是系统中和线程相关的基础架构的组成部分( 和线程相关 ),一个RunLoop是一个事件处理环,系统利用这个事件处理环来安排事务,协调输入的各种事件。当一个iOS应用启动后,如果我们不做任何交互操作,那么该应用默认不会做任何响应,一旦我们触摸了屏幕,或者点击了某个按钮,程序就会立即做出相应的响应,给我们的操作一个反馈。就好像这个应用处于一个时刻准备着的状态,有事要做的时候,它就会马上做。没有事要做的时候,它就等待一样。应用的这一点全是靠RunLoop机制来实现的。RunLoop从字面理解可以把它看做一个运行循环,而且它会事件相关联,所有这里我们也暂时把它当做一个事件运行循环。

       在系统中,所有的事件响应都由这个事件运行循环来派发和调度的。当系统没有接收到事件,运行循环就会处于休眠状态,来节约CPU的资源。当系统接收到事件,运行循环就会被唤醒,来分发和处理事件(这里涉及到事件的传递和响应者链条)。所以有了这个运行循环的存在,应用就不需要一直处于活跃状态,一切都由RunLoop来监管,这样大大的节约了系统资源。

       苹果官方为我们提供了两个这样的运行循环对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。下面结合这两个对象来介绍运行循环

RunLoop浅析

       当应用调用main函数中的UIApplicationMain()方法启动应用的时候,系统一方面加载视图文件(storyboard或者是xib文件)和info.plist文件,创建必要的视图对象;另一方面创建主线程,并在主线程中创建一个RunLoop对象(称为主运行循环,MainRunLoop),并把该对象存放在一个全局的静态字典中,以线程对象作为key。官方源码:

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

       由此可以看出,一个线程对象就对应一个RunLoop对象。创建后,默认启动该MainRunLoop对象。其内部是一个do-while循环。由此保证了应用程序的持续运行。其官方源码如下:

[objc]  view plain  copy
  1. /// 用DefaultMode启动  
  2. void CFRunLoopRun(void) {  
  3.     CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10false);  
  4. }  
  5.   
  6. /// 用指定的Mode启动,允许设置RunLoop超时时间  
  7. int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {  
  8.     return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);  
  9. }  
  10.   
  11. /// RunLoop的实现  
  12. int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {  
  13.       
  14.     /// 首先根据modeName找到对应mode  
  15.     CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);  
  16.     /// 如果mode里没有source/timer/observer, 直接返回。  
  17.     if (__CFRunLoopModeIsEmpty(currentMode)) return;  
  18.       
  19.     /// 1. 通知 Observers: RunLoop 即将进入 loop。  
  20.     __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);  
  21.       
  22.     /// 内部函数,进入loop  
  23.     __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {  
  24.           
  25.         Boolean sourceHandledThisLoop = NO;  
  26.         int retVal = 0;  
  27.         do {  
  28.   
  29.             /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。  
  30.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);  
  31.             /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。  
  32.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);  
  33.             /// 执行被加入的block  
  34.             __CFRunLoopDoBlocks(runloop, currentMode);  
  35.               
  36.             /// 4. RunLoop 触发 Source0 (非port) 回调。  
  37.             sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);  
  38.             /// 执行被加入的block  
  39.             __CFRunLoopDoBlocks(runloop, currentMode);  
  40.   
  41.             /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。  
  42.             if (__Source0DidDispatchPortLastTime) {  
  43.                 Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)  
  44.                 if (hasMsg) goto handle_msg;  
  45.             }  
  46.               
  47.             /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。  
  48.             if (!sourceHandledThisLoop) {  
  49.                 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);  
  50.             }  
  51.               
  52.             /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。  
  53.             /// • 一个基于 port 的Source 的事件。  
  54.             /// • 一个 Timer 到时间了  
  55.             /// • RunLoop 自身的超时时间到了  
  56.             /// • 被其他什么调用者手动唤醒  
  57.             __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {  
  58.                 mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg  
  59.             }  
  60.   
  61.             /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。  
  62.             __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);  
  63.               
  64.             /// 收到消息,处理消息。  
  65.             handle_msg:  
  66.   
  67.             /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。  
  68.             if (msg_is_timer) {  
  69.                 __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())  
  70.             }   
  71.   
  72.             /// 9.2 如果有dispatch到main_queue的block,执行block。  
  73.             else if (msg_is_dispatch) {  
  74.                 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);  
  75.             }   
  76.   
  77.             /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件  
  78.             else {  
  79.                 CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);  
  80.                 sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);  
  81.                 if (sourceHandledThisLoop) {  
  82.                     mach_msg(reply, MACH_SEND_MSG, reply);  
  83.                 }  
  84.             }  
  85.               
  86.             /// 执行加入到Loop的block  
  87.             __CFRunLoopDoBlocks(runloop, currentMode);  
  88.               
  89.   
  90.             if (sourceHandledThisLoop && stopAfterHandle) {  
  91.                 /// 进入loop时参数说处理完事件就返回。  
  92.                 retVal = kCFRunLoopRunHandledSource;  
  93.             } else if (timeout) {  
  94.                 /// 超出传入参数标记的超时时间了  
  95.                 retVal = kCFRunLoopRunTimedOut;  
  96.             } else if (__CFRunLoopIsStopped(runloop)) {  
  97.                 /// 被外部调用者强制停止了  
  98.                 retVal = kCFRunLoopRunStopped;  
  99.             } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {  
  100.                 /// source/timer/observer一个都没有了  
  101.                 retVal = kCFRunLoopRunFinished;  
  102.             }  
  103.               
  104.             /// 如果没超时,mode里没空,loop也没被停止,那继续loop。  
  105.         } while (retVal == 0);  
  106.     }  
  107.       
  108.     /// 10. 通知 Observers: RunLoop 即将退出。  
  109.     __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);  
  110. }  

       既然知道RunLoop机制是用来分发和处理事件的,那么我们来看看RunLoop能处理的事件类型:输入源(input source)和定时源(timer source)

  1. 输入源(input source):传递异步事件,消息通常来自于其他线程,处理其他线程的消息,如下载操作执行完毕,要回到主线程中更新UI,这些异步事件就是由RunLoop来监听和管理的。
  2. 定时源(timer source):传递同步事件,发生在特定时间,或者时间间隔,如定时检查UI界面上有没有刷新事件、点击事件等等,也就是处理本线程上的事件。

       除了处理输入源,RunLoop也会生成关于RunLoop行为的notification。注册的RunLoop 观察者可以收到这些notification,并做相应的处理。可以使用Core Foundation在你的线程注册RunLoop观察者。相应的通知如下:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {  
  2.     kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop  
  3.     kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer  
  4.     kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source  
  5.     kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠  
  6.     kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒  
  7.     kCFRunLoopExit          = (1UL << 7), // 即将退出Loop  
  8. };  

输入源、定时源、观察者解析:

  • 输入源对应Core Foundation框架中的CFRunLoopSourceRef类:CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。
  • 定时源对应Core Foundation框架中的CFRunLoopTimerRef类:CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
  • 观察者对应Core Foundation框架中的CFRunLoopTimerRef类:CFRunLoopObserverRef 是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

       RunLoop只有在一定的模式下才会运行,就是说要想启动RunLoop就要指定其运行的模式。系统已经提供了RunLoop运行的5种模式,如下

  1. NSDefaultRunLoopMode:是RunLoop默认的模式,表示程序空闲。如果我们用NSTimer来每秒打印输出的时候,一旦有手势操作(如滑动、滚动等操作),那么NSTimer就会停止执行。
  2. UITrackingRunLoopMode:跟踪模式(是UIScrollView专用模式)。上面提到的NSTimer例子,一旦有滚动等操作,RunLoop就会自动从NSDefaultRunLoopMode模式切换到UITrackingRunLoopMode模式,目的就是为了保证滚动的流畅性,给用户提供流畅的体验。
  3. NSRunLoopCommonMode:这种模式会包含以上两种模式。我们执行滑动操作的同时,NSTimer会始终调用方法来执行打印,但是,一旦所调用的方法中有耗时的操作时,效果就会卡顿。所有在实际开发中,不建议使用该模式。但是,有的需求就是又要有耗时操作又要保证流畅。解决办法就是将耗时操作放到子线程中(子线程RunLoop需要手动启动:CFRunLoopRun(),CFRunLoopStop(CFRunLoopCurrent())停止循环)。
  4. UIInitializationRunLoopMode:在程序刚启动的时进入该模式,启动完就不在使用。
  5. GSEventReceiveRunLoopMode:接受系统事件的内部mode,通常用不到。

RunLoop构成

       从上面可以看出RunLoop的结构。不过这里有一个重要的概念就是上面提到的模式:mode。RunLoop模式是所有要监视的输入源和定时源以及要通知的注册观察者的集合。每次运行RunLoop都会指定其运行在哪个模式下。以后,只有相应的源会被监视并允许接收他们传递的消息。(类似的,只有相应的观察者会收到通知)。其他模式关联的源只有在RunLoop运行在其模式下才会运行,否则处于暂停状态。

       通常代码中通过指定名字来确定模式。Cocoa和Core Foundation定义了默认的以及一系列常用的模式,都是用字符串来标识。当然你也可以指定字符串来自定义模式。虽然你可以给模式指定任何名字,但是所有的模式内容都是相同的。你必须添加输入源,定时器或者RunLoop观察者到你定义的模式中。

       所有由RunLoop的构成就清楚了,RunLoop必须指定一个mode,在mode中必须添加输入源,定时器或者RunLoop观察者。如果在启动RunLoop的时候,没有指定其模式,该RunLoop是没有任何效果的。如果没有添加任何源事件或Timer事件,线程会一直在无限循环的空转中,会一直占用CPU时间片,没有实现资源的合理分配。如果没有while循环驱动且没有添加任何源事件或Timer事件的线程,线程会直接完成,被系统回收。

RunLoop功能

分发和处理事件

       在应用程序运行的时候,系统创建一个主线程,并创建一个主运行循环来管理该主线程。那运行循环是如何对事件进行分发和处理的呢?

       苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收http://iphonedevwiki.net/index.php/IOHIDFamily 。SpringBoard 只接收按键(锁屏/静音等),触摸,加速,接近传感器等几种 Event,随后用 mach port 转发给需要的App进程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。

       _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。

更多事件处理可以阅读这篇文章

AutoreleasePool

       在学习OC的内存管理的时候就一直接触AutoreleasePool这个概念,对它也是一知半解的。学习这篇文字后,我就会知道AutoreleasePool是在什么时候创建的,又是在什么时候被销毁的了。这里我们再简单的回顾一下AutoreleasePool的作用。

       AutoreleasePool被称为自动释放池,在释放池中的调用了autorelease方法的对象都会被压在该池的顶部(以栈的形式管理对象)。当自动释放池被销毁的时候,在该池中的对象会自动调用release方法来释放资源,销毁对象。以此来达到自动管理内存的目的。

       其实我们在开发中很少主动去创建AutoreleasePool对象,这是为什么呢?不是说用它来自动管理内存吗?其实系统在运行的时候就帮我们创建了AutoreleasePool对象,只是我们不知道而已。那么它是在什么时候被创建的呢?来看看官方源码:

[objc]  view plain  copy
  1. CFRunLoop {  
  2.     current mode = kCFRunLoopDefaultMode  
  3.     common modes = {  
  4.         UITrackingRunLoopMode  
  5.         kCFRunLoopDefaultMode  
  6.     }  
  7.   
  8.     common mode items = {  
  9.   
  10.         // source0 (manual)  
  11.         CFRunLoopSource {order =-1, {  
  12.             callout = _UIApplicationHandleEventQueue}}  
  13.         CFRunLoopSource {order =-1, {  
  14.             callout = PurpleEventSignalCallback }}  
  15.         CFRunLoopSource {order = 0, {  
  16.             callout = FBSSerialQueueRunLoopSourceHandler}}  
  17.   
  18.         // source1 (mach port)  
  19.         CFRunLoopSource {order = 0,  {port = 17923}}  
  20.         CFRunLoopSource {order = 0,  {port = 12039}}  
  21.         CFRunLoopSource {order = 0,  {port = 16647}}  
  22.         CFRunLoopSource {order =-1, {  
  23.             callout = PurpleEventCallback}}  
  24.         CFRunLoopSource {order = 0, {port = 2407,  
  25.             callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}  
  26.         CFRunLoopSource {order = 0, {port = 1c03,  
  27.             callout = __IOHIDEventSystemClientAvailabilityCallback}}  
  28.         CFRunLoopSource {order = 0, {port = 1b03,  
  29.             callout = __IOHIDEventSystemClientQueueCallback}}  
  30.         CFRunLoopSource {order = 1, {port = 1903,  
  31.             callout = __IOMIGMachPortPortCallback}}  
  32.   
  33.         // Ovserver  
  34.         CFRunLoopObserver {order = -2147483647, activities = 0x1// Entry  
  35.             callout = _wrapRunLoopWithAutoreleasePoolHandler}  
  36.         CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaiting  
  37.             callout = _UIGestureRecognizerUpdateObserver}  
  38.         CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit  
  39.             callout = _afterCACommitHandler}  
  40.         CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit  
  41.             callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}  
  42.         CFRunLoopObserver {order = 2147483647, activities = 0xa0// BeforeWaiting | Exit  
  43.             callout = _wrapRunLoopWithAutoreleasePoolHandler}  
  44.   
  45.         // Timer  
  46.         CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0,  
  47.             next fire date = 453098071 (-4421.76019 @ 96223387169499),  
  48.             callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)}  
  49.     },  
  50.   
  51.     modes = {  
  52.         CFRunLoopMode {  
  53.             sources0 =  { /* same as 'common mode items' */ },  
  54.             sources1 =  { /* same as 'common mode items' */ },  
  55.             observers = { /* same as 'common mode items' */ },  
  56.             timers =    { /* same as 'common mode items' */ },  
  57.         },  
  58.   
  59.         CFRunLoopMode {  
  60.             sources0 =  { /* same as 'common mode items' */ },  
  61.             sources1 =  { /* same as 'common mode items' */ },  
  62.             observers = { /* same as 'common mode items' */ },  
  63.             timers =    { /* same as 'common mode items' */ },  
  64.         },  
  65.   
  66.         CFRunLoopMode {  
  67.             sources0 = {  
  68.                 CFRunLoopSource {order = 0, {  
  69.                     callout = FBSSerialQueueRunLoopSourceHandler}}  
  70.             },  
  71.             sources1 = (null),  
  72.             observers = {  
  73.                 CFRunLoopObserver >{activities = 0xa0, order = 2000000,  
  74.                     callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}  
  75.             )},  
  76.             timers = (null),  
  77.         },  
  78.   
  79.         CFRunLoopMode {  
  80.             sources0 = {  
  81.                 CFRunLoopSource {order = -1, {  
  82.                     callout = PurpleEventSignalCallback}}  
  83.             },  
  84.             sources1 = {  
  85.                 CFRunLoopSource {order = -1, {  
  86.                     callout = PurpleEventCallback}}  
  87.             },  
  88.             observers = (null),  
  89.             timers = (null),  
  90.         },  
  91.           
  92.         CFRunLoopMode {  
  93.             sources0 = (null),  
  94.             sources1 = (null),  
  95.             observers = (null),  
  96.             timers = (null),  
  97.         }  
  98.     }  
  99. }  

       App启动后,,系统在主线程RunLoop 里注册两个Observser,其回调都是_wrapRunLoopWithAutoreleasePoolHandler()。第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其优先级最高,保证创建释放池发生在其他所有回调之前。第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 优先级最低,保证其释放池子发生在其他所有回调之后。在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

       现在我们知道了AutoreleasePool是在RunLoop即将进入RunLoop和准备进入休眠这两种状态的时候被创建和销毁的。所以AutoreleasePool的释放有如下两种情况。一种是Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。还有一种就是手动调用AutoreleasePool的释放方法(drain方法)来销毁AutoreleasePool。

NSTimer

       RunLoop还可以用来开启线程的运行循环。经过学习,我们知道主线程的运行循环是系统默认创建和开启的。只有那些子线程需要我们获取RunLoop,并手动开启。下面利用NSTimer的例子来介绍一下,如何开启当前线程的RunLoop。

       首先创建一个子线程并在子线程中执行打印任务。代码如下:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. - (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.       
  4.     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  5.         NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];  
  6.     });  
  7. }  
  8.   
  9. - (void)testTag {  
  10.     NSLog(@"testTag");  
  11. }  
       运行看效果:没有任何效果!!!

       为什么呢?不知道NSTimer看上去是不是能让你们想到CFRunLoopTimerRef类。其实NSTimer可以看成就是CFRunLoopTimerRef。而CFRunLoopTimerRef是RunLoop机制中的定时源。还记得前面说过,要想运行RunLoop,就必须指定其运行模式,并向模式中添加输入源、定时源或者观察者。既然NSTimer是一种定时源,要想在子线程中运行就必须将该定时源添加到子线程的RunLoop中去才行啊,如下代码:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. - (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.       
  4.     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  5.         NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];  
  6.         [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];  
  7.     });  
  8. }  
  9.   
  10. - (void)testTag {  
  11.     NSLog(@"testTag");  
  12. }  
       再运行看看,还是没有效果。这又是为什么呢。我们想想,当前线程是一个子线程,子线程中的RunLoop默认是没有开启的呀。只有主线程才会默认开启。那么我们来手动开启该RunLoop。代码如下:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. - (void)viewDidLoad {  
  2.     [super viewDidLoad];  
  3.       
  4.     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  5.         NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTag) userInfo:nil repeats:YES];  
  6.         [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];  
  7.         [[NSRunLoop currentRunLoop] run];  
  8.     });  
  9. }  
  10.   
  11. - (void)testTag {  
  12.     NSLog(@"testTag");  
  13. }  
       再运行,成功了!!!可以得到,当当前线程为子线程的时候,要想运行RunLoop,必须手动开启RunLoop。

       其实与定时源描述相关联的方法,在子线程中被调用都要手动开启RunLoop。这些方法如下:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;  
  2.   
  3. - (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;  
  4.   
  5. - (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;  

RunLoop实现

       上面例子中,我们通过开启一个线程的RunLoop来执行定时源的回调方法。而且RunLoop也是一个对象。前面也说过,RunLoop内部其实就是一个do-while循环在驱动着,那么我们能不能自己实现一个RunLoop机制呢?下面我们来试试。

       首先创建一个子线程,只有线程存在,RunLoop才会存在。还记得开头说的吧.........提示一下全局的静态字典。

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. - (void)testNewThread1{  
  2.     // 获取当前线程RunLoop  
  3.     NSRunLoop *runLoop = [NSRunLoop currentRunLoop];  
  4.    // 向当前线程RunLoop添加源,并指定运行模式  
  5.     [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];  
  6.    // 利用while循环驱动RunLoop运行  
  7.     while (!self.isCancelled && !self.isFinished) {  
  8.         @autoreleasepool {  
  9.             [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];  
  10.         }  
  11.     }  
  12. }  
       这样实现的RunLoop机制会一直占用CPU资源,CPU资源不能合理分配。再看看下面的代码,也是AFNetworking中实现的RunLoop机制的代码:

[objc]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. + (void)networkRequestThreadEntryPoint:(id)__unused object  
  2. {  
  3.     @autoreleasepool {  
  4.         [[NSThread currentThread] setName:@"AFNetworking"];  
  5.         NSRunLoop *runLoop = [NSRunLoop currentRunLoop];     // 这里主要是监听某个 port,目的是让这个 Thread 不会回收  
  6.         [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];  
  7.         [runLoop run];  
  8.     }  
  9. }  
  10.   
  11. // 创建一个常驻线程,提外界使用  
  12. + (NSThread *)networkRequestThread {  
  13.     static NSThread *_networkRequestThread = nil;  
  14.   
  15.     static dispatch_once_t oncePredicate;  
  16.     dispatch_once(&oncePredicate, ^{  
  17.   
  18.         _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];  
  19.         [_networkRequestThread start];  
  20.     });  
  21.     return _networkRequestThread;  
  22. }  
       可以看出,RunLoop被开启的线程会一直存在。因为在没有事件发生的时候处于休眠状态,有事件发生的时候处于工作状态。以此来节约CPU资源。这样就可以让一个线程成为常驻线程,也就是说该线程一直存在。

RunLoop总结

RunLoop总结:

RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统。

RunLoop是一种事件运行循环机制,是保持应用程序持续运行的一种机制。正是由于该机制的存在,应用程序才能在没有事件发生的时候处于休眠状态,有事件发生的时候处于工作状态。以此来节约CPU资源。这也是它的一大特点。

NSRunLoop是Cocoa框架中的类,与之对应的,在Core Foundation中是CFRunLoopRef类。两者的区别是前者不是线程安全的,后者是线程安全的,且两者可以相互转化。

RunLoop和线程的关系:

RunLoop是用来管理线程的,每个线程对应一个RunLoop对象。我们不可以去创建当前线程的RunLoop对象,但是我们可以去获取当前线程的RunLoop。RunLoop就是来监听该线程有无事件发生,如果有就工作,如果没有就休眠。

主线程的RunLoop对象默认开启,其他线程默认不开启。

RunLoop与AutoreleasePool;

RunLoop处理的事件类型;

RunLoop的运行模式mode;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值