美文网首页
runloop的底层分析

runloop的底层分析

作者: 梁炜东 | 来源:发表于2018-01-19 18:37 被阅读0次

    runloop定义

    • runloop:运行循环

    不管是我们的操作系统,还是我们的程序,他难道是运行起来之后,泡完一堆代码就停了吗,肯定不是的,他们都是运行玩一堆代码之后,即使没事干了,他要是在运行着呢,等待有事件过来触发,他在继续运行,这就是runloop
    在我们的iOS程序中有一个main函数:


    image.png

    第一:这个main函数是执行完就退出了吗,当然不是。
    第二:以前有没有听老师说过这个main函数是个死循环吗,这个函数里的 UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))会开启一个死循环,而这个死循环就是我们的runloop

    • runloop的作用

      1. 保证程序不退出。他就是一个死循环
      2. 负责监听事件,触摸,时钟,网络事件(系统事件,内核事件等等)
      3. 如果没有事情,我就睡觉
    • runloop逐步分析学习

    image.png

    由上图代码可知:
    我们这个定时器的方法是在主线程上跑的,那么我们想个问题,如果此时我们有何scrollview,我们不停的在拖拽住这个scrollview,那么这个定时器方法还会走吗?
    答案:肯定是不会走的,为什么呢?首先我们会说主线程阻塞了,主线程在干UI的事,所以时钟事件就被阻塞了,这个也对,但是今天我们从runloop的角度来分析一下这个问题:
    我们学习runloop相信大家都见到过苹果api文档里面的这个图:


    image.png

    这个图呢,也就是苹果官方文档告诉我们有上图这三种事情是有runloop监听的(Observer, Source, Timer)
    至此:我们发现在“其中一种”模式下,runloop可以监听三种事情,但是要知道某一时刻他只能处理这个模式一下的一种事件。

    继续引入

    上面我们提到了说在其中一种模式下这个模式的概念,那么runloop一共有几种模式呢,答案是5种模式(我们需要掌握的是2种即可)即--UITraking和Default模式


    image.png

    现在我们就分析一下上面的timer方法不走的问题:
    1,拖拽scrollview也就是触摸事件,他最终就是Source事件
    2,我们的定时器就是Timer,它是timer事件
    3,我们要知道scrollview的触摸事件是在UITraking模式的,即它是UITraking模式下的 Source事件
    4,我们还要知道NSTimer的定时器默认是加在Default模式下的,即它是Default模式下的Timer事件
    好,有了这四个常识之后,我们来说一下runloop处理上面问题的过程:

    • 首先上去没事干的时候runloop是在一边睡觉的(当然会有一些系统事件需要他处理,不过会非常快,这里我们就不考虑了,不影响我们分析问题,只是让你知道他并不是完全的在睡觉)
    • 接下来我们不是在有了一个定时器吗,它是在Default模式下的timer事件,这时候由于定时器是一秒一次,所以我们的runloop就是一秒跑到default模式下处理一下他的timer事件(当然如果default模式下有observer或者source事件他也处理,就是跑到default模式下处理他的事件),处理完之后,啪,runloop就走了,就去睡觉了,所以我们只弄定时器的时候发现打印很及时,就是因为他一秒来一次,然后睡觉,在一秒又过来处理,完了再去睡觉,很及时,所以我们发现都是再正常打印
    • 然后我们开始不断的拖拽触摸这个scrollview,因为这个是在UITraking模式下的source事件,这时候,runloop的过程就是:由于我们是拖拽这个scrollview,所以这个UITraking模式下的source事件就不停的要走,所以runloop就是来处理UITraking模式下的source事件,处理完刚走立马又被叫回来处理,特别快,不停的处理睡觉,处理睡觉,完全顾不上去处理Default模式下的timer事件,所以就造成了我们上面问题中的定时器方法不走。(你会疑问,我草不是定时器先开始的吗,不应该是先处理我们的定时器吗,即使不是,也应该处理一段sources事件之后,我们定时器需要了他应该过来一次再去处理UITraking模式下的source事件啊,怎么完全就不过来了呢,这里牵涉到一个基本优先级问题,就是如果UITraking模式和Default模式都有事件需要runloop处理,那么他会优先处理UITraking模式模式下的事件)。
    • 到这里就通过runloop说明了为什么定时器方法不走了

    继续引入

    由于上面那样做的话定时器不会及时走,但是我们还是想要让定时器的方法在拖拽scrollview的时候能够继续走,我们怎么办呢?
    答案:就是把这个定时器加到UITraking模式下,这样他就在和scrollview的触摸事件source在同一个模式下了


    image.png

    这样就把定时器加到了UITraking模式下,这时候你去拖拽scrollview的时候这个定时器的方法还会走,还会打印。
    但是没完,你会发现,卧槽,当我松开不去拖拽scrollview的时候,这个定时器也不走了,这是什么情况:

    • 这是因为,这个UITraking模式是在有UI交互的事件触发的时候,才会唤醒runloop过来处理,而现在我们不动scrollview了,runloop就去睡觉了,所以UITraking模式下的timer事件也就不会执行了(UITraking 模式,只有UI交互的时候才会去唤醒runloop过来处理,所以这个定时的事件不能够唤醒runloop过来处理)。
    • 所以稍微总结一下,这个定时器默认的都是default模式,所以runloop就是,当有UI交互事件的时候,他会立马被叫到UITraking模式这里来处理(因为优先级比default模式高),如果没有UI交互事件他就去休息了,如果有default模式下的事件的时候,他就被叫到default模式下了(除了我们所说的UI交互事件,其他的默认的事件一般都是在default模式下的,当然我们可以像上面的定时器一样来修改她所在的模式)

    上面说了那么多,问题依然没被完全解决,现在不我们停止了用户交互,runloop就离开了UITraking模式,所以定时器也不被执行了,我们想要的是拖拽的时候不影响执行定时器,不拖拽还不影响,那么我们思考,这该怎么办呢,我们想有没有一种模式是可以把这个定时器既添加到UITraking模式也添加到default模式呢,答案是有,就是下面写的runloop模式的第三种模式NSRunLoopCommonModes


    image.png

    到这里OK了,问题彻底被解决了,定时器可以欢快的跑起来了

    runloop模式

    • NSDefaultRunLoopMode--默认模式
    • UITrackingRunLoopMode--用户交互模式(只要用用户交互事件!!runloop就会切换到这个模式下)
    • NSRunLoopCommonModes--占位模式!占有了上面两种模式。上面两种模式下都有效(上面两种是特定模式,这个并不是特定模式,只是上面两种都有效果,所以我们最初讲的就没说有三种模式而是说有两种模式)
    • 4.5两种模式不要管,系统内核事件模式,项目初始化模式,这两种模式我们根本无法处理和触摸得到,我们根本管不了,所以我们不要管这两种模式

    线程和runloop的几个概念

    1. 每一条线程上都有一个runloop
    2. 主线程为什么不会挂掉???因为主线程的runloop正在run
    3. 子线程上面的runloop默认不启动,所以我们平时用的子线程执行完代码就挂掉了

    分析1,我们每开辟一个子线程都会有一个对应的runloop,只是这个runloop默认没有开启
    分析2,主线程的runloop默认是开启的,并且主线程为什么一直不会挂掉呢,就是我们开头说的main函数里面是个死循环,一直保持runloop在run,所以runloop在run所以主线程就不会挂掉
    分析3,子线程平时我们通过gcd开辟的子线程使用完之后不就销毁了,这就是因为runloop默认不启动。你会疑问,觉得gcd的确是自己销毁了,但是我们平时学的NSThread这种我来个全局对象,这个字线程执行完代码,self.thread对象不是还在吗,没有挂掉啊,这个你怎么解释,其实这个并不是线程没有挂掉,而是你的oc线程对象没有销毁,其实内部的线程已经没有了,你可以在此时再次试图通过self.thread去start一下,就会崩了,这就说明,只是你的oc对象还在,他对应的里面的线程已经被销毁了

    引入,通过上面的学习,你觉得我们上面解决定时器的那个问题,有问题吗?

    答案是有的,为什么呢,你想啊我们刚才不管是通过打印还是学到的知识都知道当前的定时器是添加在主线程的runloop上的,这个没疑问吧,现在问题来了:
    1,如果我在定时器方法里面做耗时操作怎么办(比如说我让当前线程(主线程)睡眠5秒中),这时候我们拖scrollview是不是就会特别卡,当然我们学习oc第一天老师就告诉我们不能在主线程做耗时操作,但是目前我的需求就是这样要在定时器方法里面做耗时操作,这时我们应该怎么办?
    2,怎么办,当然是把这个定时器放到子线程了,接下来看截图中的代码


    image.png

    这样是不是就可以了,肯定不行的,我们上面说了,子线程执行完就销毁了,还怎么去执行定时器方法啊,那么为什么会销毁,上面也说了,因为默认子线程的runloop没有开启,所以我们需要把子线程的runloop开启


    image.png
    开启完之后,这子线程就不会销毁了,所以定时器的方法就可以愉快的在子线程的runloop处理下运行了,这时候即使在定时器的方法中做耗时操作也丝毫不影响我们拖拽scrollview的卡顿问题

    提问:如果我们现在在runloop的run之后紧挨着写一个打印,他会执行吗?


    image.png

    答案肯定是不会的,因为[[NSRunLoop currentRunLoop] run];一运行就相当于死循环了,这个线程下面的代码就不会走了,所以肯定就不会打印了,这个和主线程的runloop类似,也就是和咱们上去讲的main函数里的一样,死循环了

    你会疑问,你都死循环了,我程序怎么还会走,你想啊,人家这个字线程死循环管你程序什么事,只是我这个线程里面死循环了,你外面该怎么着还怎么着就行了

    继续引入
    卧槽,尽管咱们说没关系,不影响程序跑,但是在子线程中死循环并且永远也停不了,不可控制,这总归不好吧,所以我们要自己实现这个runloop,他不就是个死循环吗,我们自己也来模拟这个死循环,并且用过变量来控制是否结束死循环这不就完美了吗?
    看下面代码:


    image.png

    到此终于结束了,我们完美解决了问题

    还有啊,咱们这个完美觉了定时器被阻塞,或者定时器耗时操作卡顿住UI,但是你没发现有点小麻烦啊,你会说,我平时都不用NSTimer定时器我用的都是gcd的那种:


    image.png

    谁还用你说的这个NSTimer,在此说明一点这个gcd的内部封装其实就是咱们上面自己写的,gcd只是做了封装而已,仅此而已

    写在最后

    上面我们说了给timer定时器加到了子线程里,但是子线程执行完就销毁了。

    1. 提问,子线程什么时候销毁?
    2. 提问,怎么才能让自线程不销毁?
      答1:线程销毁分为:1》主动销毁(程序员自己写代码exit让其销毁)2》被动销毁(线程没有任务了,就是没事干了,就是他的代码执行完了就销毁了)。这就是我们平时所说的“线程销毁”
      答2:不让其销毁,根据1,我们说让他一直有任务,你怎么让他一直有任务,写个死循环啊,不现实,那么就用到了我们的runloop,就让让这歌自线程的runloop run起来。这就是我们平时所说的“常驻线程”

    相关文章

      网友评论

          本文标题:runloop的底层分析

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