本帖最后由 Simpon 于 2016-10-19 11:52 编辑
这篇文章主要介绍iOS开发中一个非常重要的对象:RunLoop。中文名:运行循环 为了能让大家更好的理解这个对象在iOS开发的作用,我请来了我的好朋友,外号人称黑妞。呵呵,别看名字有个妞喔,那可是大老爷们。而我呢,我就没有这么霸气的外号咯。江湖人称小智。
黑妞:哎,别费话这么多,赶紧上代码吧。哦,不对,赶紧给我讲讲运行循环。
小智:黑妞发话,谁敢不从。马上开始咯。
黑妞:先给我说说运行循环有什么作用?
小智:哎,还说你有四年iOS开发经验,竟然问这么无知的问题。不过我理解你的无知,毕竟在实际开发,真正需要我们程序员写代码用到他们的地方不多。但是你怎么说也是一个资深的iOS开发工程师了,如果对这个对象还理解不清楚的话,就有点说不过去了。
黑妞:真的要真正理解这个对象的作用,才能说自己是资深的iOS开发工程师吗?
小智:这不是废话吗?别BB了,听我说吧,有什么问题再提问。
黑妞:好吧,就让我静静看着你装逼。
小智:RunLoop在iOS开发中扮演着非常重要的角色,在整个iOS应用运行过程中,没有RunLoop的话,应用也运行不起来。
黑妞:我从事iOS开发也四年了,我怎么没感受到这个对象的威力?甚至可以完全不用跟这个对象打交道也一样可以完成开发呀,怎么就被你说得这么神奇,这么牛掰了。既然这样,你给我说说什么运行循环?
小智:哎,都不知你这四年iOS开发学到了什么?那我就给你说说什么是运行循环?运行循环在官方有一个更牛逼的名字,叫RunLoop。这个RunLoop拆解出来就是由Run + Loop两个单词组成,国内的开发者就将这两个单词翻译并组合后就得到:运行循环 这四个字。别告诉我你不知道我一直在说的运行循环就是官方文档常见的RunLoop。
黑妞:你的意思就是RunLoop和运行循环就是同一个东西两种叫法?
小智:非常聪明。理解能力不错。真的适合搞开发。为了显示自己更专业,下面我们就统一叫RunLoop。
黑妞:不用夸我,是人都知道我聪明。你刚刚也只是简单的说了运行循环的名字而已,这个我都懂。既然RunLoop它是一个对象。那么它是什么时候创建出来的呢?我们程序员并没有主动创建过它呀。
小智:这个问题问得非常好。官方文档明确的告诉了我们开发者,RunLoop是不需要程序员手动创建的,在实际开发中,如果我们需要用RunLoop,则需要直接获得就行了。一看你就是不常看官方文档的人,这是不行的,我建议你有空的时候还是看看官方文档吧,里面会有很多你还未触及的知识点呀。
黑妞:确实,平时比较少看官方文档。好吧,以后有时间就去看看。原来RunLoop是不需要我们手动创建的。我一直以为只要是对象就必须程序员alloc。今天终于涨姿势了。那它是什么时候创建的?在哪里被创建的呢?
小智:在回答这两个问题之前,我先跟你说说RunLoop的作用先,只要搞清楚了它的作用,其他问题就好理解。要不你先说说你理解的RunLoop在iOS应用中起的作用?
黑妞:额.......不知道.....还是......你说吧
小智:不想打击你了。其实要理解RunLoop的作用不难,先从它的名字开始说吧,RunLoop翻译成中文比较接地气的名字就是:运行循环。用大白话说就是运行一个循环,类似各种编程语言中的while和for循环。别说你不懂什么是循环...开玩笑的。我们都知道循环的特点就是当某一条件成立,就重复的做某件事情或某项功能,直到条件不成立,如果条件一直是成立的,那么这是什么循环?
黑妞:死循环呗。
小智:恭喜你,答对了。果然内功深厚,这么高深的问题竟然能秒答。佩服佩服......
黑妞:失禁失禁......
小智:呵呵...接着说吧。刚刚说到如果条件一直是成立的,那么这个循环就是死循环了。而在我们iOS开发中,允许出现死循环吗?
黑妞:那必须不能呀。
小智:no,no,no....你这个回答可以说是对的,也可以说是不对的。
黑妞:为什么?
小智:说是对的,是因为在实际开发中,对应我们程序员来说,确实不能出现死循环。说不对,是因为在iOS开发中还必须要存在死循环。
黑妞:怎么理解这句话?
小智:先问你个问题:你有学习过C语言吗?
黑妞:那必须呀,C语言可以说每一个程序员走上编程之路第一个学习的内容。可以说它是全有的编程语言的鼻祖呀。
小智:那就好办了。那我们先回想下我们在刚学习C语言的时候,所有的代码是不是都写在main函数中呀?
黑妞:是呀,因为main函数是程序执行的入口呀,不仅C语言的执行入口是main函数,所有的编程语言的执行入口都main呀。iOS开发也不例外呀。
小智:完全同意你的说法。那C语言的main函数执行完毕之后,程序是否就退出了?
黑妞:退出了呀。执行完main函数,程序就会被终止呀。
小智:C语言程序确实是执行完main函数就退出了,但是iOS应用程序是这样的吗?
黑妞:你傻呀,如果iOS应用程序启动执行完main函数就退出了,那用户怎么使用这个程序?怎么去跟应用交互?很明显iOS程序跟C语言程序不一样呀,它执行main函数之后,并不会退出程序的。
小智:说得一级棒。你这么牛逼,你能不能告诉我,为什么iOS程序执行main之后不会直接退出程序呢?tell me why?
黑妞:我想想...我勒个去,还真没考虑过这个问题呢。不是你傻,是我傻了....难不成你知道为什么?
小智:这不是废话吗?不知道我还敢跟你叫嚣。呵呵...洗耳恭听吧!!!
黑妞:我倒是看看你能说些什么牛逼的东西出来。
小智:iOS程序执行完main函数之所以不退出,都是RunLoop起的作用。如果没有RunLoop,iOS程序执行完main函数之后,也会像C语言程序一样,直接就退出了。说到这里,你能大概猜测出RunLoop的作用了吗?
黑妞:恩,我知道了。RunLoop的作用就是保持iOS应用程序永远不退出。这应该不会错了吧。
小智:终于答到对了。四年开发经验果然名不虚传呀。那我再问你,RunLoop凭什么就能保持iOS应用程序永远不退出呢?
黑妞:我x...还能不能愉快的聊下去了,老是问我不知道的问题。
小智:哈哈...看你天王名字这么霸气,还以为你什么都知道呢。不知道很正常呀,很多你知道的东西我也不知道呀。对吧。相互学习,共同进步就对了。
小智:在回答RunLoop凭什么就能保持iOS应用程序永远不退出这个问题之前,你帮我解决一个需求问题。
黑妞:什么问题? 你说吧,我知道肯定告诉你怎么做。
小智:如果我想实现这么一个功能:当C语言程序执行main函数的时候,永远不会退出,你有什么实现方案吗?
黑妞:请给我两份钟时间想想.......
小智:10,9,8,7,6,5,4,3,2,1。时间到。想到了吗?
黑妞:我去...还以为很难呢,这么简单的问题。在main内部搞一个死循环就实现了main永不退出了呀......等等,我明白了,原来你是想告诉我,iOS程序运行之后能永远不退出是因为内部有一个死循环,而这个死循环就是我们一直在说的RunLoop,RunLoop本身就是一个死循环?
小智:全对!!!聪明就是不一样,一点就通了。
黑妞:跟小智哥您的差距还是一大截呀。你是怎么知道这些底层的东西的呀?
小智:看官方文档呀,都说得很清楚的。但是你别得意,你以为RunLoop的作用就是简单的搞个死循环让程序永不退出就行了? 那你就错了,这仅仅是RunLoop其中的一个作用而已。
黑妞:不是吧,还有其他作用?赶紧的...都告诉我吧,好让我以后能装装逼呀。
小智:着毛线急呀。RunLoop的其他作用,等会再告诉你,现在你只要记住它的第一个作用:iOS程序之所以启动完毕一直保持运行状态完全是因为内部有一个RunLoop,RunLoop本身就是一个死循环。RunLoop不退出,程序就不会退出。(不考虑程序异常奔溃或用户主动杀死程序情况)
黑妞:好,我记住了。这些底层的原理知道后,突然有种豁然开朗的感觉呀。小智哥,你真是.....
小智:....行了,不要赞美我。现在我们先来解决下开始遗留的两个问题先。
黑妞:哪两个问题呀?
小智:你妹呀,这么快就忘了,倒回去看聊天记录..RunLoop是什么时候创建的?在哪里被创建的呢? 你的记忆...哎。现在你能回答这两个问题了吗?
黑妞:我想我大概知道它是什么时候创建的了。
小智:知道就说呀,装什么。
黑妞:根据RunLoop的第一个作用,我可以大胆的猜想:当程序启动的时候,在执行main函数中的UIApplicationMain函数内部会将RunLoop对象创建出来。通过查看UIApplicationMain函数声明,该函数会返回一个int类型的值,但通过刚刚的结论,这个UIApplicationMain函数永远也不会有返回值,因为如果这个UIApplicationMain函数有返回值,那么main函数就会接收到返回值就直接退出了,这样就不能保持应用程序一直处于运行状态了。所以UIApplicationMain内部必然会创建RunLoop对象来实现永不退出的功能。结论:在程序启动的时候,在执行UIApplicationMain函数内部,系统会负责创建RunLoop对象。
小智:Well Done!!!太精彩的分析了。此处应该有掌声...分析得很到位。看来你已经慢慢跟上我的步伐了。哈哈哈...你已经回到了刚刚的两个问题了。你说得没错:RunLoop会在程序启动的时候被系统创建,在执行UIApplicationMain函数内部被创建的。但是,在用户使用应用的过程中,系统还会创建其他的RunLoop对象。
黑妞:不是吧,一个iOS程序不是有一个RunLoop就够用了吗?还会创建多个吗?
小智:谁告诉你有一个RunLoop就够用了?你知道得太少了,加油吧...骚年。
黑妞:快快快...把你的一生绝学都传授给我吧。
小智:好吧,看在我们好基友一辈子的面子上,就再传授点知识给你吧。RunLoop的作用除了刚刚说的之外,还要其他的重要作用,其中最为突出的作用就是:监听系统产生的以及用户产生的(点击事件,触摸事件,拖拽事件...)所有事件。
黑妞:不能理解,那它是怎么监听呢?你这么一说,我一头小星星了。
小智:给你看个图,下面这个图片是从官方文档中截出来的,你看看能看得明白这图想表达的意思吗? file://localhost/Users/apple/Library/Caches/TemporaryItems/msoclip/0/clip_image002.png
黑妞:这图我很早就看到过了,看不懂就不了了之,这个跟RunLoop有1毛钱关系吗?
小智:这不是1毛钱那么简单,这张图就能充分验证RunLoop的另一个重要的作用:监听系统产生和用户产生的事件。跟着我的思路来,我给你分析分析这图所表达的意思:从左向右,向下向左来剖析这张图,当应用程序启动时系统内部就会创建这个RunLoop对象了,当用户触摸或点击屏幕时,系统底层就会根据当前用户触摸或点击的位置创建一个事件对象,这个事件对象会经过操作系统,经过对应的端口,进入应用程序的事件队列中。一旦有事件进入事件队列中,RunLoop就会立即作出响应,并从事件队列中取出前面的事件,然后将事件对象传递给UIApplication对象,UIApplication对象接收到RunLoop传递给它的事件对象之后,继续将事件对象传递下去,查找一个最合适处理事件的对象(UIResponder),找到之后,就可以开始处理事件。这就是事件处理的流程了。其实最重要的环节在哪里?你知道吗?
黑妞:恩恩...最重要的环节应该是当事件队列中有事件对象的时候,RunLoop会从事件队列中取出前面的事件对象,将事件传递给UIApplication对象。事件对象才有机会被处理...是这样吗?
小智:对的。通过刚刚的分析,我们可以得出这样的结论:RunLoop另一重要的作用:监听事件和传递事件。没有RunLoop,任何事件都得不到处理,得不到响应。
黑妞:哇...豁然开朗了,关于RunLoop在iOS扮演的角色一直困扰了我很久,今天总算明白了。
小智:哈哈...那你觉得RunLoop还有其他作用吗?
黑妞:不要问我了,你越问越显得我无知了,还是听你说吧。
小智:这么容易就被打击了呀,呵呵..既然你这么谦虚了,那我最后给再给你说点RunLoop的其他非常重要知识点。让你彻底弄明白RunLoop,一般人我还不告诉他呢....
黑妞:太感谢小智哥了,今晚的晚餐我包了。
小智:就知道吃...在实际开发中,除了刚刚说的,关于RunLoop有一点是必须要知道:每个线程内部都会默认创建对应的RunLoop对象。再回去看看上面的图,run loop 上面是不是有一个main,这个main表示的就是主线程。刚刚一直说的RunLoop都是主线程的RunLoop,别紧张...不管是主线程的RunLoop还是子线程的RunLoop,它们的作用跟刚刚说的是一样。
黑妞:等等...你又重新刷新了我的三观。你说每个线程内部都会默认创建对应的RunLoop对象,那我就瞬间一堆问题了。
小智:有问题就直说。
黑妞:好。问题1:根据最开始得出的结论:RunLoop是一个死循环,它的其中一个作用就是保持应用程序不退出。如果我开启了10个线程,每个线程内部有一个RunLoop的话,意味着每一个线程内部就有一个死循环,那这样不就导致线程执行完耗时操作之后永远无法退出,永远不会被销毁直到应用程序退出,这样不就导致资源浪费性能差吗?
小智:这个问题问得非常好。这就是我接下来我说的问题,每一个线程内部都会有对应的RunLoop是肯定的,但是默认只有主线程的RunLoop是开启,而子线程的RunLoop默认是不开启的。RunLoop不开启可以理解为死循环是没有执行的。所以你刚刚说的问题其实不存在的,除了主线程之外,所有子线程在执行完对应的任务之后,就会被系统销毁了。
黑妞:原来这样呀!好吧...那问题又来了:子线程在执行完对应的任务之后,就会被系统销毁了,那主线程什么时候销毁?
小智:你这个问题问得太没技术含量,这是菜鸟才会问的问题呀。在程序启动时系统内部创建的第一个RunLoop就是主线程的RunLoop呀,为了保证程序不退出,主线程的RunLoop默认就是开启的。主线程如果退出的话,那就意味着整个程序也退出了。
黑妞:这个问题确实不该问,那下一个问题:既然子线程的RunLoop默认是不开启的,为什么还要在子线程内部创建一个RunLoop?有什么作用呢?
小智:因为每一个线程内部的事件都是交给其对应的RunLoop来监听的,子线程内部如果有事件发生的话 ,那么它内部的RunLoop就可以监听到并作出处理。说到这里,你可能又会问,子线程的RunLoop默认不开启的,那么它怎么还能监听到事件呢。没错,RunLoop不启动的话是无法监听到事件的,所以如果子线程内部也要监听事件的话,则必须由我们自己手动开启子线程对应的RunLoop。
黑妞:我靠,好像好复杂呀!在我记忆中,在实际开发的时候,我从来没有开启过子线程对应的RunLoop呀,好像也没有遇到什么问题呀?
小智:确实比较少的可能性需要我们自己开启子线程的RunLoop,我给你举例一个需要主动开启子线程RunLoop的例子:
黑妞:我平时使用定时器也没有写开启RunLoop的代码呀,定时器回调也可以执行呀,为什么这里需要手动开启RunLoop才行了?
小智:那是因为我们平时写的代码都是在主线程上执行的,而主线程的RunLoop默认就是开启的。所以不需要我们程序员手动启动了。但是我这个例子的定时器是在子线程创建的,定时器的回调方法也是在子线程执行的。定时器本身也是一个事件,事件是由RunLoop监听的,所以定时器在子线程时,如果不手动开启子线程的RunLoop的话,定时器事件也无法处理了,自然回调方法也不会执行。我们以后在使用定时器的时候,要多注意下,这个定时器是在子线程还是在主线程创建的,因为定时器回调方法所在的线程就是创建定时器所在的线程。
:原来定时器本身也属于一个事件呀,又涨姿势了。那以后实际开发中,要使用定时器时,应该在子线程创建和主线程创建呢?我从来没有考虑过这个问题呀。
小智:哎....都不知道你是怎么混到现在的。在子线程还是在主线程创建定时器,主要定时器是否有耗时操作,如果有耗时操作,那肯定是在子线程创建,这样就不会阻塞主线程呀。如果没有耗时操作,则可以放到主线程中创建呀。多线程的作用是什么?就是将耗时操作放到子线程执行,不阻塞主线程呀。
黑妞:好吧,我承认技不如你。最后一个问题:RunLoop是一个死循环,那它一直在循环不是很耗性能,很耗电?
小智:这你就有所不知了,虽然RunLoop是死循环,但它是一个聪明的死循环。官方文档有这么有一句话: If noevents are present and ready to be handled, the run loop puts the thread tosleep. 这句英文的意思是说:如果没有事件要处理时,RunLoop会让线程进入睡眠状态。在睡眠的时候,消耗性能就非常少了。一旦有事件产生了,RunLoop会立刻唤醒当前线程来响应事件。事件处理完毕之后,RunLoop会继续循环检测事件的到来,如果在一定的时间内,又没有事件产生了,RunLoop又会让线程再次进入睡眠状态来节省性能开销。
黑妞:不得不为RunLoop的聪明而点赞呀......不行,以后我也要多看看iOS底层内部相关的文档了,不能继续停留在表面了。
小智:那当然呀,说了这么多RunLoop的知识,自己回去一定要好好总结下。下次见面我就来考考你了,今天我们就到这里吧!
黑妞:好的,非常感谢小智哥的详细讲解。
小智:客气...应该的...拜拜!!!
精华推荐:
|