RunLoop

2,544 阅读11分钟

RunLoop

  • runloop是事件的接受的分发机制的实现
  • runloop提供一种异步执行代码的机制,不能并行执行任务
  • 在主队列中,main Runloop直接配合任务的执行,负责处理UI事件, 定时器以及其他内核相关的事件

Runloop 的主要目的

  • 保证程序执行时不会被系统终止
  • 什么时候使用Runloop
  • 当需要和该线程进行交互的时候才会使用Runloop
  • 灭一个线程都有对应的的Runloop,但是默认的非主线程的Runloop是不执行,需要为Runloop添加至少一个事件源,然后去run。
  • 一般情况下我们不去做这些,除非你要长久去检测单独线程中的某个事件
  • Runloop,是线程进入好被线程用来响应事件以及调用事件处理函数的地方,需要在代码中使用控制语句实现Runloop的循环,需要代码提供while或者for来驱动Runloop在这个循环中,使用一个Runloop对象[NSRunLoop CurrentRunLoop]执行接收消息,调用对应的处理函数

Runloop接受两种数据源事件:Input 和timer Source

Input Source传递事件,通常是来自其他线程和不同线程和不同程序中的消息

Timer Source (定时器)传递同步事件(重复执行或者在特定时间上触发)

处理 input Source Runloop 也会产生一些关于本身的notification,注册Runloop的observe,可以接收这些notification,做一些额外的处理。(使用CoreFundation来成为Runloop的observe)

Runloop工作特点

当有时间发生时,Runloop会根据具体的事件类型通知相应程序作出相应

当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的

当事件再次发生时,Runloop会被唤醒,处理事件

Runloop组成

  1. 与线程和自动释放迟有关
  2. CFRunLoopRef构造:数据结构;创建与退出;mode切换和item依赖;Runloop启动
    • CFRunLoopModeRef:数据结构(与CFRunLoopRef放一起了);创建;类型;
    • modeItems:
      • CFRunLoopSourceRef:数据结构(source0/source1);
        • source0 :
        • source1 :
    • CFRunLoopTimerRef:数据结构;创建与生效;相关类型(GCD的timer与CADisplayLink)
    • CFRunLoopObserverRef:数据结构;创建与添加;监听的状态;
  3. Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)
    • 代码实现:
    • 函数作用栈显示:
  4. Runloop本质:mach port和mach_msg()。
  5. 如何处理事件:
    • 界面刷新:
    • 手势识别:
    • GCD任务:
    • timer:(与CADisplayLink)
    • 网络请求:
  6. 应用:
    • 滑动与图片刷新;
    • 常驻子线程,保持子线程一直处理事件

线程(创建)-->runloop将进入-->最高优先级OB创建释放池-->runloop将睡-->最低优先级OB销毁旧池创建新池-->runloop将退出-->最低优先级OB销毁新池-->线程(销毁)

RunLoop 的构造

 CFRunLoopRef构造:
// runloop数据结构
struct __CFRunLoopMode {
    CFStringRef _name;            // Mode名字, 
    CFMutableSetRef _sources0;    // Set<CFRunLoopSourceRef>
    CFMutableSetRef _sources1;    // Set<CFRunLoopSourceRef>
    CFMutableArrayRef _observers; // Array<CFRunLoopObserverRef>
    CFMutableArrayRef _timers;    // Array<CFRunLoopTimerRef>
...
};
// mode数据结构
struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set<CFStringRef>
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set<CFRunLoopModeRef>
...
};

创建与退出: model切换和item的依赖

  1. 主线程的runloop自动创建,子线程的runloop默认不创建(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候,就会创建RunLoop);

  2. runloop退出的条件:app退出;线程关闭;设置最大时间到期;modeItem为空;

  3. 同一时间一个runloop只能在一个mode,切换mode只能退出runloop,再重进指定mode(隔离modeItems使之互不干扰);

  4. 一个item可以加到不同mode;一个mode被标记到commonModes里(这样runloop不用切换mode)。

