A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 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的知识,自己回去一定要好好总结下。下次见面我就来考考你了,今天我们就到这里吧!

黑妞:好的,非常感谢小智哥的详细讲解。

小智:客气...应该的...拜拜!!!

精华推荐:


11 个回复

倒序浏览
这个讲解形式真的是666,太厉害了
回复 使用道具 举报
会不会有人为黑妞打抱不平呢?哈哈哈哈 小智太嚣张了 哈哈哈哈哈哈哈哈
回复 使用道具 举报
小智都来了,你们环等什么
回复 使用道具 举报
谢谢您的讲解
回复 使用道具 举报
哈哈,上一个评论还是我,一天了都....这...
回复 使用道具 举报
helo world
回复 使用道具 举报
Simpon 中级黑马 2016-5-12 09:07:05
8#
曼巴老酒 发表于 2016-5-12 00:11
哈哈,上一个评论还是我,一天了都....这...

你骄傲吗
回复 使用道具 举报

  还阔以
回复 使用道具 举报
小智哥与黑妞姐果真是双剑合璧呀
回复 使用道具 举报
lvdong22 发表于 2016-5-12 23:10
小智哥与黑妞姐果真是双剑合璧呀

那个剑?你骂谁呢  不想混了吗哈哈哈哈
回复 使用道具 举报
又学到了东西!
回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马