RunLoop

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容