runloop的底层分析

2018-01-19  本文已影响0人  梁炜东

runloop定义

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


image.png

第一:这个main函数是执行完就退出了吗,当然不是。
第二:以前有没有听老师说过这个main函数是个死循环吗,这个函数里的 UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]))会开启一个死循环,而这个死循环就是我们的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处理上面问题的过程:

继续引入

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


image.png

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

上面说了那么多,问题依然没被完全解决,现在不我们停止了用户交互,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起来。这就是我们平时所说的“常驻线程”
上一篇下一篇

猜你喜欢

热点阅读