RunLoop 启动

  • 用DefaultMode启动

     void CFRunLoopRun(void) {
         CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
     }
    
  • 用指定的Mode启动,允许设置RunLoop最大时间(假无限循环),执行完毕是否退出

    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
    
  • RunLoop 自动创建对应的model,model 只能添加不能删除

    添加model CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    

    类型

    1. kCFRunLoopDefaultMode: 默认 mode,通常主线程在这个 Mode 下运行。
    2. UITrackingRunLoopMode: 追踪mode,保证Scrollview滑动顺畅不受其他 mode 影响。
    3. UIInitializationRunLoopMode: 启动程序后的过渡mode,启动完成后就不再使用。
    4. GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
    5. kCFRunLoopCommonModes: 占位mode,作为标记DefaultMode和CommonMode用。

    modelItems

     // 添加移除item的函数(参数:添加/移除哪个item到哪个runloop的哪个mode下)
     CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
     
     CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    
     CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    
     CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    
     CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    

    CFRunLoopSourceRef:事件来源

    按照官方文档CFRunLoopSourceRef为3类,但数据结构只有两类(???)
    Port-Based Sources:与内核端口相关
    Custom Input Sources:与自定义source相关
    Cocoa Perform Selector Sources:与PerformSEL方法相关)
    

CFRunLoopObserverRef:监听runloop状态,接收回调信息(常见于自动释放池创建销毁)

  // 第一个参数用于分配该observer对象的内存空间
  // 第二个参数用以设置该observer监听什么状态
  // 第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
  // 第四个参数用于设置该observer的优先级,一般为0
  // 第五个参数用于设置该observer的回调函数
  // 第六个参数observer的运行状态   
  CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
     // 执行代码
  }

  typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
      kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
      kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
      kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
      kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
      kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
      kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
  };

Runloop内部逻辑:关键在两个判断点(是否睡觉,是否退出)

int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {

  // 0.1 根据modeName找到对应mode
  CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
  // 0.2 如果mode里没有source/timer/observer, 直接返回。
  if (__CFRunLoopModeIsEmpty(currentMode)) return;

  // 1.1 通知 Observers: RunLoop 即将进入 loop。---(OB会创建释放池)
  __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);

  // 1.2 内部函数,进入loop
  __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {

      Boolean sourceHandledThisLoop = NO;
      int retVal = 0;
      do {

          // 2.1 通知 Observers: RunLoop 即将触发 Timer 回调。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
          // 2.2 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
          // 执行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.3 RunLoop 触发 Source0 (非port) 回调。
          sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
          // 执行被加入的block
          __CFRunLoopDoBlocks(runloop, currentMode);

          // 2.4 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
          if (__Source0DidDispatchPortLastTime) {
              Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
              if (hasMsg) goto handle_msg;
          }

          // 3.1 如果没有待处理消息,通知 Observers: RunLoop 的线程即将进入休眠(sleep)。--- (OB会销毁释放池并建立新释放池)
          if (!sourceHandledThisLoop) {
              __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
          }

          // 3.2. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
          // -  一个基于 port 的Source1 的事件。
          // -  一个 Timer 到时间了
          // -  RunLoop 启动时设置的最大超时时间到了
          // -  被手动唤醒
          __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
              mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
          }

          // 3.3. 被唤醒,通知 Observers: RunLoop 的线程刚刚被唤醒了。
          __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);

          // 4.0 处理消息。
          handle_msg:

          // 4.1 如果消息是Timer类型,触发这个Timer的回调。
          if (msg_is_timer) {
              __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
          } 

          // 4.2 如果消息是dispatch到main_queue的block,执行block。
          else if (msg_is_dispatch) {
              __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
          } 

          // 4.3 如果消息是Source1类型,处理这个事件
          else {
              CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
              sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
              if (sourceHandledThisLoop) {
                  mach_msg(reply, MACH_SEND_MSG, reply);
              }
          }

          // 执行加入到Loop的block
          __CFRunLoopDoBlocks(runloop, currentMode);


          // 5.1 如果处理事件完毕,启动Runloop时设置参数为一次性执行,设置while参数退出Runloop
          if (sourceHandledThisLoop && stopAfterHandle) {
              retVal = kCFRunLoopRunHandledSource;
          // 5.2 如果启动Runloop时设置的最大运转时间到期,设置while参数退出Runloop
          } else if (timeout) {
              retVal = kCFRunLoopRunTimedOut;
          // 5.3 如果启动Runloop被外部调用强制停止,设置while参数退出Runloop
          } else if (__CFRunLoopIsStopped(runloop)) {
              retVal = kCFRunLoopRunStopped;
          // 5.4 如果启动Runloop的modeItems为空,设置while参数退出Runloop
          } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
              retVal = kCFRunLoopRunFinished;
          }

          // 5.5 如果没超时,mode里没空,loop也没被停止,那继续loop,回到第2步循环。
      } while (retVal == 0);
  }

  // 6. 如果第6步判断后loop退出,通知 Observers: RunLoop 退出。--- (OB会销毁新释放池)
  __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}

