Runloop详解

一、概念


 runloop 程序在运行过程中循环的做一些事情;

二、 作用


处理下面的事件:

           定时器

           GCD

           事件相应识别,页面刷新

           网络请求

           atuoReleasPool

 1.程序不会马上退出,而是保持运行状态

 2.处理APP中的各种事件(比如触摸事件、定时器事件等)

 3.节省cpu资源,提高程序性能:该做事做事该休息休息

三.具体操作:


UIApplicationMain  创建runloop;已经main函数的runloop

 伪代码如下:

         int retVal = 0;

         do {

           //睡眠中等待消息

             int message = sleep_and_wait();

 //        处理消息

             retVal = perccess_message(message);


         } while (retVal ==0);

         return 0;

 四:获取runloop的api


   NSRunLoop  是基于CFRunLoopRef的一层oc包装

   CFRunLoopRef 是开源的:https://opensource.apple.com/tarballs/CF/

五、总结


 1.每条线程都有唯一的一个与之对应的RunLoop对象;

 2.RunLoop保存在一个全局的dictinary里面,线程作为key,RunLoop作为value

 3.线程刚创建时并没有runloop对象,runloop会在第一次获取它时创建

 4.runloop会在线程结束时销毁

 5.主线程的runloop已经自动获取创建,子线程默认没有开启runloop

从底层代码分析看出来:

 if (!loop) {

  CFRunLoopRef newLoop = __CFRunLoopCreate(t);

      __CFLock(&loopsLock);

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

 在获取runloop的如果没有runloop,会创建一个new的runloop对象,并且从  loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

这句看,是把线程作为key,Runloop作为value存在字典里面去,

在子线程开启:

            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode];

                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

六 、runloop底层结构分析

runloop对象的基本结构 跟runloop相关的5个类:

 CFRunLoopRef

 CFRunLoopModeRef

 CFRunLoopsourceRef

 CFRunLoopTimerRef

 CFRunLoopObserverRef



 struct __CFRunLoop {

 =========关键的5个成员变量

      pthread_t _pthread;

      CFMutableSetRef _commonModes;

      CFMutableSetRef _commonModeItems;

      CFRunLoopModeRef _currentMode;

      CFMutableSetRef _modes;

 };


 struct __CFRunLoopMode {

     CFStringRef _name;

     CFMutableSetRef _sources0;

     CFMutableSetRef _sources1;

     CFMutableArrayRef _observers;

     CFMutableArrayRef _timers;

 };

从底层结构看出来:

1)、结构分析:

 1.CFRunLoopModeRef代表runloop的运行模式;

 2.一个runloop包含若干个Mode,每个Mode又包含若干个soure0/soure1/timer/ObserVer;

 3.runloop启动时只能选择其中一个mode,作为CurrentMode

 4.如果需要切换mode,只能退出当前Loop,再重新选择一个Mode进入;

     这样的作用是将不同组的source0/source1/Timer/Observer能分隔开来,互不影响

 5.如果mode里面没有任何soure0/soure1/Timer/Observer,runloop会立即退出;

    注:此处的退出并不是runloop退出while循环,不会导致程序退出,而是,在while循环里面进行model的切换;

2)、mode的类型有

kCFRunLoopDefaultMode;NSDefaultRunLoopMode;APP的默认mode,通常主线程是在这个mode下运行;

 UITrackingRunLoopMode;  滚动model  一帮常见的是这2种模式;界面跟踪Mode,用于scrollView追踪触摸滑动,保证页面滑动时不受其他Mode影响;

 kCFRunLoopCommonModes  默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode;2种模式都兼容

//    UITrackingRunLoopMode\NSDefaultRunLoopMode才是真正存在的模式

//    NSRunLoopCommonModes并不是一个真正的模式,而是一个标记;

//    timer在设置了common标记的模式下都可以运行;

GSEventReceiveRunLoopMode

3)、modle内部参数说明

source0

        1.触摸事件处理

        2.performSelector:onThread:

    source1

       1.基于Port的线程间通信;

       2.系统事件捕捉,(先通过soure1处理,然后分发到soure0来处理);

      Timers

        [self performSelector:@selector(playInputClick) withObject:self afterDelay:2];

      Obervers

        用于监听RunLoop的状态;(休眠或者唤醒)

        UI刷新(BeforeWaiting)

        Autorelease Pool

