RunLoop

作者: 分流替躺欧阳克 | 来源:发表于2019-06-07 22:01 被阅读0次

    RunLoop:是通过内部维护的事件循环来对事件/消息进行管理的一个对象,能在程序中循环做些事情,可以让开发者间接的控制内核态能进行的操作(休眠线程,保持线程存活的前提而不占用系统的资源)。
    事件循环:

    • 维护的事件循环可以用来不断处理消息事件,对他们管理,没有消息时会从用户态转到内核态,由此可以让当前线程休眠,避免资源占用。
    • 有消息时再被唤醒处理事件,由内核态转至用户态

    应用范畴:定时器,PerformSelector,GCD Async Main Queue,网络请求,手势识别,界面刷新。
    如果UIApplicationMain里的Runloop,程序执行完后就会退出,而不会像我们打开程序那样,保持着运行状态
    基本作用,保持程序的持续运行,处理App中各种事件(触摸事件,定时器事件等)。节省CPU资源,提高程序性能,可以从操作系统层面,让线程睡眠,不占用CPU资源。

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            //这里创建了一个RunLoop
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    RunLoop与线程:

    每个线程都有一个唯一的RunLoop,存在于一个全局的字典里,线程作为Key,RunLoop作为value。线程刚刚创建时并没有RunLoop,RunLoop会在第一次获取它时创建。在线程结束时销毁。下面是获取RunLoop的源码,CFRunLoops是全局的字典。

    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,
                                                            pthreadPointer(t));
    

    iOS有两套API访问RunLoop
    Foundation: NSRunLoop(封装的CFRunLoopRef,使用更方便)
    Core Foundation: CFRunLoopRef(可控制的参数更多,更自由)
    Foundation: [NSRunLoop currentRunLoop];//获取当前线程的RunLoop
    [NSRunLoop mainRunLoop];//获取住线程的RunLoop
    Core Foundation:CFRunLoopGetCurrent();//获取当前线程的RunLoop
    CFRunLoopGetMain();//获取主线程的RunLoop对象

    RunLoop相关的类与结构

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef
    typedef struct __CFRunLoop *CFRunLoopRef;
    struct  __CFRunLoop {
              pthread_t _pthread;//所处线程
              CFMutableSetRef _commonModes;//字符串集合
              CFMutableSetRef _commonModeItems;
              CFRunLoopModeRef  _currentMode;
              CFMutableSetRef  _modes;//装着CFRunLoopModeRef
    }
    
    typedef struct  CFRunLoopMode *CFRunLoopModeRef;
    struce __CFRunLoopMode {
              CFStringRef _name;
              CFMutableSetRef _source0;//CFRunLoopSourceRef
              CFMutableSetRef _source1;//CFRunLoopSourceRef
              CFMutableArrayRef _observers;//CFRunLoopObserverRef
              CFMutableArrayRef _timers;
    };
    

    RunLoop数据结构

    • pthread:
      每个线程都有一个唯一的RunLoop,存在于一个全局的字典里,线程作为Key,RunLoop作为value。线程刚刚创建时并没有RunLoop,RunLoop会在第一次获取它时创建,在线程结束时销毁。
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,
                                                            pthreadPointer(t));//__CFRunLoops全局的一个字典。
    
    • currentModel:
      CFRunLoopMode类型
    • modes:
      NSMutableSet元素是CFRunLoopMode
    • commonModes:
      NSMutableSet元素是字符串集合
    • commonModeltems:
      NSMutableSet(多个obserber,多个timer,多个source)

    RunLoopModel数据结构

    • name
      mode名字或者也可以说是类型
      1.NSDefaulyRunLoopMode
      默认状态、空闲状态
      2.UITrackingRunLoopMode
      滑动态mode -追踪 ScrollView 滑动时的状态
      3.UIInitializationRunLoopMode
      私有,启动APP时切换进入,完成启动就被抛弃不再使用
      4.GSEventReceiveRunLoopMode
      接受系统事件的内部Mode,通常用不到。
      5.NSRunLoopCommonModes
      这个mode比较特殊,包含了DefaultMode和UITrackingRunLoopMode 的状态,一 般作占位用,结构也比较特殊

    • source0

      1. 需要手动唤醒线程
      2. 触摸事件 或者 - performSelector: onThread:
    • source1
      通过port不同线程间的通信。具有唤醒线程的能力

    • observers

      1. 监听者
    • timer

      1. 定时器、performSeletor:withObject:afterDelat:

    RunLoop和mode的关系

    1. 一个RunLoop包含了若干mode,一对多的关系。
    2. 每个mode包含了若干个source0,source1,observer,timer。
    3. RunLoop启动时只能选择一个mode作为当前currentMode,切换mode只能退出当前mode,再重新进入一个mode。如果mode没有任何source0和source1,timer,observer。RunLoop会退出。

    注意点:observer
    用于监听Runloop状态-特定类型,不能随便传;
    创建方式:

    CFRunLoopObserver observer = CFRunLoopObserverCreate(CFALLocatorRef(创建方式,通常kCFAllocatorDefault),activities(监听什么状态,kCFRunLoopAllctives所有状态),repeats(是否重复监听),顺序 填0代表不需要考虑,CFRunLoopObserverCallBack(监听返回的会调的函数),context(会把我们传进去的回调函数放到这来,可以填NULL));
    //对某个RunLoop监听
    CFRunLoopAddObserver(CFRunLoopGetMain(),observer,kCFRunLoopCommonModes);//kCFRunLoopCommonModes并不是真正的mode,而是一个标记,_commonModeItems存的mode都是这个标记的,代表监听所有的mode。
    CFRelease(observer);
    

    观测时间点

    • kCFRunLoopEntry //即将进入Loop
    • kCFRunLoopBeforeTimers //即将处理Timer
    • kCFRunLoopSources //即将处理Source
    • kCFRunLoopBeforeWaiting//即将进入休眠 (从用户态切换到内核态)
    • kCFRunLoopAfterWaiting //即将从休眠中唤醒
    • kCFRunLoopExit //即将退出
    • kCFRunLoopAllActivities //所有状态。

    Runloop运行逻辑

    1. 通知Observers:进入Loop
    2. 通知Observers:即将处理Timers
    3. 通知Observers::即将处理Sources
    4. 处理Blocks(CFRunLoopPerformBlock(CFRunLoopRef rl,CFTypeRef mode,^{})类似这种可以往Loop里添加block)
    5. 处理Source0(期间可能会处理Blocks)
    6. 如果存在source1,就跳转到第8步
    7. 通知OBservers:开始休眠(等待消息唤醒)
    8. 通知Observers:开始结束休眠(被某个消息唤醒)
      1. 处理Timer
      2. 处理GCD Async To Main Queue
      3. 处理source1
    9. 处理Blocks
    10. 根据前面的执行结果,决定如何操作
      1. 退回到02步
      2. 退出Loop
    11. 通知Observers:退出Loop。

    RunLoop应用

    1. 解决Timer在某些控件滑动时不执行不计时。

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    NSRunLoopCommonModes:

    1. 不是实际存在的一种Mode
    2. 是同步Source/Timer/Observer到多个Mode 中的一种技术解决方案。
      把一些Mode打上一个标记,表示能运行到RunLoop结构体里的_commonModeItems所装的所有模式,而NSDefaultRunLoopMode和UITrackKingRunLoopMode都是装在_commonModeItems里。
      底层原理:
    3. CFRunLoopAddTimer()把timer添加到commondMode下下的所有真实mode
    4. 判断是否是commondMode,是的话调用CFSetApplyFunction,为每个commondModes里每个mode调用__CFRunLoopAddItemToCommonModes()函数
    5. __CFRunLoopAddItemToCommonModes()函数里执行CFRunLoopAddTimer()函数。
    6. CFRunLoopAddTimer()函数这是接收到的是真实的mode,就会进入另一个逻辑,把timer添加到真实的mode下
    CFRunLoopAddTimer(CFRunLoopRef rl//RunLoop,CFRunLoopTimerRef rtl//要添加的timer,CFStringRef modeName//模式名称){
      //如果传进来的是CommondMode而不是真的mode
        if (modeName == kCFRunLoopCommonModes) {
          CFSetRef set = rl->_commonModes ;//获取所有rl下_commonModes里所有的model
          CFSetAddValue(rl->_commonModeItems, rlt);
          CFTypeRef context[2] = {rl, rlt};//把定时器和RunLoop封装成一个Context
          //调用__CFRunLoopAddItemToCommonModes,参数所有mode名字的集合,当前RunLoop和context封装的context
          CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void     *)context);
        }else{
            //执行具体mode下的timer
        }
    }
    
    static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
    CFStringRef modeName = (CFStringRef)value;//获取mode名字
    CFRunLoopRef rl = ctx[0];//取出RunLoop
    CFTypeRef item = ctx[1];//取出timer
      if(source类型){
            CFRunLoopAddSource(rl,(CFRunLoopSourceRef)item,modeName)
      }else if(Observer类型){
            CFRunLoopAddObserver(rl,(CFRunLoopObserverRef)item,modeName)
      }else if(timer类型){
            CFRunLoopAddTimer(rl,(CFRunLoopTimerRef)item,modeName)
      }
    }
    

    2.线程保活

     self.innerThread = [[NSThread alloc]initWithBlock:^{
                CFRunLoopSourceContext context = {0};
                
                CFRunLoopSourceRef source = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
                //往runloop添加source,保证RunLoop不会退出。
                CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
                CFRelease(source);
                //ture:执行完source就退出,这里false,程序一直阻塞在这里,线程就不会死。C语言提供参数更多,能控制是否执行完source退出mode,不用加判断一直添加mode
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            }];
    

    当我们不想用这个线程时,需要停止Loop,不然Thread会一直在,指针清空也没有用,线程里执行的任务卡着,线程停不掉,所以需要先停止RunLoop。

        CFRunLoopStop(CFRunLoopGetCurrent());
        self.innerThread = nil;
    

    总结:

    RunLoop是处理事件循环消息管理的对象

    RunLoop有五个重要成员

    1. pthread
    2. currentMode
    3. modes
    4. commondModes
    5. commondModesItems(Observers,timers,sources)

    Mode重要成员

    1. name
    2. source0s(需要手动唤醒线程)
    3. source1s(具备唤醒线程的能力)
    4. blocks
    5. timers

    RunLoop观测时间点(Entry,beforeTimer,Source,beforeWaiting,AfterWating,Exit)AllActivitives

    RunLoop运行机制

    Runloop应用(timer不同mode下都有效,线程保活)

    注意点:加入到某个Mode下的timer在别的Mode下是不生效的,如果要所有Mode都生效,需要添加到NSRunLoopCommonModes下
    NSRunLoopCommonModes并不是真正的Mode

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/sxudxctx.html