一步一步写具体的实现逻辑过于繁琐不便理解,按Runloop状态大致分为:

  1. Entry:通知OB(创建pool);
  2. 执行阶段:按顺序通知OB并执行timer,source0;若有source1执行source1;
  3. 休眠阶段:利用mach_msg判断进入休眠,通知OB(pool的销毁重建);被消息唤醒通知OB;
  4. 执行阶段:按消息类型处理事件;
  5. 判断退出条件:如果符合退出条件(一次性执行,超时,强制停止,modeItem为空)则退出,否则回到第2阶段;
  6. Exit:通知OB(销毁pool)

Runloop本质:mach port和mach_msg()。

Mach是XNU的内核,进程、线程和虚拟内存等对象通过端口发消息进行通信,Runloop通过mach_msg()函数发送消息,如果没有port 消息,内核会将线程置于等待状态 mach_msg_trap() 。如果有消息,判断消息类型处理事件,并通过modeItem的callback回调。
Runloop有两个关键判断点,一个是通过msg决定Runloop是否等待,一个是通过判断退出条件来决定Runloop是否循环。

如何处理事件

1.界面处理

  • 当UI改变( Frame变化、 UIView/CALayer 的继承结构变化等)时,或手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后,这个 UIView/CALayer 就被标记为待处理。
  • 苹果注册了一个用来监听BeforeWaiting和Exit的Observer,在它的回调函数里会遍历所有待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。
  1. 事件响应
  • 当一个硬件事件(触摸/锁屏/摇晃/加速等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收, 随后由mach port 转发给需要的App进程。
  • 苹果注册了一个 Source1 (基于 mach port 的) 来接收系统事件,通过回调函数触发Sourece0(所以UIEvent实际上是基于Source0的),调用 _UIApplicationHandleEventQueue() 进行应用内部的分发。
  • _UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。
  1. 手势识别
  • 如果上一步的 _UIApplicationHandleEventQueue() 识别到是一个guesture手势,会调用Cancel方法将当前的touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。
  • 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,其回调函数为_UIGestureRecognizerUpdateObserver(),其内部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。
  • 当有 UIGestureRecognizer 的变化(创建/销毁/状态改变)时,这个回调都会进行相应处理。

4.GCD任务

  • 当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调里执行这个 block。Runloop只处理主线程的block,dispatch 到其他线程仍然是由 libDispatch 处理的。

RunTime

Runtime就是系统在运行的时候的一种机制,其中最重要的就是消息机制,对于C语言,函数的调用在编译的时候就会决定调用那个函数C语言的函数调用请看这里 
)。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编
译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找
到对应的函数来调用。
  1. 封装:在这个库中,对象可以用C语言中的结构体表示,而方法可以用C函数来实现,另外再加上了一些额外的特性。这些结构体和函数被runtime函数封装后,我们就可以在程序运行时创建,检查,修改类、对象和它们的方法了。

Runtime主要实现思路

实例对象instance->类class->方法method(->SEL->IMP)->实现函数
实例对象只存放ISA指针和实例变量,由ISA指针找到所属类,类维护一个运行时可接收的方法列表;方法列表中的每个入口是一个方法(Method),其中key是一个特定名称,即选择器(SEL),其对应一个指向底层C实现函数的指针,即实现(IMP),。运行时机制最关键核心是objc_msgSend函数,通过给target(类)发送selecter(SEL)来传递消息,找到匹配的IMP,指向实现的C函数。
由于OC的运行时动态特性,在编译之后可以在运行时通过C操作函数,动态地创建修改类信息,动态绑定方法和重写实现,灵活地实现一些自定义功能。
在oc中消息机制如何调用