七:Obervers  自己添加


         typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

             kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop

             kCFRunLoopBeforeTimers = (1UL << 1),  // 即将处理Timer

             kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source

             kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠

             kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒

             kCFRunLoopExit = (1UL << 7),            // 即将退出Loop

             kCFRunLoopAllActivities = 0x0FFFFFFFU

         };

方法一:

    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);

    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);

    CFRelease(observer);

方法二:

    CFRunLoopObserverRef observer1 = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {


        switch(activity) {

            case kCFRunLoopEntry:

                NSLog(@"kCFRunLoopEntry");

                break;

            case kCFRunLoopBeforeTimers:

                NSLog(@"kCFRunLoopBeforeTimers");

                break;

            case kCFRunLoopBeforeSources:

                NSLog(@"kCFRunLoopBeforeSources");

                break;

            case kCFRunLoopBeforeWaiting:

                NSLog(@"kCFRunLoopBeforeWaiting");

                break;

            case kCFRunLoopAfterWaiting:

                NSLog(@"kCFRunLoopAfterWaiting");

                break;

            casekCFRunLoopExit:

                NSLog(@"kCFRunLoopExit");

                break;

            default:

                break;

        }

    });

    CFRunLoopAddObserver(CFRunLoopGetMain(), observer1, kCFRunLoopCommonModes);

    CFRelease(observer1);

//    处理block的逻辑

    CFRunLoopPerformBlock(CFRunLoopGetMain(), kCFRunLoopCommonModes, ^{



    });

八 、runloop底层原理分析

1)、入口函数

 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {

 //通知Oberservers进入loop

     if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

 //  具体要做的事情

     result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);


 //    通知Oberserver:退出Loop

     if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

     return result;

 }

2)、__CFRunLoopRun内部函数分析

 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {

 //通知Oberservers进入loop

     if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

 //  具体要做的事情

     result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

 //    通知Oberserver:退出Loop

     if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

     return result;

 }


 __CFRunLoopRun这个函数是loop的关键处理的代码逻辑


 static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {


     int32_t retVal = 0;

     do {

 // 通知observers:即将处理timer

         __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

 // 通知observers:即将处理Sources

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

 //处理block;

     __CFRunLoopDoBlocks(rl, rlm);

 //处理soure0

     if ( __CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {

         //处理block;

             __CFRunLoopDoBlocks(rl, rlm);

        }

 //判断有没有soure1

     if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {

 //        如果有soure1,就跳转到handle_msg

                 goto handle_msg;

             }

 // 通知OberVers:即将休眠

    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

     __CFRunLoopSetSleeping(rl);

 //  等待别的消息来唤醒当前线程

     __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);


         __CFRunLoopUnsetSleeping(rl);

 //  通知observe:休眠

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

 handle_msg:;

        if (被timer唤醒) {

 //          处理timer

            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())

        }

         else if (被GCD唤醒) {

 //          处理GCD

             __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);


         } else { //被soure1唤醒

 //        处理soure1

          __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;

         }

 //再次处理Block

     __CFRunLoopDoBlocks(rl, rlm);

 //    设置返回值

 if (sourceHandledThisLoop && stopAfterHandle) {

         retVal = kCFRunLoopRunHandledSource;

         } else if (timeout_context->termTSR < mach_absolute_time()) {

             retVal = kCFRunLoopRunTimedOut;

     } else if (__CFRunLoopIsStopped(rl)) {

             __CFRunLoopUnsetStopped(rl);

         retVal = kCFRunLoopRunStopped;

     } else if (rlm->_stopped) {

         rlm->_stopped = false;

         retVal = kCFRunLoopRunStopped;

     } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {

         retVal = kCFRunLoopRunFinished;

     }

     } while (0 == retVal);

     return retVal;

 }

3)、休眠函数分析

 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

休眠是线程阻塞,cup不会给它分配资源,代码不会往下执行;当前线程休息,CUP也休息;用户态切换到内核态;

 具体的实现:

 mach_msg();直接sleep;内核层面的休息;

 用户态                                  内核态

  mach_msg()------->    mach_msg()

 等待消息

 没有消息就让当前线程休息

 有消息让线程唤醒

 whiel(1);这也是一种阻塞,当前线程没有休息还是在执行,CUP没有休息;

总结如图所示:


图1-1


九、实际应用场景

    1.控制线程生命周期(AFNetWorking)

    2.解决NStimer在滑动时停止工作问题

    3.性能优化,

    4.监控应用卡顿

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

推荐阅读更多精彩内容