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