|
|
“请问你是?”
“不用请问,我就是RunLoop”
“你好,我是iOS开发者,我听说过你,不过抱歉,对你的名声我早有耳闻,只是不很熟悉。”
”嗯,不难理解。毕竟我在幕后,你在台前,我是说句不妄言的话,没有我,你们就别想玩的转。“
”哦 ? 这么说的话,我确实很好奇,请问你能不能介绍下你自己!
RunLoop的概念
人如其名,我就是RunLoooooooooooooooop,像是一个死循环,不停的跑圈,永不懈怠。除非程序不启动,或者你们代码写的太差,以至于crash,我才不得不停止了。
可是程序启动与否和你有什么关系?
程序启动伊始,有一段代码:
这段代码永远不会执行结束,不然程序也停止了。所以我伴随着程序的启动,一直存在。
当然,我不可能无意义的瞎跑。我贯穿整个程序,在奔跑的过程中帮忙处理各种事情。
我主观上听明白了你的意思,但冒昧的说一句,除了瞎跑我还真不知道你到底做了啥?
我理解,我理解,毕竟你们花费大量的时间和UIKit和Foundation的各种类打交道,丝毫不顾及我的存在感。但你有没有好奇过,你的事件响应,各种手势识别,定时器都是怎么传递的啊?
抱歉,你这么一问,我确实欠考虑了。
对啊,所以这就是我从幕后走向台前的目的。写代码不能只看表面,还要挖挖本质。话说回来,官方点说呢,我就是消息机制的处理模式,从线程start到线程end,一直在循环检测,检测inputSource(如点击,双击等操作)同步事件,检测timeSource同步事件,检测到输入源后会执行处理函数,首先会产生通知,CoreFunction向线程添加runLoop Observers来监听事件,意在监听事件发生时来做处理。
RunLoop和线程之间的关系
麻烦停一下,我只知道很多事情是线程来做的,比如页面刷新交给主线程,异步线程来下载。但听你的口气,这些功劳都是你的了?
对,从表面看来你们都是在操作线程,但这只是表面。我和线程是绑定在一起的。每个线程都有一个对应的 Runloop 对象。当然不同线程的RunLoop是有区别的,主线程(也是你们常说的UI线程)的 Runloop 会在应用启动时完成启动,其他线程的 Runloop 默认并不会启动,只是在需要使用时,你们手动启动。
RunLoop不同的Mode
你的意思是,每一个线程都有一个RunLoop,但默认情况下只有主线程的才会开启。
对,不仅每个线程都有RunLoop,而且每一个 RunLoop都包含若干个 Mode,这么给你解释吧,你肯定玩过LOL吧,知道里面有鞋子
的装备吧。
这个倒忘不掉。
那就好说了,正常来说,每个鞋子都有自己偏重的功能属性,不同英雄都只会买一种鞋子对不对。我也一样,我虽然有多个Mode,但就像穿鞋一样,每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 RunLoop,再重新指定一个 Mode 进入。这就像换鞋子一样,必须先脱掉旧鞋才能穿上新鞋。
当然,鞋子不同,有的偏重法术,有的偏重攻速,你所谓的Mode是怎么区分不同的?
其实每个 Mode 包含若干个 Source
/Timer
/Observer
,这些都属于Mode的item,item不同,Mode也不同,RunLoop分为五类。
|
|
虽然模式很多,但iOS中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
这里就有点抽象了,这么多Mode你是怎么选择的呢?
主线程的 RunLoop 里有两个预置的 Mode:NSDefaultRunLoopMode和 NSEventTrackingRunLoopMode。这两个 Mode 都已经被标记为”Common”属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创建一个 Timer 并加到 DefaultMode 时,Timer 会得到重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。
但如果我想滑动ScrollView时,不影响Timer咋办?
既然在DefaultMode下,不影响Timer,TrackingRunLoopMode下能滑动,两者都不影响,就是两种模式都要就ok了,iOS中的commonModeItems就是就是将DefaultMode和TrackingRunLoopMode组合在一起了,因此只用切换到commonModeItems就ok了。
RunLoop的内部逻辑
哈哈,果真我对你了解的太不到位了,原来你是超神的存在。
不不不,超神不至于,其实我也只是给系统跑腿罢了。但我的一举一动也要接受管理,不能随便乱来。
谁还能管的了你啊?
怎么管不了,能力大,责任大。我要时时刻刻接受系统的监督。你们对我的了解可能从NSRunLoop开始的,但这实际只是OC对我简单的封装,我的底层是C语言库CFRunLoop,这里面有一个叫CFRunLoopObserverRef的观察者,也就是前面我给你提到的Observer
,当我的状态发生改变时,观察者就会记录我的变化。
|
|
这个倒不难理解,毕竟UIView,UIController,UIApplication都有类似的管理。
RunLoop与NSTimer的准时触发
可是RunLoop与NSTimer有什么关系呢?
NSTimer其实是一种资源,但它要想起作用必须添加到RunLoop中。
NSTimer会是准时触发事件吗?
timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。
这个怎么理解?
正常情况下,你指定一个事件2秒之后触发,但若是此时恰好有一个大规模的连续耗时运算,那timer的执行必然要等到该连续事件处理结束才会开始执行,此时你就无法保证NSTimer的准时触发了。当然这只是针对于一次执行的timer,
对于重复性事件,情况也不一样。比如一个程序,你设置了周期性1秒触发,但是有个耗时事件用时两秒,此时就无法准确触发,并且以后会随着这个延迟继续延迟。
RunLoop的相关实战
说了那么多,我大约感受到你的神奇魔力了,但还是过于抽象和偏理论,有没有具体的实例来彰显你的存在感啊!
那是必然,那我虎躯一震,抖一抖我的黑魔法。给你说几个具体的场景吧。
AutoreleasePool的真谛
问你个问题:从MRC的手动管理内存,到ARC的自动管理,其关键因素是什么?
因为多了AutoreleasePool,自动释放池。只是我也不知道AutoreleasePool背后到底帮助我们做了什么?
哈哈,其实这和我RunLoop有很大的关系,在App启动后,会在主线程度的RunLoop帮我们创建两个Observer。
第一个 Observer 只监视了一个事件:监听事件在Entry(即将进入Loop)期间,其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。
第二个 Observer 监视了两个事件:在BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的,被这些inputSource和timeSource包裹。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
你这么一说我就理解了,难怪之前有人告诉我,内存的释放是在每次RunLoop结束之后呢。
这算是一个场景,当然还有其它的。
关于performSelecter:afterDelay:
方法
好的,你接着说。
你有没有遇到过这样的场景,我在子线程中执行performSelecter:
,一切ok,但加了延时,执行performSelecter:afterDelay:
方法时,却愣是没反应?
对对对,后来别人告诉我在子线程开了RunLoop就ok了,至于为何,我现在还是云里雾里的。
其实这和NSTimer有关,当调用 NSObject 的 performSelecter:afterDelay:
后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。而子线程默认没有开启RunLoop,就无法执行timer事件,自然就不执行。
原来如此。
GCD
既然聊到了线程问题,我想问下线程之间的通信问题。比如我在异步子线程执行了网络请求,想把请求回来的结果通过异步主线程
dispatch_async(dispatch_get_main_queue(), block)
,的方式将block回调给主线程,这应该也很你们有关系吧。
对的,对于子线程和主线程之间的通信。当调用 dispatch_async(dispatch_get_main_queue()
, block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。