“请问你是?”
“不用请问,我就是RunLoop”
“你好,我是iOS开发者,我听说过你,不过抱歉,对你的名声我早有耳闻,只是不很熟悉。”
”嗯,不难理解。毕竟我在幕后,你在台前,我是说句不妄言的话,没有我,你们就别想玩的转。“
”哦 ? 这么说的话,我确实很好奇,请问你能不能介绍下你自己!
RunLoop的概念
“人如其名,我就是RunLoooooooooooooooop,
像是一个死循环,不停的跑圈,不停的跑圈,永远不知道疲倦。除非程序不启动,不然我永远不会停下来,当然如果是你们代码写的太差,有crash,那我也不得不停止了。”说点官方的,我其实是消息机制的处理模式,其实我一直在循环检测,从线程start到线程end,检测inputSource(如点击,双击等操作)同步事件,检测timeSource同步事件,检测到输入源会执行处理函数,首先会产生通知,CoreFunction向线程添加runLoop observers来监听事件,意在监听事件发生时来做处理。
可是程序启动与否和你有什么关系?
程序启动伊始,有一段代码
|
|
这段代码永远不会真正停止,不然程序结束了,程序也停止了。所以我伴随着程序的启动,一直存在。
当然,我不可能无意义的瞎跑。我贯穿整个程序,在奔跑的过程中帮忙处理各种事情。
我主观上听明白了你的意思,但冒昧的说一句,除了瞎跑我还真不知道你到底做了啥?
我理解,我理解,毕竟你们花费大量的时间和UIKit和Foundation的各种类打交道,丝毫不估计我的存在感。但你有没有好奇过,你的事件响应,各种手势识别,定时器都是怎么传递的啊?
抱歉,你这么一问,我确实欠考虑了。
对啊,所以这就是我从幕后走向台前的目的。写代码不能只看表面,还要挖挖本质。我不停的run,也在不断在做事情,主要负责两大块东西:input Source
和Timer Source
。RunLoop和线程之间的关系
麻烦停一下,我只知道很多事情是线程来做的,比如页面的刷新交给主线程,异步线程来下载东西。但听你的口气,这些功劳都是你的了?
对,从表面看来就是如此,但是你还是只关注了表面不是。我和线程是绑定在一起的。每个线程(包括主线程)都有一个对应的 Runloop 对象。只是你们并不能自己创建 Runloop 对象,但是可以获取到系统提供的 Runloop 对象。
主线程(也是你们常说的UI线程)的 Runloop 会在应用启动时完成启动,其他线程的 Runloop 默认并不会启动,只是在需要使用时,你们手动启动。
RunLoop不同的Mode
你的意思是,每一个线程都有一个RunLoop,但默认情况下只有主线程的才会开启。
对,不仅每个线程都有RunLoop,而且每一个 RunLoop都包含若干个 Mode,怎么给你解释呢,你肯定玩过LOL吧,知道里面有鞋子
的装备吧。
这个倒忘不掉。
那就好说了,你知道正常来说,每个人都只会买一种鞋子对不对,每个鞋子都有自己偏重的功能属性。我也一样,我有多个Mode,每个 Mode 又包含若干个Source
/Timer
/Observer
。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,就像每次你只能穿一种鞋一样,当然这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。就像换鞋子一样,必须先脱掉旧鞋才能穿上新鞋。这样做主要是为了分隔开不同组的Source
/Timer
/Observer
,让其互不影响。
其实每个Source
/Timer
/Observer
都是Mode的不同item,item不同,Mode也不同,主要可以分为五类。
|
|
但是iOS 中公开暴露出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。 NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
这里就有点抽象了,这么多Mode你是怎么选择的呢?
既然你觉得抽象,我再拿LOL里的鞋子给你举个栗子。最基础的鞋子能满足单纯走路的需要,只有你在想增加额外属性时,就需要合成新的鞋子。RunLoop也一样,通过增加item来组合完成一个新的Mode,默认情况下,在主线程开启默认状态就是NSDefaultRunLoopMode,
可以举一个具体的例子么?
比如在滑动tableView,会从默认的NSDefaultRunLoopMode 切换为NSEventTrackingRunLoopMode,当滑动停止时,又切换为NSDefaultRunLoopMode。
RunLoop的内部逻辑
哈哈,果真我对你了解的太不到位了,原来你是超神的存在。
不不不,超神不至于,其实我也只是给系统跑腿罢了。但我的一举一动也要接受管理,不能随便乱来。
谁还能管的了你啊?
怎么管不了,能力大,责任大。我要时时刻刻接受系统的监督。你们对我的了解可能从NSRunLoop开始的,但这实际只是OC对我简单的封装,我的底层是C语言库CFRunLoop,这里面有一个叫CFRunLoopObserverRef的观察者,也就是前面我给你提到的Observer,当我的状态发生改变时,观察者就会记录我的变化。
|
|
这个倒不难理解,毕竟UIView,UIController,UIApplication都有类似的管理。
对RunLoop与NSTimer
可是RunLoop与NSTimer有什么关系呢?
NSTimer其实是一种资源,但它要想起作用必须添加到runLoop中。
NSTimer会是准时触发事件吗?
timer不是一种实时的机制,会存在延迟,而且延迟的程度跟当前线程的执行情况有关。
这个怎么理解?
正常情况下,你指定一个事件2秒之后触发,但若是此时恰好有一个大规模的连续耗时运算,那timer的执行必然要等到该连续事件处理结束才会开始执行,此时你就无法保证NSTimer的准时触发了。当然这只是针对于一次执行的timer,
1 [NSTimer scheduledTimerWithTimeInterval:1 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
对于重复性事件,情况也不一样。比如一个程序,你设置了周期性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 处理的。