Ruibin.Chow
无论你是狮子还是羚羊,你都必须奔跑,无论你是贫穷还是富有,你都必须奋斗!
2022-03-25 14:00
https://www.zruibin.cn
Ruibin.Chow
Ruibin.Chow
架构师思维脑图
https://www.zruibin.cn/article/jia_gou_shi_si_wei_nao_tu.html
2019-06-22 20:36
2019-06-22 20:36
<embed src="https://www.zruibin.cn/article/image/架构.svg" type="image/svg+xml" width="100%"/>
架构师思维脑图
《清醒思考的艺术》摘记
https://www.zruibin.cn/article/《_qing_xing_si_kao_de_yi_zhu_》_zhai_ji.html
2018-12-05 23:21
2018-12-05 23:21
<blockquote><p>在群体里容易按照他人的想法生活,在孤独中容易按照自己的想法生活。但值得记住的只是那些在群体中保持独立的人。
——爱默生</p>
</blockquote>
<p>
非理性有狂热说与冷淡说之分。狂热说源远流称。柏拉图的著作中有这样的画面:骑手驾驭狂奔的马儿。骑手代表理性,奔马象征情感。理性控制情感。如果控制不成功,非理性就会暴露出来。再看另一幅画面:情感是沸腾的熔岩,理性大多数时候可以将它们控制在盖子之下,但非理性的熔岩偶尔也会爆发。因此,非理性也是狂热的。理性实际上是一切正常,它没有缺点,只不过情感的力量经常更强大。</p>
<p>
这一非理性的狂热说流传了数百年。卡尔文认为情感是邪恶的,只有集中精力想念上帝才能阻止它们。情感的熔岩从其体内冲出的那些人是魔鬼。他们相应地会遭到迫害、杀谬。弗洛伊德主为情感(它)是受我和超我控制的,但这很少成功。虽有诸般强制,虽有诸般纪律,相信我们能够通过思考完完全全控制我们的情感,这是幻想——就像试图用意念控制头发的生长一样。</p>
<p>
相反,非理性的冷淡说还很年经。第二次世界大战后有许多人询问应该如何解释纳粹的非理性。在希特勒政权中很少发生情感爆发。就连他自己的激情演讲也只不过是演员的精彩表演。没有情感的熔岩爆发,有有冷冰冰的决定导致纳粹主义的疯狂,对红色高棉也可以做类似的解释。这是因为理性吗?显然不是。这一定有什么不对头。20世纪60年代,心理学家们开始清理弗洛伊德的荒唐观点,科学地研究我们的思维、决定和行为。结果,非理性的冷淡说就这样诞生了。它认为,思考本身是不纯洁的,是永远会犯错的,而且所有人都一样,就连高智商的人也会再三犯这些思维错误。这些错误不是偶然分布的。不同的思维错误会让我们系统性地跑向某个特定的错误方向。这让我们的错误可以诊断,从而得到一定程度的纠正。注意:是一定程度——不是完全。</p>
<p>
产生这些思维错误的原因几十年都没弄清楚。我们身体的其他部位几乎都没有毛病——心脏、肌肉、呼吸、免疫系统,为什么偏偏大脑要接二连三地犯错呢?</p>
<p>
思考是一种生物学现象。它就像动物的体形或花卉的颜色,同样都是由进化形成的。假设我们可以走回5万年前,抓住任意一们祖先,将他劫持到当代,送去理发店,随后将他塞进一套雨果·博斯牌服装中——他在大街上并不会引人注意。当然,他必须学英语、学开车、学习使用微波炉,但这些我们也必须学。生物学消除了一切怀疑:身体上的,包括大脑。我们是身穿雨果·博斯牌(也可以是海恩斯莫里斯牌)服装的猎人和采摘者。</p>
<p>
但自那时代以来发生了显著变化的,是我们生活于其中的环境。远古时代的环境简单稳定。我们大约50个人一群地生活在一起,没有什么值得一提的技术或社会进步。直到进1万年世界才开始发生急剧变化——出现了农业、畜牧业、城市和世界贸易,工业化以来环境的变化更是显著。今天任何在购物中心闲逛一小时的人,看到的人要比我们的祖先一辈子见到的人还要多。今天如果有人说自己知道世界10年后是什么样子,我们就会嘲笑他。过去1万年我们创造了一个我们再也看不懂的世界。我们让一切更加完美,但也更加复杂,相互更加依赖。结果,我们创造了令人惊叹的物质财富,可惜也产生了文明病和思维错误。如果复杂性继续增加——我们可以说,它会继续增加 ,这些思维错误就会更频繁、更严重。</p>
<p>
例如:在一个猎人和采摘都的环境里,行动得到的奖励要比思考得到的多。闪电式的快速反应决定生死存亡,长时间的苦思冥想是有害的。如果猎人和采摘者的伙伴们突然开始奔跑,跟随他们跑才有意义——不去考虑他们是否真的见到了一只剑齿虎或只是一头野猪。一个一级错误(那是一种危险动物,人们没有逃走)的代价是死亡,而二级错误(不是危险动物,而人们逃走)的代价只是消耗几个卡路里。因此犯某种特定的错误,是值得的。谁的做法与从不同,他就会从基因池里消失。今天的我们是当时倾向于跟在别人身后跑的那些人的后代。只是,这一本能行为在今天是不利的。今天的世界奖励深刻思考和自由行动。谁曾经被股市套牢过,谁就明白这句话。</p>
<p>
进化心理学在很大程度上还只是一种理论,但却是一种很有说服力的理论。它解释了大多数思维错误——虽然不是全部。我们以下列说法为例:“每块妙卡巧克力上面都有一头奶牛。因此上面有奶牛的巧克力就是妙卡巧克力。”就连智者也不时地会犯这个错误。很大程度上未受文明影响的土著也会出现这种错。有些错误显然是被输入了固定程序的,与我们环境的“突变”无关。</p>
<p>
这怎么解释呢?十分简单,进化并非绝对意义上“优化”了我们。只要我们比我们的竞争对手更好(比如尼安德特人),我们就会原谅自己的这些错误。数百万年来布谷鸟就将它们的蛋下在其他鸟的巢里,由那些鸟将蛋孵化,并喂食小布谷鸟。这是进化(还)无法消除的一个错误行为——因为它显然不是十分重要。</p>
<p>
为什么我们的思维错误这么顽固?20世纪90年代末,第二种类似的解释被分析出来了:我们的大脑是为复制设计的,而不是为发现真理设计的。换句话说,我们首先需要通过思考来说服别人。谁说服了别人,谁就确保了权力,从而确保了能够接触更多资源。这一资源接触反过来在繁殖和培养后代时又是一个关键优势。图书市场表明,我们进行思考主要不是为了发现真理。小说要比非小说类书籍好卖得多,尽管后者的真理含量要高得多。</p>
<p>
最后,第三种解释:在特定情形之下,本能决定——哪怕它们不是十分理智——更好。所谓的启发学研究关心的就是这个问题。许多决定缺少必要的信息,也就是我们被迫缩短思考,使用大拇指规则(启发学)。比如,如果你感觉有不同的女人(或男人)吸引你,你应该娶(或嫁)谁呢?靠理性是不行的,如果你只信赖,你会永远单身。简言之,有时我们会本能地决定,事后再说明我们选择的理由。许多决定(关于工作、生活伴侣、投资等)都是本能地做出的。之后我们再虚构出一个理由,它让我们感觉我们是清醒地做出了决定。比起科学家,我们的思考方式更像是律师。科学家追求的是单纯的真理,而律师精通于为一个已经确定的推论虚构出可能性最大的理由。</p>
<p>
因此,请你忘记每本研究性管理学图书里都有介绍的“左半脑和右半脑”,本能行为和理性思考之间的区别要重要的多。两者都有合理的应用领域。本能行为迅速、自发、节省能量;理性思考缓慢、费劲、消耗许多卡路里(以血糖的形式)。</p>
<p>
理性思考当然也可以转变为本能行为。如果你练习一种乐器,你刚开始是一个音符一个音符地学,指挥每一根手指。随着时间的推移,你本能地掌握了琴键或琴弦:你看面前的乐谱,双手像是自动在弹奏。沃伦·巴菲特阅读一份资产负债表,就像一位职业音乐家看总谱。这就是人们称作“能力范围”的东西:在我们拥有高超技能的地方让理性思考成为本能。可惜本能也会在我们达不到高超技能的地方发生——而且是发生在挑剔的理性能够正确干涉之前。于是就出现了思维错误。</p>
《清醒思考的艺术》摘记
算法和人生选择
https://www.zruibin.cn/article/suan_fa_he_ren_sheng_xuan_ze.html
2018-03-01 09:58
2018-03-01 09:58
<h4><p>原文出处:<a href="https://mp.weixin.qq.com/s/b926SHEKNtrLgzKdeGjJWQ" target="blank">算法和人生选择</a></p></h4>
<p>每年一到要找工作的时候,我就能收到很多人给我发来的邮件,总是问我怎么选择他们的offer,去腾讯还是去豆瓣,去外企还是去国内的企业,去创业还是去考研,来北京还是回老家,该不该去创新工场?该不该去thoughtworks?……等等,等等。今年从7月份到现在,我收到并回复了60多封这样的邮件。我更多帮他们整理思路,帮他们明白自己最想要的是什么。(注:我以后不再回复类似的邮件了)。</p>
<p>我深深地发现,对于我国这样从小被父母和老师安排各种事情长大的人,当有一天,父母和老师都跟不上的时候,我们几乎完全不知道怎么去做选择。而我最近也离开了亚马逊,换了一个工作。又正值年底,就像去年的那篇《三个故事和三个问题》一样,让我想到写一篇这样的文章。</p>
<h4>几个例子</h4>
<p>当我们在面对各种对选择的影响因子的时候,如:城市,公司规模,公司性质,薪水,项目,户口,技术,方向,眼界…… 你总会发现,你会在几个公司中纠结一些东西,举几个例子:</p>
<ul>
<li><p>某网友和我说,他们去上海腾讯,因为腾讯的规模很大,但却发现薪水待遇没有豆瓣高(低的还不是一点),如果以后要换工作的话,起薪点直接关系到了以后的高工资。我说那就去豆瓣吧,他说豆瓣在北京,污染那么严重,又没有户口,生存环境不好。我说去腾讯吧,他说腾讯最近组织调整,不稳定。我说那就去豆瓣吧,慢公司,发展很稳当。他说,豆瓣的盈利不清楚,而且用Python,自己不喜欢。我说,那就去腾讯吧,……</p>
</li>
<li><p>还有一网友和我说,他想回老家,因为老家的人脉关系比较好,能混得好。但又想留在大城市,因为大城市可以开眼界。</p>
</li>
<li><p>另一网友和我说,他想进外企,练练英语,开开眼界,但是又怕在外企里当个螺丝钉,想法得不到实施。朋友拉他去创业,觉得创业挺好的,锻炼大,但是朋友做的那个不知道能不能做好。</p>
</li>
<li><p>还有一网友在创新工场的某团队和考研之间抉择,不知道去创新工场行不行,觉得那个项目一般,但是感觉那个团队挺有激情的,另一方面觉得自己的学历还不够,读个研应该能找到更好的工作。</p>
</li>
<li><p>友问题我应该学什么技术?不应该学什么技术?或是怎么学会学得最快,技术的路径应该是什么?有的说只做后端不做前端,有的说,只做算法研究,不做工程,等等,等等。因为他们觉得人生有限,术业有专攻。</p>
</li>
<li><p>等等,等等……</p>
</li>
</ul>
<p>我个人觉得,如果是非计算机科班出生的人不会做选择,不知道怎么走也罢了,但是我们计算机科班出生的人是学过算法的,<strong>懂算法的人应该是知道怎么做选择的</strong>。</p>
<h4>排序算法</h4>
<p>你不可能要所有的东西,所以你只能要你最重要的东西,你要知道什么东西最重要,你就需要对你心内的那些欲望和抱负有清楚的认识,不然,你就会在纠结中度过。</p>
<p>所以,在选择中纠结的人有必要参考一下排序算法。</p>
<ul>
<li><p>首先,你最需要参考的就是“冒泡排序”——这种算法的思路就是每次冒泡出一个最大的数。所以,你有必要问问你自己,面对那些影响你选择的因子,如果你只能要一个的话,你会要哪个?而剩下的都可以放弃。于是,当你把最大的数,一个一个冒泡出来的时候,并用这个决策因子来过滤选项的时候,你就能比较容易地知道知道你应该选什么了。<strong>这个算法告诉我们,人的杂念越少,就越容易做出选择</strong>。</p>
</li>
<li><p>好吧,可能你已茫然到了怎么比较两个决策因子的大小,比如:你分不清楚,工资>业务前景吗?业务前景>能力提升吗?所以你完全没有办法进行冒泡法。那你,你不妨参考一个“快速排序”的思路——这个算法告诉我们,我们一开始并不需要找到最大的数,我们只需要把你价值观中的某个标准拿出来,然后,把可以满足这个价值的放到右边,不能的放到左边去。比如,你的标准是:工资大于5000元&&业务前景长于3年的公司,你可以用这个标准来过滤你的选项。然后,你可以再调整这个标准再继续递归下去。<strong>这个算法告诉我们,我们的选择标准越清晰,我们就越容易做出选择</strong>。</p>
</li>
</ul>
<p>这是排序算法中最经典的两个算法了,面试必考。相信你已烂熟于心中了。所以,我觉得你把这个算法应用于你的人生选择也应该不是什么问题。关于在于,你是否知道自己想要的是什么?</p>
<p>排序算法的核心思想就是,<strong>让你帮助你认清自己最需要的是什么,认清自己最想要的是什么,然后根据这个去做选择</strong>。</p>
<h4>贪婪算法</h4>
<p>所谓贪婪算法,是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择(注意:是当前状态下),从而希望导致结果是最好或最优的算法。贪婪算法最经典的一个例子就是哈夫曼编码。</p>
<p>对于人类来说,一般人在行为处事的时候都会使用到贪婪算法,</p>
<ul>
<li><p>比如在找零钱的时候,如果要找补36元,我们一般会按这样的顺序找钱:20元,10元,5元,1元。</p>
</li>
<li><p>或者我们在过十字路口的时候,要从到对角线的那个街区时,我们也会使用贪婪算法——哪边的绿灯先亮了我们就先过到那边去,然后再转身90度等红灯再过街。</p>
</li>
</ul>
<p>这样的例子有很多。对于选择中,大多数人都会选用贪婪算法,因为这是一个比较简单的算法,未来太复杂了,只能走一步看一步,在当前的状况下做出最利于自己的判断和选择即可。</p>
<p>有的人会贪婪薪水,有的人会贪婪做的项目,有的人会贪婪业务,有的人会贪婪职位,有的人会贪婪自己的兴趣……这些都没什么问题。贪婪算法并没有错,虽然不是全局最优解,但其可以让你找到局部最优解或是次优解。其实,有次优解也不错了。<strong>贪婪算法基本上是一种急功近利的算法,但是并不代表这种算法不好,如果贪婪的是一种长远和持续,又未尝不可呢?</strong>。</p>
<h4>动态规划</h4>
<p>但是我们知道,对于大部分的问题,贪婪法通常都不能找出最优解,因为他们一般没有测试所有可能的解。<strong>因为贪婪算法是一种短视的行为,只会跟据当前的形式做判断,也就是过早做决定,因而没法达到最佳解</strong>。</p>
<p>动态规划和贪婪算法的最大不同是,贪婪算法做出选择,不能在过程优化。动态规划则会保存以前的运算结果,并根据以前的结果对当前进行选择,会动态优化功能。</p>
<p>动态规划算法至少告诉我们两个事:</p>
<ol>
<li><p><strong>承前启后非常重要</strong>,当你准备去做遍历的时候,你的上次的经历不但能开启你以后的经历,而且还能为后面的经历所用。你的每一步都没有浪费。</p>
</li>
<li><p><strong>是否可以回退也很重要</strong>。这意思是——如果你面前有两个选择,一个是A公司一个是B公司,如果今天你选了A公司,并不是你完全放弃了B公司。而是,你知道从A公司退出来去B公司,会比从B公司退出来去A公司要容易一些。</p>
</li>
</ol>
<p>比如说:你有两个offer,一个是Yahoo,一个是Baidu,上述的第一点会让我们思考,我以前的特长和能力更符合Yahoo还是Baidu?而Yahoo和Baidu谁能给我开启更大的平台?上述的第二点告诉我们,是进入Yahoo后如果没有选好,是否还能再选择Baidu公司?还是进入Baidu公司后能容易回退到Yahoo公司?</p>
<h4>Dijkstra最短路径</h4>
<p>最短路径是一个Greedy + DP的算法。相当经典。这个算法的大意如下:</p>
<ol>
<li><p>在初始化的时候,所有的结点都和我是无穷大,默认是达不到的。</p>
</li>
<li><p>从离自己最近的结点开始贪婪。</p>
</li>
<li><p>走过去,看看又能到达什么样的结点,计算并更新到所有目标点的距离。</p>
</li>
<li><p>再贪婪与原点最短的结点,如此反复。</p>
</li>
</ol>
<p>这个算法给我们带来了一些这样的启示:</p>
<ul>
<li><p>有朋友和我说过他想成为一个架构师,或是某技术领域的专家,并会踏踏实实的向这个目标前进,永不放弃。我还是鼓励了他,但我也告诉他了这个著名的算法,我说,这个算法告诉你,架构师或某领域的专家对你来说目前的距离是无穷大,他们放在心中,先看看你能够得着的东西。<strong>所谓踏实,并不是踏踏实实追求你的目标,而是踏踏实实把你够得着看得见的就在身边的东西干好</strong>。我还记得我刚参加工作,从老家出来的时候,从来没有想过要成为一个技术牛人,也从来没有想过我的博客会那么的有影响力,在做自己力所能及,看得见摸得着的事情,我就看见什么技术就学什么,学着学着就知道怎么学更轻松,怎么学更扎实,这也许就是我的最短路径。</p>
</li>
<li><p>有很多朋友问我要不要学C++,或是问我学Python还是学Ruby,是不是不用学前端,等等。这些朋友告诉我,他们不可能学习多个语言,学了不用也就忘了,而且术业有专攻。这并没有什么不对的,只是我个人觉得,学习一个东西没有必要只有两种状态,一种是不学,另一种是精通。了解一个技术其实花不了多少时间,我学C++的目的其实是为了更懂Java,学TCP/IP协议其实是为了更懂Socket编程,很多东西都是连通和相辅相成的,学好了C/C++/Unix/TCP等这些基础技术后,我发现到达别的技术路径一下缩短了(这就是为什么我用两天时间就可以了解Go语言的原因)。<strong>这就好像这个算法一样,算法效率不高,也许达到你的目标,你在一开始花了很长时间,遍历了很多地方,但是,这也许这就是你的最短路径(比起你达不到要好得多)</strong>。</p>
</li>
</ul>
<h4>算法就是Trade-Off</h4>
<p>你根本没有办法能得到所有你想得到的东西,<strong>任何的选择都意味着放弃——当你要去获得一个东西的时候,你总是需要放弃一些东西。人生本来就是一个跷跷板,一头上,另一头必然下</strong>。这和我们做软件设计或算法设计一样,用时间换空间,用空间换时间,还有CAP理论,总是有很多的Trade-Off,正如这个短语的原意一样——<strong>你总是要用某种东西去交易某种东西</strong>。</p>
<p>我们都在用某种东西在交易我们的未来,有的人用自己的努力,有的人用自己的思考,有的人用自己的年轻,有的人用自己的自由,有的人用自己的价值观,有的人用自己的道德…… …… 有的人在交换金钱,有的人在交换眼界,有的人在交换经历,有的人在交换地位,有的人在交换能力,有的人在交换自由,有的人在交换兴趣,有的人在交换虚荣心,在交换安逸享乐…… ……</p>
<p><strong>每个人有每个人的算法,每个算法都有每个算法的purpose,就算大家在用同样的算法,但是每个人算法中的那些变量、开关和条件都不一样,得到的结果也不一样。我们就是生活在Matrix里的一段程序,我们每个人的算法决定着我们每个人的选择,我们的选择决定了我们的人生</strong>。</p>
每个人有每个人的算法,每个算法都有每个算法的purpose,就算大家在用同样的算法,但是每个人算法中的那些变量、开关和条件都不一样,得到的结果也不一样。我们就是生活在Matrix里的一段程序,我们每个人的算法决定着我们每个人的选择,我们的选择决定了我们的人生
阿里为什么不去清华招人?
https://www.zruibin.cn/article/a_li_wei_shi_yao_bu_qu_qing_hua_zhao_ren_?.html
2017-11-19 21:14
2017-11-19 21:14
<h5><p>原文出处:<a href="http://www.jianshu.com/p/19e92aac5d66" target="blank">阿里为什么不去清华招人?</a></p></h5>
<h3>阿里为什么不去清华招人?|草铺半生</h3>
<p>草铺半生|阿里巴巴</p>
<p>29岁,成为普华永道史上最年轻的合伙人之一;</p>
<p>32岁,担任百安居中国区总裁,也是最年轻的世界500强中国区总裁;</p>
<p>36岁,执掌阿里巴巴。</p>
<p>卫哲,32岁替500强打天下,36岁执掌阿里,如今他是嘉御基金的创始人,管理着10亿美金的资产,用年轻有为形容他一点也不为过。</p>
<p>草铺半生</p>
<p>文 | 卫哲 创业邦</p>
<p>本文整理了卫哲最近的演讲,讲述在阿里巴巴就职期间,在用人方面遇到的问题以及化解的方法,笑谈经验中,输出了不少干货。</p>
<hr>
<h4>01</h4>
<p>阿里不去清华招人的原因</p>
<p>2009年,马云带队去美国考察一流公司。</p>
<p>其中包括苹果、谷歌、微软、星巴克。见这些公司,我通常都会问一个问题:</p>
<p>谁是你们的竞争对手?</p>
<p>微软那时候的CEO叫做 Steve Ballmer.Steve 一听到这个问题,就瞬间来劲了,一口气讲了45分钟,我们和苹果竞争,和索尼竞争,和 Cisco 竞争,和 Oracle 竞争,我们是如何跟他们斗争的,又如何消灭他们的。</p>
<p>出来以后,马云说,这哥们是职业杀手啊。我说,你看金庸小说里面,哪个职业杀手,最后成为顶尖武林高手的?没有。</p>
<p>然后拜访谷歌,也问谷歌的创始人 Larry Page ,谁是谷歌的竞争对手?我们正期待谷歌也说,微软啊、苹果啊等等都是他们的竞争对手。</p>
<p>结果答案特别出乎我们的意料。Larry Page 说:</p>
<p>NASA(美国宇航局)、Obama administration(奥巴马政府),是我们的竞争对手。</p>
<p>我问为什么呢?他说:</p>
<p>谁跟我抢人,谁就是我们的竞争对手。</p>
<p>我们的工程师,Facebook 和苹果来抢,我们不怕。我们开更高的工资,给更多的期权、股权就好了。可是我们的工程师去NASA,1年只有7万美金,只有我们这里的五分之一,我还抢不过。</p>
<p>我们谷歌描绘了一个很大的梦想,但美国宇航局的梦想,是整个宇宙,更大,做的事更好玩儿,把我们最优秀的工程师,给吸引走了。</p>
<p>我们这儿的管理者,我们这儿的经理,年薪也是几十万美元,结果 2009年奥巴马上台后,美国政府意气风发,很多美国人,居然愿意从政了。包括谷歌里面很多优秀的经理,放弃几十万年薪,拿5万美金的年薪,去政府工作。</p>
<p>所以,谁跟我抢人,谁就是我们的竞争对手。而且,这两个竞争对手,是我最难对付的竞争对手。</p>
<p>所以,这个话题,我想跟所有人力资源方面的管理者分享,一定要深刻思考,谁才是你的竞争对手。微软呢,看到的只是谁是微软产品的竞争对手(而不是人才的竞争对手)。结
果怎么样?作为 CEO 的 Steve Ballmer 下台了。</p>
<p>一般 CEO 下台,股票都要跌的。Steve Ballmer 下台,第二天股票涨了7%。微软的 7%是多少?相当于300亿美金。我们经常说,奋斗一个独角兽。10亿美金就是独角兽了吧,一个 Steve Ballmer ,等于负能量的30个独角兽。</p>
<p>我们做人力资源也会知道,你宣布了一个比 Steve Ballmer 更强的人来当下一任CEO,大家特别看好,所以股票涨了。</p>
<p>当时,微软董事会原话是这样的:</p>
<p>Steve Ballmer 将不再担任微软 CEO,接下来谁担任,董事会正在选择当中,还没决定最终谁来接Steve Ballmer。</p>
<p>也就是说,随便换一个人都比 Steve Ballmer 强。我觉得微软到了后来发展并是不太好。</p>
<p>今年我们召集了很多 CTO 在硅谷聚会,发现一大片出自微软。所有人都在感谢微软,微软为我们中国的互联网界,增添了70%的CTO。出来后,都在声讨,微软有多么不好。</p>
<p>所以,可能因为微软流失了这些人( BAT 几乎所有 CTO 都是微软出来的),如果他们留在微软,微软会多么强大。</p>
<p>说到人员流失也好,人才流失也好。我就想起我刚到阿里巴巴的时候,阿里巴巴是对人力资源,特别重视的一个公司。但即使像这样一个特别重视人力资源的公司,在人力资源管
理方面也走过很多弯路。</p>
<p>2005、2006年,我刚去阿里巴巴的时候,曾向人力资源部门了解:我们的工程师和销售的离职率是多少,部门的同事告诉我,离职率是 10%。</p>
<p>我一听,哎哟,太了不起啦!销售和工程师都是流失率特别大的岗位,阿里巴巴才 10% !结果人力资源同事马上说:</p>
<p>不不,我刚才没有说清楚,是一个月 10% 。</p>
<p>噢,那就是年化120%,一年换一遍。</p>
<p>员工流失率这么高,最后采取什么措施没有?采取了。我们把员工流失率定了个指标,作为各级 HR ,各级干部的 KPI 挂钩考核。效果怎么样呢?还不如不定这个KPI 。</p>
<p>为什么呢?该留的一个没留,该走的一个没走。也就是留下来的,都是该走的人。走了的,都是该留的人。硬性制定了一个流失率指标,并不能解决流失率的问题。</p>
<p>为什么员工流失率这么高?核心只有一句话:</p>
<p>人力资源的源头,也就是招聘,出了问题。</p>
<p>出了哪些问题?我来分享一下:</p>
<p>① 不轻易下放招聘权</p>
<p>阿里巴巴刚创建的时候,大概在公司 400-500 人的时候,任何人加入公司,马云都要见,亲自面试。任何人,包括我们的前台接待,包括我们公司的保安。</p>
<p>所以,阿里巴巴能在今天诞生出一些传奇性的人物,很励志的人物,没什么好稀奇的。</p>
<p>阿里巴巴今天的首席人力资源官,前面做过菜鸟物流董事长的童文红,原来是军嫂,从我们前台的接待做起,然后做行政经理,做人力资源,管业务,管客服。最后,成为菜鸟董
事长。现在刚刚升任,整个阿里集团的首席人力资源官(CHO)。</p>
<p>那么,如果这个前台接待,是行政经理面试的,她的出路,应该就是行政经理。但,如果这个前台接待,是马云面试的,她就有可能成长为副总裁。</p>
<p>阿里巴巴在公司只有四五百人的时候,马云亲自来面试,就诞生了这么多奇迹。那么,后来招聘权力下放之后呢?就发现很多问题。</p>
<p>最极端的例子,我们有很多经理,自己入职才一个多月,居然他可以去招聘别人了。他并不了解公司的文化和价值观。甚至对于这个岗位有什么要求,也不清楚。就有点像以前的
解放战争,国民党拉壮丁,自己刚刚来当兵没多久,又可以去发展,为自己的队伍招人,这是多大的风险。</p>
<p>我们也碰到过中小企业的老板,大概员工人数在一两百人左右,就说我有人力资源部门了,招聘是人力资源部门的事情。我不再管招人了。</p>
<p>那我说,你在忙什么呢?</p>
<p>他说我又是开会,又要出差,又要做销售,又要做客服。我说,你在降级,做不该你做的事。那么为什么你会降级,做这些事呢?</p>
<p>因为你没有招对人,你没有把时间放在招聘上,形成了恶性循环。因为招的人不行,你要替他们去干,他们本来该干的事。</p>
<p>很多跨国公司,至少坚持跨两级招人,阿里巴巴一度恢复到跨四级招人。</p>
<p>比如,我们广东大区的总经理,下边有城市经理,城市经理下边有业务主管,业务主管下边是我们普通的销售或者客服。也就是广东大区总经理,要直接面试到销售或者客服。</p>
<p>广东大区多少人呢?广东大区1000个人。每年至少 200-300 人的流动。可想而知,他要招多少人,工作量有多大。</p>
<p>所以,阿里巴巴人力资源工作的改进,就是从招聘这个源头开始。而招聘源头的第一件事,就是不轻易下放招聘的权力。中国有很多公司,人力资源开始出问题,就在于下放了招聘权力。</p>
<p>② 重视专业技能以外的考核</p>
<p>业务技能很简单,很好判断。</p>
<p>你是做销售的,来我们公司也是做销售的,以前业绩多少,卖什么产品,管多少人。工程师,以前是写 Java的,还是写什么其他语言的,做过什么样的程序等等。水平很好判断。</p>
<p>我经常问,公司招人,难道只看业务技能吗?我希望每个公司都能回答这个问题:</p>
<p>在业务技能以外,你们需要什么样的人?</p>
<p>我们经常说,每个公司都有自己的味道。阿里巴巴招聘的时候经常闻味道,跟我们是不是一类人。因为人的分类没有对错。错的是什么呢?</p>
<p>是不同类的人,天天要坐在一起,天天要在一起共事。可想而知,心情不愉快,工作效率低。</p>
<p>那么,到底怎么去“闻味道”?</p>
<p>我们要动脑筋去设计一些问题。很多人总在打听阿里巴巴的面试问题。其实,我把答案告诉你也没有用。因为有些问题是开放性的。</p>
<p>比如说,你招聘一个岗位,特别希望他能吃苦,你不能问他,同学,你能吃苦吗?没有人会跟你说,对不起,我不能吃苦。</p>
<p>你特别希望你们公司招的人都诚信,是守信用的人,你也不能问他,你诚信吗?不能这么问。</p>
<p>那我举个例子,大家回去,多去开发一些这样的问题:</p>
<p>案例一:</p>
<p>就拿第一个问题,能吃苦吗?很简单。</p>
<p>同学,你能不能跟我描述一下,你这辈子吃的最大的苦是什么?</p>
<p>我们有个面试者回答说,那个时候,没有动车,从上海到无锡,我没有买到坐着的票。我是从上海站到无锡的。</p>
<p>这是他人生中吃过的最大的苦,可想而知,他的吃苦能力很弱。</p>
<p>案例二:</p>
<p>我们希望招的人大气一点。阿里巴巴希望招的人大气一点。那你就可以问他:</p>
<p>同学,你能不能跟我讲讲,你这辈子吃过的最大的亏是什么?</p>
<p>我们有听到过一个答案,我在读小学的时候,同桌的女同学,拿了我的橡皮,到现在都没有还。我心想,哎呀,这哪是吃亏,这是记仇啊。</p>
<p>女同学拿了块橡皮没有还,到二十多岁都还记得。而且他认为,这是他这辈子,吃过的最大的亏。那你觉得这个人,适不适合你们的味道?肯定大部分公司都不适合。</p>
<p>这些因素,我归类为非技能类因素。我希望大家能在招聘中,围绕每个公司的味道,设计一些非技能的问题,来判断一下。</p>
<p>这是阿里巴巴当年犯的第二个错误,招聘时,过度强调技能,忽略非技能因素。其实,这个错误,跨国公司经常犯,阿里也犯,不过纠正得快,纠正得坚决。</p>
<p>③ 跨一两级选拔人才</p>
<p>跨国公司犯得很严重,阿里也犯。举个例子,你要招一个月薪大约1万的人,我们通常就在 8000-10000里面去挑。结果来了以后,这些人的流失率很高,因为他们会认为,我原来挣 8000 ,跳槽后挣 10000 很正常。</p>
<p>在阿里巴巴有句话,叫做平凡的人做非凡的事情,我们不追求精英文化。</p>
<p>什么叫降级呢?你要招一个愿意给1万的人,从3000-4000收入的人里去找。他来了之后,是不是有翻身做主人的感觉?成就感特别大,特别感谢这个公司给他这个机会。因为,他在外面没有这样的机会。</p>
<p>当然,说来轻松。从 8000 块的人里挑可以给 1 万块的人容易挑,从三四千里面,确实不容易挑到这个人。</p>
<p>可是容易,还要我们干嘛?</p>
<p>我经常说,八千块里面挑一万的,你大概面试3个人就有一个。但是你要在三四千块里面,挑出来一个你愿意付1万块的人,你大概要看三四十个人,但一定会有的,我相信一定会有的。</p>
<p>高考当年就差了一分,这个人就从名校,到了普通高校。为什么在三四千块收入的人里面没有那个人,你可以把他挖掘出来,可以培养出来。</p>
<p>阿里巴巴那时候,很少去清华招工程师。我去清华做校招,人家问我,卫哲你来干嘛,马云怎么不来,李彦宏都还来呢,觉得我去都不够格。</p>
<p>那我跑到华中科技大学,一千多人的场子,挤进来两千多人。阿里很多最优秀的工程师,都是武汉邮电、华中科技这些大学招聘过来的,并不是大家心目中清华北大这样的名校。</p>
<p>清华北大,永远有比阿里更好的职位。那你到了武汉邮电,到了华中科技,阿里就是他们最好的机会。</p>
<p>这就叫做跨级招聘人才,跨级选人才。</p>
<p>所以,谈阿里的人力资源管理,可以谈很长时间。今天只是和大家分享,我们当年,在招聘环节,容易出的这3个问题,以及后来用什么方法来应对的。</p>
<h4>02</h4>
<p>别总讲规模,多谈谈效率</p>
<p>做投资五年多来,收到无数的BP计划,永远是讲“我规模有多大”、“我今天有多大”、“我未来会做得有多大”,还有“我有多快”。</p>
<p>但从来没有没有人在BP当中说:我效率有多高——我今天效率有多高;未来随着我多快、多大以后,我的效率有多高。</p>
<p>互联网最大的作用就是提升效率。一个互联网公司没有人均10万美元的利润贡献,就不是真正的互联网公司。大和快的背后,是效率。</p>
<p>不要被互联网这层外衣给迷惑了。商业的本质,除了增长以外还有效率。没有效率的增长,不是慢性自杀,而是加速自杀。</p>
<p>个人效率的提升,光是自我驱动是不够的。还是要有约束。但是90后他不喜欢被约束,那怎么办?要提倡自我约束。</p>
<p>举个例子。</p>
<p>在阿里加班,可以再吃一顿晚饭。当时,一顿晚饭公司出15块钱,大约一年两百五十个工作日,一年要吃掉差不多两千多万加班费。</p>
<p>最早的管理办法很简单,员工提申请,主管批准。主管批准加班,撕张券给你,六点钟开饭,你就去吃饭。这个制度,很多人觉得很正常,用了很多年。</p>
<p>但是,我们再问自己,这个制度是不是真的好?</p>
<p>第一,谁也没有规定工作到几点以后是加班。所以确实有一批人,六点钟吃饭,申请加班,吃完以后,六点半加班到七点钟走了。于公司,也没有什么制度说这不可以。</p>
<p>第二,我们从来没有去想,每天五、六千个人,提加班申请,主管批准,发一张券,这个动作,花了多少时间,花了多少钱。</p>
<p>好像这是不花钱的。实际上你想想看,每天五、六千个人写一个“今天要加班”,还得写一个理由:什么项目加班。主管看一眼批,还得撕一张券给你。</p>
<p>从来没有人计算这个成本。</p>
<p>后来我们跟员工讲清楚,加班晚餐是给加班的员工吃的,你不加班最好不要吃。但是如果你今天觉得很累,回去不想做饭,想吃完以后再回去,也没关系。反正公司是我们大家的,我们把公司吃光了,吃穷了,我们散伙。</p>
<p>一年下来,大概前一年加班餐费在1400万左右,取消报批制以后,多了大概100万,变成1500万了。</p>
<p>但计算一下,200多工作日,每天四、五千人打申请,主管批准发券,这事值不值一百万?中间消耗掉的人工,消耗掉的效率何止一百万。</p>
<p>你的公司,你的组织,有没有这样的制度?这是愚蠢的制度,官僚就是这么来的,组织的效率就是这么下降的。</p>
<p>提高个人效率,除了自我激励以外,不要有太多级约束。</p>
<h4>03</h4>
<p>人不是成本,人应该是投资</p>
<p>“说到新零售,我特别反对无人便利店。”</p>
<p>消费者的体验就四个字:多快好省,传统零售只能牺牲其中两个,换来另外两个。</p>
<p>比如,美国的 Costco(好市多)第一主打就是“省”,第二个是“好”,那它牺牲了什么?牺牲了“多”。</p>
<p>因为 Costco 的商品种类有 4000 种,沃尔玛有 2 万多;也牺牲了“快”,你看整个湾区没几个 Costco ,开车没个 30 分钟到不了。</p>
<p>像便利店就相反,牺牲了“省”、“多”,换来了“快”。</p>
<p>要想做好新零售,就要想清楚四个维度里如何补全。</p>
<p>我特别反对无人便利店,人不是成本,人应该是投资。要是做无人便利店还不如搞个自动贩卖机呢。零售店里的人,不是理货员,而是要变成理客员,帮助顾客最快找到想要的货。</p>
<p>盒马鲜生很重要的一点,新鲜,这就是互联网代替不了的体验。这也是我想说的第二点,新零售应该把互联网干不了的体验,补回来。</p>
<p>现在市值最高的科技公司是苹果,有人敢说苹果不是科技公司吗?</p>
<p>苹果公司这么一个技术公司,把奢侈品牌巴宝莉(Burberry) 的 CEO 找来做实体零售业的负责人,为什么?我去苹果零售店时,经常问店长和店员几个问题,问了十几家店。</p>
<p>我问店长,你知道这个月的销售目标和完成情况吗?</p>
<p>店长说:I don"t know ,I don"t care;</p>
<p>问店员,你知道这个月的销售目标和你卖一个产品提成多少吗?</p>
<p>店员说:I don"t know,I don"t have。</p>
<p>这意味着什么?意味着苹果店的店长和店员存在,并不是以销售为第一目的的,基本上没有人给你推销。但中国的手机店,还是不停给你推销,希望你用华为还是保时捷款的。</p>
<p>所以苹果店的存在,就是体验。所以苹果本身就是一种新零售。</p>
<p>新零售的店不需要这么大,要做到实体小店,虚无大店,新零售一定要对原有的零售门店加以再利用。因为传统零售有两个成本不可避免上升:</p>
<p>第一,房租成本,租金不能降低;</p>
<p>第二,人工成本,没法降低。</p>
<p>所以新零售要解决的是:人均产出大幅度上升,每平米产出(坪效)大幅度上升。如果新零售不能成倍地提高坪效,就做不下去的。</p>
<h4>04</h4>
<p>如何让我们的公司变得好玩?</p>
<p>举个例子来说:硅谷创业公司也好,大公司也好,见面第一句话,不是互相问“你们这的期权多少?”;见面第一句话都是“你们那午饭怎么样?”“你们那的中餐水平怎么样?”</p>
<p>PK一顿饭,这很重要。但以前这顿饭并不重要。</p>
<p>如何让我们的公司变得好玩?</p>
<p>对一个95后,他加不加入、留不留在你们公司,最有吸引力的,不是因为多几百块工资,而是你的那顿午饭,能不能打动他。</p>
<p>公司的那顿午饭,是不是很用心,打动了他。如果不仅是包这顿午饭,你还包他住。</p>
<p>很多公司还是提供这个福利的,但是你只是帮他找了个民居,让他在三室两厅里挤进去10个人凑和一下,还是给他一个像家一样,有年轻人的社区,有很温馨、很好玩的地方……这个对95后,变得越来越重要。</p>
<p>好吃、好玩、好住,也可以是公司的卖点,是非常重要的一部分。</p>
<p>人在一个公司,如果你只会把他当成本,那不是好的CEO,不是好的人力资源。</p>
<p>所以,我也不相信人工智能,机器人能取代人,他们只能使我们变得更加强大。他们只能给我们的人力资源管理者,提出更多的挑战,让我们变得更强大。</p>
阿里不去清华招人的原因
北京折叠
https://www.zruibin.cn/article/bei_jing_zhe_die.html
2016-12-03 11:31
2016-12-03 11:31
<blockquote><p><a href="http://baike.baidu.com/link?url=NjwoO4eU165-2pX9Xwfi6Vf59eOBPhhmQh693KrnxttyzwxPn3WQPKU-rgAegz_Gxgx5BTpMTvPjeEV4rgYip-a2DQujw5PztkL7yMnSWMmgXFXdPo4bZFZsKWnMdCft">简介</a></p>
<p>《北京折叠》是科幻作家郝景芳创作的中短篇小说。该小说创造了一个更极端的类似情景,书里的北京不知年月,空间分为三层,不同的人占据了不同的空间,也按照不同的比例,分配着每个 48 小时周期。</p>
<p>2016年8月21日,《北京折叠》获得第74届雨果奖最佳中短篇小说奖。</p>
<p>此次雨果奖获奖作品《北京折叠》也收录在《孤独深处》中。</p>
</blockquote>
<h3>(1)</h3>
<p> 清晨4:50,老刀穿过熙熙攘攘的步行街,去找彭蠡。
<br><br>
从垃圾站下班之后,老刀回家洗了个澡,换了衣服。白色衬衫和褐色裤子,这是他唯一一套体面衣服,衬衫袖口磨了边,他把袖子卷到胳膊肘。老刀四十八岁,没结婚,已经过了注意外表的年龄,又没人照顾起居,这一套衣服留着穿了很多年,每次穿一天,回家就脱了叠上。他在垃圾站上班,没必要穿得体面,偶尔参加谁家小孩的婚礼,才拿出来穿在身上。这一次他不想脏兮兮地见陌生人。他在垃圾站连续工作了五小时,很担心身上会有味道。
<br><br>
步行街上挤满了刚刚下班的人。拥挤的男人女人围着小摊子挑土特产,大声讨价还价。食客围着塑料桌子,埋头在酸辣粉的热气腾腾中,饿虎扑食一般,白色蒸汽遮住了脸。油炸的香味弥漫。货摊上的酸枣和核桃堆成山,腊肉在头顶摇摆。这个点是全天最热闹的时间,基本都收工了,忙碌了几个小时的人们都赶过来吃一顿饱饭,人声鼎沸。
<br><br>
老刀艰难地穿过人群。端盘子的伙计一边喊着让让一边推开挡道的人,开出一条路来,老刀跟在后面。
<br><br>
彭蠡家在小街深处。老刀上楼,彭蠡不在家。问邻居,邻居说他每天快到关门才回来,具体几点不清楚。
<br><br>
老刀有点担忧,看了看手表,清晨5点。
<br><br>
他回到楼门口等着。两旁狼吞虎咽的饥饿少年围绕着他。他认识其中两个,原来在彭蠡家见过一两次。少年每人面前摆着一盘炒面或炒粉,几个人分吃两个菜,盘子里一片狼藉,筷子扔在无望而锲而不舍地拨动,寻找辣椒丛中的肉星。老刀又下意识闻了闻小臂,不知道身上还有没有垃圾的腥味。周围的一切嘈杂而庸常,和每个清晨一样。
<br><br>
“哎,你们知道那儿一盘回锅肉多少钱吗?”那个叫小李的少年说。
<br><br>
“靠,菜里有沙子。”另外一个叫小丁的胖少年突然捂住嘴说,他的指甲里还带着黑泥, “坑人啊。得找老板退钱!”
<br><br>
“人家那儿一盘回锅肉,就三百四。”小李说,“三百四!一盘水煮牛肉四百二呢。”
<br><br>
“什么玩意?这么贵。”小丁捂着腮帮子咕哝道。
<br><br>
另外两个少年对谈话没兴趣,还在埋头吃面,小李低头看着他们,眼睛似乎穿过他们,看到了某个看不见的地方,目光里有热切。
<br><br>
老刀的肚子也感觉到饥饿。他迅速转开眼睛,可是来不及了,那种感觉迅速席卷了他,胃的空虚像是一个深渊,让他身体微微发颤。他有一个月不吃清晨这顿饭了。一顿饭差不多一百块,一个月三千块,攒上一年就够糖糖两个月的幼儿园开销了。
<br><br>
他向远处看,城市清理队的车辆已经缓缓开过来了。
<br><br>
他开始做准备,若彭蠡一时再不回来,他就要考虑自己行动了。虽然会带来不少困难,但时间不等人,总得走才行。身边卖大枣的女人高声叫卖,不时打断他的思绪,声音的洪亮刺得他头疼。步行街一端的小摊子开始收拾,人群像用棍子搅动的池塘里的鱼,倏一下散去。没人会在这时候和清理队较劲。小摊子收拾得比较慢,清理队的车耐心地移动。步行街通常只是步行街,但对清理队的车除外。谁若走得慢了,就被强行收拢起来。
<br><br>
这时彭蠡出现了。他剔着牙,敞着衬衫的扣子,不紧不慢地踱回来,不时打饱嗝。彭蠡六十多了,变得懒散不修边幅,两颊像沙皮狗一样耷拉着,让嘴角显得总是不满意地撇着。如果只看这幅模样,不知道他年轻时的样子,会以为他只是个胸无大志只知道吃喝的怂包。但从老刀很小的时候,他就听父亲讲过彭蠡的事。
<br><br>
老刀迎上前去。彭蠡看到他要打招呼,老刀却打断他:“我没时间和你解释。我需要去第一空间,你告诉我怎么走。”
<br><br>
彭蠡愣住了,已经有十年没人跟他提过第一空间的事,他的牙签捏在手里,不知不觉掰断了。他有片刻没回答,见老刀实在有点急了,才拽着他向楼里走。“回我家说,”彭蠡说,“要走也从那儿走。”
<br><br>
在他们身后,清理队已经缓缓开了过来,像秋风扫落叶一样将人们扫回家。“回家啦,回家啦。转换马上开始了。”车上有人吆喝着。
<br><br>
彭蠡带老刀上楼,进屋。他的单人小房子和一般公租屋无异,六平米房间,一个厕所,一个能做菜的角落,一张桌子一把椅子,胶囊床铺,胶囊下是抽拉式箱柜,可以放衣服物品。墙面上有水渍和鞋印,没做任何修饰,只是歪斜着贴了几个挂钩,挂着夹克和裤子。进屋后,彭蠡把墙上的衣服毛巾都取下来,塞到最靠边的抽屉里。转换的时候,什么都不能挂出来。老刀以前也住这样的单人公租房。一进屋,他就感到一股旧日的气息。
<br><br>
彭蠡直截了当地瞪着老刀:“你不告诉我为什么,我就不告诉你怎么走。”
<br><br>
已经5点半了,还有半个小时。
<br><br>
老刀简单讲了事情的始末。从他捡到纸条瓶子,到他偷偷躲入垃圾道,到他在第二空间接到的委托,再到他的行动。他没有时间描述太多,最好马上就走。
<br><br>
“你躲在垃圾道里?去第二空间?”彭蠡皱着眉,“那你得等24小时啊。”
<br><br>
“二十万块。”老刀说,“等一礼拜也值啊。”
<br><br>
“你就这么缺钱花?”
<br><br>
老刀沉默了一下。“糖糖还有一年多该去幼儿园了。”他说,“我来不及了。”
<br><br>
老刀去幼儿园咨询的时候,着实被吓到了。稍微好一点的幼儿园招生前两天,就有家长带着铺盖卷在幼儿园门口排队,两个家长轮着,一个吃喝拉撒,另一个坐在幼儿园门口等。就这么等上四十多个小时,还不一定能排进去。前面的名额早用钱买断了,只有最后剩下的寥寥几个名额分给苦熬排队的爹妈。这只是一般不错的幼儿园,更好一点的连排队都不行,从一开始就是钱买机会。老刀本来没什么奢望,可是自从糖糖一岁半之后,就特别喜欢音乐,每次在外面听见音乐,她就小脸放光,跟着扭动身子手舞足蹈。那个时候她特别好看。老刀对此毫无抵抗力,他就像被舞台上的灯光层层围绕着,只看到一片耀眼。无论付出什么代价,他都想送糖糖去一个能教音乐和跳舞的幼儿园。
<br><br>
彭蠡脱下外衣,一边洗脸,一边和老刀说话。说是洗脸,不过只是用水随便抹一抹。水马上就要停了,水流已经变得很小。彭蠡从墙上拽下一条脏兮兮的毛巾,随意蹭了蹭,又将毛巾塞进抽屉。他湿漉漉的头发显出油腻的光泽。
<br><br>
“你真是作死,”彭蠡说,“她又不是你闺女,犯得着吗。”
<br><br>
“别说这些了。快告我怎么走。”老刀说。
<br><br>
彭蠡叹了口气:“你可得知道,万一被抓着,可不只是罚款,得关上好几个月。”
<br><br>
“你不是去过好多次吗?”
<br><br>
“只有四次。第五次就被抓了。”
<br><br>
“那也够了。我要是能去四次,抓一次也无所谓。”
<br><br>
老刀要去第一空间送一样东西,送到了挣十万块,带来回信挣二十万。这不过是冒违规的大不韪,只要路径和方法对,被抓住的几率并不大,挣的却是实实在在的钞票。他不知道有什么理由拒绝。他知道彭蠡年轻的时候为了几笔风险钱,曾经偷偷进入第一空间好几次,贩卖私酒和烟。他知道这条路能走。
<br><br>
5:45。他必须马上走了。
<br><br>
彭蠡又叹口气,知道劝也没用。他已经上了年纪,对事懒散倦怠了,但他明白,自己在五十岁前也会和老刀一样。那时他不在乎坐牢之类的事。不过是熬几个月出来,挨两顿打,但挣的钱是实实在在的。只要抵死不说钱的下落,最后总能过去。秩序局的条子也不过就是例行公事。他把老刀带到窗口,向下指向一条被阴影覆盖的小路。
<br><br>
“从我房子底下爬下去,顺着排水管,毡布底下有我原来安上去的脚蹬,身子贴得足够紧了就能避开摄像头。从那儿过去,沿着阴影爬到边上。你能摸着也能看见那道缝。沿着缝往北走。一定得往北。千万别错了。”
<br><br>
彭蠡接着解释了爬过土地的诀窍。要借着升起的势头,从升高的一侧沿截面爬过五十米,到另一侧地面,爬上去,然后向东,那里会有一丛灌木,在土地合拢的时候可以抓住并隐藏自己。老刀没有听完,就已经将身子探出窗口,准备向下爬了。
<br><br>
彭蠡帮老刀爬出窗子,扶着他踩稳了窗下的踏脚。彭蠡突然停下来。“说句不好听的,”他说,“我还是劝你最好别去。那边可不是什么好地儿,去了之后没别的,只能感觉自己的日子有多操蛋。没劲。”
<br><br>
老刀的脚正在向下试探,身子还扒着窗台。“没事。”他说得有点费劲,“我不去也知道自己的日子有多操蛋。”
<br><br>
“好自为之吧。”彭蠡最后说。
<br><br>
老刀顺着彭蠡指出的路径快速向下爬。脚蹬的位置非常舒服。他看到彭蠡在窗口的身影,点了根烟,非常大口地快速抽了几口,又掐了。彭蠡一度从窗口探出身子,似乎想说什么,但最终还是缩了回去。窗子关上了,发着幽幽的光。老刀知道,彭蠡会在转换前最后一分钟钻进胶囊,和整个城市数千万人一样,受胶囊定时释放出的气体催眠,陷入深深睡眠,身子随着世界颠倒来去,头脑却一无所知,一睡就是整整40个小时,到次日晚上再睁开眼睛。彭蠡已经老了,他终于和这个世界其他五千万人一样了。
<br><br>
老刀用自己最快的速度向下,一蹦一跳,在离地足够近的时候纵身一跃,匍匐在地上。彭蠡的房子在四层,离地不远。爬起身,沿高楼在湖边投下的阴影奔跑。他能看到草地上的裂隙,那是翻转的地方。还没跑到,就听到身后在压抑中轰鸣的隆隆和偶尔清脆的嘎啦声。老刀转过头,高楼拦腰截断,上半截正从天上倒下,缓慢却不容置疑地压迫过来。
<br><br>
老刀被震住了,怔怔看了好一会儿。他跑到缝隙,伏在地上。
<br><br>
转换开始了。这是24小时周期的分隔时刻。整个世界开始翻转。钢筋砖块合拢的声音连成一片,像出了故障的流水线。高楼收拢合并,折叠成立方体。霓虹灯、店铺招牌、阳台和附加结构都被吸收入墙体,贴成楼的肌肤。结构见缝插针,每一寸空间都被占满。
<br><br>
大地在升起。老刀观察着地面的走势,来到缝的边缘,又随着缝隙的升起不断向上爬。他手脚并用,从大理石铺就的地面边缘起始,沿着泥土的截面,抓住土里埋藏的金属断茬,最初是向下,用脚试探着退行,很快,随着整快土地的翻转,他被带到空中。
<br><br>
老刀想到前一天晚上城市的样子。
<br><br>
当时他从垃圾堆中抬起眼睛,警觉地听着门外的声音。周围发酵腐烂的垃圾散发出刺鼻的气息,带一股发腥的甜腻味。他倚在门前。铁门外的世界在苏醒。
<br><br>
当铁门掀开的缝隙透入第一道街灯的黄色光芒,他俯下身去,从缓缓扩大的缝隙中钻出。街上空无一人,高楼灯光逐层亮起,附加结构从楼两侧探出,向两旁一节一节伸展,门廊从楼体内延伸,房檐延轴旋转,缓缓落下,楼梯降落延伸到马迷途上。步行街的两侧,一个又一个黑色立方体从中间断裂,向两侧打开,露出其中货架的结构。立方体顶端伸出招牌,连成商铺的走廊,两侧的塑料棚向头顶延伸闭合。街道空旷得如同梦境。
<br><br>
霓虹灯亮了,商铺顶端闪烁的小灯打出新疆大枣、东北拉皮、上海烤麸和湖南腊肉。
<br><br>
整整一天,老刀头脑中都忘不了这一幕。他在这里生活了四十八年,还从来没有见过这一切。他的日子总是从胶囊起,至胶囊终,在脏兮兮的餐桌和被争吵萦绕的货摊之间穿行。这是他第一次看到世界纯粹的模样。
<br><br>
每个清晨,如果有人从远处观望——就像大货车司机在高速北京入口处等待时那样——他会看到整座城市的伸展与折叠。
<br><br>
清晨六点,司机们总会走下车,站在高速边上,揉着经过一夜潦草睡眠而昏沉的眼睛,打着哈欠,相互指点着望向远处的城市中央。高速截断在七环之外,所有的翻转都在六环内发生。不远不近的距离,就像遥望西山或是海上的一座孤岛。
<br><br>
晨光熹微中,一座城市折叠自身,向地面收拢。高楼像最卑微的仆人,弯下腰,让自己低声下气切断身体,头碰着脚,紧紧贴在一起,然后再次断裂弯腰,将头顶手臂扭曲弯折,插入空隙。高楼弯折之后重新组合,蜷缩成致密的巨大魔方,密密匝匝地聚合到一起,陷入沉睡。然后地面翻转,小块小块土地围绕其轴,一百八十度翻转到另一面,将另一面的建筑楼宇露出地表。楼宇由折叠中站立起身,在灰蓝色的天空中像苏醒的兽类。城市孤岛在橘黄色晨光中落位,展开,站定,腾起弥漫的灰色苍云。
<br><br>
司机们就在困倦与饥饿中欣赏这一幕无穷循环的城市戏剧。
</p>
<h3>(2)</h3>
<p> 折叠城市分三层空间。大地的一面是第一空间,五百万人口,生存时间是从清晨六点到第二天清晨六点。空间休眠,大地翻转。翻转后的另一面是第二空间和第三空间。第二空间生活着两千五百万人口,从次日清晨六点到夜晚十点,第三空间生活着五千万人,从十点到清晨六点,然后回到第一空间。时间经过了精心规划和最优分配,小心翼翼隔离,五百万人享用二十四小时,七千五百万人享用另外二十四小时。
<br><br>
大地的两侧重量并不均衡,为了平衡这种不均,第一空间的土地更厚,土壤里埋藏配重物质。人口和建筑的失衡用土地来换。第一空间居民也因而认为自身的底蕴更厚。
<br><br>
老刀从小生活在第三空间。他知道自己的日子是什么样,不用彭蠡说他也知道。他是个垃圾工,做了二十八年垃圾工,在可预见的未来还将一直做下去。他还没找到可以独自生存的意义和最后的怀疑主义。他仍然在卑微生活的间隙占据一席。
<br><br>
老刀生在北京城,父亲就是垃圾工。据父亲说,他出生的时候父亲刚好找到这份工作,为此庆贺了整整三天。父亲本是建筑工,和数千万其他建筑工一样,从四方涌到北京寻工作,这座折叠城市就是父亲和其他人一起亲手建的。一个区一个区改造旧城市,像白蚁漫过木屋一样啃噬昔日的屋檐门槛,再把土地翻起,建筑全新的楼宇。他们埋头斧凿,用累累砖块将自己包围在中间,抬起头来也看不见天空,沙尘遮挡视线,他们不知晓自己建起的是怎样的恢弘。直到建成的日子高楼如活人一般站立而起,他们才像惊呆了一样四处奔逃,仿佛自己生下了一个怪胎。奔逃之后,镇静下来,又意识到未来生存在这样的城市会是怎样一种殊荣,便继续辛苦摩擦手脚,低眉顺眼勤恳,寻找各种存留下来的机会。据说城市建成的时候,有八千万想要寻找工作留下来的建筑工,最后能留下来的,不过两千万。
<br><br>
垃圾站的工作能找到也不容易,虽然只是垃圾分类处理,但还是层层筛选,要有力气有技巧,能分辨能整理,不怕辛苦不怕恶臭,不对环境挑三拣四。老刀的父亲靠强健的意志在汹涌的人流中抓住机会的细草,待人潮退去,留在干涸的沙滩上,抓住工作机会,低头俯身,艰难浸在人海和垃圾混合的酸朽气味中,一干就是二十年。他既是这座城市的建造者,也是城市的居住者和分解者。
<br><br>
老刀出生时,折叠城市才建好两年,他从来没去过其他地方,也没想过要去其他地方。他上了小学、中学。考了三年大学,没考上,最后还是做了垃圾工。他每天上五个小时班,从夜晚十一点到清晨四点,在垃圾站和数万同事一起,快速而机械地用双手处理废物垃圾,将第一空间和第二空间传来的生活碎屑转化为可利用的分类的材质,再丢入再处理的熔炉。他每天面对垃圾传送带上如溪水涌出的残渣碎片,从塑料碗里抠去吃剩的菜叶,将破碎酒瓶拎出,把带血的卫生巾后面未受污染的一层薄膜撕下,丢入可回收的带着绿色条纹的圆筒。他们就这么干着,以速度换生命,以数量换取薄如蝉翼的仅有的奖金。
<br><br>
第三空间有两千万垃圾工,他们是夜晚的主人。另三千万人靠贩卖衣服食物燃料和保险过活,但绝大多数人心知肚明,垃圾工才是第三空间繁荣的支柱。每每在繁花似锦的霓虹灯下漫步,老刀就觉得头顶都是食物残渣构成的彩虹。这种感觉他没法和人交流,年轻一代不喜欢做垃圾工,他们千方百计在舞厅里表现自己,希望能找到一个打碟或伴舞的工作。在服装店做一个店员也是好的选择,手指只拂过轻巧衣物,不必在泛着酸味的腐烂物中寻找塑料和金属。少年们已经不那么恐惧生存,他们更在意外表。
<br><br>
老刀并不嫌弃自己的工作,但他去第二空间的时候,非常害怕被人嫌弃。
<br><br>
那是前一天清晨的事。他捏着小纸条,偷偷从垃圾道里爬出,按地址找到写纸条的人。第二空间和第三空间的距离没那么远,它们都在大地的同一面,只是不同时间出没。转换时,一个空间高楼折起,收回地面,另一个空间高楼从地面中节节升高,踩着前一个空间的楼顶作为地面。唯一的差别是楼的密度。他在垃圾道里躲了一昼夜才等到空间敞开。他第一次到第二空间,并不紧张,唯一担心的是身上腐坏的气味。
<br><br>
所幸秦天是宽容大度的人。也许他早已想到自己将招来什么样的人,当小纸条放入瓶中的时候,他就知道自己将面对的是谁。
<br><br>
秦天很和气,一眼就明白老刀前来的目的,将他拉入房中,给他热水洗澡,还给他一件浴袍换上。“我只有依靠你了。”秦天说。
<br><br>
秦天是研究生,住学生公寓。一个公寓四个房间,四个人一人一间,一个厨房两个厕所。老刀从来没在这么大的厕所洗过澡。他很想多洗一会儿,将身上气味好好冲一冲,但又担心将澡盆弄脏,不敢用力搓动。墙上喷出泡沫的时候他吓了一跳,热蒸汽烘干也让他不适应。洗完澡,他拿起秦天递过来的浴袍,犹豫了很久才穿上。他把自己的衣服洗了,又洗了厕所盆里随意扔着的几件衣服。生意是生意,他不想欠人情。
<br><br>
秦天要送礼物给他相好的女孩子。他们在工作中认识,当时秦天有机会去第一空间实习,联合国经济司,她也在那边实习。只可惜只有一个月,回来就没法再去了。他说她生在第一空间,家教严格,父亲不让她交往第二空间的男孩,所以不敢用官方通道寄给她。他对未来充满乐观,等他毕业就去申请联合国新青年项目,如果能入选,就也能去第一空间工作。他现在研一,还有一年毕业。他心急如焚,想她想得发疯。他给她做了一个项链坠,能发光的材质,透明的,玫瑰花造型,作为他的求婚信物。
<br><br>
“我当时是在一个专题研讨会,就是上回讨论联合国国债那个会,你应该听说过吧?就是那个……anyway,我当时一看,啊……立刻跑过去跟她说话,她给嘉宾引导座位,我也不知道应该说点什么,就在她身后走过来又走过去。最后我假装要找同传,让她带我去找。她特温柔,说话细声细气的。我压根就没追过姑娘,特别紧张,……后来我们俩好了之后有一次说起这件事……你笑什么?……对,我们是好了。……还没到那种关系,就是……不过我亲过她了。”秦天也笑了,有点不好意思,“是真的。你不信吗?是。连我自己也不信。你说她会喜欢我吗?”
<br><br>
“我不知道啊。”老刀说,“我又没见过她。”
<br><br>
这时,秦天同屋的一个男生凑过来,笑道:“大叔,您这么认真干吗?这家伙哪是问你,他就是想听人说‘你这么帅,她当然会喜欢你’。”
<br><br>
“她很漂亮吧?”
<br><br>
“我跟你说也不怕你笑话。”秦天在屋里走来走去,“你见到她就知道什么叫清雅绝伦。”
<br><br>
秦天突然顿住了,不说了,陷入回忆。他想起依言的嘴,他最喜欢的就是她的嘴,那么小小的,莹润的,下嘴唇饱满,带着天然的粉红色,让人看着看着就忍不住想咬一口。她的脖子也让他动心,虽然有时瘦得露出筋,但线条是纤直而好看的,皮肤又白又细致,从脖子一直延伸到衬衫里,让人的视线忍不住停在衬衫的第二个扣子那里。他第一次轻吻她一下,她躲开,他又吻,最后她退无可退,就把眼睛闭上了,像任人宰割的囚犯,引他一阵怜惜。她的唇很软,他用手反复感受她腰和臀部的曲线。从那天开始,他就居住在思念中。她是他夜晚的梦境,是他抖动自己时看到的光芒。
<br><br>
秦天的同学叫张显,和老刀开始聊天,聊得很欢。
<br><br>
张显问老刀第三空间的生活如何,又说他自己也想去第三空间住一段。他听人说,如果将来想往上爬,有过第三空间的管理经验是很有用的。现在几个当红的人物,当初都是先到第三空间做管理者,然后才升到第一空间,若是停留在第二空间,就什么前途都没有,就算当个行政干部,一辈子级别也高不了。他将来想要进政府,已经想好了路。不过他说他现在想先挣两年钱再说,去银行来钱快。他见老刀的反应很迟钝,几乎不置可否,以为老刀厌恶这条路,就忙不迭地又加了几句解释。
<br><br>
“现在政府太混沌了,做事太慢,僵化,体系也改不动。”他说,“等我将来有了机会,我就推快速工作作风改革。干得不行就滚蛋。”他看老刀还是没说话,又说,“选拔也要放开。也向第三空间放开。”
<br><br>
老刀没回答。他其实不是厌恶,只是不大相信。
<br><br>
张显一边跟老刀聊天,一边对着镜子打领带,喷发胶。他已经穿好了衬衫,浅蓝色条纹,亮蓝色领带。喷发胶的时候一边闭着眼睛皱着眉毛避开喷雾,一边吹口哨。
<br><br>
张显夹着包走了,去银行实习上班。秦天说着话也要走。他还有课,要上到下午四点。临走前,他当着老刀的面把五万块定金从网上转到老刀卡里,说好了剩下的钱等他送到再付。老刀问他这笔钱是不是攒了很久,看他是学生,如果拮据,少要一点也可以。秦天说没事,他现在实习,给金融咨询公司打工,一个月十万块差不多。这也就是两个月工资,还出得起。老刀一个月一万块标准工资,他看到差距,但他没有说。秦天要老刀务必带回信回来,老刀说试试。秦天给老刀指了吃喝的所在,叫他安心在房间里等转换。
<br><br>
老刀从窗口看向街道。他很不适应窗外的日光。太阳居然是淡白色,不是黄色。日光下的街道也显得宽阔,老刀不知道是不是错觉,这街道看上去有第三空间的两倍宽。楼并不高,比第三空间矮很多。路上的人很多,匆匆忙忙都在急着赶路,不时有人小跑着想穿过人群,前面的人就也加起速,穿过路口的时候,所有人都像是小跑着。大多数人穿得整齐,男孩子穿西装,女孩子穿衬衫和短裙,脖子上围巾低垂,手里拎着线条硬朗的小包,看上去精干。街上汽车很多,在路口等待的时候,不时有看车的人从车窗伸出头,焦急地向前张望。老刀很少见到这么多车,他平时习惯了磁悬浮,挤满人的车厢从身边加速,呼一阵风。
<br><br>
中午十二点的时候,走廊里一阵声响。老刀从门上的小窗向外看。楼道地面化为传送带开始滚动,将各屋门口的垃圾袋推入尽头的垃圾道。楼道里腾起雾,化为密实的肥皂泡沫,飘飘忽忽地沉降,然后是一阵水,水过了又一阵热蒸汽。
<br><br>
背后突然有声音,吓了老刀一跳。他转过身,发现公寓里还有一个男生,刚从自己房间里出来。男生面无表情,看到老刀也没有打招呼。他走到阳台旁边一台机器旁边,点了点,机器里传出咔咔刷刷轰轰嚓的声音,一阵香味飘来,男生端出一盘菜又回了房间。从他半开的门缝看过去,男孩坐在地上的被子和袜子中间,瞪着空无一物的墙,一边吃一边咯咯地笑。他不时用手推一推眼镜。吃完把盘子放在脚边,站起身,同样对着空墙做击打动作,费力气顶住某个透明的影子,偶尔来一个背摔,气喘吁吁。
<br><br>
老刀对第二空间最后的记忆是街上撤退时的优雅。从公寓楼的窗口望下去,一切都带着令人羡慕的秩序感。九点十五分开始,街上一间间卖衣服的小店开始关灯,聚餐之后的团体面色红润,相互告别。年轻男女在出租车外亲吻。然后所有人回楼,世界蛰伏。
<br><br>
夜晚十点到了。他回到他的世界,回去上班。
</p>
<h3>(3)</h3>
<p> 第一和第三空间之间没有连通的垃圾道,第一空间的垃圾经过一道铁闸,运到第三空间之后,铁闸迅速合拢。老刀不喜欢从地表翻越,但他没有办法。
<br><br>
他在呼啸的风中爬过翻转的土地,抓住每一寸零落的金属残渣,找到身体和心理平衡,最后匍匐在离他最遥远的一重世界的土地上。他被整个攀爬弄得头晕脑胀,胃口也不舒服。他忍住呕吐,在地上趴了一会儿。
<br><br>
当他爬起身的时候,天亮了。
<br><br>
老刀从来没有见过这样的景象。太阳缓缓升起,天边是深远而纯净的蓝,蓝色下沿是橙黄色,有斜向上的条状薄云。太阳被一处屋檐遮住,屋檐显得异常黑,屋檐背后明亮夺目。太阳升起时,天的蓝色变浅了,但是更宁静透彻。老刀站起身,向太阳的方向奔跑。他想要抓住那道褪去的金色。蓝天中能看见树枝的剪影。他的心狂跳不已。他从来不知道太阳升起竟然如此动人。
<br><br>
他跑了一段路,停下来,冷静了。他站在街道中央。路的两旁是高大树木和大片草坪。他环视四周,目力所及,远远近近都没有一座高楼。他迷惑了,不确定自己是不是真的到了第一空间。他能看见两排粗壮的银杏。
<br><br>
他又退回几步,看着自己跑来的方向。街边有一个路牌。他打开手机里存的地图,虽然没有第一空间动态图权限,但有事先下载的静态图。他找到了自己的位置和他要去的地方。他刚从一座巨大的园子里奔出来,翻转的地方就在园子的湖边。
<br><br>
老刀在万籁俱寂的街上跑了一公里,很容易找到了要找的小区。他躲在一丛灌木背后,远远地望着那座漂亮的房子。
<br><br>
8:30,依言出来了。
<br><br>
她像秦天描述的一样清秀,只是没有那么漂亮。老刀早就能想到这点。不会有任何女孩长得像秦天描述的那么漂亮。他明白了为什么秦天着重讲她的嘴。她的眼睛和鼻子很普通,只是比较秀气,没什么好讲的。她的身材还不错,骨架比较小,虽然高,但看上去很纤细。穿了一条乳白色连衣裙,有飘逸的裙摆,腰带上有珍珠,黑色高跟皮鞋。
<br><br>
老刀悄悄走上前去。为了不吓到她,他特意从正面走过去,离得远远的就鞠了一躬。
<br><br>
她站住了,惊讶地看着他。
<br><br>
老刀走近了,说明来意,将包裹着情书和项链坠的信封从怀里掏出来。
<br><br>
她的脸上滑过一丝惊慌,小声说:“你先走,我现在不能和你说。”
<br><br>
“呃……我其实没什么要说的,”老刀说,“我只是送信的。”
<br><br>
她不接,双手紧紧地搅握着,只是说:“我现在不能收。你先走。我是说真的,拜托了,你先走吧好吗?”她说着低头,从包里掏出一张名片,“中午到这里找我。”
<br><br>
老刀低头看看,名片上写着一个银行的名字。
<br><br>
“十二点。到地下超市等我。”她又说。
<br><br>
老刀看得出她过分的不安,于是点头收起名片,回到隐身的灌木丛后,远远地观望着。很快,又有一个男人从房子里出来,到她身边。男人看上去和老刀年龄相仿,或者年轻两岁,穿着一套很合身的深灰色西装,身材高而宽阔,虽没有突出的肚子,但是觉得整个身体很厚。男人的脸无甚特色,戴眼镜,圆脸,头发向一侧梳得整齐。
<br><br>
男人搂住依言的腰,吻了她嘴唇一下。依言想躲,但没躲开,颤抖了一下,手挡在身前显得非常勉强。
<br><br>
老刀开始明白了。
<br><br>
一辆小车开到房子门前。单人双轮小车,黑色,敞篷,就像电视里看到的古代的马车或黄包车,只是没有马拉,也没有车夫。小车停下,歪向前,依言踏上去,坐下,拢住裙子,让裙摆均匀覆盖膝盖,散到地上。小车缓缓开动了,就像有一匹看不见的马拉着一样。依言坐在车里,小车缓慢而波澜不惊。等依言离开,一辆无人驾驶的汽车开过来,男人上了车。
<br><br>
老刀在原地来回踱着步子。他觉得有些东西非常憋闷,但又说不出来。他站在阳光里,闭上眼睛,清晨蓝天下清凛干净的空气沁入他的肺。空气给他一种冷静的安慰。
<br><br>
片刻之后,他才上路。依言给的地址在她家东面,3公里多一点。街上人很少。8车道的宽阔道路上行驶着零星车辆,快速经过,让人看不清车的细节。偶尔有华服的女人乘坐着双轮小车缓缓飘过他身旁,沿步行街,像一场时装秀,端坐着姿态优美。没有人注意到老刀。绿树摇曳,树叶下的林荫路留下长裙的气味。
<br><br>
依言的办公地在西单某处。这里完全没有高楼,只是围绕着一座花园有零星分布的小楼,楼与楼之间的联系气若游丝,几乎看不出它们是一体。走到地下,才看到相连的通道。
<br><br>
老刀找到超市。时间还早。一进入超市,就有一辆小车跟上他,每次他停留在货架旁,小车上的屏幕上就显示出这件货物的介绍、评分和同类货物质量比。超市里的东西都写着他看不懂的文字。食物包装精致,小块糕点和水果用诱人的方式摆在盘里,等人自取。他没有触碰任何东西。不过整个超市似乎并没有警卫或店员。
<br><br>
还不到十二点,顾客就多了起来。有穿西装的男人走进超市,取三明治,在门口刷一下就匆匆离开。还是没有人特别注意老刀。他在门口不起眼的位置等着。
<br><br>
依言出现了。老刀迎上前去,依言看了看左右,没说话,带他去了隔壁的一家小餐厅。两个穿格子裙子的小机器人迎上来,接过依言手里的小包,又带他们到位子上,递上菜单。依言在菜单上按了几下,小机器人转身,轮子平稳地滑回了后厨。
<br><br>
两个人面对面坐了片刻,老刀又掏出信封。
<br><br>
依言却没有接:“……你能听我解释一下吗?”
<br><br>
老刀把信封推到她面前:“你先收下这个。”
<br><br>
依言推回给他。
<br><br>
“你先听我解释一下行吗?”依言又说。
<br><br>
“你没必要跟我解释,”老刀说,“信不是我写的。我只是送信而已。”
<br><br>
“可是你回去要告诉说的。”依言低了低头。小机器人送上了两个小盘子,一人一份,是某种红色的生鱼片,薄薄两片,摆成花瓣的形状。依言没有动筷子,老刀也没有。信封被小盘子隔在中央,两个人谁也没再推。“我不是背叛他。去年他来的时候我就已经订婚了。我也不是故意瞒他或欺骗他,或者说……是的,我骗了他,但那是他自己猜的。他见到吴闻来接我,就问是不是我爸爸。我……我没法回答他。你知道,那太尴尬了。我……”
<br><br>
依言说不下去了。
<br><br>
老刀等了一会儿说:“我不想追问你们之前的事。你收下信就行了。”
<br><br>
依言低头好一会儿又抬起来:“你回去以后,能不能替我瞒着他?”
<br><br>
“为什么?”
<br><br>
“我不想让他以为我是坏女人耍他。其实我心里是喜欢他的。我也很矛盾。”
<br><br>
“这些和我没关系。”
<br><br>
“求你了……我是真的喜欢他。”
<br><br>
老刀沉默了一会儿,他需要做一个决定。
<br><br>
“可是你还是结婚了?”他问她。
<br><br>
“吴闻对我很好。好几年了。”依言说,“他认识我爸妈。我们订婚也很久了。况且……我比秦天大三岁,我怕他不能接受。秦天以为我是实习生。这点也是我不好,我没说实话。最开始只是随口说的,到后来就没法改口了。我真的没想到他是认真的。”
<br><br>
依言慢慢透露了她的信息。她是这个银行的总裁助理,已经工作两年多了,只是被派往联合国参加培训,赶上那次会议,就帮忙参与了组织。她不需要上班,老公挣的钱足够多,可她不希望总是一个人呆在家里,才出来上班,每天只工作半天,拿半薪。其余的时间自己安排,可以学一些东西。她喜欢学新东西,喜欢认识新人,也喜欢联合国培训的那几个月。她说像她这样的太太很多,半职工作也很多。中午她下了班,下午会有另一个太太去做助理。她说虽然对秦天没有说实话,可是她的心是真诚的。
<br><br>
“所以,”她给老刀夹了新上来的热菜,“你能不能暂时不告诉他?等我……有机会亲自向他解释可以吗?”
<br><br>
老刀没有动筷子。他很饿,可是他觉得这时不能吃。
<br><br>
“可是这等于说我也得撒谎。”老刀说。
<br><br>
依言回身将小包打开,将钱包取出来,掏出五张一万块的纸币推给老刀。“一点心意,你收下。”
<br><br>
老刀愣住了。他从来没见过一万块钱的纸钞。他生活里从来不需要花这么大的面额。他不自觉地站起身,感到恼怒。依言推出钱的样子就像是早预料到他会讹诈,这让他受不了。他觉得自己如果拿了,就是接受贿赂,将秦天出卖。虽然他和秦天并没有任何结盟关系,但他觉得自己在背叛他。老刀很希望自己这个时候能将钱扔在地上,转身离去,可是他做不到这一步。他又看了几眼那几张钱,五张薄薄的纸散开摊在桌子上,像一把破扇子。他能感觉它们在他体内产生的力量。它们是淡蓝色,和一千块的褐色与一百块的红色都不一样,显得更加幽深遥远,像是一种挑逗。他几次想再看一眼就离开,可是一直没做到。
<br><br>
她仍然匆匆翻动小包,前前后后都翻了,最后从一个内袋里又拿出五万块,和刚才的钱摆在一起。“我只带了这么多,你都收下吧。”她说,“你帮帮我。其实我之所以不想告诉他,也是不确定以后会怎么样。也许我有一天真的会有勇气和他在一起呢。”
<br><br>
老刀看看那十张纸币,又看看她。他觉得她并不相信自己的话,她的声音充满迟疑,出卖了她的心。她只是将一切都推到将来,以消解此时此刻的难堪。她很可能不会和秦天私奔,可是也不想让他讨厌她,于是留着可能性,让自己好过一点。老刀能看出她骗她自己,可是他也想骗自己。他对自己说,他对秦天没有任何义务,秦天只是委托他送信,他把信送到了,现在这笔钱是另一项委托,保守秘密的委托。他又对自己说,也许她和秦天将来真的能在一起也说不定,那样就是成人之美。他还说,想想糖糖,为什么去管别人的事而不管糖糖呢。他似乎安定了一些,手指不知不觉触到了钱的边缘。
<br><br>
“这钱……太多了。”他给自己一个台阶下,“我不能拿这么多。”
<br><br>
“拿着吧,没事。”她把钱塞到他手里,“我一个礼拜就挣出来了。没事的。”
<br><br>
“……那我怎么跟他说?”
<br><br>
“你就说我现在不能和他在一起,但是我真的喜欢他。我给你写个字条,你帮我带给他。”依言从包里找出一个画着孔雀绣着金边的小本子,轻盈地撕下一张纸,低头写字。她的字看上去像倾斜的芦苇。
<br><br>
最后,老刀离开餐厅的时候,又回头看了一眼。依言的眼睛注视着墙上的一幅画。她的姿态静默优雅,看上去就像永远都不会离开这里似的。
<br><br>
他用手捏了捏裤子口袋里的纸币。他讨厌自己,可是他想把纸币抓牢。
</p>
<h3>(4)</h3>
<p> 老刀从西单出来,依原路返回。重新走早上的路,他觉得倦意丛生,一步也跑不动了。宽阔的步行街两侧是一排垂柳和一排梧桐,正是晚春,都是鲜亮的绿色。他让暖意丛生的午后阳光照亮僵硬的面孔,也照亮空乏的心底。
<br><br>
他回到早上离开的园子,赫然发现园子里来往的人很多。园子外面两排银杏树庄严茂盛。园门口有黑色小汽车驶入。园里的人多半穿着材质顺滑、剪裁合体的西装,也有穿黑色中式正装的,看上去都有一番眼高于顶的气质。也有外国人。他们有的正在和身边人讨论什么,有的远远地相互打招呼,笑着携手向前走。
<br><br>
老刀犹豫了一下要到哪里去,街上人很少,他一个人站着极为显眼,去公共场所又容易被注意,他很想回到园子里,早一点找到转换地,到一个没人的角落睡上一觉。他太困了,又不敢在街上睡。他见出入园子的车辆并无停滞,就也尝试着向里走。直到走到园门边上,他才发现有两个小机器人左右逡巡。其他人和车走过都毫无问题,到了老刀这里,小机器人忽然发出嘀嘀的叫声,转着轮子向他驶来。声音在宁静的午后显得刺耳。园里人的目光汇集到他身上。他慌了,不知道是不是自己的衬衫太寒酸。他尝试着低声对小机器人说话,说他的西装落在里面了,可是小机器人只是嘀嘀嗒嗒地叫着,头顶红灯闪烁,什么都不听。园里的人们停下脚步看着他,像是看到小偷或奇怪的人。很快,从最近的建筑中走出三个男人,步履匆匆地向他们跑过来。老刀紧张极了,他想退出去,已经太晚了。
<br><br>
“出什么事了?”领头的人高声询问着。
<br><br>
老刀想不出解释的话,手下意识地搓着裤子。
<br><br>
一个三十几岁的男人走在最前面,一到跟前就用一个纽扣一样的小银盘上上下下地晃,手的轨迹围绕着老刀。他用怀疑的眼神打量他,像用罐头刀试图撬开他的外壳。
<br><br>
“没记录。”男人将手中的小银盘向身后更年长的男人示意,“带回去吧?”
<br><br>
老刀突然向后跑,向园外跑。
<br><br>
可没等他跑出去,两个小机器人悄无声息挡在他面前,扣住他的小腿。它们的手臂是箍,轻轻一扣就合上。他一下子踉跄了,差点摔倒又摔不倒,手臂在空中无力的乱划。
<br><br>
“跑什么?”年轻男人更严厉地走到他面前,瞪着他的眼睛。
<br><br>
“我……”老刀头脑嗡嗡响。
<br><br>
两个小机器人将他的两条小腿扣紧,抬起,放在它们轮子边上的平台上,然后异常同步地向最近的房子驶去,平稳迅速,保持并肩,从远处看上去,或许会以为老刀脚踩风火轮。老刀毫无办法,除了心里暗喊一声糟糕,简直没有别的话说。他懊恼自己如此大意,人这么多的地方,怎么可能没有安全保障。他责怪自己是困倦得昏了头,竟然在这样大的安全关节上犯如此低级的错误。这下一切完蛋了,他想,钱都没了,还要坐牢。
<br><br>
小机器人从小路绕向建筑后门,在后门的门廊里停下来。三个男人跟了上来。年轻男人和年长男人似乎就老刀的处理问题起了争执,但他们的声音很低,老刀听不见。片刻之后,年长男人走到他身边,将小机器人解锁,然后拉着他的大臂走上二楼。
<br><br>
老刀叹了一口气,横下一条心,觉得事到如今,只好认命。
<br><br>
年长者带他进入一个房间。他发现这是一个旅馆房间,非常大,比秦天的公寓客厅还大,似乎有自己租的房子两倍大。房间的色调是暗沉的金褐色,一张极宽大的双人床摆在中央。床头背后的墙面上是颜色过渡的抽象图案,落地窗,白色半透明纱帘,窗前是一个小圆桌和两张沙发。他心里惴惴。不知道年长者的身份和态度。
<br><br>
“坐吧,坐吧。”年长者拍拍他肩膀,笑笑,“没事了。”
<br><br>
老刀狐疑地看着他。
<br><br>
“你是第三空间来的吧?”年长者把他拉到沙发边上,伸手示意。
<br><br>
“您怎么知道?”老刀无法撒谎。
<br><br>
“从你裤子上。”年长者用手指指他的裤腰,“你那商标还没剪呢。这牌子只有第三空间有卖的。我小时候我妈就喜欢给我爸买这牌子。”
<br><br>
“您是……”
<br><br>
“别您您的,叫你吧。我估摸着我也比你大不了几岁。你今年多大?我五十二。……你看看,就比你大四岁。”他顿了一下,又说,“我叫葛大平,你叫我老葛吧。”
<br><br>
老刀放松了些。老葛把西装脱了,活动了一下膀子,从墙壁里接了一杯热水,递给老刀。他长长的脸,眼角眉梢和两颊都有些下坠,戴一副眼镜,也向下耷拉着,头发有点自来卷,蓬松地堆在头顶,说起话来眉毛一跳一跳,很有喜剧效果。他自己泡了点茶,问老刀要不要,老刀摇摇头。
<br><br>
“我原来也是第三空间的。咱也算半个老乡吧。”老葛说,“所以不用太拘束。我还是能管点事儿,不会把你送出去的。”
<br><br>
老刀长长地出了口气,心里感叹万幸。他于是把自己到第二、第一空间的始末讲了一遍,略去依言感情的细节,只说送到了信,就等着回去。
<br><br>
老葛于是也不见外,把他自己的情况讲了。他从小也在第三空间长大,父母都给人送货。十五岁的时候考上了军校,后来一直当兵,文化兵,研究雷达,能吃苦,技术又做得不错,赶上机遇又好,居然升到了雷达部门主管,大校军衔。家里没背景不可能再升,就申请转业,到了第一空间一个支持性部门,专给政府企业做后勤保障,组织会议出行,安排各种场面。虽然是蓝领的活儿,但因为涉及的都是政要,又要协调管理,就一直住在第一空间。这种人也不少,厨师、大夫、秘书、管家,都算是高级蓝领了。他们这个机构安排过很多重大场合,老葛现在是主任。老刀知道,老葛说的谦虚,说是蓝领,其实能在第一空间做事的都是牛人,即使厨师也不简单,更何况他从第三空间上来,能管雷达。
<br><br>
“你在这儿睡一会儿。待会儿晚上我带你吃饭去。”老葛说。
<br><br>
老刀受宠若惊,不大相信自己的好运。他心里还有担心,但是白色的床单和错落堆积的枕头显出召唤气息,他的腿立刻发软了,倒头昏昏沉沉睡了几个小时。
<br><br>
醒来的时候天色暗了,老葛正对着镜子捋头发。他向老刀指了指沙发上的一套西装制服,让他换上,又给他胸口别上一个微微闪着红光的小徽章,身份认证。
<br><br>
下楼来,老刀发现原来这里有这么多人。似乎刚刚散会,在大厅里聚集三三两两说话。大厅一侧是会场,门还开着,门看上去很厚,包着红褐色皮子;另一侧是一个一个铺着白色桌布的高脚桌,桌布在桌面下用金色缎带打了蝴蝶结,桌中央的小花瓶插着一只百合,花瓶旁边摆着饼干和干果,一旁的长桌上则有红酒和咖啡供应。聊天的人们在高脚桌之间穿梭,小机器人头顶托盘,收拾喝光的酒杯。
<br><br>
老刀尽量镇定地跟着老葛。走到会场内,他忽然看到一面巨大的展示牌,上面写着:
<br><br>
折叠城市五十年。
<br><br>
“这是……什么?”他问老葛。
<br><br>
“哦,庆典啊。”老葛正在监督场内布置,“小赵,你来一下,你去把桌签再核对一遍。机器人有时候还是不如人靠谱,它们认死理儿。”
<br><br>
老刀看到,会场里现在是晚宴的布置,每张大圆桌上都摆着鲜艳的花朵。
<br><br>
他有一种恍惚的感觉,站在角落里,看着会场中央巨大的吊灯,像是被某种光芒四射的现实笼罩,却只存在在它的边缘。舞台中央是演讲的高台,背后的布景流动播映着北京城的画面。大概是航拍,拍到了全城的风景,清晨和日暮的光影,紫红色暗蓝色天空,云层快速流转,月亮从角落上升起,太阳在屋檐上沉落。大气中正的布局,沿中轴线对称的城市设计,延伸到六环的青砖院落和大面积绿地花园。中式风格的剧院,日本式美术馆,极简主义风格的音乐厅建筑群。然后是城市的全景,真正意义上的全景,包含转换的整个城市双面镜头:大地翻转,另一面城市,边角锐利的写字楼,朝气蓬勃的上班族;夜晚的霓虹,白昼一样的天空,高耸入云的公租房,影院和舞厅的娱乐。
<br><br>
只是没有老刀上班的地方。
<br><br>
他仔细地盯着屏幕,不知道其中会不会展示建城时的历史。他希望能看见父亲的时代。小时候父亲总是用手指着窗外的楼,说“当时我们”。狭小的房间正中央挂着陈旧的照片,照片里的父亲重复着垒砖的动作,一遍一遍无穷无尽。他那时每天都要看见那照片很多遍,几乎已经腻烦了,可是这时他希望影像中出现哪怕一小段垒砖的镜头。
<br><br>
他沉浸在自己的恍惚中。这也是他第一次看到转换的全景。他几乎没注意到自己是怎么坐下的,也没注意到周围人的落座,台上人讲话的前几分钟,他并没有注意听。
<br><br>
“……有利于服务业的发展,服务业依赖于人口规模和密度。我们现在的城市服务业已经占到GDP85%以上,符合世界第一流都市的普遍特征。另外最重要的就是绿色经济和循环经济。”这句话抓住了老刀的注意力,循环经济和绿色经济是他们工作站的口号,写得比人还大贴在墙上。他望向台上的演讲人,是个白发老人,但是精神显得异常饱满,“……通过垃圾的完全分类处理,我们提前实现了本世纪节能减排的目标,减少污染,也发展出成体系成规模的循环经济,每年废旧电子产品中回收的贵金属已经完全投入再生产,塑料的回收率也已达到80%以上。回收直接与再加工工厂相连……”
<br><br>
老刀有远亲在再加工工厂工作,在科技园区,远离城市,只有工厂和工厂和工厂。据说那边的工厂都差不多,机器自动作业,工人很少,少量工人晚上聚集着,就像荒野部落。
<br><br>
他仍然恍惚着。演讲结束之后,热烈的掌声响起,才将他从自己的纷乱念头中拉出来,他也跟着鼓了掌,虽然不知道为什么。他看到演讲人从舞台上走下来,回到主桌上正中间的座位。所有人的目光都跟着他。
<br><br>
忽然老刀看到了吴闻。
<br><br>
吴闻坐在主桌旁边一桌,见演讲人回来就起身去敬酒,然后似乎有什么话要问演讲人。演讲人又站起身,跟吴闻一起到大厅里。老刀不自觉地站起来,心里充满好奇,也跟着他们。老葛不知道到哪里去了,周围开始上菜。
<br><br>
老刀到了大厅,远远地观望,对话只能听见片段。
<br><br>
“……批这个有很多好处。”吴闻说,“是,我看过他们的设备了……自动化处理垃圾,用溶液消解,大规模提取材质……清洁,成本也低……您能不能考虑一下?”
<br><br>
吴闻的声音不高,但老刀清楚地听见“处理垃圾”的字眼,不由自主凑上前去。
<br><br>
白发老人的表情相当复杂,他等吴闻说完,过了一会儿才问:“你确定溶液无污染?”
<br><br>
吴闻有点犹豫:“现在还是有一点……不过很快就能减低到最低。”
<br><br>
老刀离得很近了。
<br><br>
白发老人摇了摇头,眼睛盯着吴闻:“事情哪是那么简单的,你这个项目要是上马了,大规模一改造,又不需要工人,现在那些劳动力怎么办,上千万垃圾工失业怎么办?”
<br><br>
白发老人说完转过身,又返回会场。吴闻呆愣愣地站在原地。一个从始至终跟着老人的秘书模样的人走到吴闻身旁,同情地说:“您回去好好吃饭吧。别想了。其实您应该明白这道理,就业的事是顶天的事。您以为这种技术以前就没人做吗?”
<br><br>
老刀能听出这是与他有关的事,但他摸不准怎样是好的。吴闻的脸显出一种迷惑、懊恼而又顺从的神情,老刀忽然觉得,他也有软弱的地方。
<br><br>
这时,白发老人的秘书忽然注意到老刀。
<br><br>
“你是新来的?”他突然问。
<br><br>
“啊……嗯。”老刀吓了一跳。
<br><br>
“叫什么名字?我怎么不知道最近进人了。”
<br><br>
老刀有些慌,心砰砰跳,他不知道该说些什么。他指了指胸口上别着的工作人员徽章,仿佛期望那上面有个名字浮现出来。但徽章上什么都没有。他的手心涌出汗。秘书看着他,眼中的怀疑更甚了。他随手拉着一个会务人员,那人说不认识老刀。
<br><br>
秘书的脸铁青着,一只手抓住老刀的手臂,另一只手拨了通讯器。
<br><br>
老刀的心提到嗓子眼,就在那一刹那,他看到了老葛的身影。
<br><br>
老葛一边匆匆跑过来,一边按下通讯器,笑着和秘书打招呼,点头弯腰,向秘书解释说这是临时从其他单位借调过来的同事,开会人手不够,临时帮忙的。秘书见老葛知情,也就不再追究,返回会场。老葛将老刀又带回自己的房间,免得再被人撞见查检。深究起来没有身份认证,老葛也做不得主。
<br><br>
“没有吃席的命啊。”老葛笑道,“你等着吧,待会儿我给你弄点吃的回来。”
<br><br>
老刀躺在床上,又迷迷糊糊睡了。他反复想着吴闻和白发老人说的话,自动垃圾处理,这是什么样的呢,如果真的这样,是好还是不好呢。
<br><br>
再次醒来时,老刀闻到一碟子香味,老葛已经在小圆桌上摆了几碟子菜,还正在从墙上的烤箱中把剩下一个菜端出来。老葛又拿来半瓶白酒和两个玻璃杯,倒上。
<br><br>
“有一桌就坐了俩人,我把没怎么动过的菜弄了点回来,你凑合吃,别嫌弃就行。他们吃了一会儿就走了。”老葛说。
<br><br>
“哪儿能嫌弃呢。”老刀说,“有口吃的就感激不尽了。这么好的菜。这些菜很贵吧?”
<br><br>
“这儿的菜不对外,所以都不标价。我也不知道多少钱。”老葛已经开动了筷子,“也就一般吧。估计一两万之间,个别贵一点可能三四万。就那么回事。”
<br><br>
老刀吃了两口就真的觉得饿了。他有抗饥饿的办法,忍上一天不吃东西也可以,身体会有些颤抖发飘,但精神不受影响。直到这时,他才发觉自己的饥饿。他只想快点咀嚼,牙齿的速度赶不上胃口空虚的速度。吃得急了,就喝一口。这白酒很香,不辣。老葛慢悠悠的,微笑着看着他。
<br><br>
“对了,”老刀吃得半饱时,想起刚才的事,“今天那个演讲人是谁?我看着很面熟。”
<br><br>
“也总上电视嘛。”老葛说,“我们的顶头上司。很厉害的老头儿。他可是管实事儿的,城市运作的事儿都归他管。”
<br><br>
“他们今天说起垃圾自动处理的事儿。你说以后会改造吗?”
<br><br>
“这事儿啊,不好说,”老葛砸了口酒,打了个嗝,“我看够呛。关键是,你得知道当初为啥弄人工处理。其实当初的情况就跟欧洲二十世纪末差不多,经济发展,但失业率上升,印钱也不管用,菲利普斯曲线不符合。”
<br><br>
他看老刀一脸茫然,呵呵笑了起来:“算了,这些东西你也不懂。”
<br><br>
他跟老刀碰了碰杯子,两人一齐喝了又斟上。
<br><br>
“反正就说失业吧,这你肯定懂。”老葛接着说,“人工成本往上涨,机器成本往下降,到一定时候就是机器便宜,生产力一改造,升级了,GDP上去了,失业也上去了。怎么办?政策保护?福利?越保护工厂越不雇人。你现在上城外看看,那几公里的厂区就没几个人。农场不也是吗。大农场一搞几千亩地,全设备耕种,根本要不了几个人。咱们当时怎么搞过欧美的,不就是这么规模化搞的吗。但问题是,地都腾出来了,人都省出来了,这些人干嘛去呢。欧洲那边是强行减少每人工作时间,增加就业机会,可是这样没活力你明白吗。最好的办法是彻底减少一些人的生活时间,再给他们找到活儿干。你明白了吧?就是塞到夜里。这样还有一个好处,就是每次通货膨胀几乎传不到底层去,印钞票、花钞票都是能贷款的人消化了,GDP涨了,底下的物价却不涨。人们根本不知道。”
<br><br>
老刀听得似懂非懂,但是老葛的话里有一股凉意,他还是能听出来的。老葛还是嬉笑的腔调,但与其说是嬉笑,倒不如说是不愿意让自己的语气太直白而故意如此。
<br><br>
“这话说着有点冷。”老葛自己也承认,“可就是这么回事。我也不是住在这儿了就说话向着这儿。只是这么多年过来,人就木了,好多事儿没法改变,也只当那么回事了。”
<br><br>
老刀有点明白老葛的意思了,可他不知道该说什么好。
<br><br>
两人都有点醉。他们趁着醉意,聊了不少以前的事,聊小时候吃的东西,学校的打架。老葛最喜欢吃酸辣粉和臭豆腐,在第一空间这么久都吃不到,心里想得痒痒。老葛说起自己的父母,他们还在第三空间,他也不能总回去,每次回去都要打报告申请,实在不太方便。他说第三空间和第一空间之间有官方通道,有不少特殊的人也总是在其中往来。他希望老刀帮他带点东西回去,弥补一下他自己亏欠的心。老刀讲了他孤独的少年时光。
<br><br>
昏黄的灯光中,老刀想起过去。一个人游荡在垃圾场边缘的所有时光。
<br><br>
不知不觉已经是深夜。老葛还要去看一下夜里会场的安置,就又带老刀下楼。楼下还有未结束的舞会末尾,三三两两男女正从舞厅中走出。老葛说企业家大半精力旺盛,经常跳舞到凌晨。散场的舞厅器物凌乱,像女人卸了妆。老葛看着小机器人在狼藉中一一收拾,笑称这是第一空间唯一真实的片刻。
<br><br>
老刀看了看时间,还有三个小时转换。他收拾了一下心情,该走了。
</p>
<h3>(5)</h3>
<p> 白发演讲人在晚宴之后回到自己的办公室,处理了一些文件,又和欧洲进行了视频通话。十二点感觉疲劳,摘下眼镜揉了揉鼻梁两侧,准备回家。他经常工作到午夜。
<br><br>
电话突然响了,他按下耳机。是秘书。
<br><br>
大会研究组出了状况。之前印好的大会宣言中有一个数据之前计算结果有误,白天突然有人发现。宣言在会议第二天要向世界宣读,因而会议组请示要不要把宣言重新印刷。白发老人当即批准。这是大事,不能有误。他问是谁负责此事,秘书说,是吴闻主任。
<br><br>
他靠在沙发上小睡。清晨四点,电话又响了。印刷有点慢,预计还要一个小时。
<br><br>
他起身望向窗外。夜深人静,漆黑的夜空能看到静谧的猎户座亮星。
<br><br>
猎户座亮星映在镜面般的湖水中。老刀坐在湖水边上,等待转换来临。
<br><br>
他看着夜色中的园林,猜想这可能是自己最后一次看这片风景。他并不忧伤留恋,这里虽然静美,可是和他没关系,他并不钦羡嫉妒。他只是很想记住这段经历。夜里灯光很少,比第三空间遍布的霓虹灯少很多,建筑散发着沉睡的呼吸,幽静安宁。
<br><br>
清晨五点,秘书打电话说,材料印好了,还没出车间,问是否人为推迟转换的时间。
<br><br>
白发老人斩钉截铁地说,废话,当然推迟。
<br><br>
清晨五点四十分,印刷品抵达会场,但还需要分装在三千个会议夹子中。
<br><br>
老刀看到了依稀的晨光,这个季节六点还没有天亮,但已经能看到蒙蒙曙光。
<br><br>
他做好了一切准备,反复看手机上的时间。有一点奇怪,已经只有一两分钟到六点了,还是没有任何动静。他猜想也许第一空间的转换更平稳顺滑。
<br><br>
清晨六点十分,分装结束。
<br><br>
白发老人松了一口气,下令转换开始。
<br><br>
老刀发现地面终于动了,他站起身,活动了一下有点麻木的手脚,小心翼翼来到边缘。土地的缝隙开始拉大,缝隙两边同时向上掀起。他沿着其中一边往截面上移动,背身挪移,先用脚试探着,手扶住地面退行。大地开始翻转。
<br><br>
六点二十分,秘书打来紧急电话,说吴闻主任不小心将存着重要文件的数据key遗忘在会场,担心会被机器人清理,需要立即取回。
<br><br>
白发老人有点恼怒,但也只好令转换停止,恢复原状。
<br><br>
老刀在截面上正慢慢挪移,忽然感觉土地的移动停止了,接着开始调转方向,已错开的土地开始合拢。他吓了一跳,连忙向回攀爬。他害怕滚落,手脚并用,异常小心。
<br><br>
土地回归的速度比他想象的快,就在他爬到地表的时候,土地合拢了,他的一条小腿被两块土地夹在中间,尽管是泥土,不足以切筋断骨,但力量十足,他试了几次也无法脱出。他心里大叫糟糕,头顶因为焦急和疼痛渗出汗水。他不知道是否被人发现了。
<br><br>
老刀趴在地上,静听着周围的声音。他似乎听到匆匆接近的脚步声。他想象着很快就有警察过来,将他抓起来,夹住的小腿会被砍断,带着疮口扔到监牢里。他不知道自己是什么时候暴露了身份。他伏在青草覆盖的泥土上,感觉到晨露的冰凉。湿气从领口和袖口透入他的身体,让他觉得清醒,却又忍不住战栗。他默数着时间,期盼这只是技术故障。他设想着自己如果被抓住了该说些什么。也许他该交待自己二十八年工作的勤恳诚实,赚一点同情分。他不知道自己会不会被审判。命运在前方逼人不已。
<br><br>
命运直抵胸膛。回想这四十八小时的全部经历,最让他印象深刻的是最后一晚老葛说过的话。他觉得自己似乎接近了些许真相,因而见到命运的轮廓。可是那轮廓太远,太冷静,太遥不可及。他不知道了解一切有什么意义,如果只是看清楚一些事情,却不能改变,又有什么意义。他连看都还无法看清,命运对他就像偶尔显出形状的云朵,倏忽之间又看不到了。他知道自己仍然是数字。在5128万这个数字中,他只是最普通的一个。如果偏生是那128万中的一个,还会被四舍五入,就像从来没存在过,连尘土都不算。他抓住地上的草。
<br><br>
六点三十分,吴闻取回数据key。六点四十分,吴闻回到房间。
<br><br>
六点四十五分,白发老人终于疲倦地倒在办公室的小床上。指令已经按下,世界的齿轮开始缓缓运转。书桌和茶几表面伸出透明的塑料盖子,将一切物品罩住并固定。小床散发出催眠气体,四周立起围栏,然后从地面脱离,地面翻转,床像一只篮子始终保持水平。
<br><br>
转换重新启动了。
<br><br>
老刀在三十分钟的绝望之后突然看到生机。大地又动了起来。他在第一时间拼尽力气将小腿抽离出来,在土地掀起足够高度的时候重新回到截面上。他更小心地撤退。血液复苏的小腿开始刺痒疼痛,如百爪挠心,几次让他摔倒,疼得无法忍受,只好用牙齿咬住拳头。他摔倒爬起,又摔倒又爬起,在角度飞速变化的土地截面上维持艰难地平衡。
<br><br>
他不记得自己怎么拖着腿上楼,只记得秦天开门时,他昏了过去。
<br><br>
在第二空间,老刀睡了十个小时。秦天找同学来帮他处理了腿伤。肌肉和软组织大面积受损,很长一段时间会妨碍走路,但所幸骨头没断。他醒来后将依言的信交给秦天,看秦天幸福而又失落的样子,什么话也没有说。他知道,秦天会沉浸距离的期冀中很长时间。
<br><br>
再回到第三空间,他感觉像是已经走了一个月。城市仍然在缓慢苏醒,城市居民只过了平常的一场睡眠,和前一天连续。不会有人发现老刀的离开。
<br><br>
他在步行街营业的第一时间坐到塑料桌旁,要了一盘炒面,生平第一次加了一份肉丝。只是一次而已,他想,可以犒劳一下自己。然后他去了老葛家,将老葛给父母的两盒药带给他们。两位老人都已经不大能走动了,一个木讷的小姑娘住在家里看护他们。
<br><br>
他拖着伤腿缓缓踱回自己租的房子。楼道里喧扰嘈杂,充满刚睡醒时洗漱冲厕所和吵闹的声音,蓬乱的头发和乱敞的睡衣在门里门外穿梭。他等了很久电梯,刚上楼就听见争吵。他仔细一看,是隔壁的女孩阑阑和阿贝在和收租的老太太争吵。整栋楼是公租房,但是社区有统一收租的代理人,每栋楼又有分包,甚至每层有单独的收租人。老太太也是老住户了,儿子不知道跑到哪里去了,她长得瘦又干,单独一个人住着,房门总是关闭,不和人来往。阑阑和阿贝在这一层算是新人,两个卖衣服的女孩子。阿贝的声音很高,阑阑拉着她,阿贝抢白了阑阑几句,阑阑倒哭了。
<br><br>
“咱们都是按合同来的哦。”老太太用手戳着墙壁上屏幕里滚动的条文,“我这个人从不撒谎唉。你们知不知道什么是合同咧?秋冬加收10%取暖费,合同里写得清清楚楚唉。”
<br><br>
“凭什么啊?凭什么?”阿贝扬着下巴,一边狠狠地梳着头发,“你以为你那点小猫腻我们不知道?我们上班时你全把空调关了,最后你这按电费交钱,我们这给你白交供暖费。你蒙谁啊你!每天下班回来这屋里冷得跟冰一样。你以为我们新来的好欺负吗?”
<br><br>
阿贝的声音尖而脆,划得空气道道裂痕。老刀看着阿贝的脸,年轻、饱满而意气的脸,很漂亮。她和阑阑帮他很多,他不在家的时候,她们经常帮他照看糖糖,也会给他熬点粥。他忽然想让阿贝不要吵了,忘了这些细节,只是不要吵了。他想告诉她女孩子应该安安静静坐着,让裙子盖住膝盖,微微一笑露出好看的牙齿,轻声说话,那样才有人爱。可是他知道她们需要的不是这些。
<br><br>
他从衣服的内衬掏出一张一万块的钞票,虚弱地递给老太太。老太太目瞪口呆,阿贝、阑阑看得傻了。他不想解释,摆摆手回到自己的房间。
<br><br>
摇篮里,糖糖刚刚睡醒,正迷糊着揉眼睛。他看着糖糖的脸,疲倦了一天的心软下来。他想起最初在垃圾站门口抱起糖糖时,她那张脏兮兮的哭累了的小脸。他从没后悔将她抱来。她笑了,吧唧了一下小嘴。他觉得自己还是幸运的。尽管伤了腿,但毕竟没被抓住,还带了钱回来。他不知道糖糖什么时候才能学会唱歌跳舞,成为一个淑女。
<br><br>
他看看时间,该去上班了。
</p>
<!--
评论
https://m.douban.com/book/review/8071188/
-->
《北京折叠》是科幻作家郝景芳创作的中短篇小说。该小说创造了一个更极端的类似情景,书里的北京不知年月,空间分为三层,不同的人占据了不同的空间,也按照不同的比例,分配着每个 48 小时周期。
如何阅读一本书?
https://www.zruibin.cn/article/ru_he_yue_du_yi_ben_shu_?.html
2016-12-01 15:03
2016-12-01 15:03
<!--
####<p>原文出处:<a href="http://conndots.github.io/2016/11/19/how_read_a_book/" target="blank">如何阅读一本书?</a></p>
-->
<h2>你会读书吗?</h2>
<p>对于一个上过学的人来讲,如果问你『你会读书吗?』,我想大多数人都会说,我特么就是读书考试长大的。堂堂的大学生不会读书?事实上,我自己是硕士毕业,已经工作一年有余了,扪心自问,我会读书吗?答案还真没那么确定。</p>
<h2>为什么读书?</h2>
<p>现代人,有了电视和互联网,娱乐方式千奇百怪,即使是学习方式也是五花八门。我们充斥在各种短小的新闻资讯、鸡汤中,大家都在倡导碎片化阅读,倡导多媒体学习,通过读书来提高自己的方式成了『古董』。</p>
<p>无法否认的是,读书的投入产出比对于许多情景来说确实不高。比如,对于一个程序员,学习一项新技术的最佳学习方式是通过官网的tutorial、通过他人的博客分享迅
速上手,实践出真知,而非等待专业人士出一本书,然后读书学习。</p>
<p>但是,令人惊讶的是,<strong>只是掌握知识的提纲并不会提高你的专业水平。当然,掌握它们可以解决手上的问题。</strong>[1] 举例来说,对一个程序员,通过到处都是的博客和
tutorial,迅速学习了Rails,或者Spring、Netty等看起来复杂无比的框架,搭起来了一个很像样的服务,解决了眼前的需求。这能说明你是一个学习能力很强,上手很迅速的人。但是重复地为了上手、应用目的的学习不同的工具、框架并无法跟你投入的精力成正比地提高你的专业能力。</p>
<blockquote><p>[1] 参见Klemp, G. O. "Three Factors of Success" in <em>Related Work and Education</em>与"Identifying the Knowledge which Underpins Performance" in <em>Knowledge</em>,还有<em>Competence: Current Issues in Education and Training</em>。</p>
</blockquote>
<p><strong>大脑构建的模型、为构建模型所提出的问题和你的日常经验和实践对你的能力成长更加重要。它们才能提高你的竞争力和专长。仅仅掌握知识是不够的。你需要持续的目标,需要及时的反馈以了解你的进展,需要更加主动全面地学习。</strong></p>
<p>主动学习需要良好设定的目标,和合理、科学的计划,可以参考之前的文章《<a href="http://conndots.github.io/2016/11/13/target_and_plan/">[方法论] 2017来了,如何设定目标,如何制定计划?</a>。正确的主动学习需要几个方面(参考《程序员的思维修炼》主动学习章节):</p>
<ul>
<li>主动学习。</li>
<li>结合实践。</li>
<li><strong>及时获取反馈,并针对反馈做针对性的训练、学习。</strong> 这点通常会被很多人所忽略,事实上,<strong>提供及时准确的反馈给学员以针对性训练</strong>,是针对成年人的导师/教练/老师的最大价值。 (参考 《万万没想到:用理工科思维理解世界》:练习一万小时成天才?)</li>
</ul>
<p>读书是一种具有良好best practice的、系统化的主动学习方法。本文的读书方法适合『严肃』阅读,目的是为了提高自己的能力与认知的阅读,而非『娱乐性』阅读,或者为了直接性功利目的阅读(考试、面试等)。阅读小说、青春疼痛文学、偶像小说、漫画等可以视为娱乐的一种。而许多教辅、教材的存在往往培养了我们错误的阅读习
惯。</p>
<p>关于读书的一些真相,这些问题也广泛存在于我自己的阅读之中(参考 《万万没想到:用理工科思维理解世界》:用强力研读书):</p>
<ol>
<li><strong>大多数人都没有严肃阅读</strong>。</li>
<li><strong>看这些严肃的书的人大多都没有看完</strong>。统计:kindle电子书大部分只看了四分之一,标注越到后面越少。比如一些有名的书,《思考,快与慢》:6.8%。《时间简史》:6.6%。《二十一世纪资本论》:2.4%。我自己《思考,快与慢》只看了一半。</li>
<li><strong>看完了和没有看差不多。很快就忘记了,甚至就没有看懂</strong>。这个对于我自己就很常见。看完了很多量子力学和相对论的科普书籍后,在分享会中,回答别人一个比较简单的问题的时候,仍然非常懵逼,而这个问题自己读书的时候其实想清楚过。</li>
</ol>
<p>所以,读书需要方法论。而这个方法论,一定<strong>不好玩</strong>,一定会让你不舒服,需要刻意地实践,需要严肃与认真。</p>
<h2>阅读的目的和层次</h2>
<blockquote><p>本章节小结自《如何阅读一本书》。</p>
</blockquote>
<p>所有主动的阅读(并非拿起书这种动作的主动性)的目的无非有两种:<strong>为了获得资讯的阅读</strong>和<strong>为了提高理解能力的阅读</strong>。</p>
<p>前一种目的的阅读,可以说我们常提的碎片化阅读就是这样的阅读。这些资讯每天被我们吸收,但大部分时候也会迅速消失,并没有什么卵用。这里并非是『知识』,而只是『资讯』。如同你中学老师说的,『你脑子怎么长的,左脑袋进,右脑袋出?』。呵呵。</p>
<p>我们需要主动阅读,<strong>越主动,效率越高</strong>。所有的主动阅读,是努力在与作者沟通的过程。我们不仅仅满足于get作者想要表达什么,更要去思考,他为什么这么想?他到底想表达什么?他说的对吗?</p>
<p>而对于不同的场景,我们可以分为不同的层次,使用不同的方法阅读。</p>
<ul>
<li><strong>基础阅读</strong> 最低水平、摆脱文盲的阅读。这个没什么好说的,许多时候我们的阅读都存在于这个水平。</li>
<li><strong>检视阅读</strong> 通常在有限时间内确定一本书的大体内容,或者粗读一本书,都属于检视阅读。</li>
<li><strong>分析阅读</strong> 以增进理解力为目标的阅读,是自我要求较高的阅读方法,常应用于专业与相关领域的严肃阅读中。 为了把这本书彻底变成自己的,需要借助良好的读书笔记还有其他方法。《如何阅读一本书》里面对此有详细的论述,也可以参考知乎读者『寒山远火』的专栏文章:《如何阅读一本书》----读书方法的整理。</li>
<li><strong>主题阅读</strong> 可以看做是你对你的专业领域,或者你想要进入的领域的一个话题的系统性的系列阅读。你需要比较同一个话题,不同的作者之间持有哪些互相冲突的看法,或者类似的看法。通过使用分析阅读的方法阅读同一个主题下的多本书,相互取长补短地学习,形成自己的知识框架。</li>
</ul>
<p>《如何阅读一本书》是一本比较厚的书,还涉及了各种阅读不同类型书籍的不同方法。我其实想说《如何阅读《如何阅读一本书》》也是一个巨大的挑战。这本书不免过于学究。不如使用一些更加Practical的方法,更加注重实效,而非套路。</p>
<h2>A Practical Reader Should do</h2>
<p>这里结合我的阅读经验,还有自己的实践,谈一谈作为一个practical reader,如何阅读。</p>
<h3>如何选择一本书</h3>
<p>每个人都有不同的兴趣,如何选择一本合适的书,合自己口味的书,并不是一个简单的事情。这里有一些经验:</p>
<h4>1. 牛人推荐或者另一本书籍里提到的书</h4>
<p>对于一些编撰比较专业的书来说,他们通常都有references和标注他们提到相关的观点的信息源。而许多博客里对于他们文章中涉及到的相关书籍或论文或作者也会有提及,如果你当前看的这篇博文或者书籍是你感兴趣的,那么他们推荐的相关的阅读,通常你也会感兴趣的。这些书籍,可以加到你的备选书单里。比如读完《暗时间》还有作者的博客<a href="http://mindhacks.cn/">Mind Hacks</a>的文章后,我的豆瓣想读里添加了许多作者刘未鹏推荐的相关书籍。</p>
<h4>2. 豆瓣豆列或者你喜欢的公众号推荐的书单</h4>
<p>还有就是豆瓣的豆列的作者往往都有收集癖,经常会把同一个主题的书籍收集到一个豆列里。许多阅读相关的公众号也会推送一些书单。</p>
<h4>3. 领域经典书籍</h4>
<p>每个领域一定会有经典书籍,从它们触发,使用上面的方法宽度优先遍历,一定可以得到一个比较完整的领域书单。</p>
<h4>对书的筛选</h4>
<p>一本书,到底值不值得自己读,也需要耗费一些精力。首先,我会去看豆瓣评分,过低分的书基本都会一票否决。对于书籍的评论,也会翻翻好评,还有最关键的:差评的内容。然后,就要使用『略读』方法确定这本书是不是你的菜了。简要来说,略读方法的要点是(来自《如何阅读一本书》):</p>
<ol>
<li>看书名,看序。把书可以归类。</li>
<li>看目录。经典的书籍通常目录就对书的内容作了系统的概括。</li>
<li>浏览索引,如果有的话。书中出现的高频词汇也反映了书的关键字。</li>
<li>挑几个感兴趣的章节来看。</li>
<li>随机挑几个段落来看。对于理论性的书籍,章节的开头与结尾值得一看。</li>
</ol>
<p>当你选定一本书后,就可以开始阅读了。</p>
<h3>测试驱动阅读</h3>
<h4>阅读方法论框架</h4>
<p>《程序员思维修炼:开发认知潜能的九堂课》这本书提到了一种SQ3R的阅读方法[2],SQ3R分别是如下内容的缩写:</p>
<ol>
<li><strong>S</strong>urvey 调查:对应章节<em>如何选择一本书</em>里的选择、调研书的方法。</li>
<li><strong>Q</strong>uestion 问题:带着问题开始阅读,记录你的问题。</li>
<li><strong>R</strong>ead 阅读:阅读全部内容。</li>
<li><strong>R</strong>ecite 复述:总结,做读书笔记,用自己的话来叙述。</li>
<li><strong>R</strong>eview 回顾:重读,扩展笔记,写博客或者其他方式分享,与伙伴讨论。</li>
</ol>
<blockquote><p>概念来源参见<a href="https://books.google.com/books/about/Effective_study.html?id=OsVrAAAAIAAJ">Francis Pleasant Robinson <em>Effective Study</em></a></p>
</blockquote>
<p>这里需要强调的是Q,也就是在略读之后,我们需要针对整本书,还有每个章节,提出一些问题,记录下来,然后寻求验证。 如果你是一个程序员,在阅读一本技术书籍,比如Rails on the way,你首先要问:我为什么读这本书?Rails能为我的项目带来什么好处?它有什么缺点? 本质上,Q是为了明确自己阅读的目的,让阅读更加紧密贴合自己的情境,更加有收获。</p>
<p>那么,你手里有本书,如何阅读?结合我自己的经验,你可能需要读两遍,按照下面的流程:</p>
<p>(1)<strong>略读,筛选,提问</strong>经过了<em>如何选择一本书</em>这节提到的筛选、略读后,记录你关于这本书的问题,在读每一个章节之前也要记录你的问题。</p>
<p>(2)<strong>粗读一遍</strong> 从头到尾,阅读一遍。对于难以理解或者困难的部分,要避免耽误太多时间,可以跳过,避免一叶障目。 粗读对于一个上班族来说,很适合于在通勤交通工具上通过kindle或者手机app来进行。注意阅读速度并非一成不变的匀速,在遇到困难部分,降低速度来避免思维脱节。</p>
<p>(3)<strong>笔记</strong> 第一遍阅读过程中,任何值得记录或者讨论的作者的观点或者事例可以标记下来,或者印象比较深刻的章节,在当天的空闲的时候,把他们整理成读书笔记
,记录在Evernote里(或者其他知识管理软件)。</p>
<p>(4)<strong>复述,讨论</strong>如果有伙伴的话,当天可以把你阅读到的内容口头复述给你的伙伴听,可以是一个观点,一个事例,然后回答他们提出的问题。</p>
<p>(5)<strong>重读</strong> 在粗读一遍后,根据自己的理解和评价,决定这本书的价值是否需要重读,哪些章节需要重读。这次,需要使用更高级的分析阅读的方式,针对部分章节,并且可以跳过一些不重要的内容,同时扩展你的读书笔记。</p>
<p>(6)<strong>分享,讨论</strong> 可以通过豆瓣书评,写博客的方式分享你读书的见解,或者在读书分享会上分享你的读书收获,评价。积极讨论、分享,获得反馈。</p>
<h4>测试驱动阅读</h4>
<p><strong>测试驱动阅读(TDR, Test Driven Reading)</strong>的方法也来自于《程序员思维修炼:开发认知潜能的九堂课》的6.7节:使用SQ3R方法主动阅读。这个概念来自于软件开发领域的测试驱动开发(TDD,Test Driven Development)。</p>
<h5>阅读中的测试</h5>
<p>对于技术书籍来说,测试方法是显而易见的,就是把书中提到的技术应用到实践中。对于软件开发来说,测试成本比较低,可以快速地在自己的计算机上实践书中的技术。 当然有<strong>更加通用的测试方法:Recite & Review(复述与回顾、讨论反馈)</strong>。也就是我们在上面的方法论框架里提到的:</p>
<ol>
<li>记录读书笔记(用自己的话记录,无视文笔、格式)。</li>
<li>把读书内容口头复述给同伴并讨论。</li>
<li>写博客小结自己在一个主题下的阅读成果并分享到博客/论坛里分享给别人,鼓励讨论。</li>
<li>组织学习小组或者读书小组,回顾、分享自己的读书成果,并讨论。</li>
</ol>
<h5>测试带来的记忆加成</h5>
<p>测试除了通过回顾加深读书成果的记忆之外,还有主动学习中的<strong>获得及时反馈</strong>的效果,我们可以通过多种途径的反馈来针对性的改进我们的想法,促进我们的思考。《暗时间》中,有一个有意思的观点,<strong>当我们把我们的想法写下来的时候(读书笔记,博客),我们对记录的想法的记忆也会更加深入。</strong>记录读书笔记不仅是把想法记录
在了外部载体中,也促进了大脑对这些想法的整理。</p>
<p><strong>人的记忆能力并非体现在我们记住了多少东西。</strong>通常情况下,我们能够记住大部分的东西。<strong>而在于,在需要的时候,我们能不能快速地找到这些信息</strong>(理论上,当然可以遍历,然而我们的大脑非常不擅长枚举、遍历)。</p>
<p>联想计算机的数据仓库,我们需要对数据建立合理、高效的索引,才能快速地找到数据。我们的大脑也是一样的,在记忆的时候,需要通过技巧对孤立的知识点建立更多的与其它
知识点的连接,如同数据库的索引一样,提高我们的记忆效果。</p>
<p>记录读书笔记,写作博文,复述,针对主题的讨论,都是提高记忆效果的有效手段。</p>
<h5>利用间隔效应重复测试</h5>
<p>因为人的记忆是会遗忘的,下图是著名的艾宾浩斯遗忘曲线。</p>
<p><img src="https://www.zruibin.cn/article/image/20140325170800-1679076577.jpg" alt="艾宾浩斯遗忘曲线"></p>
<p>可以看到,在白天读书的当天晚上,我们的记忆就只会保留1/3,甚至更少。如果你想要记住一组知识,短时间高强度的记忆并无法提高你的记忆效果,而是需要利用间隔效用重复做阅读测试。比如可以按照2-2-2-6的规律重复测试,在2小时,2天,2周,6个月重复。</p>
<p>重点不是严格按照这样的区间来严格测试,而是记住,高强度地重复记忆并不会提高你的记忆效果,你需要利用间隔效用。</p>
<h3>如何记读书笔记</h3>
<p>那么,如何记录读书笔记便非常重要。</p>
<p>我曾经是这样记录读书笔记的,把书里重点的语句复制,甚至是重写在笔记里。显然,这样的笔记并没有带来效果。而常常自己也不写笔记,许多书读过,就忘记了大半了。</p>
<p>而许多现在很流行的读书app,比如微信阅读,读书的标记和笔记虽然相比kindle能够更加高效的记录,然而却不能导出,无论是导出到evernote,还是文字导出,只能图片导出(又是为了装逼的所谓社交需求)。微信阅读强调了把笔记分享到读书圈中,让我哭笑不得,只言片语的片段加上一个人的评论,除了骗赞、装逼,对于没有看过书的朋友们到底有什么用?</p>
<p>首先说,读书笔记不是什么:</p>
<ol>
<li>重复书籍的片段。 重复书中的话是低效的,kindle的笔记和标记导出就可以做到,多看阅读也可以自动导出笔记到Evernote。</li>
<li>严格的文章。 完全不需要文笔,格式,手法,可以是任何载体,任何简略、快速的表达,但是需要整理。</li>
</ol>
<p>你的笔记需要做到以下几点。</p>
<h4>笔记要回答问题</h4>
<p>这里的问题包括你在略读后针对这本书或者每个章节提出的问题,也包括以下通用的问题(来自于《如何阅读一本书》):</p>
<ol>
<li>这本书(这一章/这一节)整体上说了什么? 也就是主题是什么。</li>
<li>这本书有哪些观点?找出这本书(这一章/这一节)所有的论点。</li>
<li>作者这些观点说的对吗?学会质疑,学会批判性思维(<a href="https://en.wikipedia.org/wiki/Critical_thinking">Critical Thinking)</a>。</li>
<li>这本书(这一章/这一节)与我有什么关系?找出自己从这本书想得到的和最后得到的。</li>
</ol>
<h4>笔记要包含每一章节的逻辑脉络</h4>
<p>也即笔记要勾画出作者的论点,论证流程,结论。每一章的逻辑脉络可能只有几句话,但却散布在很多页里。</p>
<p>作者通常会举很多的事例,这些事例往往都会为了说明一个或多个观点。看书不能只看树木,不见树林。</p>
<p>现代人喜欢小段子,读者们往往记住了段子而忘记了段子的本意。 《娱乐至死》里提到一个例子。</p>
<blockquote><p>美国历史上,在没有电视、没有网络、更没有twitter的『印刷机时代』,斯蒂芬*道格拉斯(曾经和林肯竞争过总统,还竞争过老婆,都失败了)曾经和林肯有过7场辩论。他口才极好,常出妙语,但是他经常告诫听众们不要为妙语鼓掌。甚至他经常批评他的听众,说他需要的不是听众的激情而是他们的理解。他的听众应该是沉思默想的读者才好。</p>
</blockquote>
<p>阅读要求的是理性思考, 一个好的读者应当忙于分析这本书的这棵大树的脉络结构,而不是为它的一片树叶而止步。</p>
<h4>笔记要复述而不是复制观点</h4>
<p>你需要用自己的话把作者的观点表述出来,而非复制黏贴原话。这个过程的效果我们在『测试带来的记忆加成』中已经提到,经过我们大脑记忆、分析、处理然后复述出的观点会加强我们记忆的『索引』和『连接』。 而自己复述的话语,也可以做到更加抽象。</p>
<h4>笔记要有自己的看法,建立与其它知识点的联系</h4>
<p>人的联想能力是丰富的,尤其是除了当前书籍外还有大量相关阅读或者知识输入的前提下。我们通常能够联想起最近输入的关于当前内容、观点相关的其他作者的观点、看法。</p>
<p>我们当然也会结合自己的经验和想法有很多相关的评论、想法。</p>
<p>在阅读的时候需要把这些想法记录下来。在当天空闲时候,在读书笔记把这些灵光一现的想法、联想、评论整理出来,记录下来。</p>
<p>如果你使用wiki或者Evernote,可以很方便地通过链接、笔记链接把这些知识连接起来,形成系统的网络。对应的,我们记忆中的连接也会相应被多次的『测试』行
为所强化。</p>
<p>我们阅读的越多,这样『灵光一现』的moment越多,我们能够建立的连接与联想越多。</p>
<p>有些人标榜爱读书,其实是爱收藏,每本书都保存的一尘不染,没有丝毫标注与笔记。这些都不是真正的读书人。据说,人们都不喜欢把书借给毛泽东看,因为他看完之后别人就没法看了,书上密密麻麻都是他的标注与批注。(这个梗来自于《万万没想到:用理工科思维理解世界》:用强力阅读书)</p>
<p>如果你能够做到这一点,你的阅读历程就是比较系统的主题阅读,知识点能够建立起丰富的网络连接。</p>
<h4>对于系统的笔记组合,整理并分享</h4>
<p>当我们在进行了一系列的阅读与整理笔记后,我们的知识网络有了一定规模后,可以把一个主题的思考、心得整理为博客文章或者读书分享会上的slides分享,分享给其他
的人,通过与伙伴、网友的讨论获得更多的反馈,这对我们的思想与知识的进化是非常有益的。</p>
<blockquote><p>读书分享会和写博客是很好的学习方式。</p>
</blockquote>
<p>每一天,也可以与你亲密的伙伴复述一下今天阅读的收获,并且互相约定,复述后必须提给对方最少3个问题。上述的方法都是可以驱动阅读的测试方法。</p>
<h3>读书不是银弹</h3>
<p>读书应当关注的是与作者思维交流的过程,还有你对书籍的相关的思考,借由思考带来的与其他人的讨论与反馈。</p>
<p>但是,读书不是银弹。读书,相比于任何经验式的主动学习方法,阅读是效率比较低的方法。有很多比读书重要的方法,在做事实践与读书之间,当然优先选择做事实践。<br>
读书是当『躬行』的可能性太低、或者成本太高的时候最好的获取知识、经验的方法之一。</p>
<h2>最后</h2>
<p>不管怎样,开始阅读才是最重要的。读书如武功,需要持久地修炼、琢磨。但是,不要成了读书效率低、产出低的『书虫』,方法论是很重要的。</p>
<p>那些依靠自己努力的优秀的人没有不在持续每天阅读的,每年都能看到Facebook CEO扎克伯格的阅读清单,据说,2013年比尔<em>盖茨一共读了139本书[3],数目无法证实,从比尔盖茨的博客就可以看出,他读了很多书,而且大部分都是非小说。每年,比尔</em>盖茨都会写一篇The Best Books I Read in XXXX,大家可以参考下比尔大大的书单。巴菲特自称醒着时候一半时间都在读书。</p>
<blockquote><p>[3] 参考这篇比尔的博客:<a href="https://www.gatesnotes.com/About-Bill-Gates/Best-Books-2013">The Best Books I Read in 2013</a></p>
</blockquote>
<h2>参考书目</h2>
<ul>
<li>《如何阅读一本书》</li>
<li>《程序员思维修炼:开发认知潜能的九堂课》</li>
<li>《万万没想到:用理工科思维理解世界》</li>
<li>《暗时间》</li>
<li>《如何阅读一本书》----读书方法的整理<a href="http://zhuanlan.zhihu.com/p/23241617"> | 『寒山远火』的知乎专栏</a></li>
</ul>
如何阅读一本书?
《iOS设计模式解析》笔记5:策略模式、命令模式、享元模式、代理模式、备忘录模式
https://www.zruibin.cn/article/《_ios_she_ji_mo_shi_jie_xi_》_bi_ji_5_:_ce_lve_mo_shi_ming_li.html
2016-06-05 23:11
2016-06-05 23:11
<h3>策略模式</h3>
<blockquote><p>定义一系列算法,把它们一个个封装起来,并且使它们可相互替换。策略模式使得算法可以独立于使用它的客户而变化。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>一个类在其操作中使用多个条件语句来定义许多行为。我们可以把相关的条件分支移到它们的策略类中</li>
<li>需要算法的各种变体</li>
<li>需要避免把复杂的,与算法相关的数据结构暴露给客户端</li>
</ul>
<h4>模型-视图-控制器中的策略模式</h4>
<blockquote><p>模型-视图-控制器中,控制器决定视图对模型数据进行显示的时机和内容。视图本身知道如何绘图,但需要控制器告诉它要显示的内容。同一个视图如果与不同的控制器合作,数据的输出格式可能一样,但数据的类型和格式可能随不同控制的不同输出而不同。因此这种情况下的控制是视图的策略。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/策略模式类图.png" alt="策略模式类图"></p>
<center><strong>策略模式的类结构</strong></center><hr>
<h3>命令模式</h3>
<blockquote><p>将请求封装为一个对象,从而可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>想让应用程序支持撤销与恢复</li>
<li>想用对象参数化一个动作以执行操作,并用不同命令对象来代替回调函数</li>
<li>想要在不同时刻对请求进行指定,排列和执行</li>
<li>想记录修改日志,这样在系统故障时,这些修改可在后来重做一遍</li>
<li>想让系统支持事务,事务封装了对数据的一系列修改。事务可以建模为命令对象</li>
</ul>
<blockquote><p>命令模式允许封装在命令对象中的可执行指令。这使得在实现撤销和恢复基础设施的时候自然会选择这个模式。但这个模式的用途不只如此。命令对象的另一个为人熟知的应用是推迟调用器的执行。调用器可以是菜单项或按钮。使用命令对象连接不同对象之间的操作相当常见,比如,单击视图控制器中的按钮,可以执行一个命令对象,对另一个视图控制器进行某些操作。命令对象隐藏了与这些操作有关的所有细节。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/命令模式类图.png" alt="命令模式类图"></p>
<center><strong>命令模式结构的类图</strong></center><ul>
<li>Client(客户端)创建ConcreteCommand对象并设定其reciver</li>
<li>Invoker要求通用命令(实际上是ConcreteCommand)实现请求</li>
<li>Command是为了Invoker所知的通用接口(协议)</li>
<li>ConcreteCommand起Reciver和对它的操作action之间的中间人的作用</li>
<li>Reciver可以是随着由Command(ConcreteCommand)对象实施的相应请求,而执行实际操作的任何对象</li>
</ul>
<hr>
<h3>享元模式</h3>
<blockquote><p>运用共享技术有效地支持大量细粒度的对象</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>应用程序使用很多对象</li>
<li>在内存中保存对象会影响内存性能</li>
<li>对象的多数特有状态(外在状态)可以放到外部而轻量化</li>
<li>移除了外在状态之后,可以用较少的共享对象替代原来的那组对象</li>
<li>应用程序不依赖于对象标识,因为共享对象不能提供唯一的标识</li>
</ul>
<h4>通过享元对象能够节省的空间取决于几个因素:</h4>
<ol>
<li>通过共享减少的对象总数</li>
<li>每个对象内在状态(即,可共享的状态)的数量</li>
<li>外在状态是计算出来的还是保存的</li>
</ol>
<blockquote><p>然而,对享元对象外在状态的传递,查找和计算,可能产生运行时的开销,尤其在外在状态原本是作为内在状态来保存的时候。当享元的共享越来越多时,空间的节省会抵消这些开销。共享的享元越多,节省的存储就越多。节省直接跟共享的状态相关。如果对象有大量内在和外在状态,外在状态又能够计算出来而不用存储的时候,就能节省最大的空间。这样我们以两种方式节省了存储空间:共享减少了内在状态的开销,通过牺牲计算时间又节省了外在状态的存储空间。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/享元模式类图.png" alt="享元模式类图"></p>
<center><strong>享元模式的类图</strong></center><blockquote><p>Flyweight是两个具体享元类ConcreteFlyweight1和ConcreteFlyweight2的父接口(协议)。每个ConcreteFlyweight类维护不能用于识别对象的内在状态instrinsicState。Flyweight声明了operation:extrinsticState方法,由这两个ConcreteFlyweight类实现。instricState是享元对象中可被共享的部分,而extrinsicState补充缺少的信息,让享元对象唯一。客户端向operation:消息提供extrinsicState,让享元对象使用extrinsicState中的独一无二的信息完成其任务。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/享元模式类图2.png" alt="享元模式类图2"></p>
<center><strong>运行时享元对象如何共享的对象图</strong></center><hr>
<h3>代理模式</h3>
<blockquote><p>为其他对象提供一种代理以控制对这个对象的访问</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>需要一个远程代理,为位于不同地址空间或网络中的对象提供本地代表</li>
<li>需要一个虚拟代理,来根据要求创建重型的对象</li>
<li>需要一个保护代理,来根据不同访问权限控制对原对象的访问</li>
<li>需要一个智能引用代理,通过对实体对象的引用进行计数来管理内存。也能用于锁定实体对象,让其他对象不能修改它</li>
</ul>
<blockquote><p>在iOS应用开发中,总是要关注内存的使用量。不论应用程序在何种设备上,出于性能的考虑,总是推荐懒加载技术,对开销大的数据实施懒加载,如文件系统中的大图像文件或者通过低速网络从服务器下载的大型数据。如果大开销的对象收到请求之前不需要加载,则可能过虚拟代理向客户端提供某些轻量的信息。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/代理模式类图.png" alt="代理模式类图"></p>
<center><strong>代理模式的类图</strong></center><p><img src="https://www.zruibin.cn/article/image/代理模式类图2.png" alt="代理模式类图2"></p>
<center><strong>代理模式在运行时的一种可能的对象结构</strong></center><hr>
<h3>备忘录模式</h3>
<blockquote><p>在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保持这个状态。这样以后就可以将该对象恢复到原先保存的状态</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>需要保存一个对象(或某部分)在某一个时刻的状态,这样以后就可以恢复到先前的状态</li>
<li>用于获取状态的接口会暴露实现的细节,需要将其隐藏起来</li>
</ul>
<p><img src="https://www.zruibin.cn/article/image/备忘录模式类图.png" alt="备忘录模式类图"></p>
<center><strong>备忘录模式结构的类图</strong></center><blockquote><p>当看管人请求Originator对象保存其状态时,Originator对象将使用其内部状态创建一个新的Memento实例。然后看管人保管Memento对象,或者把它保存到文件系统,一段时间后再把它传回给Originator对象。Originator对象不知道这个Menentor对象将如何被保存。看管人也不知道Memento对象里是什么。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/备忘录模式时序图.png" alt="备忘录模式时序图"></p>
<center><strong>备忘录模式的时序图</strong></center><blockquote><p>这个设计的关键是维持Memento对象的私有性,只让Originator对象访问保存在Memento对象中的内部状态(即Originator过去的内部状态)。Memento类应该有两个接口:一个宽接口,给Originator用;一个窄接口,给其他对象用。时序图中,setState:、state和init方法应该定义为私有,不让Originator和Memento以外的对象使用。</p>
</blockquote>
《iOS设计模式解析》笔记5:策略模式、命令模式、享元模式、代理模式、备忘录模式
《iOS设计模式解析》笔记4:访问者模式、装饰模式、责任链模式、模板方法模式
https://www.zruibin.cn/article/《_ios_she_ji_mo_shi_jie_xi_》_bi_ji_4_:_fang_wen_zhe_mo_shi_z.html
2016-06-02 21:34
2016-06-02 21:34
<h3>访问者模式</h3>
<blockquote><p>表示一个作用于某对象结构中的各元素的操作。它让我们可以在不改变各元素的前提下定义作用于这些的新操作。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>一个复杂的对象结构包含很多其他对象,它们有不同的接口(比如组合体),但是想对这些对象实施一些依赖于其具体类型的操作</li>
<li>需要对一个组合结构中的对象进行很多不相关的操作,但是不想让这些操作“污染”这些对象的类。可以将相关的操作集中起来,定义在一个访问者类中,并在需要在访问者中定义的操作时使用它</li>
<li>定义复杂结构的类很少作修改,但经常需要向其添加新的操作</li>
</ul>
<blockquote><p>访问者模式有个需要注意的缺点,那就是,访问者与目标耦合在一起。因此,如果访问者需要支持新的类,访问者的父类和子类都需要修改,才能反映新的功能。不过,要是不经常往目标类家族中添加新类,也没什么大问题。</p>
<p>由于将来对访问者的修改不可预见,为每个访问者准备一个“万能”的访问方法,来支持未来的目标类是个好主意。然而,这只是权宜之计,要是经常需要增加新节点,就应该下定决心,修改访问者的接口,以支持新的节点类型。</p>
</blockquote>
<p><br></p>
<blockquote><p>访问者模式是个扩展组合结构功能的一种强有力的方式。如果组合结构具有精心设计的基本操作,而且结构将来也不会变更,就可以使用访问者模式,用各种不同用途的访问者,以同样的方式访问这个组合结构。访问者模式用尽可能少的修改,可以把组合结构与其它访问者类中的相关算法分离。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/访问者模式类图.png" alt="访问者模式类图"></p>
<center><strong>访问者模式的静态结构的类图</strong></center><hr>
<h3>装饰模式</h3>
<blockquote><p>动态地给一个对象添加一些额外的职责。就扩展功能来说,装饰模式相比生成子类更为灵活。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>想要在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责</li>
<li>想扩展一个类的行为,却做不到。类定义可能被隐藏,无法进行子类化;或者,对类的每个行为的扩展,为支持每种功能组合,将产生大量的子类</li>
<li>对类的职责的扩展是可选的</li>
</ul>
<h4>装饰者模式与策略模式的差异的总结</h4>
<table>
<thead><tr>
<th style="text-align:center">“外表”变更(装饰)</th>
<th style="text-align:center">“内容”变更(策略)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">从外部变更</td>
<td style="text-align:center">从内部变更</td>
</tr>
<tr>
<td style="text-align:center">每个节点变更</td>
<td style="text-align:center">每个节点知道一组预定义的变更方式</td>
</tr>
</tbody>
</table>
<p><br></p>
<blockquote><p>真正子类方式的实现使用一种较为结构化的方式连接各种装饰器。类别的方式则更为简单和轻量,适用于现有类只需要少量装饰器的应用程序。虽然类别不同于实际的子类化,不能实现装饰模式的原始风格,但它实现了解决同样问题的意图。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/装饰模式类图.png" alt="装饰模式类图"></p>
<center><strong>装饰模式的类图</strong></center><p><img src="https://www.zruibin.cn/article/image/装饰模式功能图.png" alt="装饰模式功能图"></p>
<center><strong>装饰模式的一种实现,扩展了装饰性的功能</strong></center><hr>
<h3>责任链模式</h3>
<blockquote><p>使多个对象都有机会处理请求,从而避免请求的发送者和接收之间发生耦合。此模式将这些对象连连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>有多个对象可以处理请求,而处理程序只有在运行时才能确定</li>
<li>向一组对象发出请求,而不想显示指定处理请求的特定处理程序</li>
</ul>
<p><img src="https://www.zruibin.cn/article/image/责任链模式图.png" alt="责任链模式图"></p>
<center><strong>责任链模式的类图</strong></center><p><img src="https://www.zruibin.cn/article/image/责任链模式功能图.png" alt="责任链模式功能图"></p>
<center><strong>运行时的请求处理程序链的一种典型结构</strong></center><hr>
<h3>模板方法模式</h3>
<blockquote><p>定义一个操作中算法的骨架,而将一些步骤延迟到子类中。模板方法使子类可以重定义算法的某些特定步骤而不改变该算法的结构。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>需要一次性实现算法的不变部分,并将可变的行为留子类实现</li>
<li>子类的共同行为应该被提取出放到公共类中,以避免代码。现胡代码的差别应该被分离为新的操作。然后用一个调用这些新操作的模板方法来替换这些不同的代码</li>
<li>需要控制子类的扩展。可以定义一个在特定点调用“钩子”(hook)操作的模板方法。子类可以通过对钩子操作实现在这些点扩展功能</li>
</ul>
<blockquote><p>钩子操作给出了默认行为,子类可对其扩展。默认行为通常什么都不做。子类可以重载这个方法,为模板算法提供附加的操作。</p>
<p>模板方法模式中的控制结构流程是倒转的,因为父类的模板就去调用其子类的操作,而不是子类调用父类的操作。这与“好莱坞原则”类似:别给我们打电话,我们会打给你。</p>
</blockquote>
<h4>模板方法会调用5种类型的操作:</h4>
<ol>
<li>对具体类或客户端类的具体操作;</li>
<li>对抽象类的具体操作;</li>
<li>抽象操作;</li>
<li>工厂方法;</li>
<li>钩子操作;</li>
</ol>
<h4>模板方法与委托的比较</h4>
<blockquote><p>模板方法和委托模式(也叫适配器模式)常见于Cocoa Touch框架。它们对框架类设计来说是非常自然的选择。为什么呢?用户应用程序可以复用(或扩展)框架类,而且框架在设计时不会知道什么样的类会使用它们。可是对于特定的软件设计问题应该使用哪一种模式呢?以下是简要的指导方针。</p>
</blockquote>
<p><br></p>
<table>
<thead><tr>
<th style="text-align:center">模板方法</th>
<th style="text-align:center">委托(适配器)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">父类定义一个一般算法,但缺少某些特定可选的信息或算法,它通过这些缺少的信息或算法起到一个算法“食谱”的作用</td>
<td style="text-align:center">委托(适配器)与预先定义好的委托接口一起定义一个特定算法</td>
</tr>
<tr>
<td style="text-align:center">缺少的信息由子类通过继承来提供</td>
<td style="text-align:center">特定算法由任何对象通过对象组合来提供</td>
</tr>
</tbody>
</table>
<p><br></p>
<blockquote><p>在框架设计中,模板方法模式相当常见。模板方法是代码复用的基本技术。通过它,框架的设计师可以把算法中应用程序相关的元素留给应用程序去实现。模板方法抽出共同行为放入框架类中的手段。这一方式有助于提高可扩展性与可复用性,而维持各种类(框架类与用户类)之间的松耦合。Cocoa Touch框架也采用了模板方法。在框架中经常能看到这些框架类,虽然不如Delegation那么常见。</p>
<p>如:</p>
<p>UIView中的drawRect方法;</p>
<p>UIViewController中的viewDidLoad、shouldAutorateToInterfaceOrienation、roatingHeaderView.roatingFooterView等;</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/模板方法模式类图.png" alt="模板方法模式类图"></p>
<center><strong>模板方法的类图</strong></center><p><br></p>
<blockquote><p>ConcretClass重载AbstractClass的primitiverOperation1和primitiverOperation2,以Client调用AbstractClass中的templateMethod时提供独特的操作。</p>
</blockquote>
《iOS设计模式解析》笔记4:访问者模式、装饰模式、责任链模式、模板方法模式
桶排序(Bucket Sort)
https://www.zruibin.cn/article/tong_pai_xu_bucketsort.html
2016-06-02 13:01
2016-06-02 13:01
<blockquote><p>引用:<a href="http://blog.csdn.net/ACE1985/article/category/688344">CSDN算法之美</a></p>
</blockquote>
<h3>海量数据</h3>
<p>一年的全国高考考生人数为500 万,分数使用标准分,最低100 ,最高900 ,没有小数,要求对这500 万元素的<a href="http://baike.baidu.com/view/209670.htm">数组</a>进行排序。
分析:对500W<a href="http://baike.baidu.com/view/2586460.htm">数据排序</a>,如果基于比较的先进排序,平均比较次数为O(5000000*log5000000)≈1.112亿。但是我们发现,这些数据都有特殊的条件: 100=<score<=900。那么我们就可以考虑桶排序这样一个“投机取巧”的办法、让其在毫秒级别就完成500万排序。</p>
<p>方法:创建801(900-100)个桶。将每个考生的分数丢进f(score)=score-100的桶中。这个过程从头到尾遍历一遍数据只需要500W次。然后根据桶号大小依次将桶中数值输出,即可以得到一个有序的序列。而且可以很容易的得到100分有<strong><em>人,501分有</em></strong>人。</p>
<p>实际上,桶排序对数据的条件有特殊要求,如果上面的分数不是从100-900,而是从0-2亿,那么分配2亿个桶显然是不可能的。所以桶排序有其局限性,适合元素值集合并不大的情况。</p>
<h3>典型</h3>
<p>在一个文件中有10G个整数,乱序排列,要求找出中位数。内存限制为2G。只写出思路即可(内存限制为2G意思是可以使用2G空间来运行程序,而不考虑本机上其他软件内存占用情况。) 关于中位数:数据排序后,位置在最中间的数值。即将数据分成两部分,一部分大于该数值,一部分小于该数值。中位数的位置:当样本数为奇数时,中位数=(N+1)/2 ; 当样本数为偶数时,中位数为N/2与1+N/2的均值(那么10G个数的中位数,就第5G大的数与第5G+1大的数的均值了)。</p>
<p>分析:既然要找中位数,很简单就是排序的想法。那么基于<a href="http://baike.baidu.com/view/60408.htm">字节</a>的桶排序是一个可行的方法。</p>
<p>思想:将整型的每1byte作为一个关键字,也就是说一个整形可以拆成4个keys,而且最高位的keys越大,整数越大。如果高位keys相同,则比较次高位的keys。整个比较过程类似于字符串的<a href="http://baike.baidu.com/view/4670107.htm">字典序</a>。</p>
<h4>第一步:把10G整数每2G读入一次内存,然后一次遍历这536,870,912即(1024<em>1024</em>1024)*2 /4个数据。每个数据用位运算">>"取出最高8位(31-24)。这8bits(0-255)最多表示256个桶,那么可以根据8bit的值来确定丢入第几个桶。最后把每个桶写入一个磁盘文件中,同时在内存中统计每个桶内数据的数量NUM[256]。</h4>
<h5>代价:</h5>
<p>(1) 10G数据依次读入内存的IO代价(这个是无法避免的,CPU不能直接在<a href="http://baike.baidu.com/view/157418.htm">磁盘</a>上运算)。
(2)在内存中遍历536,870,912个数据,这是一个O(n)的线性<a href="http://baike.baidu.com/view/104946.htm">时间复杂度</a>。
(3)把256个桶写回到256个磁盘文件空间中,这个代价是额外的,也就是多付出一倍的10G数据转移的时间。</p>
<h4>第二步:根据内存中256个桶内的数量NUM[256],计算中位数在第几个桶中。很显然,2,684,354,560个数中位数是第1,342,177,280个。假设前127个桶的数据量相加,发现少于1,342,177,280,把第128个桶数据量加上,大于1,342,177,280。说明,中位数必在<a href="http://baike.baidu.com/view/157418.htm">磁盘</a>的第128个桶中。而且在这个桶的第1,342,177,280-N(0-127)个数位上。N(0-127)表示前127个桶的数据量之和。然后把第128个文件中的整数读入内存。(若数据大致是均匀分布的,每个文件的大小估计在10G/256=40M左右,当然也不一定,但是超过2G的可能性很小)。注意,变态的情况下,这个需要读入的第128号文件仍然大于2G,那么整个读入仍然可以按照第一步分批来进行读取。</h4>
<h5>代价:</h5>
<p>(1)循环计算255个桶中的数据量累加,需要O(M)的代价,其中m<255。
(2)读入一个大概80M左右文件大小的IO代价。</p>
<h4>第三步:继续以内存中的某个桶内整数的次高8bit(他们的最高8bit是一样的)进行桶排序(23-16)。过程和第一步相同,也是256个桶。</h4>
<h4>第四步:一直下去,直到最低字节(7-0bit)的桶排序结束。我相信这个时候完全可以在内存中使用一次快排就可以了。</h4>
<p>整个过程的<a href="http://baike.baidu.com/view/104946.htm">时间复杂度</a>在O(n)的线性级别上(没有任何<a href="http://baike.baidu.com/view/7144415.htm">循环嵌套</a>)。但主要时间消耗在第一步的第二次内存-磁盘数据交换上,即10G数据分255个文件写回磁盘上。一般而言,如果第二步过后,内存可以容纳下存在中位数的某一个文件的话,直接快排就可以了(修改者注:我想,继续桶排序但不写回磁盘,效率会更高?)。
</p>
桶排序、海量数据排序
链表问题集锦
https://www.zruibin.cn/article/lian_biao_wen_ti_ji_jin.html
2016-06-01 19:20
2016-06-01 19:20
<p>原文出处:<a href="http://wuchong.me/blog/2014/03/25/interview-link-questions/" target="blank">链表问题集锦</a></p>
<p>链表问题在面试过程中也是很重要也很基础的一部分,链表本身很灵活,很考查编程功底,所以是很值得考的地方。我将复习过程中觉得比较好的链表问题整理了下。</p>
<p>下面是本文所要用到链表节点的定义:</p>
<div class="highlight"><pre><code><span class="k">struct</span> <span class="n">Node</span><span class="p">{</span>
<span class="kt">int</span> <span class="n">data</span><span class="p">;</span>
<span class="n">Node</span><span class="o">*</span> <span class="n">next</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div>
<h3>1. 在O(1)时间删除链表节点</h3>
<p><strong>题目描述:</strong>给定链表的头指针和一个节点指针,在O(1)时间删除该节点。[Google面试题]</p>
<p><strong>分析:</strong>本题与《编程之美》上的「从无头单链表中删除节点」类似。主要思想都是「狸猫换太子」,即用下一个节点数据覆盖要删除的节点,然后删除下一个节点。但是如果节点是尾节点时,该方法就行不通了。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//O(1)时间删除链表节点,从无头单链表中删除节点 </span>
<span class="kt">void</span> <span class="nf">deleteRandomNode</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">cur</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">assert</span><span class="p">(</span><span class="n">cur</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">assert</span><span class="p">(</span><span class="n">cur</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">);</span> <span class="c1">//不能是尾节点 </span>
<span class="n">Node</span><span class="o">*</span> <span class="n">pNext</span> <span class="o">=</span> <span class="n">cur</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">cur</span><span class="o">-></span><span class="n">data</span> <span class="o">=</span> <span class="n">pNext</span><span class="o">-></span><span class="n">data</span><span class="p">;</span>
<span class="n">cur</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">pNext</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">delete</span> <span class="n">pNext</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>2. 单链表的转置</h3>
<p><strong>题目描述:</strong>输入一个单向链表,输出逆序反转后的链表</p>
<p><strong>分析:</strong>链表的转置是一个很常见、很基础的数据结构题了,非递归的算法很简单,用三个临时指针 pre、head、next 在链表上循环一遍即可。递归算法也是比较简单的,但是如果思路不清晰估计一时半会儿也写不出来吧。</p>
<p>下面是循环版本和递归版本的链表转置代码:</p>
<div class="highlight"><pre><code><span class="c1">//单链表的转置,循环方法 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">reverseByLoop</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">head</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span> <span class="n">head</span><span class="o">-></span><span class="n">next</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="k">return</span> <span class="n">head</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">pre</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">next</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">next</span> <span class="o">=</span> <span class="n">head</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">head</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">pre</span><span class="p">;</span>
<span class="n">pre</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="n">head</span> <span class="o">=</span> <span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">pre</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">//单链表的转置,递归方法 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">reverseByRecursion</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//第一个条件是判断异常,第二个条件是结束判断 </span>
<span class="k">if</span><span class="p">(</span><span class="n">head</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span> <span class="n">head</span><span class="o">-></span><span class="n">next</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="k">return</span> <span class="n">head</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">newHead</span> <span class="o">=</span> <span class="n">reverseByRecursion</span><span class="p">(</span><span class="n">head</span><span class="o">-></span><span class="n">next</span><span class="p">);</span>
<span class="n">head</span><span class="o">-></span><span class="n">next</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="n">head</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">return</span> <span class="n">newHead</span><span class="p">;</span> <span class="c1">//返回新链表的头指针 </span>
<span class="p">}</span>
</code></pre></div>
<h3>3. 求链表倒数第k个节点</h3>
<p><strong>题目描述:</strong>输入一个单向链表,输出该链表中倒数第k个节点,链表的倒数第0个节点为链表的尾指针。</p>
<p><strong>分析:</strong>设置两个指针 p1、p2,首先 p1 和 p2 都指向 head,然后 p2 向前走 k 步,这样 p1 和 p2 之间就间隔 k 个节点,最后 p1 和 p2 同时向前移动,直至 p2 走到链表末尾。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//倒数第k个节点 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">theKthNode</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span><span class="kt">int</span> <span class="n">k</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">k</span> <span class="o"><</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">//异常判断 </span>
<span class="n">Node</span> <span class="o">*</span><span class="n">slow</span><span class="p">,</span><span class="o">*</span><span class="n">fast</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">k</span><span class="p">;</span>
<span class="k">for</span><span class="p">(;</span><span class="n">i</span><span class="o">></span><span class="mi">0</span> <span class="o">&&</span> <span class="n">fast</span><span class="o">!=</span><span class="nb">NULL</span><span class="p">;</span><span class="n">i</span><span class="o">--</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">i</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">//考虑k大于链表长度的case </span>
<span class="k">while</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">slow</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>4. 求链表的中间节点</h3>
<p><strong>题目描述:</strong>求链表的中间节点,如果链表的长度为偶数,返回中间两个节点的任意一个,若为奇数,则返回中间节点。</p>
<p><strong>分析:</strong>此题的解决思路和第3题「求链表的倒数第 k 个节点」很相似。可以先求链表的长度,然后计算出中间节点所在链表顺序的位置。但是如果要求只能扫描一遍链表,如何解决呢?最高效的解法和第3题一样,通过两个指针来完成。用两个指针从链表头节点开始,一个指针每次向后移动两步,一个每次移动一步,直到快指针移到到尾节点,那么慢指针即是所求。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//求链表的中间节点 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">theMiddleNode</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">head</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">slow</span><span class="p">,</span><span class="o">*</span><span class="n">fast</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="c1">//如果要求在链表长度为偶数的情况下,返回中间两个节点的第一个,可以用下面的循环条件 </span>
<span class="c1">//while(fast && fast->next != NULL && fast->next->next != NULL) </span>
<span class="k">while</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">slow</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>5. 判断单链表是否存在环</h3>
<p><strong>题目描述:</strong>输入一个单向链表,判断链表是否有环?</p>
<p><strong>分析:</strong>通过两个指针,分别从链表的头节点出发,一个每次向后移动一步,另一个移动两步,两个指针移动速度不一样,如果存在环,那么两个指针一定会在环里相遇。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//判断单链表是否存在环,参数circleNode是环内节点,后面的题目会用到 </span>
<span class="kt">bool</span> <span class="nf">hasCircle</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span><span class="n">Node</span> <span class="o">*&</span><span class="n">circleNode</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">slow</span><span class="p">,</span><span class="o">*</span><span class="n">fast</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">fast</span> <span class="o">==</span> <span class="n">slow</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">circleNode</span> <span class="o">=</span> <span class="n">fast</span><span class="p">;</span>
<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>6. 找到环的入口点</h3>
<p><strong>题目描述:</strong>输入一个单向链表,判断链表是否有环。如果链表存在环,如何找到环的入口点?</p>
<p><strong>解题思路:</strong> 由上题可知,按照 p2 每次两步,p1 每次一步的方式走,发现 p2 和 p1 重合,确定了单向链表有环路了。接下来,让p2回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当 p1 和 p2 再次相遇的时候,就是环路的入口了。</p>
<p><strong>为什么?:</strong>假定起点到环入口点的距离为 a,p1 和 p2 的相交点M与环入口点的距离为b,环路的周长为L,当 p1 和 p2 第一次相遇的时候,假定 p1 走了 n 步。那么有:</p>
<p>p1走的路径: <code>a+b = n</code>;<br>
p2走的路径: <code>a+b+k*L = 2*n</code>; p2 比 p1 多走了k圈环路,总路程是p1的2倍</p>
<p>根据上述公式可以得到 <code>k*L=a+b=n</code>显然,如果从相遇点M开始,p1 再走 n
步的话,还可以再回到相遇点,同时p2从头开始走的话,经过n步,也会达到相遇点M。</p>
<p>显然在这个步骤当中 p1 和 p2 只有前 a 步走的路径不同,所以当 p1 和 p2 再次重合的时候,必然是在链表的环路入口点上。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//找到环的入口点 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">findLoopPort</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">//如果head为空,或者为单结点,则不存在环 </span>
<span class="k">if</span><span class="p">(</span><span class="n">head</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span> <span class="n">head</span><span class="o">-></span><span class="n">next</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">slow</span><span class="p">,</span><span class="o">*</span><span class="n">fast</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span>
<span class="c1">//先判断是否存在环 </span>
<span class="k">while</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="n">fast</span> <span class="o">==</span> <span class="n">slow</span><span class="p">)</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="n">slow</span><span class="p">)</span> <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span> <span class="c1">//不存在环 </span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">head</span><span class="p">;</span> <span class="c1">//快指针从头开始走,步长变为1 </span>
<span class="k">while</span><span class="p">(</span><span class="n">fast</span> <span class="o">!=</span> <span class="n">slow</span><span class="p">)</span> <span class="c1">//两者相遇即为入口点 </span>
<span class="p">{</span>
<span class="n">fast</span> <span class="o">=</span> <span class="n">fast</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">slow</span> <span class="o">=</span> <span class="n">slow</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">fast</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>7. 编程判断两个链表是否相交</h3>
<p><strong>题目描述:</strong>给出两个单向链表的头指针(如下图所示),</p>
<p><img src="https://www.zruibin.cn/article/image/81b78497tw1eesdi7bb2kj20d403qaa0.jpg" alt=""></p>
<p>比如h1、h2,判断这两个链表是否相交。这里为了简化问题,我们假设两个链表均不带环。</p>
<p><strong>解题思路:</strong></p>
<ol>
<li><p>直接循环判断第一个链表的每个节点是否在第二个链表中。但,这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法,至少不能是O(N^2)的复杂度。</p>
</li>
<li><p>针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个节点是否在hash表出现,如果所有的第二个链表的节点都能在hash表中找到,即说明第二个链表与第一个链表有相同的节点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?</p>
</li>
<li><p>转换为环的问题。把第二个链表接在第一个链表后面,如果得到的链表有环,则说明两个链表相交。如何判断有环的问题上面已经讨论过了,但这里有更简单的方法。因为如果有环,则第二个链表的表头一定也在环上,即第二个链表会构成一个循环链表,我们只需要遍历第二个链表,看是否会回到起始点就可以判断出来。这个方法的时间复杂度是线性的,空间是常熟。</p>
</li>
<li><p>进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断两个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。<br>
所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得
到了一个时间复杂度,它为O((Length(h1) +
Length(h2)),而且只用了一个额外的指针来存储最后一个节点。这个方法时间复杂度为线性O(N),空间复杂度为O(1),显然比解法三更胜一筹。</p>
</li>
</ol>
<p>解法四的代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//判断两个链表是否相交 </span>
<span class="kt">bool</span> <span class="nf">isIntersect</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">h1</span><span class="p">,</span><span class="n">Node</span> <span class="o">*</span><span class="n">h2</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">h1</span> <span class="o">==</span> <span class="nb">NULL</span> <span class="o">||</span> <span class="n">h2</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">//异常判断 </span>
<span class="k">while</span><span class="p">(</span><span class="n">h1</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">h1</span> <span class="o">=</span> <span class="n">h1</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">while</span><span class="p">(</span><span class="n">h2</span><span class="o">-></span><span class="n">next</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">h2</span> <span class="o">=</span> <span class="n">h2</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span><span class="p">(</span><span class="n">h1</span> <span class="o">==</span> <span class="n">h2</span><span class="p">)</span> <span class="k">return</span> <span class="nb">true</span><span class="p">;</span> <span class="c1">//尾节点是否相同 </span>
<span class="k">else</span> <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>8. 扩展:链表有环,如何判断相交</h3>
<p><strong>题目描述:</strong>上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?上面的方法还同样有效么?</p>
<p><strong>分析:</strong>如果有环且两个链表相交,则两个链表都有共同一个环,即环上的任意一个节点都存在于两个链表上。因此,就可以判断一链表上俩指针相遇的那个节点,在不在另一条链表上。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//判断两个带环链表是否相交 </span>
<span class="kt">bool</span> <span class="nf">isIntersectWithLoop</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">h1</span><span class="p">,</span><span class="n">Node</span> <span class="o">*</span><span class="n">h2</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">circleNode1</span><span class="p">,</span><span class="o">*</span><span class="n">circleNode2</span><span class="p">;</span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">hasCircle</span><span class="p">(</span><span class="n">h1</span><span class="p">,</span><span class="n">circleNode1</span><span class="p">))</span> <span class="c1">//判断链表带不带环,并保存环内节点 </span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span> <span class="c1">//不带环,异常退出 </span>
<span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="n">hasCircle</span><span class="p">(</span><span class="n">h2</span><span class="p">,</span><span class="n">circleNode2</span><span class="p">))</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="n">Node</span> <span class="o">*</span><span class="n">temp</span> <span class="o">=</span> <span class="n">circleNode2</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="k">while</span><span class="p">(</span><span class="n">temp</span> <span class="o">!=</span> <span class="n">circleNode2</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">temp</span> <span class="o">==</span> <span class="n">circleNode1</span><span class="p">)</span>
<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="n">temp</span> <span class="o">=</span> <span class="n">temp</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>9. 扩展:两链表相交的第一个公共节点</h3>
<p><strong>题目描述:</strong>如果两个无环单链表相交,怎么求出他们相交的第一个节点呢?</p>
<p><strong>分析:</strong>采用对齐的思想。计算两个链表的长度 L1 , L2,分别用两个指针 p1 , p2 指向两个链表的头,然后将较长链表的 p1(假设为 p1)向后移动<code>L2 - L1</code>个节点,然后再同时向后移动p1 , p2,直到 <code>p1 = p2</code>。相遇的点就是相交的第一个节点。</p>
<p>代码如下:</p>
<div class="highlight"><pre><code><span class="c1">//求两链表相交的第一个公共节点 </span>
<span class="n">Node</span><span class="o">*</span> <span class="nf">findIntersectNode</span><span class="p">(</span><span class="n">Node</span> <span class="o">*</span><span class="n">h1</span><span class="p">,</span><span class="n">Node</span> <span class="o">*</span><span class="n">h2</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">len1</span> <span class="o">=</span> <span class="n">listLength</span><span class="p">(</span><span class="n">h1</span><span class="p">);</span> <span class="c1">//求链表长度 </span>
<span class="kt">int</span> <span class="n">len2</span> <span class="o">=</span> <span class="n">listLength</span><span class="p">(</span><span class="n">h2</span><span class="p">);</span>
<span class="c1">//对齐两个链表 </span>
<span class="k">if</span><span class="p">(</span><span class="n">len1</span> <span class="o">></span> <span class="n">len2</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">len1</span><span class="o">-</span><span class="n">len2</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">h1</span><span class="o">=</span><span class="n">h1</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o"><</span><span class="n">len2</span><span class="o">-</span><span class="n">len1</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">h2</span><span class="o">=</span><span class="n">h2</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">while</span><span class="p">(</span><span class="n">h1</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="n">h1</span> <span class="o">==</span> <span class="n">h2</span><span class="p">)</span>
<span class="k">return</span> <span class="n">h1</span><span class="p">;</span>
<span class="n">h1</span> <span class="o">=</span> <span class="n">h1</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">h2</span> <span class="o">=</span> <span class="n">h2</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h3>10. 总结</h3>
<p>可以发现,在链表的问题中,通过两个的指针来提高效率是很值得考虑的一个解决方案,所以一定要记住这种解题思路。记住几种典型的链表问题解决方案,很多类似的题目都可
以转换到熟悉的问题再解决。</p>
<h3>参考文献</h3>
<ul>
<li><a href="http://blog.csdn.net/v_JULY_v/article/details/6447013" target="_blank" rel="external">程序员编程艺术:第九章、闲话链表追赶问题</a></li>
<li><a href="http://www.cppblog.com/humanchao/archive/2008/04/17/47357.html" target="_blank" rel="external">判断单链表是否存在环,判断两个链表是否相交问题详解</a></li>
<li><a href="http://blog.csdn.net/anonymalias/article/details/11020477" target="_blank" rel="external">面试算法之链表操作集锦</a></li>
</ul>
链表问题集锦
《iOS设计模式解析》笔记3:中介者模式、观察者模式、组合模式、迭代器模式
https://www.zruibin.cn/article/《_ios_she_ji_mo_shi_jie_xi_》_bi_ji_3_:_zhong_jie_zhe_mo_shi_.html
2016-05-30 23:23
2016-05-30 23:23
<h3>中介者模式</h3>
<blockquote><p>用一个对象来封装一系列对象的交互方式。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>对象间的交互虽定义明确然而非常复杂,导致一组对象彼此相互依赖而且难以理解</li>
<li>因为对象引用了许多其他对象并与通讯,导致对象难以复用</li>
<li>想要定制一个分布在多个类中的逻辑或行为,又不想生成太多子类</li>
</ul>
<blockquote><p>中介者模式以中介者内部的复杂性代替交互的复杂性。因为中介者封装与合并了colleague(同事)的各种协作逻辑,自身可能变得比它们任何一个都复杂。这会让中介者本身成为无所不知的庞然大物,并且难以维护。(如果在实际应用程序中if-else语句块变成巨无霸,就应该采用策略模式)</p>
</blockquote>
<p><br></p>
<blockquote><p>虽然对于处理应用程序的行为分散于不同对象并且对象互相依存的情况,中介者模式非常有用,但是应当注意避免让中介者类过于庞大而难以维护。如果已经这样了,可以考虑使用另一种设计模式把它分解。要创造性地混合和组合各种设计模式解决同一个问题。每个设计模式就像一个乐高积木块。整个应用程序可能要使用彼此配合的各种“积木块”来建造。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/中介者模式类图.png" alt="中介者模式类图"></p>
<center><strong>中介者模式类图</strong></center><hr>
<h3>观察者模式</h3>
<blockquote><p>定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>有两种抽象类型相互依赖。将它们封装在各自的对象中,就可以对它们单独进行改变和复用</li>
<li>对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变</li>
<li>一个对象必须通知其他对象,而它又不需要知道其他对象是什么</li>
</ul>
<h4>通知与键-值观察之间的主要差别</h4>
<table>
<thead><tr>
<th style="text-align:center">通知</th>
<th style="text-align:center">键-值观察</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">一个中心对象为所有观察者提供变更通知</td>
<td style="text-align:center">被观察的对象直接向观察者发送通知</td>
</tr>
<tr>
<td style="text-align:center">主要从广义上关注程序事件</td>
<td style="text-align:center">绑定于特定对象属性的值</td>
</tr>
</tbody>
</table>
<p><img src="https://www.zruibin.cn/article/image/观察者模式类图.png" alt="观察者模式类图"></p>
<center><strong>观察者模式类图</strong></center><p><img src="https://www.zruibin.cn/article/image/观察者模式时序图.png" alt="观察者模式时序图"></p>
<center><strong>观察者模式时序图</strong></center><hr>
<h3>组合模式</h3>
<blockquote><p>将对象组合成树形结构以表示“部分-整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>想获得对象抽象的树形表示(部分-整体层次结构)</li>
<li>想让客户端统一处理组合结构中的所有对象</li>
</ul>
<blockquote><p>在Cocoa Touch框架中,UIView被组成一个组合结构。每个UIView的实例可以包含UIView的其它实例,形成统一的树形结构。让客户端对单个UIView对象和UIView的组合统一对待。</p>
<p>窗口中的UIView在内部形成视图的树形结构。层次结构的根部是一个UIWindow对象和它的内容视图。添加进来的其它UIView成为它的子视图。它们的每一个可以包含其它视图而变成自己的子视图的超视图。UIView对象只能有一个超视图,可以有零到多个子视图。</p>
</blockquote>
<p><br></p>
<blockquote><p>视图组合结构参与绘图事件处理。当请求超视图为显示进行渲染时,消息会先在超视图被处理,然后传给子视图,消息会传播到遍及整个树的其他子视图。因为它们都是相同的类型--UIView,它们可以被统一处理,而且UIView层次结构的一个分支也可以同样当做一个视图来处理。</p>
<p>统一的视图也作为一个响应链,用于事件处理和动作消息。绘图消息像事件处理消息那样,顺着结构从超视图向子视图传递。Cocoa Touch框架中的响应者链是对责任链模式的实现。</p>
</blockquote>
<p><br></p>
<blockquote><p>组合模式的主要意图是让树型结构中的每个节点具有相同的接口。这样整个结构可作为一个统一抽象结构使用,而不是暴露其内部表示。对每个节点(叶节点或组合)的任何操作,可以通过协议或者抽象其类中定义的相同接口来进行。</p>
<p>对这个结构新增的操作可以用访问者模式来实现,让访问者“访问”每一个节点进行进一步处理,而不必修改现有的组合结构。</p>
<p>组合结构的内部表示不应该暴露给客户端,因此组合模式总是跟迭代器模式一起使用,以遍历组合对象中的每一个项目。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/组合对象结构.png" alt="组合对象结构"></p>
<center><strong>组合对象结构</strong></center><p><img src="https://www.zruibin.cn/article/image/组合模式的概念性结构的类图.png" alt="组合模式的概念性结构的类图"></p>
<center><strong>组合模式的概念性结构的类图</strong></center><hr>
<h3>迭代器模式</h3>
<blockquote><p>提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>需要访问组合对象的内容,而又不暴露其内部表示</li>
<li>需要通过多种方式遍历组合对象</li>
<li>需要提供一个统一的接口,用来遍历各种类型的组合对象</li>
</ul>
<h4>外部迭代器与内部迭代器的区别</h4>
<table>
<thead><tr>
<th style="text-align:center">外部迭代器</th>
<th style="text-align:center">内部迭代器</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">客户端需要知道外部迭代器才能使用,但是它为客户端提供了更多的控制</td>
<td style="text-align:center">客户端不需要知道任何外部迭代器,而是可以通过集合对象的特殊接口,或者一次访问一个元素,或者向集合中的每个元素发送消息</td>
</tr>
<tr>
<td style="text-align:center">客户端创建并维护外部迭代器</td>
<td style="text-align:center">集合对象本身创建并维护它的外部迭代器</td>
</tr>
<tr>
<td style="text-align:center">客户端可以使用不同外部迭代器实现多种类型的遍历</td>
<td style="text-align:center">集合对象可以在不修改客户端代码的情况下,选择不同的外部迭代器</td>
</tr>
</tbody>
</table>
<p><br></p>
<blockquote><p>在遍历时修改聚合体对象可能有危险。如果向聚合体添加或从聚合体删除了元素,可能导致对一个元素访问两次或完全漏掉一个元素。简单的方法是对聚合体进行一个深复制,再对副本进行遍历,但是如果创建和存储聚合体的别一个副本可能影响性能,代价就比较大。有很多方法可以实现不受元素插入或删除影响的迭代器。大部分依靠向聚合体注册迭代器。一种实现方法是在插入与删除操作时,聚合体或者调整由它生成的迭代器的内部状态,或者在内部维护信息,以保证正确的遍历。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/迭代器模式.png" alt="迭代器模式"></p>
<center><strong>迭代器模式类图</strong></center>
《iOS设计模式解析》笔记3:中介者模式、观察者模式、组合模式、迭代器模式
《iOS设计模式解析》笔记2:单例模式、适配器模式、桥接模式、外观模式
https://www.zruibin.cn/article/《_ios_she_ji_mo_shi_jie_xi_》_bi_ji_2_:_dan_li_mo_shi_shi_pei.html
2016-05-29 21:05
2016-05-29 21:05
<h3>单例模式</h3>
<blockquote><p>保证一个类仅有一个实例,并提供一个访问它的全局访问点。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>类只能有一个实例,而且必须从一个为人熟知的访问点对其进行访问,比如工厂方法</li>
<li>这个唯一的实例只能对过子类化进行扩展,而且扩展的对象不会破坏客户端代码</li>
</ul>
<blockquote><p>单例类提供创建与访问类的唯一对象的访问点,并保证它唯一,一致而且为人熟知。这一模式提供了灵活性,使其任何子类可以重载实例方法并且完全控制自身的对象创建,而不必修改客户端的代码。更好的是,父类中的实例实现可以处理动态对象创建。类的实际类型可以在运行时决定,以保证创建正确的对象。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/单例模式类图.png" alt=""></p>
<center><strong>单例模式类图</strong></center><hr>
<h3>适配器模式</h3>
<blockquote><p>将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容面不能一起工作的那些类可以一起工作。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>已有类的接口与需求不匹配</li>
<li>想要一个可复用的类,该类能够同可能带有兼容接口的其他类协作</li>
<li>需要适配一个类的几个不同子类,可是让每一个子类去子类化一个类适配器又不现实,那么可以使用对象适配器(委托)来适配其父类的接口</li>
</ul>
<h4>类适配器与对象适配器的对比</h4>
<table>
<thead><tr>
<th style="text-align:center">类适配品</th>
<th style="text-align:center">对象适配器</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">只针对单一的具体Adaptee类,把Adaptee适配到Target</td>
<td style="text-align:center">可以适配多个Adaptee及其子类</td>
</tr>
<tr>
<td style="text-align:center">易于重载Adaptee的行为,因为是通过直接的子类化进行的适配</td>
<td style="text-align:center">难以重载Adaptee的行为,需要借助子类的对象而不是Adaptee本身</td>
</tr>
<tr>
<td style="text-align:center">只有一个Adapter对象,无需额外的指针间接访问Adaptee</td>
<td style="text-align:center">需要额外的指针以间接访问Adaptee并适配其行为</td>
</tr>
</tbody>
</table>
<p><br></p>
<h5>类适配器</h5>
<p><img src="https://www.zruibin.cn/article/image/类适配器类图.png" alt="类适配器类图"></p>
<center><strong>类适配器类图</strong></center><blockquote><p>Adapter是一个Target类型,同时也是一个Adaptee类型。Adapter重载Target的request方法。但是Adapter没有重载Adaptee的specificRequest方法,而是在Adapter的request方法的实现中,调用超类的specificRequest方法。request方法在运行时向超类发送[super specificRequest]消息。super就是Adaptee,它在Adapter的request方法的作用域内,按自己的方式执行specificRequest方法。只有当Target是协议而不是类时,类适配器才能够用Objective-C来实现。</p>
</blockquote>
<h5>对象适配器</h5>
<p><img src="https://www.zruibin.cn/article/image/对象适配器类图.png" alt="对象适配器类图"></p>
<center><strong>对象适配器类图</strong></center><blockquote><p>Target和Adapter之间的关系跟类适配器相同,而Adapter和Adaptee之间的关系从“属于”变成了“包含”。这种关系下,Adapter需要保持一个对Adaptee的引用。在request方法中,Adapter发送[adaptee specificRequest]消息给引用adaptee,以间接访问它的行为,然后实现客户端请求的其余部分。由于Adapter与Adaptee之间是一种“包含”的关系,用Adapter去适配Adaptee的子类也没什么问题。</p>
</blockquote>
<hr>
<h3>桥接模式</h3>
<blockquote><p>将抽象部分与它的实现部分分离,使它们都可以独立地变化。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>不想在抽象与其实现之间形成固定的绑定关系(这样就能在运行时切换实现)</li>
<li>抽象及其实现都应可以通过子类化独立进行扩展</li>
<li>对抽象的实现进行修改不应该影响客户端代码</li>
<li>如果每个实现需要额外的子类以细化抽象,则说明有必要把它们分成两个部分</li>
<li>想在带有不同抽象接口的多个对象之间共享一个实现</li>
</ul>
<p><img src="https://www.zruibin.cn/article/image/桥接模式类图.png" alt="桥接模式类图"></p>
<center><strong>桥接模式类图</strong></center><hr>
<h3>外观模式</h3>
<blockquote><p>为系统中的一组接口提供一个统一的接口。外观定义一个高层接口,让子系统更易于使用。</p>
</blockquote>
<h4>使用情景</h4>
<ul>
<li>子系统正逐渐变得复杂。应用模式的过程中演化出许多类。可以使用外观为这些子系统提供一个较简单的接口</li>
<li>可以使用外观对子系统进行分层。每个子系统级别有一个外观作为入口点。让它们通过其外观进行通信,可以简化它们的依赖关系</li>
</ul>
<blockquote><p>当程序逐渐变大变复杂时,会有越来越多小型的类从设计和应用模式中演化出来。如果没有一种简化的方式来使用这些类,客户端代码最终将变得越来越大,越来越难理解,而且,维护起来会繁琐无趣。外观有助于提供一种更为简洁的方式来使用子系统中的这些类。处理这些子系统类的默认行为,可能只是定义在外观中的一个简单方法,而不必直接去使用这些类。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/外观模式类图.png" alt="外观模式类图"></p>
<center><strong>外观模式类图</strong></center>
《iOS设计模式解析》笔记2:单例模式、适配器模式、桥接模式、外观模式
《iOS设计模式解析》笔记1:原型模式、工厂方法、抽象工厂、生成器模式
https://www.zruibin.cn/article/《_ios_she_ji_mo_shi_jie_xi_》_bi_ji_1_:_yuan_xing_mo_shi_gong.html
2016-05-28 23:25
2016-05-28 23:25
<h3>原型模式</h3>
<blockquote><p>应用于“复制”操作的模式</p>
</blockquote>
<h5>使用情景</h5>
<ul>
<li>需要创建的对象应独立于其类型与创建方式</li>
<li>要实例化的类是在运行时决定的</li>
<li>不想要与产品层次相对应的工厂层次</li>
<li>不同类的实例间的差异仅是状态的若干组合,因此复制相应数量的原型比手工实现更加方便</li>
<li>类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易 </li>
</ul>
<p><img src="https://www.zruibin.cn/article/image/原型模式类图.png" alt=""></p>
<center><strong>单例模式类图</strong></center><hr>
<h3>工厂方法</h3>
<blockquote><p>工厂方法也称为虚构造器。适用情况:一个类无法预期需要生成哪个类的对象,想让其子类来指定所生成的对象。</p>
</blockquote>
<h5>使用情景</h5>
<ul>
<li>编译时无法准确预期要创建的对象的类</li>
<li>类想让其子类决定在运行时创建什么</li>
<li>类有若干辅助为其子类,而你想将返回哪个子类这一信息局部化</li>
</ul>
<blockquote><p>与直接创建新的对象相比,使用工厂方法创建对象可算作一种最佳的做法。工厂方法模式让客户程序可以要求由工厂方法创建的对象拥有一组共同的行为。所以往类层次结构中引入新的具体产品并不需要修改客户端代码,国为返回的任何具体对象的接口都跟客户端一直在用的从前的接口相用。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/工厂方法模式.png" alt=""></p>
<center><strong>工厂方法类图</strong></center><hr>
<h3>抽象工厂</h3>
<blockquote><p>提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们的具体的类。</p>
</blockquote>
<h5>抽象工厂与工厂方法的对比</h5>
<table>
<thead><tr>
<th>抽象工厂</th>
<th style="text-align:center">工厂方法</th>
</tr>
</thead>
<tbody>
<tr>
<td>通过对象组合创建抽象产品</td>
<td style="text-align:center">通过类继承创建抽象产品</td>
</tr>
<tr>
<td>通过对象组合创建抽象产品</td>
<td style="text-align:center">通过类继承创建抽象产品</td>
</tr>
<tr>
<td>必须修改父类的接口才能支持新的产品</td>
<td style="text-align:center">子类化创建者并重载工厂方法以创建新产品</td>
</tr>
</tbody>
</table>
<blockquote><p>黄金法则:变动需要抽象。</p>
</blockquote>
<p><br></p>
<blockquote><p>当现有的抽象工厂需要支持新的产品时,需要向父类添加相应的新工厂方法。这意味着也要修改其子类以支持新产品的新工厂方法。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/抽象工厂模式.png" alt=""></p>
<center><strong>抽象工厂类图</strong></center><hr>
<h3>生成器模式</h3>
<blockquote><p>将一个复杂对象的构建与它的表现分离,使得同样的构建过程可以创建不同的表现。</p>
</blockquote>
<h5>使用情景</h5>
<ul>
<li>需要创建涉及各种部件的复杂对象。创建对象的算法应该独立于部件的装配方式。常见例子是构建组合对象</li>
<li>构建过程需要以不同的方式(例如,部件或表现的不同组合)构建对象</li>
</ul>
<h5>生成器与抽象工厂的对比</h5>
<table>
<thead><tr>
<th>生成器</th>
<th style="text-align:center">抽象工厂</th>
</tr>
</thead>
<tbody>
<tr>
<td>构建复杂对象</td>
<td style="text-align:center">构造简单或复杂对象</td>
</tr>
<tr>
<td>以多个步骤构建对象</td>
<td style="text-align:center">以单一步骤构建对象</td>
</tr>
<tr>
<td>以多种方式构建对象</td>
<td style="text-align:center">以单一方式构建对象</td>
</tr>
<tr>
<td>在构建过程的最后一步返回产品</td>
<td style="text-align:center">立刻返回产品</td>
</tr>
<tr>
<td>专注一个特定产品</td>
<td style="text-align:center">强调一套产品</td>
</tr>
</tbody>
</table>
<p><br></p>
<blockquote><p>aClient要从aBuilder得到产品,需要知道aDirector和aBuilder。你可能想知道,如果让aDirector从它的construct方式返回产品,或getRusult以某种方式在aDirector中实现,是否aClient可以不必知道aBuilder,那样的话,aDirector就变成了工厂,它的construct方法就变成了返回抽象产品的工厂。而且,aDirector将与所有支持的产品固定在一起,这降低了模式的可复用性。整体思想是分离“什么”与“如何”,使得aDirector能把同一个“什么”(规格)应用到不同的aBuilder,而它懂得“如何”按照给定的规格建造自己特定产品,反之亦然。</p>
</blockquote>
<p><img src="https://www.zruibin.cn/article/image/生成器模式类图.png" alt=""></p>
<center><strong>生成器模式类图</strong></center><p><img src="https://www.zruibin.cn/article/image/生成器模式时序图.png" alt=""></p>
<center><strong>生成器模式时序图</strong></center>
《iOS设计模式解析》笔记1:原型模式、工厂方法、抽象工厂、生成器模式
面向对象设计的六大原则
https://www.zruibin.cn/article/mian_xiang_dui_xiang_she_ji_de_liu_da_yuan_ze.html
2016-05-07 23:36
2016-05-07 23:36
<h2>开闭原则(Open Close Principle)</h2>
<blockquote><p>Software entites like classes, modules and funtcions should be open for extension but closed for modification.(一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。)
<br><br>
开闭就是说对扩展开放,对修改关闭。在程序需要进行扩展的时候,不能去修改原有代码,实现一个热插拔的结果,所以一句话概括就是:为了使程序的扩展性好,易于维护各升级,想要达到这样的效果,需要用接口和抽象类。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。</p>
</blockquote>
<ol>
<li>开闭原则对测试的影响</li>
<li>开闭原则可以提高复用性</li>
<li>开闭原则可以提高可维护性</li>
<li>面向对象开发的要求</li>
</ol>
<ul>
<li>对象约束</li>
<li>元数据(metadata)控制模块行为</li>
<li>制定项目章程</li>
<li>封装变化</li>
</ul>
<h4>注意</h4>
<blockquote><p>开闭原则也只是一个原则</p>
<p>项目规章非常重要</p>
<p>预知变化</p>
</blockquote>
<p><br></p>
<h2>里氏替换原则(Liskov Substitution Principle)</h2>
<blockquote><p>If for each object o1 for type S there is an object o2 of type T such that all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtyple of T.(如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。)
<br><br>
Functions that use pointers or referencesss to base classes must be able to use objects of derived classes without knowing it.(所有引用基类的地方必须能透明地使用其子类的对象。)
<br><br>
里氏替换原则面向对象设计的基本原则之一,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用基石,只有当衍生类可以替换掉基类,软件单位的功能不受影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏替换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键就是抽象化,而基类与了类的关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。</p>
</blockquote>
<ol>
<li>子类必须完全实现父类的方法</li>
<li>子类可以有自己的个性</li>
<li>覆盖或实现父类的方法时输入参数可以被放大(子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松)</li>
<li>覆写或实现父类的方法时输出结果可以被缩小</li>
</ol>
<h4>目的</h4>
<ul>
<li>增加程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。</li>
</ul>
<h4>建议</h4>
<blockquote><p>采用里氏替换原则时,尽量避免子类的“个性”,一旦子类有“个性”,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的“个性”被抹杀(委屈点);把子类单独作为一个业务使用,则会让代码间的耦合关系变得扑朔迷离(缺乏类替换的标准)。</p>
</blockquote>
<p><br></p>
<h2>依赖倒置原则(Dependence Inversion Principle)</h2>
<blockquote><p>High level modules should not depend upon low level modules. Both should depend upon abstractios. Abstractions should not depend upon details. Details should depend upon abstractions.(高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。)
<br><br>
开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。</p>
</blockquote>
<ol>
<li>高层模块不应该依赖低层模块,两者都应该依赖其抽象;</li>
<li>抽象不应该依赖细节;</li>
<li><p>细节应该依赖抽象。</p>
</li>
<li><p>模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的;</p>
</li>
<li>接口或抽象类不依赖实现类;</li>
<li>实现类依赖接口或抽象类。</li>
</ol>
<h4>写法</h4>
<ol>
<li>构造函数传递依赖对象</li>
<li>Setter方法传递依赖对象</li>
<li>接口声明依赖对象</li>
</ol>
<h4>建议</h4>
<blockquote><p>每个类尽量都有接口或抽象类,或者抽象类和接口两者都具备</p>
<p>变量的表面类型尽量是接口或者是抽象类</p>
<p>任何类都不应该从具体类派生</p>
<p>尽量不要覆写基类的方法</p>
<p>结合里氏替换原则使用</p>
</blockquote>
<p><br></p>
<h2>接口隔离原则(Interface Segregation Principle)</h2>
<blockquote><p>Client should not be forced to depend upon interfaces that they don"t use.(客户端不应该依赖它不需要的接口。)
<br><br>
The dependency of one class to another on should depend on the smallest possible interface.(类间的依赖关系应该建立在最小的接口上。)
<br><br>
这个原则的意思是:使用多个隔离的接口,比使用单个接口要好,这有一个是降低类之间的耦合度意思,从这可以看出,其实设计模式就是一个软件的设计思想,从大型软件架构出发,为了升级和维护方便。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。</p>
</blockquote>
<ul>
<li>接口要尽量小(根据接口隔离原则拆分接口时,首先必须满足单一职责原则)</li>
<li>接口要高内聚</li>
<li>定制服务</li>
<li>接口设计是有限度的</li>
</ul>
<h4>建议</h4>
<blockquote><p>一个接口只服务于一个子模块或者业务逻辑;</p>
<p>通过业务逻辑压缩接口中的public方法,接口时常去回顾,尽量让接口达到“满身筋骨肉”,而不是“肥嘟嘟”的一大堆方法;</p>
<p>已经被污染了的接口,尽量去修改,若变更的风险较大,则采用适配器模式进行转化处理;</p>
<p>了解环境,拒绝盲从。每个项目或产品都有特定的环境因素,别看到大师是这样做的你就照抄。千万别,环境不同,接口拆分的标准就不同。深入了解业务逻辑,最好的接口设计就出自你的手中!</p>
</blockquote>
<p><br/></p>
<h2>迪米特法则(Law of Demeter)(最少知道原则)</h2>
<blockquote><p>Only talk to your immediate friends(只与直接的朋友通信。)
<br><br>
最少知识原则(Least Knowledge Principle,LKP)。一个对象应该对其他对象有最少的了解,即,一个类应该对自己需要耦合或调用的类知道得最少,被耦合或调用的类的内部是如何复杂都和我没关系,那是你的事情,我就知道你提供的这么多public方法,我就调用这么多,其他的我一概不关心。
<br><br>
为什么叫最少知道原则,就是说:一个实体应当尽量少的与其他实体之间发生相互作用,使得系统功能模块相对独立。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>尽量降低类与类之间的耦合。</p>
</blockquote>
<ul>
<li>只和朋友交流</li>
<li>朋友间也是有距离的</li>
<li>是自己的就是自己的</li>
<li>谨慎使用Serializable</li>
</ul>
<h4>建议</h4>
<blockquote><p>迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合了以后,类的复用率才可以提高。其要求的结果就是产生了大量的中转或跳转类,导致系统的复杂性提高,同时也维护带来了难度。</p>
</blockquote>
<p><br></p>
<h2>单一职责原则(Single Responsibility Principle)</h2>
<blockquote><p>There should never be more than one reason for a class to change.(不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。)
<br><br>
一个类,只有一个引起它变化的原因。应该只有一个职责。每一个职责都是变化的一个轴线,如果一个类有一个以上的职责,这些职责就耦合在了一起。这会导致脆弱的设计。当一个职责发生变化时,可能会影响其它的职责。另外,多个职责耦合在一起,会影响复用性。例如:要实现逻辑和界面的分离。如果一个类承担的职责过多,就等于把这些职责耦合在一起了。一个职责的变化可能会削弱或者抑制这个类完成其他职责的能力。这种耦合会导致脆弱的设计,当发生变化时,设计会遭受到意想不到的破坏。而如果想要避免这种现象的发生,就要尽可能的遵守单一职责原则。此原则的核心就是解耦和增强内聚性。</p>
</blockquote>
<h4>问题由来</h4>
<blockquote><p>类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。</p>
</blockquote>
<h4>解决方案</h4>
<blockquote><p>遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。</p>
</blockquote>
<h4>好处</h4>
<ul>
<li>类的复杂性降低,实现什么职责都有清晰明确定义;</li>
<li>可读性提高,复杂性降低,那当然可读性提高了;</li>
<li>可维护性提高,可读性提高,那当然更容易维护了;</li>
<li>变更引起的风险降低,变更是必不可少的,如果接口的单一职责做得好,一个接口修改只对相应的实现类有影响,对其他的接口无影响,这对系统的扩展性、维护性都有非常大的帮助。 </li>
</ul>
<h4>建议</h4>
<blockquote><p>接口一定要做到单一职责,类的设计尽量做到只有一个原因引起变化。</p>
</blockquote>
面向对象设计的六大原则
Objective-C的动态特性
https://www.zruibin.cn/article/objective-c_de_dong_tai_te_xing.html
2015-11-01 20:42
2015-11-01 20:42
<p>原文出处:<a href="http://limboy.me/ios/2013/08/03/dynamic-tips-and-tricks-with-objective-c.html" target="blank">Objective-C的动态特性</a></p>
<p>这是一篇译文,原文<a href="http://pilky.me/view/21">在此</a>,上一篇文章就是受这篇文章启发,这次干脆都翻译过来。</p>
<p>过去的几年中涌现了大量的Objective-C开发者。有些是从动态语言转过来的,比如Ruby或Python,有些是从强类型语言转过来的,如Java或C#,当然也有直接以Objective-C作为入门语言的。也就是说有很大一部分开发者都没有使用Objective-C太长时间。当你接触一门新语言时,更多地会关注基础知识,如语法和特性等。但通常有一些更高级的,更鲜为人知又有强大功能的特性等待你去开拓。</p>
<p>这篇文章主要是来领略下Objective-C的运行时(runtime),同时解释是什么让Objective-C如此动态,然后感受下这些动态化的技术细节。希望这回让你对Objective-C和Cocoa是如何运行的有更好的了解。</p>
<h2>The Runtime</h2>
<p>Objective-C是一门简单的语言,95%是C。只是在语言层面上加了些关键字和语法。真正让Objective-C如此强大的是它的运行时。它很小但却很强大。它的核心是消息分发。</p>
<h3>Messages</h3>
<p>如果你是从动态语言如Ruby或Python转过来的,可能知道什么是消息,可以直接跳过进入下一节。那些从其他语言转过来的,继续看。</p>
<p>执行一个方法,有些语言,编译器会执行一些额外的优化和错误检查,因为调用关系很直接也很明显。但对于消息分发来说,就不那么明显了。在发消息前不必知道某个对象是否能够处理消息。你把消息发给它,它可能会处理,也可能转给其他的Object来处理。一个消息不必对应一个方法,一个对象可能实现一个方法来处理多条消息。</p>
<p>在Objective-C中,消息是通过objc_msgSend()这个runtime方法及相近的方法来实现的。这个方法需要一个target,selector,还有一些参数。理论上来说,编译器只是把消息分发变成objc_msgSend来执行。比如下面这两行代码是等价的。</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">array</span> <span class="nl">insertObject</span><span class="p">:</span><span class="n">foo</span> <span class="nl">atIndex</span><span class="p">:</span><span class="mi">5</span><span class="p">];</span>
<span class="n">objc_msgSend</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">insertObject</span><span class="p">:</span><span class="nl">atIndex</span><span class="p">:),</span> <span class="n">foo</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
</code></pre></div>
<h3>Objects, Classes, MetaClasses</h3>
<p>大多数面向对象的语言里有 classes 和 objects 的概念。Objects通过Classes生成。但是在Objective-C中,classes本身也是objects(译者注:这点跟python很像),也可以处理消息,这也是为什么会有类方法和实例方法。具体来说,Objective-C中的Object是一个结构体(struct),第一个成员是isa,指向自己的class。这是在objc/objc.h中定义的。</p>
<div class="highlight"><pre><code><span class="k">typedef</span> <span class="k">struct</span> <span class="n">objc_object</span> <span class="p">{</span>
<span class="n">Class</span> <span class="n">isa</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">id</span><span class="p">;</span>
</code></pre></div>
<p>object的class保存了方法列表,还有指向父类的指针。但classes也是objects,也会有isa变量,那么它又指向哪儿呢?这里就引出了第三个类型: metaclasses。一个 metaclass被指向class,class被指向object。它保存了所有实现的方法列表,以及父类的metaclass。如果想更清楚地了解objects,classes以及metaclasses是如何一起工作地,可以阅读<a href="http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html">这篇文章</a>。</p>
<h3>Methods, Selectors and IMPs</h3>
<p>我们知道了运行时会发消息给对象。我们也知道一个对象的class保存了方法列表。那么这些消息是如何映射到方法的,这些方法又是如何被执行的呢?</p>
<p>第一个问题的答案很简单。class的方法列表其实是一个字典,key为selectors,IMPs为value。一个IMP是指向方法在内存中的实现。很重要的一点是,selector和IMP之间的关系是在运行时才决定的,而不是编译时。这样我们就能玩出些花样。</p>
<p>IMP通常是指向方法的指针,第一个参数是self,类型为id,第二个参数是cmd,类型为SEL,余下的是方法的参数。这也是self和<code>cmd</code>被定义的地方。下面演示了Method和IMP</p>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="nl">doSomethingWithInt</span><span class="p">:(</span><span class="kt">int</span><span class="p">)</span><span class="n">aInt</span><span class="p">{}</span>
<span class="n">id</span> <span class="n">doSomethingWithInt</span><span class="p">(</span><span class="n">id</span> <span class="n">self</span><span class="p">,</span> <span class="n">SEL</span> <span class="n">_cmd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">aInt</span><span class="p">){}</span>
</code></pre></div>
<h3>其他运行时的方法</h3>
<p>现在我们知道了objects,classes,selectors,IMPs以及消息分发,那么运行时到底能做什么呢?主要有两个作用:</p>
<p>1.创建、修改、自省classes和objects</p>
<p>2.消息分发</p>
<p>之前已经提过消息分发,不过这只是一小部分功能。所有的运行时方法都有特定的前缀。下面是一些有意思的方法:</p>
<h3>class</h3>
<p>class开头的方法是用来修改和自省classes。方法如<strong>class_addIvar</strong>, <strong>class_addMethod</strong>, <strong>class_addProperty</strong>和<strong>class_addProtocol</strong>允许重建classes。<strong>class_copyIvarList</strong>, <strong>class_copyMethodList</strong>, <strong>class_copyProtocolList</strong>和<strong>class_copyPropertyList</strong>能拿到一个class的所有内容。而<strong>class_getClassMethod</strong>, <strong>class_getClassVariable</strong>, <strong>class_getInstanceMethod</strong>, <strong>class_getInstanceVariable</strong>, <strong>class_getMethodImplementation</strong>和<strong>class_getProperty</strong>返回单个内容。</p>
<p>也有一些通用的自省方法,如<strong>class_conformsToProtocol</strong>, <strong>class_respondsToSelector</strong>, <strong>class_getSuperclass</strong>。最后,你可以使用<strong>class_createInstance</strong>来创建一个object。</p>
<h3>ivar</h3>
<p>这些方法能让你得到名字,内存地址和Objective-C type encoding。</p>
<h3>method</h3>
<p>这些方法主要用来自省,比如<strong>method_getName</strong>, <strong>method_getImplementation</strong>, <strong>method_getReturnType</strong>等等。也有一些修改的方法,包括<strong>method_setImplementation</strong>和<strong>method_exchangeImplementations</strong>,这些我们后面会讲到。</p>
<h3>objc</h3>
<p>一旦拿到了object,你就可以对它做一些自省和修改。你可以get/set ivar, 使用<strong>object_copy</strong>和<strong>object_dispose</strong>来copy和free object的内存。最NB的不仅是拿到一个class,而是可以使用<strong>object_setClass</strong>来改变一个object的class。待会就能看到使用场景。</p>
<h3>property</h3>
<p>属性保存了很大一部分信息。除了拿到名字,你还可以使用<strong>property_getAttributes</strong>来发现property的更多信息,如返回值、是否为atomic、getter/setter名字、是否为dynamic、背后使用的ivar名字、是否为弱引用。</p>
<h3>protocol</h3>
<p>Protocols有点像classes,但是精简版的,运行时的方法是一样的。你可以获取method, property, protocol列表, 检查是否实现了其他的protocol。</p>
<h3>sel</h3>
<p>最后我们有一些方法可以处理 selectors,比如获取名字,注册一个selector等等。</p>
<p>现在我们对Objective-C的运行时有了大概的了解,来看看它们能做哪些有趣的事情。</p>
<h2>Classes And Selectors From Strings</h2>
<p>比较基础的一个动态特性是通过String来生成Classes和Selectors。Cocoa提供了NSClassFromString和NSSelectorFromString方法,使用起来很简单:</p>
<div class="highlight"><pre><code><span class="n">Class</span> <span class="n">stringclass</span> <span class="o">=</span> <span class="n">NSClassFromString</span><span class="p">(</span><span class="err">@</span><span class="s">"NSString"</span><span class="p">);</span>
</code></pre></div>
<p>于是我们就得到了一个string class。接下来:</p>
<div class="highlight"><pre><code><span class="n">NSString</span> <span class="o">*</span><span class="n">myString</span> <span class="o">=</span> <span class="p">[</span><span class="n">stringclass</span> <span class="nl">stringWithString</span><span class="p">:</span><span class="err">@</span><span class="s">"Hello World"</span><span class="p">];</span>
</code></pre></div>
<p>为什么要这么做呢?直接使用Class不是更方便?通常情况下是,但有些场景下这个方法会很有用。首先,可以得知是否存在某个class,NSClassFromString 会返回nil,如果运行时不存在该class的话。比如可以检查NSClassFromString(@"NSRegularExpression")是否为nil来判断是否为iOS4.0+。</p>
<p>另一个使用场景是根据不同的输入返回不同的class或method。比如你在解析一些数据,每个数据项都有要解析的字符串以及自身的类型(String,Number,Array)。你可以在一个方法里搞定这些,也可以使用多个方法。其中一个方法是获取type,然后使用if来调用匹配的方法。另一种是根据type来生成一个selector,然后调用之。以下是两种实现方式:</p>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">parseObject</span><span class="p">:(</span><span class="n">id</span><span class="p">)</span><span class="n">object</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">id</span> <span class="n">data</span> <span class="n">in</span> <span class="n">object</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">([[</span><span class="n">data</span> <span class="n">type</span><span class="p">]</span> <span class="nl">isEqualToString</span><span class="p">:</span><span class="err">@</span><span class="s">"String"</span><span class="p">])</span> <span class="p">{</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">parseString</span><span class="p">:[</span><span class="n">data</span> <span class="n">value</span><span class="p">]];</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">([[</span><span class="n">data</span> <span class="n">type</span><span class="p">]</span> <span class="nl">isEqualToString</span><span class="p">:</span><span class="err">@</span><span class="s">"Number"</span><span class="p">])</span> <span class="p">{</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">parseNumber</span><span class="p">:[</span><span class="n">data</span> <span class="n">value</span><span class="p">]];</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">([[</span><span class="n">data</span> <span class="n">type</span><span class="p">]</span> <span class="nl">isEqualToString</span><span class="p">:</span><span class="err">@</span><span class="s">"Array"</span><span class="p">])</span> <span class="p">{</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">parseArray</span><span class="p">:[</span><span class="n">data</span> <span class="n">value</span><span class="p">]];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">parseObjectDynamic</span><span class="p">:(</span><span class="n">id</span><span class="p">)</span><span class="n">object</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="n">id</span> <span class="n">data</span> <span class="n">in</span> <span class="n">object</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">performSelector</span><span class="p">:</span><span class="n">NSSelectorFromString</span><span class="p">([</span><span class="n">NSString</span> <span class="nl">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s">"parse%@:"</span><span class="p">,</span> <span class="p">[</span><span class="n">data</span> <span class="n">type</span><span class="p">]])</span> <span class="nl">withObject</span><span class="p">:[</span><span class="n">data</span> <span class="n">value</span><span class="p">]];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">parseString</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">aString</span> <span class="p">{}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">parseNumber</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">aNumber</span> <span class="p">{}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">parseArray</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">aArray</span> <span class="p">{}</span>
</code></pre></div>
<p>可一看到,你可以把7行带if的代码变成1行。将来如果有新的类型,只需增加实现方法即可,而不用再去添加新的 else if。</p>
<h3>Method Swizzling</h3>
<p>之前我们讲过,方法由两个部分组成。Selector相当于一个方法的id;IMP是方法的实现。这样分开的一个便利之处是selector和IMP之间的对应关系可以被改变。比如一个 IMP 可以有多个 selectors 指向它。</p>
<p>而 Method Swizzling 可以交换两个方法的实现。或许你会问“什么情况下会需要这个呢?”。我们先来看下Objective-C中,两种扩展class的途径。首先是 subclassing。你可以重写某个方法,调用父类的实现,这也意味着你必须使用这个subclass的实例,但如果继承了某个Cocoa class,而Cocoa又返回了原先的class(比如 NSArray)。这种情况下,你会想添加一个方法到NSArray,也就是使用Category。99%的情况下这是OK的,但如果你重写了某个方法,就没有机会再调用原先的实现了。</p>
<p>Method Swizzling 可以搞定这个问题。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过<strong>method_exchangeImplementations</strong>这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写<strong>addObject:</strong>方法来纪录每一个新添加的对象。</p>
<div class="highlight"><pre><code><span class="cp">#import <objc/runtime.h></span>
<span class="err">@</span><span class="n">interface</span> <span class="n">NSMutableArray</span> <span class="p">(</span><span class="n">LoggingAddObject</span><span class="p">)</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">logAddObject</span><span class="p">:(</span><span class="n">id</span><span class="p">)</span><span class="n">aObject</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
<span class="err">@</span><span class="n">implementation</span> <span class="n">NSMutableArray</span> <span class="p">(</span><span class="n">LoggingAddObject</span><span class="p">)</span>
<span class="o">+</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">load</span> <span class="p">{</span>
<span class="n">Method</span> <span class="n">addobject</span> <span class="o">=</span> <span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">addObject</span><span class="p">:));</span>
<span class="n">Method</span> <span class="n">logAddobject</span> <span class="o">=</span> <span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">logAddObject</span><span class="p">:));</span>
<span class="n">method_exchangeImplementations</span><span class="p">(</span><span class="n">addObject</span><span class="p">,</span> <span class="n">logAddObject</span><span class="p">);</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">logAddObject</span><span class="p">:(</span><span class="n">id</span><span class="p">)</span><span class="n">aobject</span> <span class="p">{</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">logAddObject</span><span class="p">:</span><span class="n">aObject</span><span class="p">];</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"Added object %@ to array %@"</span><span class="p">,</span> <span class="n">aObject</span><span class="p">,</span> <span class="n">self</span><span class="p">);</span>
<span class="p">}</span>
<span class="err">@</span><span class="n">end</span>
</code></pre></div>
<p>我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:</p>
<p><img src="http://pilky.me/static/blogmedia/objcdynamictips_methodswizzling.png" alt=""></p>
<h3>动态继承、交换</h3>
<p>我们可以在运行时创建新的class,这个特性用得不多,但其实它还是很强大的。你能通过它创建新的子类,并添加新的方法。</p>
<p>但这样的一个子类有什么用呢?别忘了Objective-C的一个关键点:object内部有一个叫做isa的变量指向它的class。这个变量可以被改变,而不需要重新创建。然后就可以添加新的ivar和方法了。可以通过以下命令来修改一个object的class</p>
<div class="highlight"><pre><code><span class="n">object_setClass</span><span class="p">(</span><span class="n">myObject</span><span class="p">,</span> <span class="p">[</span><span class="n">MySubclass</span> <span class="n">class</span><span class="p">]);</span>
</code></pre></div>
<p>这可以用在Key Value Observing。当你开始observing an object时,Cocoa会创建这个object的class的subclass,然后将这个object的isa指向新创建的subclass。点击这里查看更详细的解释。</p>
<h3>动态方法处理</h3>
<p>目前为止,我们讨论了方法交换,以及已有方法的处理。那么当你发送了一个object无法处理的消息时会发生什么呢?很明显,"it breaks"。大多数情况下确实如此,但Cocoa和runtime也提供了一些应对方法。</p>
<p>首先是动态方法处理。通常来说,处理一个方法,运行时寻找匹配的selector然后执行之。有时,你只想在运行时才创建某个方法,比如有些信息只有在运行时才能得到。要实现这个效果,你需要重写+resolveInstanceMethod: 和/或 +resolveClassMethod:。如果确实增加了一个方法,记得返回YES。</p>
<div class="highlight"><pre><code><span class="o">+</span> <span class="p">(</span><span class="n">BOOL</span><span class="p">)</span><span class="nl">resolveInstanceMethod</span><span class="p">:(</span><span class="n">SEL</span><span class="p">)</span><span class="n">aSelector</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">aSelector</span> <span class="o">==</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">myDynamicMethod</span><span class="p">))</span> <span class="p">{</span>
<span class="n">class_addMethod</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">aSelector</span><span class="p">,</span> <span class="p">(</span><span class="n">IMP</span><span class="p">)</span><span class="n">myDynamicIMP</span><span class="p">,</span> <span class="s">"v@:"</span><span class="p">);</span>
<span class="k">return</span> <span class="n">YES</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="p">[</span><span class="n">super</span> <span class="nl">resolveInstanceMethod</span><span class="p">:</span><span class="n">aSelector</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>那Cocoa在什么场景下会使用这些方法呢?Core Data用得很多。NSManagedObjects有许多在运行时添加的属性用来处理get/set属性和关系。那如果Model在运行时被改变了呢?</p>
<h3>消息转发</h3>
<p>如果 resolve method 返回NO,运行时就进入下一步骤:消息转发。有两种常见用例。1) 将消息转发到另一个可以处理该消息的object。2) 将多个消息转发到同一个方法。</p>
<p>消息转发分两步。首先,运行时调用<strong>-forwardingTargetForSelector:</strong>,如果只是想把消息发送到另一个object,那么就使用这个方法,因为更高效。如果想要修改消息,那么就要使用<strong>-forwardInvocation:</strong>,运行时将消息打包成NSInvocation,然后返回给你处理。处理完之后,调用<strong>invokeWithTarget:</strong>。</p>
<p>Cocoa有几处地方用到了消息转发,主要的两个地方是代理(Proxies)和响应链(Responder Chain)。NSProxy是一个轻量级的class,它的作用就是转发消息到另一个object。如果想要惰性加载object的某个属性会很有用。NSUndoManager也有用到,不过是截取消息,之后再执行,而不是转发到其他的地方。</p>
<p>响应链是关于Cocoa如何处理与发送事件与行为到对应的对象。比如说,使用Cmd+C执行了copy命令,会发送-copy:到响应链。首先是First Responder,通常是当前的UI。如果没有处理该消息,则转发到下一个-nextResponder。这么一直下去直到找到能够处理该消息的object,或者没有找到,报错。</p>
<h3>使用Block作为Method IMP</h3>
<p>iOS 4.3带来了很多新的runtime方法。除了对properties和protocols的加强,还带来一组新的以 imp 开头的方法。通常一个 IMP 是一个指向方法实现的指针,头两个参数为 object(self)和selector(cmd)。iOS 4.0和Mac OS X 10.6 带来了block,<code>impimplementationWithBlock()</code> 能让我们使用block作为 IMP,下面这个代码片段展示了如何使用block来添加新的方法。</p>
<div class="highlight"><pre><code><span class="n">IMP</span> <span class="n">myIMP</span> <span class="o">=</span> <span class="n">imp_implementationWithBlock</span><span class="p">(</span><span class="o">^</span><span class="p">(</span><span class="n">id</span> <span class="n">_self</span><span class="p">,</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">string</span><span class="p">)</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"Hello %@"</span><span class="p">,</span> <span class="n">string</span><span class="p">);</span>
<span class="p">});</span>
<span class="n">class_addMethod</span><span class="p">([</span><span class="n">MYclass</span> <span class="n">class</span><span class="p">],</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">sayHello</span><span class="p">:),</span> <span class="n">myIMP</span><span class="p">,</span> <span class="s">"v@:@"</span><span class="p">);</span>
</code></pre></div>
<p>如果想知道这是如何实现的,可以查看这篇文章</p>
<p>可以看到,Objective-C 表面看起来挺简单,但还是很灵活的,可以带来很多可能性。动态语言的优势在于在不扩展语言本身的情况下做很多很灵巧的事情。比如Key Value Observing,提供了优雅的API可以与已有的代码无缝结合,而不需要新增语言级别的特性。</p>
<p>希望这篇文章能让你更深入地了解Objective-C,在开发app时也能开阔思路,考虑更多的可能性。</p>
Objective-C的动态特性
Vim入门基础
https://www.zruibin.cn/article/vim_ru_men_ji_chu.html
2015-10-26 12:43
2015-10-26 12:53
<h2>Vim入门基础</h2>
<p>原文出处:<a href="http://www.jianshu.com/p/bcbe916f97e1" target="blank">Vim入门基础</a></p>
<p><img src="http://images.cnitblog.com/blog/379772/201306/14142737-eaf681b3f8d04c5085d0c6efdd85684f.jpg" alt="--"></p>
<blockquote><p>公司新员工学习有用到,Vim官网的手册又太大而全,而网上各方资料要么不全面,要么不够基础。在网上搜集各方资料,>按照自己的框架整理一份Vim入门基础教程,分享出来。特点是偏向基础,但对入门者来说足够全面,而且结构框架清晰。
另外,参考资料众多,没有一一标出来,如果作者看到,请联系我确认一下是否参考了你的资料,我会在文中标注出来。</p>
</blockquote>
<h3>1. 简介</h3>
<p>Vim(Vi[Improved])编辑器是功能强大的跨平台文本文件编辑工具,继承自Unix系统的Vi编辑器,支持Linux/Mac OS X/Windows系统,利用它可以建立、修改文本文件。进入Vim编辑程序,可以在终端输入下面的命令:</p>
<div class="highlight"><pre><code><span class="err">$</span><span class="n">vim</span> <span class="p">[</span><span class="n">filename</span><span class="p">]</span>
</code></pre></div>
<p>其中filename是要编辑器的文件的路径名。如果文件不存在,它将为你建立一个新文件。Vim编辑程序有三种操作模式,分别称为 编辑模式、插入模式 和 命令模式,当运行Vim时,首先进入编辑模式。</p>
<h3>2. 编辑模式</h3>
<p>Vim编辑方式的主要用途是在被编辑的文件中移动光标的位置。一旦光标移到到所要的位置,就可以进行剪切和粘贴正文块,删除正文和插入新的正文。当完成所有的编辑工作后,需要保存编辑器结果,退出编辑程序回到终端,可以发出ZZ命令,连续按两次大写的Z键。</p>
<h4>2.1 跳转</h4>
<p>如果键盘上有上、下、左、右箭头的导航键,就由这些键来完成光标的移动。另外,可以用下面的键完成同样的 按字符移动 功能:</p>
<div class="highlight"><pre><code><span class="n">k</span> <span class="err">上移;</span>
<span class="n">j</span> <span class="err">下移;</span>
<span class="n">h</span> <span class="err">左移;</span>
<span class="n">l</span> <span class="err">右移。</span>
</code></pre></div>
<p>上面这4个键将光标位置每次移动一行或一个 字符 。Vim还提供稍大范围移动光标的命令:</p>
<div class="highlight"><pre><code><span class="n">ctrl</span><span class="o">+</span><span class="n">f</span> <span class="err">在文件中前移一页(相当于</span> <span class="n">page</span> <span class="n">down</span><span class="err">);</span>
<span class="n">ctrl</span><span class="o">+</span><span class="n">b</span> <span class="err">在文件中后移一页(相当于</span> <span class="n">page</span> <span class="n">up</span><span class="err">);</span>
</code></pre></div>
<p>更大范围的移动:</p>
<div class="highlight"><pre><code><span class="o">*</span> <span class="err">当光标停留在一个单词上,</span><span class="o">*</span> <span class="err">键会在文件内搜索该单词,并跳转到下一处;</span>
<span class="cp"># 当光标停留在一个单词上,# 在文件内搜索该单词,并跳转到上一处;</span>
<span class="p">(</span><span class="o">/</span><span class="p">)</span> <span class="err">移动到</span> <span class="err">前</span><span class="o">/</span><span class="err">后</span> <span class="err">句</span> <span class="err">的开始;</span>
<span class="p">{</span><span class="o">/</span><span class="p">}</span> <span class="err">跳转到</span> <span class="err">当前</span><span class="o">/</span><span class="err">下一个</span> <span class="err">段落</span> <span class="err">的开始。</span>
<span class="n">g_</span> <span class="err">到本行最后一个不是</span> <span class="n">blank</span> <span class="err">字符的位置。</span>
<span class="n">fa</span> <span class="err">到下一个为</span> <span class="n">a</span> <span class="err">的字符处,你也可以</span><span class="n">fs</span><span class="err">到下一个为</span><span class="n">s</span><span class="err">的字符。</span>
<span class="n">t</span><span class="p">,</span> <span class="err">到逗号前的第一个字符。逗号可以变成其它字符。</span>
<span class="mf">3f</span><span class="n">a</span> <span class="err">在当前行查找第三个出现的</span> <span class="n">a</span><span class="err">。</span>
<span class="n">F</span><span class="o">/</span><span class="n">T</span> <span class="err">和</span> <span class="n">f</span> <span class="err">和</span> <span class="n">t</span> <span class="err">一样,只不过是相反方向</span><span class="p">;</span>
<span class="n">gg</span> <span class="err">将光标定位到文件第一行起始位置;</span>
<span class="n">G</span> <span class="err">将光标定位到文件最后一行起始位置;</span>
<span class="n">NG</span><span class="err">或</span><span class="n">Ngg</span> <span class="err">将光标定位到第</span> <span class="n">N</span> <span class="err">行的起始位置。</span>
</code></pre></div>
<p>在屏幕中找到需要的 一页 时,可以用下面的命令快速移动光标:</p>
<div class="highlight"><pre><code><span class="n">H</span> <span class="err">将光标移到屏幕上的起始行(或最上行);</span>
<span class="n">M</span> <span class="err">将光标移到屏幕中间;</span>
<span class="n">L</span> <span class="err">将光标移到屏幕最后一行。</span>
</code></pre></div>
<p>同样需要注意字母的大小写。H 和 L 命令还可以加数字。如 2H 表示将光标移到屏幕的第2行,3L 表示将光标移到屏幕的倒数第3行。
当将光标移到所要的行是,行内移动 光标可以用下面的命令来实现:</p>
<div class="highlight"><pre><code><span class="n">w</span> <span class="err">右移光标到下一个字的开头;</span>
<span class="n">e</span> <span class="err">右移光标到一个字的末尾;</span>
<span class="n">b</span> <span class="err">左移光标到前一个字的开头;</span>
<span class="mi">0</span> <span class="err">数字0,左移光标到本行的开始;</span>
<span class="err">$</span> <span class="err">右移光标,到本行的末尾;</span>
<span class="o">^</span> <span class="err">移动光标,到本行的第一个非空字符。</span>
</code></pre></div>
<h4>2.2 搜索匹配</h4>
<p>和许多先进的编辑器一样,Vim 提供了强大的字符串搜索功能。要查找文件中指定字或短语出现的位置,可以用Vim直接进行搜索,而不必以手工方式进行。搜索方法是:键入字符 / ,后面跟以要搜索的字符串,然后按回车键。编辑程序执行正向搜索(即朝文件末尾方向),并在找到指定字符串后,将光标停到该字符串的开头;键入 n 命令可以继续执行搜索,找出这一字符串下次出现的位置。用字符 ? 取代 / ,可以实现反向搜索(朝文件开头方向)。例如:</p>
<div class="highlight"><pre><code><span class="o">/</span><span class="n">str1</span> <span class="err">正向搜索字符串</span> <span class="n">str1</span><span class="err">;</span>
<span class="n">n</span> <span class="err">继续搜索,找出</span> <span class="n">str1</span> <span class="err">字符串下次出现的位置;</span>
<span class="n">N</span> <span class="err">继续搜索,找出</span> <span class="n">str1</span> <span class="err">字符串上一次出现的位置;</span>
<span class="o">?</span><span class="n">str2</span> <span class="err">反向搜索字符串</span> <span class="n">str2</span> <span class="err">。</span>
</code></pre></div>
<p>无论搜索方向如何,当到达文件末尾或开头时,搜索工作会循环到文件的另一端并继续执行。
Vim中执行搜索匹配最强大的地方是结合 正则表达式 来搜索,后续将会介绍。</p>
<h4>2.3 替换和删除</h4>
<p>Vim常规的删除命令是 d、 x (前者删除 行 ,后者删除 字符 ),结合Vim的其他特性可以实现基础的删除功能。将光标定位于文件内指定位置后,可以用其他字符来替换光标所指向的字符,或从当前光标位置删除一个或多个字符或一行、多行。例如:</p>
<div class="highlight"><pre><code><span class="n">rc</span> <span class="err">用</span> <span class="n">c</span> <span class="err">替换光标所指向的当前字符;</span>
<span class="n">nrc</span> <span class="err">用</span> <span class="n">c</span> <span class="err">替换光标所指向的前</span> <span class="n">n</span> <span class="err">个字符;</span>
<span class="mi">5</span><span class="n">rA</span> <span class="err">用</span> <span class="n">A</span> <span class="err">替换光标所指向的前</span> <span class="mi">5</span> <span class="err">个字符;</span>
<span class="n">x</span> <span class="err">删除光标所指向的当前字符;</span>
<span class="n">nx</span> <span class="err">删除光标所指向的前</span> <span class="n">n</span> <span class="err">个字符;</span>
<span class="mi">3</span><span class="n">x</span> <span class="err">删除光标所指向的前</span> <span class="mi">3</span> <span class="err">个字符;</span>
<span class="n">dw</span> <span class="err">删除光标右侧的字;</span>
<span class="n">ndw</span> <span class="err">删除光标右侧的</span> <span class="n">n</span> <span class="err">个字;</span>
<span class="mi">3</span><span class="n">dw</span> <span class="err">删除光标右侧的</span> <span class="mi">3</span> <span class="err">个字;</span>
<span class="n">db</span> <span class="err">删除光标左侧的字;</span>
<span class="n">ndb</span> <span class="err">删除光标左侧的</span> <span class="n">n</span> <span class="err">个字;</span>
<span class="mi">5</span><span class="n">db</span> <span class="err">删除光标左侧的</span> <span class="mi">5</span> <span class="err">个字;</span>
<span class="n">dd</span> <span class="err">删除光标所在行,并去除空隙;</span>
<span class="n">ndd</span> <span class="err">删除(剪切)</span> <span class="n">n</span> <span class="err">行内容,并去除空隙;</span>
<span class="mi">3</span><span class="n">dd</span> <span class="err">删除(剪切)</span> <span class="mi">3</span> <span class="err">行内容,并去除空隙;</span>
</code></pre></div>
<p>其他常用的删除命令有:</p>
<div class="highlight"><pre><code><span class="n">d</span><span class="err">$</span> <span class="err">从当前光标起删除字符直到行的结束;</span>
<span class="n">d0</span> <span class="err">从当前光标起删除字符直到行的开始;</span>
<span class="n">J</span> <span class="err">删除本行的回车符(</span><span class="n">CR</span><span class="err">),并和下一行合并。</span>
</code></pre></div>
<p>Vim常规的替换命令有 c 和 s ,结合Vim的其他特性可以实现基础的替换功能,不过替换命令执行以后,通常会由 编辑模式 进入 插入模式 :</p>
<div class="highlight"><pre><code><span class="n">s</span> <span class="err">用输入的正文替换光标所指向的字符;</span>
<span class="n">S</span> <span class="err">删除当前行,并进入编辑模式;</span>
<span class="n">ns</span> <span class="err">用输入的正文替换光标右侧</span> <span class="n">n</span> <span class="err">个字符;</span>
<span class="n">nS</span> <span class="err">删除当前行在内的</span> <span class="n">n</span> <span class="err">行,并进入编辑模式;</span>
<span class="n">cw</span> <span class="err">用输入的正文替换光标右侧的字;</span>
<span class="n">cW</span> <span class="err">用输入的正文替换从光标到行尾的所有字符(同</span> <span class="n">c</span><span class="err">$</span> <span class="p">)</span><span class="err">;</span>
<span class="n">ncw</span> <span class="err">用输入的正文替换光标右侧的</span> <span class="n">n</span> <span class="err">个字;</span>
<span class="n">cb</span> <span class="err">用输入的正文替换光标左侧的字;</span>
<span class="n">ncb</span> <span class="err">用输入的正文替换光标左侧的</span> <span class="n">n</span> <span class="err">个字;</span>
<span class="n">cd</span> <span class="err">用输入的正文替换光标的所在行;</span>
<span class="n">ncd</span> <span class="err">用输入的正文替换光标下面的</span> <span class="n">n</span> <span class="err">行;</span>
<span class="n">c</span><span class="err">$</span> <span class="err">用输入的正文替换从光标开始到本行末尾的所有字符;</span>
<span class="n">c0</span> <span class="err">用输入的正文替换从本行开头到光标的所有字符。</span>
</code></pre></div>
<h4>2.4 复制粘贴</h4>
<p>从正文中删除的内容(如字符、字或行)并没有真正丢失,而是被剪切并复制到了一个内存缓冲区中。用户可将其粘贴到正文中的指定位置。完成这一操作的命令是:</p>
<div class="highlight"><pre><code><span class="n">p</span> <span class="err">小写字母</span> <span class="n">p</span><span class="err">,将缓冲区的内容粘贴到光标的后面;</span>
<span class="n">P</span> <span class="err">大写字母</span> <span class="n">P</span><span class="err">,将缓冲区的内容粘贴到光标的前面。</span>
</code></pre></div>
<p>如果缓冲区的内容是字符或字,直接粘贴在光标的前面或后面;如果缓冲区的内容为整行正文,执行上述粘贴命令将会粘贴在当前光标所在行的上一行或下一行。
注意上述两个命令中字母的大小写。Vim 编辑器经常以一对大、小写字母(如 p 和 P)来提供一对相似的功能。通常,小写命令在光标的后面进行操作,大写命令在光标的前面进行操作。</p>
<p>有时需要复制一段正文到新位置,同时保留原有位置的内容。这种情况下,首先应当把指定内容复制(而不是剪切)到内存缓冲区。完成这一操作的命令是:</p>
<div class="highlight"><pre><code><span class="n">yy</span> <span class="err">复制当前行到内存缓冲区;</span>
<span class="n">nyy</span> <span class="err">复制</span> <span class="n">n</span> <span class="err">行内容到内存缓冲区;</span>
<span class="mi">5</span><span class="n">yy</span> <span class="err">复制</span> <span class="mi">5</span> <span class="err">行内容到内存缓冲区;</span>
<span class="err">“</span><span class="o">+</span><span class="n">y</span> <span class="err">复制</span> <span class="mi">1</span> <span class="err">行到操作系统的粘贴板;</span>
<span class="err">“</span><span class="o">+</span><span class="n">nyy</span> <span class="err">复制</span> <span class="n">n</span> <span class="err">行到操作系统的粘贴板。</span>
</code></pre></div>
<h4>2.5 撤销和重复</h4>
<p>在编辑文档的过程中,为消除某个错误的编辑命令造成的后果,可以用撤消命令。另外,如果用户希望在新的光标位置重复前面执行过的编辑命令,可用重复命令。</p>
<div class="highlight"><pre><code><span class="n">u</span> <span class="err">撤消前一条命令的结果;</span>
<span class="p">.</span> <span class="err">重复最后一条修改正文的命令。</span>
</code></pre></div>
<h3>3. 插入模式</h3>
<h4>3.1 进入插入模式</h4>
<p>在编辑模式下正确定位光标之后,可用以下命令切换到插入模式:</p>
<div class="highlight"><pre><code><span class="n">i</span> <span class="err">在光标左侧插入正文</span>
<span class="n">a</span> <span class="err">在光标右侧插入正文</span>
<span class="n">o</span> <span class="err">在光标所在行的下一行增添新行</span>
<span class="n">O</span> <span class="err">在光标所在行的上一行增添新行</span>
<span class="n">I</span> <span class="err">在光标所在行的开头插入</span>
<span class="n">A</span> <span class="err">在光标所在行的末尾插入</span>
</code></pre></div>
<h4>3.2 退出插入模式</h4>
<p>退出插入模式的方法是,按 ESC 键或组合键 Ctrl+[ ,退出插入模式之后,将会进入编辑模式 。</p>
<h3>4. 命令模式</h3>
<p>在Vim的命令模式下,可以使用复杂的命令。在编辑模式下键入 : ,光标就跳到屏幕最后一行,并在那里显示冒号,此时已进入命令模式。命令模式又称 末行模式 ,用户输入的内容均显示在屏幕的最后一行,按回车键,Vim 执行命令。</p>
<h4>4.1 打开、保存、退出</h4>
<p>在已经启动的Vim中打开一个文件需要用 :e 命令:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">e</span> <span class="n">path_to_file</span><span class="o">/</span><span class="n">filename</span>
</code></pre></div>
<p>保存当前编辑的文件需要用 :w 命令(单词 write 的缩写):</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">w</span>
</code></pre></div>
<p>将当前文件另存为 file_temp 则:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">w</span> <span class="n">file_temp</span>
</code></pre></div>
<p>在编辑模式下可以用 ZZ 命令退出Vim编辑程序,该命令保存对正文所作的修改,覆盖原始文件。如果只需要退出编辑程序,而不打算保存编辑的内容,可用下面的命令:</p>
<div class="highlight"><pre><code><span class="o">:</span> <span class="n">q</span> <span class="err">在未作修改的情况下退出;</span>
<span class="o">:</span> <span class="n">q</span><span class="o">!</span> <span class="err">放弃所有修改,退出编辑程序。</span>
</code></pre></div>
<p>保存并退出则可以讲两条命令结合起来使用(注意命令顺序,先保存,后退出):</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">wq</span>
</code></pre></div>
<h4>4.2 行号与文件</h4>
<p>编辑中的每一行正文都有自己的行号,用下列命令可以移动光标到指定行(效果与 编辑模式 下的 ngg 或 nG 相同):</p>
<div class="highlight"><pre><code><span class="o">:</span> <span class="n">n</span> <span class="err">将光标移到第</span> <span class="n">n</span> <span class="err">行</span>
</code></pre></div>
<p>命令模式下,可以规定命令操作的行号范围。数值用来指定绝对行号;字符“.”表示光标所在行的行号;字符符“$”表示正文最后一行的行号;简单的表达式,例如“.+5”表示当前行往下的第 5 行。例如:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="mi">345</span> <span class="err">将光标移到第</span> <span class="mi">345</span> <span class="err">行</span>
<span class="o">:</span><span class="mi">345</span><span class="n">w</span> <span class="n">file</span> <span class="err">将第</span> <span class="mi">345</span> <span class="err">行写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="mi">3</span><span class="p">,</span><span class="mi">5</span><span class="n">w</span> <span class="n">file</span> <span class="err">将第</span> <span class="mi">3</span> <span class="err">行至第</span> <span class="mi">5</span> <span class="err">行写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="mi">1</span><span class="p">,.</span><span class="n">w</span> <span class="n">file</span> <span class="err">将第</span> <span class="mi">1</span> <span class="err">行至当前行写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="p">.,</span><span class="err">$</span><span class="n">w</span> <span class="n">file</span> <span class="err">将当前行至最后一行写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="p">.,.</span><span class="o">+</span><span class="mi">5</span><span class="n">w</span> <span class="n">file</span> <span class="err">从当前行开始将</span> <span class="mi">6</span> <span class="err">行内容写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="mi">1</span><span class="p">,</span><span class="err">$</span><span class="n">w</span> <span class="n">file</span> <span class="err">将所有内容写入</span> <span class="n">file</span> <span class="err">文件,相当于</span> <span class="o">:</span><span class="n">w</span> <span class="n">file</span> <span class="err">命令</span>
</code></pre></div>
<p>在命令模式下,允许从文件中读取正文,或将正文写入文件。例如:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">w</span> <span class="err">将编辑的内容写入原始文件,用来保存编辑的中间结果</span>
<span class="o">:</span><span class="n">wq</span> <span class="err">将编辑的内容写入原始文件并退出编辑程序(相当于</span> <span class="n">ZZ</span> <span class="err">命令)</span>
<span class="o">:</span><span class="n">w</span> <span class="n">file</span> <span class="err">将编辑的内容写入</span> <span class="n">file</span> <span class="err">文件,保持原有文件的内容不变</span>
<span class="o">:</span><span class="n">a</span><span class="p">,</span><span class="n">bw</span> <span class="n">file</span> <span class="err">将第</span> <span class="n">a</span> <span class="err">行至第</span> <span class="n">b</span> <span class="err">行的内容写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:</span><span class="n">r</span> <span class="n">file</span> <span class="err">读取</span> <span class="n">file</span> <span class="err">文件的内容,插入当前光标所在行的后面</span>
<span class="o">:</span><span class="n">e</span> <span class="n">file</span> <span class="err">编辑新文件</span> <span class="n">file</span> <span class="err">代替原有内容</span>
<span class="o">:</span><span class="n">f</span> <span class="n">file</span> <span class="err">将当前文件重命名为</span> <span class="nl">file</span>
<span class="p">:</span><span class="n">f</span> <span class="err">打印当前文件名称和状态,如文件的行数、光标所在的行号等</span>
</code></pre></div>
<h4>4.3 字符串搜索</h4>
<p>在 编辑模式 讲过字符串的搜索,此处的 命令模式 也可以进行字符串搜索,给出一个字符串,可以通过搜索该字符串到达指定行。如果希望进行正向搜索,将待搜索的字符串置于两个 / 之间;如果希望反向搜索,则将字符串放在两个 ? 之间。例如:</p>
<div class="highlight"><pre><code><span class="o">:/</span><span class="n">str</span><span class="o">/</span> <span class="err">正向搜索,将光标移到下一个包含字符串</span> <span class="n">str</span> <span class="err">的行</span>
<span class="o">:?</span><span class="n">str</span><span class="o">?</span> <span class="err">反向搜索,将光标移到上一个包含字符串</span> <span class="n">str</span> <span class="err">的行</span>
<span class="o">:/</span><span class="n">str</span><span class="o">/</span><span class="n">w</span> <span class="n">file</span> <span class="err">正向搜索,并将第一个包含字符串</span> <span class="n">str</span> <span class="err">的行写入</span> <span class="n">file</span> <span class="err">文件</span>
<span class="o">:/</span><span class="n">str1</span><span class="o">/</span><span class="p">,</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">w</span> <span class="n">file</span> <span class="err">正向搜索,并将包含字符串</span> <span class="n">str1</span> <span class="err">的行至包含字符串</span> <span class="n">str2</span> <span class="err">的行写</span>
</code></pre></div>
<h4>4.4 Vim中的正则表达式</h4>
<p>当给Vim指定搜索字符串时,可以包含具有特殊含义的字符。包含这些特殊字符的搜索字符串称为正则表达式(Regular Expressions)。例如,要搜索一行正文,这行正文的开头包含 struct 字。下面的命令做不到这一点:</p>
<div class="highlight"><pre><code><span class="o">:/</span><span class="k">struct</span><span class="o">/</span>
</code></pre></div>
<p>因为它只找出在行中任意位置包含 struct的第一行,并不一定在行的开始包含 struct 。解决问题的办法是在搜索字符串前面加上特殊字符^:</p>
<div class="highlight"><pre><code><span class="o">:/^</span><span class="k">struct</span><span class="o">/</span>
</code></pre></div>
<p>^ 字符比较每行开头的字符串。所以上面的命令表示:找出以字符串 struct 开头的行。
也可以用类似办法在搜索字符串后面加上表示行的末尾的特殊字符 $ 来找出位于行末尾的字:</p>
<div class="highlight"><pre><code><span class="o">:/^</span><span class="k">struct</span><span class="o">/</span>
</code></pre></div>
<p>下表给出大多数特殊字符和它们的含义:</p>
<div class="highlight"><pre><code><span class="o">^</span> <span class="err">放在字符串前面,匹配行首的字;</span>
<span class="err">$</span> <span class="err">放在字符串后面,匹配行尾的字;</span>
<span class="err">\</span><span class="o"><</span> <span class="err">匹配一个字的字头;</span>
<span class="err">\</span><span class="o">></span> <span class="err">匹配一个字的字尾;</span>
<span class="p">.</span> <span class="err">匹配任何单个正文字符;</span>
<span class="p">[</span><span class="n">str</span><span class="p">]</span> <span class="err">匹配</span> <span class="n">str</span> <span class="err">中的任何单个字符;</span>
<span class="p">[</span><span class="o">^</span><span class="n">str</span><span class="p">]</span> <span class="err">匹配任何不在</span> <span class="n">str</span> <span class="err">中的单个字符;</span>
<span class="p">[</span><span class="n">a</span><span class="o">-</span><span class="n">b</span><span class="p">]</span> <span class="err">匹配</span> <span class="n">a</span> <span class="err">到</span> <span class="n">b</span> <span class="err">之间的任一字符;</span>
<span class="o">*</span> <span class="err">匹配前一个字符的</span> <span class="mi">0</span> <span class="err">次或多次出现;</span>
<span class="err">\</span> <span class="err">转义后面的字符。</span>
</code></pre></div>
<p>简单介绍这么多,正则表达式知识可以参考</p>
<p>《正则表达式30分钟入门》:<a href="http://deerchao.net/tutorials/regex/regex.htm" target="blank">http://deerchao.net/tutorials/regex/regex.htm</a></p>
<p>另外,进阶的Vim正则表达式还有对Magic 模式的介绍,可以参考</p>
<p>《Vim正则表达式详解》:<a href="http://blog.csdn.net/salc3k/article/details/8222397" target="blank">http://blog.csdn.net/salc3k/article/details/8222397</a></p>
<h4>4.5 正文替换</h4>
<p>利用 :s 命令可以实现字符串的替换。具体的用法包括:</p>
<div class="highlight"><pre><code><span class="o">:%</span><span class="n">s</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span> <span class="err">用字符串</span> <span class="n">str2</span> <span class="err">替换行中首次出现的字符串</span> <span class="nl">str1</span>
<span class="p">:</span><span class="n">s</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">g</span> <span class="err">用字符串</span> <span class="n">str2</span> <span class="err">替换行中所有出现的字符串</span> <span class="nl">str1</span>
<span class="p">:.,</span><span class="err">$</span> <span class="n">s</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">g</span> <span class="err">用字符串</span> <span class="n">str2</span> <span class="err">替换正文当前行到末尾所有出现的字符串</span> <span class="nl">str1</span>
<span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="err">$</span> <span class="n">s</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">g</span> <span class="err">用字符串</span> <span class="n">str2</span> <span class="err">替换正文中所有出现的字符串</span> <span class="nl">str1</span>
<span class="p">:</span><span class="n">g</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">s</span><span class="c1">//str2/g 功能同上</span>
<span class="o">:</span><span class="n">m</span><span class="p">,</span><span class="n">ns</span><span class="o">/</span><span class="n">str1</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">g</span> <span class="err">将从</span><span class="n">m</span><span class="err">行到</span><span class="n">n</span><span class="err">行的</span><span class="n">str1</span><span class="err">替换成</span><span class="n">str2</span>
</code></pre></div>
<p>从上述替换命令可以看到:</p>
<ol>
<li>g 放在命令末尾,表示对搜索字符串的每次出现进行替换,不止匹配每行中的第一次出现;不加 g,表示只对搜索字符串的首次出现进行替换;g 放在命令开头,表示对正文中所有包含搜索字符串的行进行替换操作;</li>
<li>s 表示后面跟着一串替换的命令;</li>
<li>% 表示替换范围是所有行,即全文。</li>
</ol>
<p>另外一个实用的命令,在Vim中统计当前文件中字符串 str1 出现的次数,可用替换命令的变形:</p>
<div class="highlight"><pre><code><span class="o">:%</span><span class="n">s</span><span class="o">/</span><span class="n">str1</span><span class="o">/&/</span><span class="n">gn</span>
</code></pre></div>
<h4>4.6 删除正文</h4>
<p>在命令模式下,同样可以删除正文中的内容。例如:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">d</span> <span class="err">删除光标所在行</span>
<span class="o">:</span><span class="mi">3</span><span class="n">d</span> <span class="err">删除</span> <span class="mi">3</span> <span class="err">行</span>
<span class="o">:</span><span class="p">.,</span><span class="err">$</span><span class="n">d</span> <span class="err">删除当前行至正文的末尾</span>
<span class="o">:/</span><span class="n">str1</span><span class="o">/</span><span class="p">,</span><span class="o">/</span><span class="n">str2</span><span class="o">/</span><span class="n">d</span> <span class="err">删除从字符串</span> <span class="n">str1</span> <span class="err">到</span> <span class="n">str2</span> <span class="err">的所有行</span>
<span class="o">:</span><span class="n">g</span><span class="o">/^</span><span class="err">\</span><span class="p">(.</span><span class="o">*</span><span class="err">\</span><span class="p">)</span><span class="err">$\</span><span class="n">n</span><span class="err">\</span><span class="mi">1</span><span class="err">$</span><span class="o">/</span><span class="n">d</span> <span class="err">删除连续相同的行,保留最后一行</span>
<span class="o">:</span><span class="n">g</span><span class="o">/</span><span class="err">\</span><span class="o">%</span><span class="p">(</span><span class="o">^</span><span class="err">\</span><span class="mi">1</span><span class="err">$\</span><span class="n">n</span><span class="err">\</span><span class="p">)</span><span class="err">\@</span><span class="o"><=</span><span class="err">\</span><span class="p">(.</span><span class="o">*</span><span class="err">\</span><span class="p">)</span><span class="err">$</span><span class="o">/</span><span class="n">d</span> <span class="err">删除连续相同的行,保留最开始一行</span>
<span class="o">:</span><span class="n">g</span><span class="o">/^</span><span class="err">\</span><span class="n">s</span><span class="o">*</span><span class="err">$\</span><span class="n">n</span><span class="err">\</span><span class="n">s</span><span class="o">*</span><span class="err">$</span><span class="o">/</span><span class="n">d</span> <span class="err">删除连续多个空行,只保留一行空行</span>
<span class="o">:</span><span class="mi">5</span><span class="p">,</span><span class="mi">20</span><span class="n">s</span><span class="o">/^</span><span class="err">#</span><span class="c1">//g 删除5到20行开头的 # 注释</span>
</code></pre></div>
<p>总之,Vim的初级删除命令是用 d ,高级删除命令可以用 正则替换 的方式执行。</p>
<h4>4.7 恢复文件</h4>
<p>Vim 在编辑某个文件时,会另外生成一个临时文件,这个文件的名称通常以 . 开头,并以 .swp 结尾。Vim 在正常退出时,该文件被删除,若意外退出,而没有保存文件的最新修改内容,则可以使用恢复命令 :recover 来恢复文件,也可以在启动Vim时用 -r 选项。</p>
<h4>4.8 选项设置</h4>
<p>为控制不同的编辑功能,Vim 提供了很多内部选项。利用 :set 命令可以设置选项。基本语法为:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">set</span> <span class="n">option</span> <span class="err">设置选项</span> <span class="n">option</span>
</code></pre></div>
<p>常见的功能选项包括:</p>
<div class="highlight"><pre><code><span class="n">autoindent</span> <span class="err">设置该选项,则正文自动缩进</span>
<span class="n">ignorecase</span> <span class="err">设置该选项,则忽略规则表达式中大小写字母的区别</span>
<span class="n">number</span> <span class="err">设置该选项,则显示正文行号</span>
<span class="n">ruler</span> <span class="err">设置该选项,则在屏幕底部显示光标所在行、列的位置</span>
<span class="n">tabstop</span> <span class="err">设置按</span> <span class="n">Tab</span> <span class="err">键跳过的空格数。例如</span> <span class="o">:</span><span class="n">set</span> <span class="n">tabstop</span><span class="o">=</span><span class="n">n</span><span class="err">,</span><span class="n">n</span> <span class="err">默认值为</span> <span class="mi">8</span>
<span class="n">mk</span> <span class="err">将选项保存在当前目录的</span> <span class="p">.</span><span class="n">exrc</span> <span class="err">文件中</span>
</code></pre></div>
<h4>4.9 Shell切换</h4>
<p>当处于编辑的对话过程中时,可能需要执行一些Linux命令。如果需要保存当前的结果,退出编辑程序,再执行所需的Linux命令,然后再回头继续编辑过程,就显得十分累赘。如果能在编辑的环境中运行Linux命令就要省事得多。在Vim中,可以用下面的命令来做到这一点:</p>
<div class="highlight"><pre><code><span class="o">:!</span><span class="n">shell_command</span> <span class="err">执行完</span> <span class="n">shell_command</span> <span class="err">后回到</span><span class="n">Vim</span>
</code></pre></div>
<p>这称为Shell切换。它允许执行任何可以在标准的Shell提示符下执行的命令。当这条命令执行完毕,控制返回给编辑程序。又可以继续编辑对话过程。</p>
<h4>4.10 分屏与标签页</h4>
<p>分屏
普通的Vim模式,打开一个Vim程序只能查看一个文件,如果想同时查看多个文件,就需要用到Vim分屏与标签页功能。
Vim的分屏,主要有两种方式:上下分屏(水平分屏)和左右分屏(垂直分屏),在命令模式分别敲入以下命令即可:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">split</span><span class="err">(可用缩写</span> <span class="o">:</span><span class="n">sp</span><span class="err">)</span> <span class="err">上下分屏;</span>
<span class="o">:</span><span class="n">vsplit</span><span class="err">(可用缩写</span> <span class="o">:</span><span class="n">vsp</span><span class="err">)</span> <span class="err">左右分屏。</span>
</code></pre></div>
<p>另外,也可以在终端里启动vim时就开启分屏操作:</p>
<div class="highlight"><pre><code><span class="n">vim</span> <span class="o">-</span><span class="n">On</span> <span class="n">file1</span> <span class="n">file2</span><span class="p">...</span> <span class="err">打开</span> <span class="n">file1</span> <span class="err">和</span> <span class="n">file2</span> <span class="err">,垂直分屏</span>
<span class="n">vim</span> <span class="o">-</span><span class="n">on</span> <span class="n">file1</span> <span class="n">file2</span><span class="p">...</span> <span class="err">打开</span> <span class="n">file1</span> <span class="err">和</span> <span class="n">file2</span> <span class="err">,水平分屏</span>
</code></pre></div>
<p>理论上,一个Vim窗口,可以分为多个Vim屏幕,切换屏幕需要用键盘快捷键,命令分别有:</p>
<div class="highlight"><pre><code><span class="n">Ctrl</span><span class="o">+</span><span class="n">w</span><span class="o">+</span><span class="n">h</span> <span class="err">切换到当前分屏的左边一屏;</span>
<span class="n">Ctrl</span><span class="o">+</span><span class="n">w</span><span class="o">+</span><span class="n">l</span> <span class="err">切换到当前分屏的右边一屏;</span>
<span class="n">Ctrl</span><span class="o">+</span><span class="n">w</span><span class="o">+</span><span class="n">j</span> <span class="err">切换到当前分屏的下方一屏;</span>
<span class="n">Ctrl</span><span class="o">+</span><span class="n">w</span><span class="o">+</span><span class="n">k</span> <span class="err">切换到当前分屏的上方一屏。</span>
</code></pre></div>
<p>即键盘上的h,j,k,l 四个Vim专用方向键,配合Ctrl键和w键(window的缩写),就能跳转到目标分屏。另外,也可以直接按 Ctrl+w+w 来跳转分屏,不过跳转方向则是在当前Vim窗口所有分屏中,按照逆时针方向跳转。
下面是改变尺寸的一些操作,主要是高度,对于宽度你可以使用 [Ctrl+W <] 或是 [Ctrl+W >] ,但这可能需要最新的版本才支持。</p>
<div class="highlight"><pre><code><span class="n">Ctr</span>
<span class="n">l</span><span class="o">+</span><span class="n">W</span> <span class="o">=</span> <span class="err">让所有的屏都有一样的高度;</span>
<span class="n">Ctrl</span><span class="o">+</span><span class="n">W</span> <span class="o">+</span> <span class="err">增加高度;</span>
<span class="n">Ctrl</span><span class="o">+</span><span class="n">W</span> <span class="o">-</span> <span class="err">减少高度。</span>
</code></pre></div>
<p>标签页
Vim的标签(Tab)页,类似浏览器的标签页,一个标签页打开一个Vim的窗口,一个Vim的窗口可以支持N个分屏。
在Vim中新建一个标签的命令是:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">tabnew</span>
</code></pre></div>
<p>如果要在新建标签页的同时打开一个文件,则可以在命令后面直接附带文件路径:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">tabnew</span> <span class="n">filename</span>
</code></pre></div>
<p>Vim中的每个标签页有一个唯一的数字序号,第一个标签页的序号是0,从左向右依次加一。关于标签页有一系列操作命令,简介如下:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">tN</span><span class="p">[</span><span class="n">ext</span><span class="p">]</span> <span class="err">跳转到上一个匹配的标签</span>
<span class="o">:</span><span class="n">tabN</span><span class="p">[</span><span class="n">ext</span><span class="p">]</span> <span class="err">跳到上一个标签页</span>
<span class="o">:</span><span class="n">tabc</span><span class="p">[</span><span class="n">lose</span><span class="p">]</span> <span class="err">关闭当前标签页</span>
<span class="o">:</span><span class="n">tabdo</span> <span class="err">为每个标签页执行命令</span>
<span class="o">:</span><span class="n">tabe</span><span class="p">[</span><span class="n">dit</span><span class="p">]</span> <span class="err">在新标签页里编辑文件</span>
<span class="o">:</span><span class="n">tabf</span><span class="p">[</span><span class="n">ind</span><span class="p">]</span> <span class="err">寻找</span> <span class="err">'</span><span class="n">path</span><span class="err">'</span> <span class="err">里的文件,在新标签页里编辑之</span>
<span class="o">:</span><span class="n">tabfir</span><span class="p">[</span><span class="n">st</span><span class="p">]</span> <span class="err">转到第一个标签页</span>
<span class="o">:</span><span class="n">tabl</span><span class="p">[</span><span class="n">ast</span><span class="p">]</span> <span class="err">转到最后一个标签页</span>
<span class="o">:</span><span class="n">tabm</span><span class="p">[</span><span class="n">ove</span><span class="p">]</span> <span class="n">N</span> <span class="err">把标签页移到序号为</span><span class="n">N</span><span class="err">位置</span>
<span class="o">:</span><span class="n">tabnew</span> <span class="p">[</span><span class="n">filename</span><span class="p">]</span> <span class="err">在新标签页里编辑文件</span>
<span class="o">:</span><span class="n">tabn</span><span class="p">[</span><span class="n">ext</span><span class="p">]</span> <span class="err">转到下一个标签页</span>
<span class="o">:</span><span class="n">tabo</span><span class="p">[</span><span class="n">nly</span><span class="p">]</span> <span class="err">关闭所有除了当前标签页以外的所有标签页</span>
<span class="o">:</span><span class="n">tabp</span><span class="p">[</span><span class="n">revious</span><span class="p">]</span> <span class="err">转到前一个标签页</span>
<span class="o">:</span><span class="n">tabr</span><span class="p">[</span><span class="n">ewind</span><span class="p">]</span> <span class="err">转到第一个标签页</span>
</code></pre></div>
<h4>4.11 与外部工具集成</h4>
<p>Vim可以与许多外部程序集成,功能十分强大,比如 diff , ctags , sort , xxd 等等,下面选取几个简单介绍一下。</p>
<h5>diff</h5>
<p>Linux命令 diff 用来对比两个文件的内容,不过对比结果显示在终端里,可读性比较差。结合Vim,在终端里可以直接输入命令 vimdiff,后面跟两个文件名作为参数:</p>
<div class="highlight"><pre><code><span class="n">vimdiff</span> <span class="n">file1</span> <span class="n">file2</span>
</code></pre></div>
<p>即可在Vim里分屏显示两个文件内容的对比结果,对文件内容差异部分进行高亮标记,还可以同步滚动两个文件内容,更可以实时修改文件内容,方便程度和用户体验大大提高。</p>
<div class="highlight"><pre><code><span class="n">vimdiff</span> <span class="n">a</span><span class="p">.</span><span class="n">txt</span> <span class="n">b</span><span class="p">.</span><span class="n">txt</span>
</code></pre></div>
<p>如果直接给 -d 选项是一样的</p>
<div class="highlight"><pre><code><span class="n">vim</span> <span class="o">-</span><span class="n">d</span> <span class="n">a</span><span class="p">.</span><span class="n">txt</span> <span class="n">b</span><span class="p">.</span><span class="n">txt</span>
</code></pre></div>
<p>除了在终端里开启vimdiff 功能,也可以在打开Vim后,在Vim的命令模式输入相关命令来开启 vimdiff 功能:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">diffsplit</span> <span class="n">abc</span><span class="p">.</span><span class="n">txt</span>
</code></pre></div>
<p>如果你现在已经开启了一个文件,想Vim帮你区分你的文件跟 abc.txt 有什么区别,可以在Vim中用 diffsplit 的方式打开第二个文件,这个时 候Vim会用 split(分上下两屏)的方式开启第二个文件,并且通过颜色,fold来显示两个文件的区别
这样Vim就会用颜色帮你区分开2个文件的区别。如果文件比较大(源码)重复的部分会帮你折叠起来。</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">diffpatch</span> <span class="n">filename</span>
</code></pre></div>
<p>通过 :diffpatch 你的patch的文件名,就可以以当前文件加上你的patch来显示。vim会split一个新的屏,显示patch后的信息并且用颜色标明区别。
如果不喜欢上下对比,喜欢左右(比较符合视觉)可以在前面加 vert ,例如:</p>
<div class="highlight"><pre><code><span class="o">:</span><span class="n">vert</span> <span class="n">diffsplit</span> <span class="n">abc</span><span class="p">.</span><span class="nl">txt</span>
<span class="p">:</span><span class="n">vert</span> <span class="n">diffpatch</span> <span class="n">abc</span><span class="p">.</span><span class="n">txt</span>
</code></pre></div>
<p>看完diff,用 :only 回到原本编辑的文件,觉得diff的讨厌颜色还是在哪里,只要用 :diffoff 关闭就好了。
还有个常用的diff中的就是 :diffu ,这个是 :diffupdate 的简写,更新的时候用。
Vim的diff功能显示效果如下所示:</p>
<p><img src="http://blog.mc-zone.me/wp-content/uploads/2014/05/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7-2014-05-28-%E4%B8%8B%E5%8D%889.26.59.png" alt=""></p>
<h5>sort</h5>
<p>Linux命令 sort 可以对文本内容进行按行中的字符比较、排序,但在终端里使用 sort 命令处理文件,并不能实时查看文件内容。具体用法请自查手册。</p>
<h5>xxd</h5>
<p>vim+xxd 是Linux下最常用的二进制文本编辑工具,xxd其实是Vim外部的一个转换程序,随Vim一起发布,在Vim里调用它来编辑二进制文本非常方便。
首先以二进制模式在终端里打开一个文件:</p>
<div class="highlight"><pre><code><span class="n">vim</span> <span class="o">-</span><span class="n">b</span> <span class="n">filename</span>
</code></pre></div>
<p>Vim 的 -b 选项是告诉 Vim 打开的是一个二进制文件,不指定的话,会在后面加上 0x0a ,即一个换行符。
然后在Vim的命令模式下键入:</p>
<div class="highlight"><pre><code><span class="o">:%!</span><span class="n">xxd</span>
</code></pre></div>
<p>即可看到二进制模式显示出来的文本,看起来像这样:</p>
<div class="highlight"><pre><code><span class="mo">0000000</span><span class="o">:</span> <span class="mf">1f</span><span class="mi">8</span><span class="n">b</span> <span class="mi">0808</span> <span class="mi">39</span><span class="n">d7</span> <span class="mi">173</span><span class="n">b</span> <span class="mo">0203</span> <span class="mi">7474</span> <span class="mo">002</span><span class="n">b</span> <span class="mf">4e49</span> <span class="p">...</span><span class="mf">.9</span><span class="p">..;..</span><span class="n">tt</span><span class="p">.</span><span class="o">+</span><span class="n">NI</span>
<span class="mo">0000010</span><span class="o">:</span> <span class="mi">4</span><span class="n">b2c</span> <span class="mi">8660</span> <span class="n">eb9c</span> <span class="n">ecac</span> <span class="n">c462</span> <span class="n">eb94</span> <span class="mi">345</span><span class="n">e</span> <span class="mf">2e30</span> <span class="n">K</span><span class="p">,......</span><span class="n">b</span><span class="p">.</span><span class="mf">.4</span><span class="o">^</span><span class="mf">.0</span>
<span class="mo">0000020</span><span class="o">:</span> <span class="mi">373</span><span class="n">b</span> <span class="mi">2731</span> <span class="mi">0</span><span class="n">b22</span> <span class="mi">0</span><span class="n">ca6</span> <span class="n">c1a2</span> <span class="n">d669</span> <span class="mi">1035</span> <span class="mi">39</span><span class="n">d9</span> <span class="mi">7</span><span class="p">;</span><span class="err">'</span><span class="mf">1.</span><span class="s">".....i.59</span>
</code></pre></div>
<p>然后就可以在二进制模式下编辑该文件,编辑后保存,然后用下面命令从二进制模式转换到普通模式:</p>
<div class="highlight"><pre><code><span class="o">:%!</span><span class="n">xxd</span> <span class="o">-</span><span class="n">r</span>
</code></pre></div>
<p>另外,也可以调整二进制的显示模式,默认是 2 个字节为一组,可以通过 g 参数调整每组字节数:</p>
<div class="highlight"><pre><code><span class="o">:%!</span><span class="n">xxd</span> <span class="o">-</span><span class="n">g</span> <span class="mi">1</span> <span class="err">表示每</span><span class="mi">1</span><span class="err">个字节为</span><span class="mi">1</span><span class="err">组</span>
<span class="o">:%!</span><span class="n">xxd</span> <span class="o">-</span><span class="n">g</span> <span class="mi">2</span> <span class="err">表示每</span><span class="mi">2</span><span class="err">个字节为</span><span class="mi">1</span><span class="err">组</span><span class="p">(</span><span class="err">默认</span><span class="p">)</span>
<span class="o">:%!</span><span class="n">xxd</span> <span class="o">-</span><span class="n">g</span> <span class="mi">4</span> <span class="err">表示每</span><span class="mi">4</span><span class="err">个字节为</span><span class="mi">1</span><span class="err">组</span>
</code></pre></div>
<h3>5. Vim配置</h3>
<p>最初安装的Vim功能、特性支持比较少,用起来比较费劲,想要稍微“好用”一点,需做一些初步的配置。Vim的配置主要分为Vim本身特性的配置和外部插件的配置两部分。
Vim的配置是通常是存放在用户主目录的 .vimrc 的隐藏文件中的。就Vim本身特性来说,基础的配置有编程语言语法高亮、缩进设置、行号显示、搜索高亮、TAB键设置、字体设置、Vim主题设置等等,稍微高级一些的有编程语言缩进、自动补全设置等,具体配置项可以自行查资料,全面详细的配置项介绍可以参考:</p>
<p>《Vim Options》:<a href="http://vimcdoc.sourceforge.net/doc/options.html#%27completeopt%27" target="blank">http://vimcdoc.sourceforge.net/doc/options.html#%27completeopt%27</a></p>
<h4>6. Vim插件</h4>
<p>Vim“编辑器之神”的称号并不是浪得虚名,然而,这个荣誉的背后,或许近半的功劳要归功于强大的插件支持特性,以及社区开发的各种各样功能强大的插件。</p>
<p>平时开发人员常用插件主要是目录(文件)查看和管理、编程语言缩进与自动补全、编程语言Docs支持、函数跳转、项目管理等等,简单配置可以参考下面:</p>
<p>《Vim插件简单介绍》:<a href="http://blog.segmentfault.com/xuelang/1190000000630547" target="blank">http://blog.segmentfault.com/xuelang/1190000000630547</a></p>
<p>《手把手教你把Vim改装成一个IDE编程环境(图文)》:<a href="http://blog.csdn.net/wooin/article/details/1858917" target="blank">http://blog.csdn.net/wooin/article/details/1858917</a></p>
<p>《将Vim改造为强大的IDE》:<a href="http://www.cnblogs.com/zhangsf/archive/2013/06/13/3134409.html" target="blank">http://www.cnblogs.com/zhangsf/archive/2013/06/13/3134409.html</a></p>
<p>当然,这些插件都是拜Vim本身的插件支持特性所赐。Vim为了支持丰富的第三方插件,自身定义了一套简单的脚本开发语言,供程序员自行开发自己所需要的插件,插件开发介绍可以参考:</p>
<p>《Writing Vim Plugins》:<a href="http://stevelosh.com/blog/2011/09/writing-vim-plugins/" target="blank">http://stevelosh.com/blog/2011/09/writing-vim-plugins/</a></p>
<h3>7. Vim完整文档</h3>
<p>Vim官方文档:<a href="http://vimdoc.sourceforge.net/" target="blank">http://vimdoc.sourceforge.net/</a></p>
<p>Vim中文用户手册7_3.pdf :<a href="http://pan.baidu.com/s/1jGzbTBo" target="blank">http://pan.baidu.com/s/1jGzbTBo</a></p>
<h3>其它</h3>
<p>有哪些编程必备的 Vim 配置? <a href="http://www.zhihu.com/question/19989337" target="blank">http://www.zhihu.com/question/19989337</a></p>
<p>vim(gvim)相关插件整理 <a href="http://www.vimer.cn/2010/06/%E6%9C%AC%E5%8D%9A%E4%BD%BF%E7%94%A8%E7%9A%84vimgvim%E7%9B%B8%E5%85%B3%E6%8F%92%E4%BB%B6%E6%95%B4%E7%90%86.html" target="blank">相关插件整理</a></p>
Vim入门基础
Objective-C类成员变量深度剖析
https://www.zruibin.cn/article/objective-c_lei_cheng_yuan_bian_liang_shen_du_pou_xi.html
2015-10-21 17:02
2015-10-21 17:12
<p>原文出处:<a href="http://quotation.github.io/objc/2015/05/21/objc-runtime-ivar-access.html" target="blank">Objective-C类成员变量深度剖析</a></p>
<h4>目录</h4>
<!-- MarkdownTOC -->
<ul>
<li>Non Fragile ivars</li>
<li>为什么Non Fragile ivars很关键</li>
<li>如何寻址类成员变量</li>
<li>真正的“如何寻址类成员变量”</li>
<li>Non Fragile ivars布局调整</li>
<li>为什么Objective-C类不能动态添加成员变量</li>
<li>总结</li>
</ul>
<!-- /MarkdownTOC -->
<p>看下面的代码,考虑Objective-C里最常见的操作之一——类成员变量访问。</p>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">doSomething</span><span class="p">:(</span><span class="n">SomeClass</span> <span class="o">*</span><span class="p">)</span><span class="n">obj</span>
<span class="p">{</span>
<span class="n">obj</span><span class="o">-></span><span class="n">ivar1</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span> <span class="c1">// 访问obj对象的public成员变量</span>
<span class="kt">int</span> <span class="n">n</span> <span class="o">=</span> <span class="n">self</span><span class="o">-></span><span class="n">ivar2</span><span class="p">;</span> <span class="c1">// 访问当前类实例的成员变量</span>
<span class="n">ivar2</span> <span class="o">=</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// 访问当前类的成员变量</span>
<span class="p">}</span>
</code></pre></div>
<p>可能大多数人都没有意识到的是:</p>
<ul>
<li>Objective-C的 <code>-></code> 操作符不是C语言指针操作!</li>
<li>Objective-C对象不能简单对应于一个C struct,访问成员变量不等于访问C struct成员!</li>
</ul>
<p>我一直到昨天中午之前也不知道这些。当明白真相后,发现还没有文章真正讲清楚过Objective-C的类成员变量(ivar,instance variables,类实例变量),于是有必要做个深度剖析。</p>
<h2>Non Fragile ivars</h2>
<p>我们常说Objective-C是“C语言的超集”,直觉上认为C语言的语法和特性在Objective-C里都有,Objective-C只是在C的基础上增加了面向对象、动态特性、block等等。我也一直不假思索地以为,Objective-C的成员变量跟C++相同。在C++中,成员变量的访问会被编译器转成一条指令,用“对象地址”加“成员变量偏移值”即可访问到成员变量的值。</p>
<p>昨天一个朋友问我runtime的问题,我看着“non-fragile instance variables”的概念,突然意识到,这不能用C++的对象内存模型来解决。</p>
<blockquote><p>The most notable new feature is that instance variables in the modern runtime are “non-fragile”:</p>
<p>In the legacy runtime, if you change the layout of instance variables in a class, you must recompile classes that inherit from it.
In the modern runtime, if you change the layout of instance variables in a class, you do not have to recompile classes that inherit from it.</p>
</blockquote>
<p>这是苹果官方文档<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html#//apple_ref/doc/uid/TP40008048-CH106-SW1">Objective-C Runtime Programming Guide</a>上的一段话,意思是在“modern runtime”里,如果你修改了基类的成员变量布局(比如增加成员变量),子类不需要重新编译。这是一个巨大的改动,在文档中当做“modern runtime”最重要的修改点被提出来。</p>
<p>Cocoa Samurai的文章<a href="http://www.sealiesoftware.com/blog/archive/2009/01/27/objc_explain_Non-fragile_ivars.html">Understanding the Objective-C Runtime</a>用几张图清晰地解释了Non Fragile ivars。以下借助他的图举例说明。</p>
<p>1) 用旧版OSX SDK编译的MyObject类成员变量布局是这样的,MyObject的成员变量依次排列在基类NSObject的成员后面。</p>
<p><img src="http://quotation.github.io/images/20150521/nf1.png" alt="旧版本SDK的成员变量布局"></p>
<p>2) 当苹果发布新版本OSX SDK后,NSObject增加了两个成员变量。如果没有Non Fragile ivars特性,我们的代码将无法正常运行,因为MyObject类成员变量布局在编译时已经确定,有两个成员变量和基类的内存区域重叠了。此时,我们只能重新编译MyObject代码,程序才能在新版本系统上运行。如果更悲催一点,MyObject类是来自第三方提供的静态库,我们就只能眼巴巴等着库作者更新版本了。</p>
<p><img src="http://quotation.github.io/images/20150521/nf2.png" alt="新版本SDK的成员变量布局"></p>
<p>3) Non Fragile ivars特性出场了。在程序启动后,runtime加载MyObject类的时候,通过计算基类的大小,runtime动态调整了MyObject类成员变量布局,把MyObject成员变量的位置向后移动8个字节。于是我们的程序无需编译,就能在新版本系统上运行。</p>
<p><img src="http://quotation.github.io/images/20150521/nf3.png" alt="Runtime调整后的布局"></p>
<h2>为什么Non Fragile ivars很关键</h2>
<p>这个特性的重大意义在于,Objective-C的库从此具有了<strong>“二进制兼容性”</strong>。举例来说,你在项目里用了第三方提供的静态库SDK,包含一些<code>.h</code>和一个<code>.a</code>文件。当iOS SDK的版本从6升到了7,又从7升到了8时,你都不需要更新这个SDK。虽然iOS SDK版本升级时,苹果在UIView等基类中加入了更多的成员变量,但是以前发布的静态库SDK不需要重新编译还能正常使用。</p>
<p>幸好我们已经不在那个黑暗时代了,iOS从一开始就是用的modern runtime。可以想象以前的Mac开发者是如何忍受这个问题的:每次MacOS发布新版本,都要重新编译自己的程序,跟着发布新版本。</p>
<p>Non Fragile ivars的基本原理就是这样。听起来并没多么先进,很多编程语言都能做到,比如Java、C#,都有二进制兼容性。可是Objective-C毕竟不是“那么”动态的语言,Objective-C代码编译后是真正的native二进制,不是byte code。Objective-C程序也不是运行在VM上,底下只有个很小的runtime。这两点,Java、C#做不到。</p>
<p>那Non Fragile ivars是如何实现的呢?最关键的点是,<strong>当成员变量布局调整后,静态编译的native程序怎么能找到变量的新偏移位置呢</strong>?</p>
<h2>如何寻址类成员变量</h2>
<p>我们借助两个工具来探索答案:Objective-C runtime源码和LLVM。</p>
<p>首先去<a href="http://opensource.apple.com/">http://opensource.apple.com/</a>下载runtime源码,在“OSX”分类里,当前最新版本是objc4-646.tar.gz。解压后打开Xcode工程,查找<code>struct objc_object</code>定义。</p>
<p>我们已经知道,每个Objective-C对象对应于<code>struct objc_object</code>,后者的<code>isa</code>指向类定义,即<code>struct objc_class</code>。</p>
<div class="highlight"><pre><code><span class="k">struct</span> <span class="n">objc_object</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="kt">isa_t</span> <span class="n">isa</span><span class="p">;</span>
<span class="c1">//...</span>
<span class="p">};</span>
<span class="k">struct</span> <span class="nl">objc_class</span> <span class="p">:</span> <span class="n">objc_object</span> <span class="p">{</span>
<span class="c1">// Class ISA;</span>
<span class="n">Class</span> <span class="n">superclass</span><span class="p">;</span>
<span class="kt">cache_t</span> <span class="n">cache</span><span class="p">;</span> <span class="c1">// formerly cache pointer and vtable</span>
<span class="kt">class_data_bits_t</span> <span class="n">bits</span><span class="p">;</span> <span class="c1">// class_rw_t * plus custom rr/alloc flags</span>
<span class="kt">class_rw_t</span> <span class="o">*</span><span class="nf">data</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">bits</span><span class="p">.</span><span class="n">data</span><span class="p">();</span>
<span class="p">}</span>
<span class="c1">//...</span>
<span class="p">};</span>
</code></pre></div>
<p>沿着<code>objc_class</code>的<code>data()->ro->ivars</code>找下去,<code>struct ivar_list_t</code>是类所有成员变量的定义列表。</p>
<div class="highlight"><pre><code><span class="k">struct</span> <span class="kt">ivar_list_t</span> <span class="p">{</span>
<span class="kt">uint32_t</span> <span class="n">entsize</span><span class="p">;</span>
<span class="kt">uint32_t</span> <span class="n">count</span><span class="p">;</span>
<span class="kt">ivar_t</span> <span class="n">first</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div>
<p>通过<code>first</code>字段,可以取得类里任意一个类成员变量的定义。</p>
<div class="highlight"><pre><code><span class="k">struct</span> <span class="kt">ivar_t</span> <span class="p">{</span>
<span class="kt">int32_t</span> <span class="o">*</span><span class="n">offset</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>
<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">type</span><span class="p">;</span>
<span class="c1">//...</span>
<span class="p">};</span>
</code></pre></div>
<p>我们看到了敏感词<code>offset</code>,这里一定是记录着这个成员变量在对象中的偏移位置喽。也就是说,runtime在发现基类大小变化时,通过修改<code>offset</code>,来更新子类成员变量的偏移值。那Objective-C中获取对象的第N个成员变量偏移位置就需要这样一长串代码:</p>
<div class="highlight"><pre><code><span class="o">*</span><span class="p">((</span><span class="o">&</span><span class="n">obj</span><span class="o">-></span><span class="n">isa</span><span class="p">.</span><span class="n">cls</span><span class="o">-></span><span class="n">data</span><span class="p">()</span><span class="o">-></span><span class="n">ro</span><span class="o">-></span><span class="n">ivars</span><span class="o">-></span><span class="n">first</span><span class="p">)[</span><span class="n">N</span><span class="p">]</span><span class="o">-></span><span class="n">offset</span><span class="p">)</span>
</code></pre></div>
<p>这么多次寻址,看起来很可怕吧。每个成员变量都这样访问的话,性能一定无法接受。看看编译器到底是如何实现的吧,我们祭出LLVM。</p>
<h2>真正的“如何寻址类成员变量”</h2>
<p>LLVM在编译时,首先生成一种中间语言(IR,intermediate representation);后续的一些优化、分析步骤都在IR上进行;最后再把IR转化成native可执行文件。由于IR比汇编可读性要好,我们利用IR来分析编译后的Objective-C程序是怎么执行的。</p>
<p>创建测试代码<code>test.m</code>。</p>
<div class="highlight"><pre><code><span class="cp">#import <Foundation/Foundation.h></span>
<span class="c1">// 特意选个大一点的基类,方便看</span>
<span class="err">@</span><span class="n">interface</span> <span class="nl">MyClass</span> <span class="p">:</span> <span class="n">NSError</span> <span class="p">{</span>
<span class="err">@</span><span class="n">public</span>
<span class="kt">int</span> <span class="n">myInt</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">@</span><span class="n">end</span>
<span class="err">@</span><span class="n">implementation</span> <span class="n">MyClass</span>
<span class="err">@</span><span class="n">end</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">MyClass</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MyClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">obj</span><span class="o">-></span><span class="n">myInt</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>在命令行执行</p>
<div class="highlight"><pre><code><span class="n">clang</span> <span class="o">-</span><span class="n">cc1</span> <span class="o">-</span><span class="n">S</span> <span class="o">-</span><span class="n">emit</span><span class="o">-</span><span class="n">llvm</span> <span class="o">-</span><span class="n">fblocks</span> <span class="n">test</span><span class="p">.</span><span class="n">m</span>
</code></pre></div>
<p>编译结果<code>test.ll</code>就是LLVM IR代码。推荐用Sublime Text安装LLVM插件,有语法高亮。可以看到IR格式比较繁琐,比汇编简单,比C复杂。这里就不写出IL的分析过程了,直接说结论。</p>
<p>编译后的<code>obj->myInt = 42</code>调用对应于如下的简单C语言代码。</p>
<div class="highlight"><pre><code><span class="kt">int32_t</span> <span class="n">g_ivar_MyClass_myInt</span> <span class="o">=</span> <span class="mi">40</span><span class="p">;</span> <span class="c1">// 全局变量</span>
<span class="o">*</span><span class="p">(</span><span class="kt">int32_t</span> <span class="o">*</span><span class="p">)((</span><span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span><span class="n">obj</span> <span class="o">+</span> <span class="n">g_ivar_MyClass_myInt</span><span class="p">)</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
</code></pre></div>
<p>两条CPU指令搞定。第一条取<code>g_ivar_MyClass_myInt</code>的值,第二条寻址并赋值。根本不需要一长串的指针调用。LLVM为<strong>每个类</strong>的<strong>每个成员变量</strong>都分配了一个全局变量,用于存储该成员变量的偏移值。</p>
<p>这也就是为什么<code>ivar_t.offset</code>用<strong>int指针</strong>来存储偏移值,而不是直接放一个int的原因。在这个设计中,真正存放偏移值的地址是固定不变的,在编译时就确定了下来。因此才能用区区2条指令搞定动态布局的成员变量。</p>
<p>这就是Objective-C类成员变量的寻址方式。编译器通过这种方式,达到了灵活性和执行效率的完美平衡!</p>
<h2>Non Fragile ivars布局调整</h2>
<p>有了这种灵活而高效的寻址方式,那runtime是在什么时候调整成员变量偏移值的呢?从IR中可以看到,在编译时,LLVM计算出基类NSError对象的大小为40字节,然后记录在MyClass的类定义中,如下是对应的C代码。在编译后的可执行程序中,写死了“40”这个魔术数字,记录了在此次编译时MyClass基类的大小。</p>
<div class="highlight"><pre><code><span class="kt">class_ro_t</span> <span class="n">class_ro_MyClass</span> <span class="o">=</span> <span class="p">{</span>
<span class="p">.</span><span class="n">instanceStart</span> <span class="o">=</span> <span class="mi">40</span><span class="p">,</span>
<span class="p">.</span><span class="n">instanceSize</span> <span class="o">=</span> <span class="mi">48</span><span class="p">,</span>
<span class="c1">//...</span>
<span class="p">}</span>
</code></pre></div>
<p>现在假如苹果发布了OSX 11 SDK,NSError类大小增加到48字节。当我们的程序启动后,runtime加载MyClass类定义的时候,发现基类的真实大小和MyClass的<code>instanceStart</code>不相符,得知基类的大小发生了改变。于是runtime遍历MyClass的所有成员变量定义,将<code>offset</code>指向的值增加8。具体的实现代码在<code>runtime/objc-runtime-new.mm</code>的<code>moveIvars()</code>函数中。</p>
<p>并且,MyClass类定义的<code>instanceSize</code>也要增加8。这样runtime在创建MyClass对象的时候,能分配出正确大小的内存块。</p>
<h2>为什么Objective-C类不能动态添加成员变量</h2>
<p>这个问题的答案与Non Fragile ivars无关,但既然此文是关于类成员变量的,因此一并讨论。很多人在学到Category时都会有疑问,既然允许用Category给类增加方法和属性,那为什么不允许增加成员变量?</p>
<p>在<a href="https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html">Objective-C提供的runtime函数</a>中,确实有一个<code>class_addIvar()</code>函数用于给类添加成员变量,但是文档中特别说明:</p>
<blockquote><p>This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.</p>
</blockquote>
<p>意思是说,这个函数只能在“构建一个类的过程中”调用。一旦完成类定义,就不能再添加成员变量了。经过编译的类在程序启动后就被runtime加载,没有机会调用addIvar。程序在运行时动态构建的类需要在调用<code>objc_registerClassPair</code>之后才可以被使用,同样没有机会再添加成员变量。</p>
<p>我们设想一下如果Objective-C允许动态增加成员变量,会发生什么事情。假设如下代码可以执行。</p>
<p><img src="http://quotation.github.io/images/20150521/nf1.png" alt="成员变量布局"></p>
<div class="highlight"><pre><code><span class="n">MyObject</span> <span class="o">*</span><span class="n">obj</span> <span class="o">=</span> <span class="p">[[</span><span class="n">MyObject</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="c1">// 基类增加一个4字节的成员变量someVar</span>
<span class="n">class_addIvar</span><span class="p">([</span><span class="n">NSObject</span> <span class="n">class</span><span class="p">],</span> <span class="s">"someVar"</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="p">...);</span>
<span class="c1">// 基类增加方法someMethod,用到了someVar</span>
<span class="n">class_addMethod</span><span class="p">([</span><span class="n">NSObject</span> <span class="n">class</span><span class="p">],</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">someMethod</span><span class="p">),</span> <span class="p">...);</span>
<span class="c1">// 调用someMethod,修改了someVar</span>
<span class="p">[</span><span class="n">obj</span> <span class="n">someMethod</span><span class="p">];</span>
<span class="c1">// 访问子类成员变量,会发生什么?</span>
<span class="p">[</span><span class="n">obj</span><span class="o">-></span><span class="n">students</span> <span class="n">length</span><span class="p">];</span>
</code></pre></div>
<p>显然,这样做会带来严重问题,为基类动态增加成员变量会导致所有已创建出的子类实例都无法使用。那为什么runtime允许动态添加方法和属性,而不会引发问题呢?</p>
<p>因为方法和属性并不“属于”类实例,而成员变量“属于”类实例。我们所说的“类实例”概念,指的是一块内存区域,包含了isa指针和所有的成员变量。所以假如允许动态修改类成员变量布局,已经创建出的类实例就不符合类定义了,变成了无效对象。但方法定义是在<code>objc_class</code>中管理的,不管如何增删类方法,都不影响类实例的内存布局,已经创建出的类实例仍然可正常使用。</p>
<h2>总结</h2>
<p>Objective-C的“Non Fragile ivars”特性,以极低的运行时开销换取了程序的二进制兼容性。并且可执行文件仍然是目标平台上的native程序,不需要运行在VM上。实在是设计权衡取舍的典范。</p>
Objective-C类成员变量深度剖析
GCD 深入理解:第二部分
https://www.zruibin.cn/article/gcd_shen_ru_li_jie_:_di_er_bu_fen.html
2015-10-18 15:18
2015-10-18 15:18
<h1>GCD 深入理解:第二部分</h1>
<p>原文出处:<a href="https://github.com/nixzhu/dev-blog/blob/master/2014-05-14-grand-central-dispatch-in-depth-part-2.md" target="blank">深入理解:第二部分</a></p>
<p>本文翻译自 <a href="http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2">http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2</a></p>
<p>原作者:<a href="http://www.raywenderlich.com/u/Lolgrep">Derek Selander</a></p>
<p>译者:<a href="http://weibo.com/riven0951">Riven</a>、<a href="https://twitter.com/nixzhu">@nixzhu</a></p>
<p>前半部分由 Riven 翻译,但他由于太忙而搁置,后由 NIX 整理校对并翻译后半部分。</p>
<p>==============================</p>
<p>欢迎来到GCD深入理解系列教程的第二部分(也是最后一部分)。</p>
<p>在本系列的[第一部分][4]中,你已经学到超过你想像的关于并发、线程以及GCD 如何工作的知识。通过在初始化时利用 <code>dispatch_once</code>,你创建了一个线程安全的 <code>PhotoManager</code> 单例,而且你通过使用 <code>dispatch_barrier_async</code> 和 <code>dispatch_sync</code> 的组合使得对 <code>Photos</code> 数组的读取和写入都变得线程安全了。</p>
<p>除了上面这些,你还通过利用 <code>dispatch_after</code> 来延迟显示提示信息,以及利用 <code>dispatch_async</code> 将 CPU 密集型任务从 ViewController 的初始化过程中剥离出来异步执行,达到了增强应用的用户体验的目的。</p>
<p>如果你一直跟着第一部分的教程在写代码,那你可以继续你的工程。但如果你没有完成第一部分的工作,或者不想重用你的工程,你可以<a href="http://cdn2.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_End_1.zip">下载第一部分最终的代码</a>。</p>
<p>那就让我们来更深入地探索 GCD 吧!</p>
<h2>纠正过早弹出的提示</h2>
<p>你可能已经注意到当你尝试用 Le Internet 选项来添加图片时,一个 <code>UIAlertView</code> 会在图片下载完成之前就弹出,如下如所示:</p>
<p><img src="https://camo.githubusercontent.com/709340de2a8b4d8c91d18b8a72b043db52648481/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f53637265656e2d53686f742d323031342d30312d31372d61742d352e34392e35312d504d2d333038783530302e706e67" alt=""></p>
<p>问题的症结在 PhotoManagers 的 <code>downloadPhotoWithCompletionBlock:</code> 里,它目前的实现如下:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadPhotosWithCompletionBlock:</span><span class="p">(</span><span class="n">BatchPhotoDownloadingCompletionBlock</span><span class="p">)</span><span class="nv">completionBlock</span>
<span class="p">{</span>
<span class="k">__block</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">NSInteger</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">3</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kOverlyAttachedGirlfriendURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kSuccessKidURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kLotsOfFacesURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="o">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">error</span> <span class="o">=</span> <span class="n">_error</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}];</span>
<span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">]</span> <span class="nl">addPhoto</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="n">completionBlock</span><span class="p">)</span> <span class="p">{</span>
<span class="n">completionBlock</span><span class="p">(</span><span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>在方法的最后你调用了 <code>completionBlock</code> ——因为此时你假设所有的照片都已下载完成。但很不幸,此时并不能保证所有的下载都已完成。</p>
<p><code>Photo</code> 类的实例方法用某个 URL 开始下载某个文件并立即返回,但此时下载并未完成。换句话说,当 <code>downloadPhotoWithCompletionBlock:</code> 在其末尾调用 <code>completionBlock</code> 时,它就假设了它自己所使用的方法全都是同步的,而且每个方法都完成了它们的工作。</p>
<p>然而,<code>-[Photo initWithURL:withCompletionBlock:]</code> 是异步执行的,会立即返回——所以这种方式行不通。</p>
<p>因此,只有在所有的图像下载任务都调用了它们自己的 Completion Block 之后,<code>downloadPhotoWithCompletionBlock:</code> 才能调用它自己的 <code>completionBlock</code> 。问题是:你该如何监控并发的异步事件?你不知道它们何时完成,而且它们完成的顺序完全是不确定的。</p>
<p>或许你可以写一些比较 Hacky 的代码,用多个布尔值来记录每个下载的完成情况,但这样做就缺失了扩展性,而且说实话,代码会很难看。</p>
<p>幸运的是, 解决这种对多个异步任务的完成进行监控的问题,恰好就是设计 dispatch_group 的目的。</p>
<h3>Dispatch Groups(调度组)</h3>
<p>Dispatch Group 会在整个组的任务都完成时通知你。这些任务可以是同步的,也可以是异步的,即便在不同的队列也行。而且在整个组的任务都完成时,Dispatch Group 可以用同步的或者异步的方式通知你。因为要监控的任务在不同队列,那就用一个 <code>dispatch_group_t</code> 的实例来记下这些不同的任务。</p>
<p>当组中所有的事件都完成时,GCD 的 API 提供了两种通知方式。</p>
<p>第一种是 <code>dispatch_group_wait</code> ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。这恰好是你目前所需要的。</p>
<p>打开 PhotoManager.m,用下列实现替换 <code>downloadPhotosWithCompletionBlock:</code>:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadPhotosWithCompletionBlock:</span><span class="p">(</span><span class="n">BatchPhotoDownloadingCompletionBlock</span><span class="p">)</span><span class="nv">completionBlock</span>
<span class="p">{</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 1</span>
<span class="k">__block</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="kt">dispatch_group_t</span> <span class="n">downloadGroup</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">();</span> <span class="c1">// 2</span>
<span class="k">for</span> <span class="p">(</span><span class="n">NSInteger</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">3</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kOverlyAttachedGirlfriendURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kSuccessKidURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kLotsOfFacesURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="o">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_enter</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">error</span> <span class="o">=</span> <span class="n">_error</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_leave</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span> <span class="c1">// 4</span>
<span class="p">}];</span>
<span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">]</span> <span class="nl">addPhoto</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span>
<span class="p">}</span>
<span class="n">dispatch_group_wait</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">,</span> <span class="n">DISPATCH_TIME_FOREVER</span><span class="p">);</span> <span class="c1">// 5</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 6</span>
<span class="k">if</span> <span class="p">(</span><span class="n">completionBlock</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 7</span>
<span class="n">completionBlock</span><span class="p">(</span><span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>按照注释的顺序,你会看到:</p>
<ol>
<li>因为你在使用的是同步的 <code>dispatch_group_wait</code> ,它会阻塞当前线程,所以你要用 <code>dispatch_async</code> 将整个方法放入后台队列以避免阻塞主线程。</li>
<li>创建一个新的 Dispatch Group,它的作用就像一个用于未完成任务的计数器。</li>
<li><code>dispatch_group_enter</code> 手动通知 Dispatch Group 任务已经开始。你必须保证 <code>dispatch_group_enter</code> 和 <code>dispatch_group_leave</code> 成对出现,否则你可能会遇到诡异的崩溃问题。</li>
<li>手动通知 Group 它的工作已经完成。再次说明,你必须要确保进入 Group 的次数和离开 Group 的次数相等。</li>
<li><code>dispatch_group_wait</code> 会一直等待,直到任务全部完成或者超时。如果在所有任务完成前超时了,该函数会返回一个非零值。你可以对此返回值做条件判断以确定是否超出等待周期;然而,你在这里用 <code>DISPATCH_TIME_FOREVER</code> 让它永远等待。它的意思,勿庸置疑就是,永-远-等-待!这样很好,因为图片的创建工作总是会完成的。</li>
<li>此时此刻,你已经确保了,要么所有的图片任务都已完成,要么发生了超时。然后,你在主线程上运行 <code>completionBlock</code> 回调。这会将工作放到主线程上,并在稍后执行。</li>
<li>最后,检查 <code>completionBlock</code> 是否为 nil,如果不是,那就运行它。</li>
</ol>
<p>编译并运行你的应用,尝试下载多个图片,观察你的应用是在何时运行 completionBlock 的。</p>
<blockquote><p>注意:如果你是在真机上运行应用,而且网络活动发生得太快以致难以观察 completionBlock 被调用的时刻,那么你可以在 Settings 应用里的开发者相关部分里打开一些网络设置,以确保代码按照我们所期望的那样工作。只需去往 Network Link Conditioner 区,开启它,再选择一个 Profile,“Very Bad Network” 就不错。</p>
</blockquote>
<p>如果你是在模拟器里运行应用,你可以使用 [来自 GitHub 的 Network Link Conditioner][9] 来改变网络速度。它会成为你工具箱中的一个好工具,因为它强制你研究你的应用在连接速度并非最佳的情况下会变成什么样。</p>
<p>目前为止的解决方案还不错,但是总体来说,如果可能,最好还是要避免阻塞线程。你的下一个任务是重写一些方法,以便当所有下载任务完成时能异步通知你。</p>
<p>在我们转向另外一种使用 Dispatch Group 的方式之前,先看一个简要的概述,关于何时以及怎样使用有着不同的队列类型的 Dispatch Group :</p>
<ul>
<li>自定义串行队列:它很适合当一组任务完成时发出通知。</li>
<li>主队列(串行):它也很适合这样的情况。但如果你要同步地等待所有工作地完成,那你就不应该使用它,因为你不能阻塞主线程。然而,异步模型是一个很有吸引力的能用于在几个较长任务(例如网络调用)完成后更新 UI 的方式。</li>
<li>并发队列:它也很适合 Dispatch Group 和完成时通知。</li>
</ul>
<h3>Dispatch Group,第二种方式</h3>
<p>上面的一切都很好,但在另一个队列上异步调度然后使用 dispatch_group_wait 来阻塞实在显得有些笨拙。是的,还有另一种方式……</p>
<p>在 PhotoManager.m 中找到 <code>downloadPhotosWithCompletionBlock:</code> 方法,用下面的实现替换它:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadPhotosWithCompletionBlock:</span><span class="p">(</span><span class="n">BatchPhotoDownloadingCompletionBlock</span><span class="p">)</span><span class="nv">completionBlock</span>
<span class="p">{</span>
<span class="c1">// 1</span>
<span class="k">__block</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="kt">dispatch_group_t</span> <span class="n">downloadGroup</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">();</span>
<span class="k">for</span> <span class="p">(</span><span class="n">NSInteger</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">3</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kOverlyAttachedGirlfriendURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kSuccessKidURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kLotsOfFacesURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="o">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_enter</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span> <span class="c1">// 2</span>
<span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">error</span> <span class="o">=</span> <span class="n">_error</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_leave</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span> <span class="c1">// 3</span>
<span class="p">}];</span>
<span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">]</span> <span class="nl">addPhoto</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span>
<span class="p">}</span>
<span class="n">dispatch_group_notify</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">,</span> <span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 4</span>
<span class="k">if</span> <span class="p">(</span><span class="n">completionBlock</span><span class="p">)</span> <span class="p">{</span>
<span class="n">completionBlock</span><span class="p">(</span><span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>下面解释新的异步方法如何工作:</p>
<ol>
<li>在新的实现里,因为你没有阻塞主线程,所以你并不需要将方法包裹在 <code>async</code> 调用中。</li>
<li>同样的 <code>enter</code> 方法,没做任何修改。</li>
<li>同样的 <code>leave</code> 方法,也没做任何修改。</li>
<li><code>dispatch_group_notify</code> 以异步的方式工作。当 Dispatch Group 中没有任何任务时,它就会执行其代码,那么 <code>completionBlock</code> 便会运行。你还指定了运行 <code>completionBlock</code> 的队列,此处,主队列就是你所需要的。</li>
</ol>
<p>对于这个特定的工作,上面的处理明显更清晰,而且也不会阻塞任何线程。</p>
<h2>太多并发带来的风险</h2>
<p>既然你的工具箱里有了这些新工具,你大概做任何事情都想使用它们,对吧?</p>
<p><img src="https://camo.githubusercontent.com/1c1884b2e3a6036b3ee195b5af3a6519eb03d5c6/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f5468726561645f416c6c5f5468655f436f64655f4d656d652e6a7067" alt=""></p>
<p>看看 PhotoManager 中的 <code>downloadPhotosWithCompletionBlock</code> 方法。你可能已经注意到这里的 <code>for</code> 循环,它迭代三次,下载三个不同的图片。你的任务是尝试让 <code>for</code> 循环并发运行,以提高其速度。</p>
<p><code>dispatch_apply</code> 刚好可用于这个任务。</p>
<p><code>dispatch_apply</code> 表现得就像一个 <code>for</code> 循环,但它能并发地执行不同的迭代。这个函数是同步的,所以和普通的 <code>for</code> 循环一样,它只会在所有工作都完成后才会返回。</p>
<p>当在 Block 内计算任何给定数量的工作的最佳迭代数量时,必须要小心,因为过多的迭代和每个迭代只有少量的工作会导致大量开销以致它能抵消任何因并发带来的收益。而被称为<code>跨越式(striding)</code>的技术可以在此帮到你,即通过在每个迭代里多做几个不同的工作。</p>
<blockquote><p>译者注:大概就能减少并发数量吧,作者是提醒大家注意并发的开销,记在心里!</p>
</blockquote>
<p>那何时才适合用 <code>dispatch_apply</code> 呢?</p>
<ul>
<li>自定义串行队列:串行队列会完全抵消 <code>dispatch_apply</code> 的功能;你还不如直接使用普通的 <code>for</code> 循环。</li>
<li>主队列(串行):与上面一样,在串行队列上不适合使用 <code>dispatch_apply</code> 。还是用普通的 <code>for</code> 循环吧。</li>
<li>并发队列:对于并发循环来说是很好选择,特别是当你需要追踪任务的进度时。</li>
</ul>
<p>回到 <code>downloadPhotosWithCompletionBlock:</code> 并用下列实现替换它:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadPhotosWithCompletionBlock:</span><span class="p">(</span><span class="n">BatchPhotoDownloadingCompletionBlock</span><span class="p">)</span><span class="nv">completionBlock</span>
<span class="p">{</span>
<span class="k">__block</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">;</span>
<span class="kt">dispatch_group_t</span> <span class="n">downloadGroup</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">();</span>
<span class="n">dispatch_apply</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span><span class="p">;</span>
<span class="k">switch</span> <span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="mi">0</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kOverlyAttachedGirlfriendURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">1</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kSuccessKidURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">case</span> <span class="mi">2</span><span class="o">:</span>
<span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">kLotsOfFacesURLString</span><span class="p">];</span>
<span class="k">break</span><span class="p">;</span>
<span class="k">default</span><span class="o">:</span>
<span class="k">break</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_enter</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span>
<span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">error</span> <span class="o">=</span> <span class="n">_error</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">dispatch_group_leave</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">);</span>
<span class="p">}];</span>
<span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">]</span> <span class="nl">addPhoto</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span>
<span class="p">});</span>
<span class="n">dispatch_group_notify</span><span class="p">(</span><span class="n">downloadGroup</span><span class="p">,</span> <span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">completionBlock</span><span class="p">)</span> <span class="p">{</span>
<span class="n">completionBlock</span><span class="p">(</span><span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>你的循环现在是并行运行的了;在上面的代码中,在调用 <code>dispatch_apply</code> 时,你用第一次参数指明了迭代的次数,用第二个参数指定了任务运行的队列,而第三个参数是一个 Block。</p>
<p>要知道虽然你有代码保证添加相片时线程安全,但图片的顺序却可能不同,这取决于线程完成的顺序。</p>
<p>编译并运行,然后从 “Le Internet” 添加一些照片。注意到区别了吗?</p>
<p>在真机上运行新代码会稍微更快的得到结果。但我们所做的这些提速工作真的值得吗?</p>
<p>实际上,在这个例子里并不值得。下面是原因:</p>
<ul>
<li>你创建并行运行线程而付出的开销,很可能比直接使用 <code>for</code> 循环要多。若你要以合适的步长迭代非常大的集合,那才应该考虑使用 <code>dispatch_apply</code>。</li>
<li>你用于创建应用的时间是有限的——除非实在太糟糕否则不要浪费时间去提前优化代码。如果你要优化什么,那去优化那些明显值得你付出时间的部分。你可以通过在 Instruments 里分析你的应用,找出最长运行时间的方法。看看 [如何在 Xcode 中使用 Instruments][11] 可以学到更多相关知识。</li>
<li>通常情况下,优化代码会让你的代码更加复杂,不利于你自己和其他开发者阅读。请确保添加的复杂性能换来足够多的好处。</li>
</ul>
<p>记住,不要在优化上太疯狂。你只会让你自己和后来者更难以读懂你的代码。</p>
<h2>GCD 的其他趣味</h2>
<p>等一下!还有更多!有一些额外的函数在不同的道路上走得更远。虽然你不会太频繁地使用这些工具,但在对的情况下,它们可以提供极大的帮助。</p>
<h3>阻塞——正确的方式</h3>
<p>这可能听起来像是个疯狂的想法,但你知道 Xcode 已有了测试功能吗?:] 我知道,虽然有时候我喜欢假装它不存在,但在代码里构建复杂关系时编写和运行测试非常重要。</p>
<p>Xcode 里的测试在 <code>XCTestCase</code> 的子类上执行,并运行任何方法签名以 <code>test</code> 开头的方法。测试在主线程运行,所以你可以假设所有测试都是串行发生的。</p>
<p>当一个给定的测试方法运行完成,XCTest 方法将考虑此测试已结束,并进入下一个测试。这意味着任何来自前一个测试的异步代码会在下一个测试运行时继续运行。</p>
<p>网络代码通常是异步的,因此你不能在执行网络获取时阻塞主线程。也就是说,整个测试会在测试方法完成之后结束,这会让对网络代码的测试变得很困难。也就是,除非你在测试方法内部阻塞主线程直到网络代码完成。</p>
<blockquote><p>注意:有一些人会说,这种类型的测试不属于集成测试的首选集(Preferred Set)。一些人会赞同,一些人不会。但如果你想做,那就去做。</p>
</blockquote>
<p><img src="https://camo.githubusercontent.com/fe666764a3dfdebc740c87602d1753b68b9d3b98/687474703a2f2f63646e312e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f47616e64616c665f53656d6170686f72652e706e67" alt=""></p>
<p>导航到 GooglyPuffTests.m 并查看 <code>downloadImageURLWithString:</code>,如下:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadImageURLWithString:</span><span class="p">(</span><span class="bp">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">URLString</span>
<span class="p">{</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">URLString</span><span class="p">];</span>
<span class="k">__block</span> <span class="kt">BOOL</span> <span class="n">isFinishedDownloading</span> <span class="o">=</span> <span class="nb">NO</span><span class="p">;</span>
<span class="n">__unused</span> <span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span>
<span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">XCTFail</span><span class="p">(</span><span class="s">@"%@ failed. %@"</span><span class="p">,</span> <span class="n">URLString</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">isFinishedDownloading</span> <span class="o">=</span> <span class="nb">YES</span><span class="p">;</span>
<span class="p">}];</span>
<span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">isFinishedDownloading</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div>
<p>这是一种测试异步网络代码的幼稚方式。 While 循环在函数的最后一直等待,直到 <code>isFinishedDownloading</code> 布尔值变成 True,它只会在 Completion Block 里发生。让我们看看这样做有什么影响。</p>
<p>通过在 Xcode 中点击 Product / Test 运行你的测试,如果你使用默认的键绑定,也可以使用快捷键 ⌘+U 来运行你的测试。</p>
<p>在测试运行时,注意 Xcode debug 导航栏里的 CPU 使用率。这个设计不当的实现就是一个基本的 [自旋锁][14] 。它很不实用,因为你在 While 循环里浪费了珍贵的 CPU 周期;而且它也几乎没有扩展性。</p>
<blockquote><p>译者注:所谓自旋锁,就是某个线程一直抢占着 CPU 不断检查以等到它需要的情况出现。因为现代操作系统都是可以并发运行多个线程的,所以它所等待的那个线程也有机会被调度执行,这样它所需要的情况早晚会出现。</p>
</blockquote>
<p>你可能需要使用前面提到的 Network Link Conditioner ,已便清楚地看到这个问题。如果你的网络太快,那么自旋只会在很短的时间里发生,难以观察。</p>
<blockquote><p>译者注:作者反复提到网速太快,而我们还需要对付 GFW,简直泪流满面!</p>
</blockquote>
<p>你需要一个更优雅、可扩展的解决方案来阻塞线程直到资源可用。欢迎来到信号量。</p>
<h3>信号量</h3>
<p>信号量是一种老式的线程概念,由非常谦卑的 Edsger W. Dijkstra 介绍给世界。信号量之所以比较复杂是因为它建立在操作系统的复杂性之上。</p>
<p>如果你想学到更多关于信号量的知识,看看这个链接[它更细致地讨论了信号量理论][15]。如果你是学术型,那可以看一个软件开发中经典的[哲学家进餐问题][16],它需要使用信号量来解决。</p>
<p>信号量让你控制多个消费者对有限数量资源的访问。举例来说,如果你创建了一个有着两个资源的信号量,那同时最多只能有两个线程可以访问临界区。其他想使用资源的线程必须在一个…你猜到了吗?…FIFO队列里等待。</p>
<p>让我们来使用信号量吧!</p>
<p>打开 GooglyPuffTests.m 并用下列实现替换 <code>downloadImageURLWithString:</code>:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">downloadImageURLWithString:</span><span class="p">(</span><span class="bp">NSString</span> <span class="o">*</span><span class="p">)</span><span class="nv">URLString</span>
<span class="p">{</span>
<span class="c1">// 1</span>
<span class="kt">dispatch_semaphore_t</span> <span class="n">semaphore</span> <span class="o">=</span> <span class="n">dispatch_semaphore_create</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
<span class="bp">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">URLString</span><span class="p">];</span>
<span class="n">__unused</span> <span class="n">Photo</span> <span class="o">*</span><span class="n">photo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">Photo</span> <span class="n">alloc</span><span class="p">]</span>
<span class="nl">initwithURL</span><span class="p">:</span><span class="n">url</span>
<span class="nl">withCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(</span><span class="bp">UIImage</span> <span class="o">*</span><span class="n">image</span><span class="p">,</span> <span class="bp">NSError</span> <span class="o">*</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span> <span class="p">{</span>
<span class="n">XCTFail</span><span class="p">(</span><span class="s">@"%@ failed. %@"</span><span class="p">,</span> <span class="n">URLString</span><span class="p">,</span> <span class="n">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// 2</span>
<span class="n">dispatch_semaphore_signal</span><span class="p">(</span><span class="n">semaphore</span><span class="p">);</span>
<span class="p">}];</span>
<span class="c1">// 3</span>
<span class="kt">dispatch_time_t</span> <span class="n">timeoutTime</span> <span class="o">=</span> <span class="n">dispatch_time</span><span class="p">(</span><span class="n">DISPATCH_TIME_NOW</span><span class="p">,</span> <span class="n">kDefaultTimeoutLengthInNanoSeconds</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">dispatch_semaphore_wait</span><span class="p">(</span><span class="n">semaphore</span><span class="p">,</span> <span class="n">timeoutTime</span><span class="p">))</span> <span class="p">{</span>
<span class="n">XCTFail</span><span class="p">(</span><span class="s">@"%@ timed out"</span><span class="p">,</span> <span class="n">URLString</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>下面来说明你代码中的信号量是如何工作的:</p>
<ol>
<li>创建一个信号量。参数指定信号量的起始值。这个数字是你可以访问的信号量,不需要有人先去增加它的数量。(注意到增加信号量也被叫做发射信号量)。译者注:这里初始化为0,也就是说,有人想使用信号量必然会被阻塞,直到有人增加信号量。</li>
<li>在 Completion Block 里你告诉信号量你不再需要资源了。这就会增加信号量的计数并告知其他想使用此资源的线程。</li>
<li>这会在超时之前等待信号量。这个调用阻塞了当前线程直到信号量被发射。这个函数的一个非零返回值表示到达超时了。在这个例子里,测试将会失败因为它以为网络请求不会超过 10 秒钟就会返回——一个平衡点!</li>
</ol>
<p>再次运行测试。只要你有一个正常工作的网络连接,这个测试就会马上成功。请特别注意 CPU 的使用率,与之前使用自旋锁的实现作个对比。</p>
<p>关闭你的网络链接再运行测试;如果你在真机上运行,就打开飞行模式。如果你的在模拟器里运行,你可以直接断开 Mac 的网络链接。测试会在 10 秒后失败。这很棒,它真的能按照预想的那样工作!</p>
<p>还有一些琐碎的测试,但如果你与一个服务器组协同工作,那么这些基本的测试能够防止其他人就最新的网络问题对你说三道四。</p>
<h3>使用 Dispatch Source</h3>
<p>GCD 的一个特别有趣的特性是 Dispatch Source,它基本上就是一个低级函数的 grab-bag ,能帮助你去响应或监测 Unix 信号、文件描述符、Mach 端口、VFS 节点,以及其它晦涩的东西。所有这些都超出了本教程讨论的范围,但你可以通过实现一个 Dispatch Source 对象并以一个相当奇特的方式来使用它来品尝那些晦涩的东西。</p>
<p>第一次使用 Dispatch Source 可能会迷失在如何使用一个源,所以你需要知晓的第一件事是 <code>dispatch_source_create</code> 如何工作。下面是创建一个源的函数原型:</p>
<div class="highlight"><pre><code><span class="kt">dispatch_source_t</span> <span class="nf">dispatch_source_create</span><span class="p">(</span>
<span class="kt">dispatch_source_type_t</span> <span class="n">type</span><span class="p">,</span>
<span class="kt">uintptr_t</span> <span class="n">handle</span><span class="p">,</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">mask</span><span class="p">,</span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span><span class="p">);</span>
</code></pre></div>
<p>第一个参数是 <code>dispatch_source_type_t</code> 。这是最重要的参数,因为它决定了 handle 和 mask 参数将会是什么。你可以查看 [Xcode 文档][17] 得到哪些选项可用于每个 <code>dispatch_source_type_t</code> 参数。</p>
<p>下面你将监控 <code>DISPATCH_SOURCE_TYPE_SIGNAL</code> 。如[文档所显示的][18]:</p>
<p>一个监控当前进程信号的 Dispatch Source。 handle 是信号编号,mask 未使用(传 0 即可)。</p>
<p>这些 Unix 信号组成的列表可在头文件 [signal.h][19] 中找到。在其顶部有一堆 <code>#define</code> 语句。你将监控此信号列表中的 <code>SIGSTOP</code> 信号。这个信号将会在进程接收到一个无法回避的暂停指令时被发出。在你用 LLDB 调试器调试应用时你使用的也是这个信号。</p>
<p>去往 PhotoCollectionViewController.m 并添加如下代码到 <code>viewDidLoad</code> 的顶部,就在 <code>[super viewDidLoad]</code> 下面:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="c1">// 1</span>
<span class="cp">#if DEBUG</span>
<span class="c1">// 2</span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_get_main_queue</span><span class="p">();</span>
<span class="c1">// 3</span>
<span class="k">static</span> <span class="kt">dispatch_source_t</span> <span class="n">source</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="c1">// 4</span>
<span class="n">__typeof</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="k">__weak</span> <span class="n">weakSelf</span> <span class="o">=</span> <span class="nb">self</span><span class="p">;</span>
<span class="c1">// 5</span>
<span class="k">static</span> <span class="kt">dispatch_once_t</span> <span class="n">onceToken</span><span class="p">;</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">onceToken</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 6</span>
<span class="n">source</span> <span class="o">=</span> <span class="n">dispatch_source_create</span><span class="p">(</span><span class="n">DISPATCH_SOURCE_TYPE_SIGNAL</span><span class="p">,</span> <span class="n">SIGSTOP</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">queue</span><span class="p">);</span>
<span class="c1">// 7</span>
<span class="k">if</span> <span class="p">(</span><span class="n">source</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// 8</span>
<span class="n">dispatch_source_set_event_handler</span><span class="p">(</span><span class="n">source</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 9</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"Hi, I am: %@"</span><span class="p">,</span> <span class="n">weakSelf</span><span class="p">);</span>
<span class="p">});</span>
<span class="n">dispatch_resume</span><span class="p">(</span><span class="n">source</span><span class="p">);</span> <span class="c1">// 10</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="cp">#endif</span>
<span class="c1">// The other stuff</span>
<span class="p">}</span>
</code></pre></div>
<p>这些代码有点儿复杂,所以跟着注释一步步走,看看到底发生了什么:</p>
<ol>
<li>最好是在 DEBUG 模式下编译这些代码,因为这会给“有关方面(Interested Parties)”很多关于你应用的洞察。 :] </li>
<li>Just to mix things up,你创建了一个 <code>dispatch_queue_t</code> 实例变量而不是在参数上直接使用函数。当代码变长,分拆有助于可读性。</li>
<li>你需要 <code>source</code> 在方法范围之外也可被访问,所以你使用了一个 static 变量。</li>
<li>使用 <code>weakSelf</code> 以确保不会出现保留环(Retain Cycle)。这对 <code>PhotoCollectionViewController</code> 来说不是完全必要的,因为它会在应用的整个生命期里保持活跃。然而,如果你有任何其它会消失的类,这就能确保不会出现保留环而造成内存泄漏。</li>
<li>使用 <code>dispatch_once</code> 确保只会执行一次 Dispatch Source 的设置。</li>
<li>初始化 <code>source</code> 变量。你指明了你对信号监控感兴趣并提供了 <code>SIGSTOP</code> 信号作为第二个参数。进一步,你使用主队列处理接收到的事件——很快你就好发现为何要这样做。</li>
<li>如果你提供的参数不合格,那么 Dispatch Source 对象不会被创建。也就是说,在你开始在其上工作之前,你需要确保已有了一个有效的 Dispatch Source 。</li>
<li>当你收到你所监控的信号时,<code>dispatch_source_set_event_handler</code> 就会执行。之后你可以在其 Block 里设置合适的逻辑处理器(Logic Handler)。</li>
<li>一个基本的 <code>NSLog</code> 语句,它将对象打印到控制台。</li>
<li>默认的,所有源都初始为暂停状态。如果你要开始监控事件,你必须告诉源对象恢复活跃状态。</li>
</ol>
<p>编译并运行应用;在调试器里暂停并立即恢复应用,查看控制台,你会看到这个来自黑暗艺术的函数确实可以工作。你看到的大概如下:</p>
<div class="highlight"><pre>2014-03-29 17:41:30.610 GooglyPuff[8181:60b] Hi, I am:
</code></pre></div>
<p>你的应用现在具有调试感知了!这真是超级棒,但在真实世界里该如何使用它呢?</p>
<p>你可以用它去调试一个对象并在任何你想恢复应用的时候显示数据;你同样能给你的应用加上自定义的安全逻辑以便在恶意攻击者将一个调试器连接到你的应用上时保护它自己(或用户的数据)。</p>
<blockquote><p>译者注:好像挺有用!</p>
</blockquote>
<p>一个有趣的主意是,使用此方式的作为一个堆栈追踪工具去找到你想在调试器里操纵的对象。</p>
<p><img src="https://camo.githubusercontent.com/19aa903a2b0ffa0e6850896b81256e5510209ebd/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f576861745f4d656d652e6a7067" alt=""></p>
<p>稍微想想这个情况。当你意外地停止调试器,你几乎从来都不会在所需的栈帧上。现在你可以在任何时候停止调试器并在你所需的地方执行代码。如果你想在你的应用的某一点执行的代码非常难以从调试器访问的话,这会非常有用。有机会试试吧!</p>
<p><img src="https://camo.githubusercontent.com/9836421e6e7845170e5f3f29c07edcdcb98710af/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f495f5365655f576861745f596f755f4469645f4d656d652e706e67" alt=""></p>
<p>将一个断点放在你刚添加在 viewDidLoad 里的事件处理器的 <code>NSLog</code> 语句上。在调试器里暂停,然后再次开始;应用会到达你添加的断点。现在你深入到你的 PhotoCollectionViewController 方法深处。你可以访问 PhotoCollectionViewController 的实例得到你关心的内容。非常方便!</p>
<blockquote><p>注意:如果你还没有注意到在调试器里的是哪个线程,那现在就看看它们。主线程总是第一个被 libdispatch 跟随,它是 GCD 的坐标,作为第二个线程。之后,线程计数和剩余线程取决于硬件在应用到达断点时正在做的事情。</p>
</blockquote>
<p>在调试器里,键入命令:<code>po [[weakSelf navigationItem] setPrompt:@"WOOT!"]</code></p>
<p>然后恢复应用的执行。你会看到如下内容:</p>
<p><img src="https://camo.githubusercontent.com/5d84fae4277cca8af6b197510bb349102aeed698/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f44697370617463685f536f75726365735f58636f64655f427265616b706f696e745f436f6e736f6c652d363530783530302e706e67" alt=""></p>
<p><img src="https://camo.githubusercontent.com/5d84fae4277cca8af6b197510bb349102aeed698/687474703a2f2f63646e352e72617977656e6465726c6963682e636f6d2f77702d636f6e74656e742f75706c6f6164732f323031342f30312f44697370617463685f536f75726365735f58636f64655f427265616b706f696e745f436f6e736f6c652d363530783530302e706e67" alt=""></p>
<p>使用这个方法,你可以更新 UI、查询类的属性,甚至是执行方法——所有这一切都不需要重启应用并到达某个特定的工作状态。相当优美吧!</p>
<blockquote><p>译者注:发挥这一点,是可以做出一些调试库的吧?</p>
</blockquote>
<h2>之后又该往何处去?</h2>
<p>你可以<a href="http://cdn3.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff-Final.zip">在此下载最终的项目</a>。</p>
<p>我讨厌再次提及此主题,但你真的要看看 [如何使用 Instruments][11] 教程。如果你计划优化你的应用,那你一定要学会使用它。请注意 Instruments 擅长于分析相对执行:比较哪些区域的代码相对于其它区域的代码花费了更长的时间。如果你尝试计算出某个方法实际的执行时间,那你可能需要拿出更多的自酿的解决方案(Home-brewed Solution)。</p>
<p>同样请看看 [如何使用 NSOperations 和 NSOperationQueues][25] 吧,它们是建立在 GCD 之上的并发技术。大体来说,如果你在写简单的用过就忘的任务,那它们就是使用 GCD 的最佳实践,。NSOperations 提供更好的控制、处理大量并发操作的实现,以及一个以速度为代价的更加面向对象的范例。</p>
<p>记住,除非你有特别的原因要往下流走(译者的玩笑:即使用低级别 API),否则永远应尝试并坚持使用高级的 API。如果你想学到更多或想做某些非常非常“有趣”的事情,那你就应该冒险进入 Apple 的黑暗艺术。</p>
<p>祝你好运,玩得开心!有任何问题或反馈请在下方的讨论区贴出!</p>
GCD 深入理解:第二部分
GCD 深入理解:第一部分
https://www.zruibin.cn/article/gcd_shen_ru_li_jie_:_di_yi_bu_fen.html
2015-10-18 14:04
2015-10-18 14:04
<h1>GCD 深入理解:第一部分</h1>
<p>原文出处:<a href="https://github.com/nixzhu/dev-blog/blob/master/2014-04-19-grand-central-dispatch-in-depth-part-1.md" target="blank">深入理解:第一部分</a></p>
<p>本文翻译自 <a href="http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1">http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1</a></p>
<p>原作者:<a href="http://www.raywenderlich.com/u/Lolgrep">Derek Selander</a></p>
<p>译者:<a href="https://twitter.com/nixzhu">@nixzhu</a></p>
<p>==========================================</p>
<p>虽然 GCD 已经出现过一段时间了,但不是每个人都明了其主要内容。这是可以理解的;并发一直很棘手,而 GCD 是基于 C 的 API ,它们就像一组尖锐的棱角戳进 Objective-C 的平滑世界。我们将分两个部分的教程来深入学习 GCD 。</p>
<p>在这两部分的系列中,第一个部分的将解释 GCD 是做什么的,并从许多基本的 GCD 函数中找出几个来展示。在<a href="https://github.com/nixzhu/dev-blog/blob/master/2014-05-14-grand-central-dispatch-in-depth-part-2.md">第二部分</a>,你将学到几个 GCD 提供的高级函数。</p>
<h2>什么是 GCD</h2>
<p>GCD 是 <code>libdispatch</code> 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:</p>
<ul>
<li>GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。</li>
<li>GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。</li>
<li>GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。</li>
</ul>
<p>本教程假设你对 Block 和 GCD 有基础了解。如果你对 GCD 完全陌生,先看看 <a href="http://www.raywenderlich.com/4295/multithreading-and-grand-central-dispatch-on-ios-for-beginners-tutorial">iOS 上的多线程和 GCD 入门教程</a> 学习其要领。</p>
<h2>GCD 术语</h2>
<p>要理解 GCD ,你要先熟悉与线程和并发相关的几个概念。这两者都可能模糊和微妙,所以在开始 GCD 之前先简要地回顾一下它们。</p>
<h4>Serial vs. Concurrent 串行 vs. 并发</h4>
<p>这些术语描述当任务相对于其它任务被执行,任务串行执行就是每次只有一个任务被执行,任务并发执行就是在同一时间可以有多个任务被执行。</p>
<p>虽然这些术语被广泛使用,本教程中你可以将任务设定为一个 Objective-C 的 Block 。不明白什么是 Block ?看看 <a href="http://www.raywenderlich.com/9328/creating-a-diner-app-using-blocks-part-1">iOS 5 教程中的如何使用 Block</a> 。实际上,你也可以在 GCD 上使用函数指针,但在大多数场景中,这实际上更难于使用。Block 就是更加容易些!</p>
<h4>Synchronous vs. Asynchronous 同步 vs. 异步</h4>
<p>在 GCD 中,这些术语描述当一个函数相对于另一个任务完成,此任务是该函数要求 GCD 执行的。一个<em>同步</em>函数只在完成了它预定的任务后才返回。</p>
<p>一个<em>异步</em>函数,刚好相反,会立即返回,预定的任务会完成但不会等它完成。因此,一个异步函数不会阻塞当前线程去执行下一个函数。</p>
<p>注意——当你读到同步函数“阻塞(Block)”当前线程,或函数是一个“阻塞”函数或阻塞操作时,不要被搞糊涂了!动词“阻塞”描述了函数如何影响它所在的线程而与名词“代码块(Block)”没有关系。代码块描述了用 Objective-C 编写的一个匿名函数,它能定义一个任务并被提交到 GCD 。</p>
<p>译者注:中文不会有这个问题,“阻塞”和“代码块”是两个词。</p>
<h4>Critical Section 临界区</h4>
<p>就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。这很常见,因为代码去操作一个共享资源,例如一个变量若能被并发进程访问,那么它很可能会变质(译者注:它的值不再可信)。</p>
<h4>Race Condition 竞态条件</h4>
<p>这种状况是指基于特定序列或时机的事件的软件系统以不受控制的方式运行的行为,例如程序的并发任务执行的确切顺序。竞态条件可导致无法预测的行为,而不能通过代码检查立即发现。</p>
<h4>Deadlock 死锁</h4>
<p>两个(有时更多)东西——在大多数情况下,是线程——所谓的死锁是指它们都卡住了,并等待对方完成或执行其它操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。</p>
<h4>Thread Safe 线程安全</h4>
<p>线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 <code>NSDictionary</code> 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,<code>NSMutableDictionary</code> 就不是线程安全的,应该保证一次只能有一个线程访问它。</p>
<h4>Context Switch 上下文切换</h4>
<p>一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。</p>
<h3>Concurrency vs Parallelism 并发与并行</h3>
<p>并发和并行通常被一起提到,所以值得花些时间解释它们之间的区别。</p>
<p>并发代码的不同部分可以“同步”执行。然而,该怎样发生或是否发生都取决于系统。多核设备通过并行来同时执行多个线程;然而,为了使单核设备也能实现这一点,它们必须先运行一个线程,执行一个上下文切换,然后运行另一个线程或进程。这通常发生地足够快以致给我们并发执行地错觉,如下图所示:</p>
<p><img src="http://cdn1.raywenderlich.com/wp-content/uploads/2014/01/Concurrency_vs_Parallelism.png" alt="Concurrency_vs_Parallelism"></p>
<p>虽然你可以编写代码在 GCD 下并发执行,但 GCD 会决定有多少并行的需求。并行<em>要求</em>并发,但并发并不能<em>保证</em>并行。</p>
<p>更深入的观点是并发实际上是关于<em>构造</em>。当你在脑海中用 GCD 编写代码,你组织你的代码来暴露能同时运行的多个工作片段,以及不能同时运行的那些。如果你想深入此主题,看看 <a href="http://vimeo.com/49718712">这个由Rob Pike做的精彩的讲座</a> 。</p>
<h3>Queues 队列</h3>
<p>GCD 提供有 <code>dispatch queues</code> 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。</p>
<p>所有的调度队列(dispatch queues)自身都是线程安全的,你能从多个线程并行的访问它们。当你了解了调度队列如何为你自己代码的不同部分提供线程安全后,GCD的优点就是显而易见的。关于这一点的关键是选择正确<em>类型</em>的调度队列和正确的<em>调度函数</em>来提交你的工作。</p>
<p>在本节你会看到两种调度队列,都是由 GCD 提供的,然后看一些描述如何用调度函数添加工作到队列的例子。</p>
<h4>Serial Queues 串行队列</h4>
<p>串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且,你不知道在一个 Block 结束和下一个开始之间的时间长度,如下图所示:</p>
<p><img src="http://cdn4.raywenderlich.com/wp-content/uploads/2014/01/Serial-Queue-480x272.png" alt="Serial-Queue"></p>
<p>这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。</p>
<p>由于在串行队列中不会有两个任务并发运行,因此不会出现同时访问临界区的风险;相对于这些任务来说,这就从竞态条件下保护了临界区。所以如果访问临界区的唯一方式是通过提交到调度队列的任务,那么你就不需要担心临界区的安全问题了。</p>
<h4>Concurrent Queues 并发队列</h4>
<p>在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但这就是全部的保证了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。再说一遍,这完全取决于 GCD 。</p>
<p>下图展示了一个示例任务执行计划,GCD 管理着四个并发任务:</p>
<p><img src="http://cdn3.raywenderlich.com/wp-content/uploads/2014/01/Concurrent-Queue-480x272.png" alt="Concurrent-Queue"></p>
<p>注意 Block 1,2 和 3 都立马开始运行,一个接一个。在 Block 0 开始后,Block 1等待了好一会儿才开始。同样, Block 3 在 Block 2 之后才开始,但它先于 Block 2 完成。</p>
<p>何时开始一个 Block 完全取决于 GCD 。如果一个 Block 的执行时间与另一个重叠,也是由 GCD 来决定是否将其运行在另一个不同的核心上,如果那个核心可用,否则就用上下文切换的方式来执行不同的 Block 。</p>
<p>有趣的是, GCD 提供给你至少五个特定的队列,可根据队列类型选择使用。</p>
<h4>Queue Types 队列类型</h4>
<p>首先,系统提供给你一个叫做 <code>主队列(main queue)</code> 的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 <code>UIView</code> 或发送通知的。</p>
<p>系统同时提供给你好几个并发队列。它们叫做 <code>全局调度队列(Global Dispatch Queues)</code> 。目前的四个全局队列有着不同的优先级:<code>background</code>、<code>low</code>、<code>default</code> 以及 <code>high</code>。要知道,Apple 的 API 也会使用这些队列,所以你添加的任何任务都不会是这些队列中唯一的任务。</p>
<p>最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有<em>五个</em>队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。</p>
<p>以上是调度队列的大框架!</p>
<p>GCD 的“艺术”归结为选择合适的队列来调度函数以提交你的工作。体验这一点的最好方式是走一遍下边的列子,我们沿途会提供一些一般性的建议。</p>
<h2>入门</h2>
<p>既然本教程的目标是优化且安全的使用 GCD 调用来自不同线程的代码,那么你将从一个近乎完成的叫做 <code>GooglyPuff</code> 的项目入手。</p>
<p>GooglyPuff 是一个没有优化,线程不安全的应用,它使用 Core Image 的人脸检测 API 来覆盖一对曲棍球眼睛到被检测到的人脸上。对于基本的图像,可以从相机胶卷选择,或用预设好的URL从互联网下载。</p>
<p><a href="http://cdn4.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_Start_1.zip">点击此处下载项目</a></p>
<p>完成项目下载之后,将其解压到某个方便的目录,再用 Xcode 打开它并编译运行。这个应用看起来如下图所示:</p>
<p><img src="http://cdn3.raywenderlich.com/wp-content/uploads/2014/01/Workflow1.png" alt="Workflow"></p>
<p>注意当你选择 <code>Le Internet</code> 选项下载图片时,一个 <code>UIAlertView</code> 过早地弹出。你将在本系列教程地第二部分修复这个问题。</p>
<p>这个项目中有四个有趣的类:</p>
<ul>
<li>PhotoCollectionViewController:它是应用开始的第一个视图控制器。它用缩略图展示所有选定的照片。</li>
<li>PhotoDetailViewController:它执行添加曲棍球眼睛到图像上的逻辑,并用一个 UIScrollView 来显示结果图片。</li>
<li>Photo:这是一个类簇,它根据一个 <code>NSURL</code> 的实例或一个 <code>ALAsset</code> 的实例来实例化照片。这个类提供一个图像、缩略图以及从 URL 下载的状态。</li>
<li>PhotoManager:它管理所有 <code>Photo</code> 的实例.</li>
</ul>
<h2>用 dispatch_async 处理后台任务</h2>
<p>回到应用并从你的相机胶卷添加一些照片或使用 <code>Le Internet</code> 选项下载一些。</p>
<p>注意在按下 <code>PhotoCollectionViewController</code> 中的一个 <code>UICollectionViewCell</code> 到生成一个新的 <code>PhotoDetailViewController</code> 之间花了多久时间;你会注意到一个明显的滞后,特别是在比较慢的设备上查看很大的图。</p>
<p>在重载 <code>UIViewController 的 viewDidLoad</code> 时容易加入太多杂乱的工作(too much clutter),这通常会引起视图控制器出现前更长的等待。如果可能,最好是卸下一些工作放到后台,如果它们不是绝对必须要运行在加载时间里。</p>
<p>这听起来像是 <code>dispatch_async</code> 能做的事情!</p>
<p>打开 <code>PhotoDetailViewController</code> 并用下面的实现替换 <code>viewDidLoad</code> :</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="n">NSAssert</span><span class="p">(</span><span class="n">_image</span><span class="p">,</span> <span class="s">@"Image not set; required to use view controller"</span><span class="p">);</span>
<span class="nb">self</span><span class="p">.</span><span class="n">photoImageView</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">_image</span><span class="p">;</span>
<span class="c1">//Resize if neccessary to ensure it's not pixelated</span>
<span class="k">if</span> <span class="p">(</span><span class="n">_image</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o"><=</span> <span class="nb">self</span><span class="p">.</span><span class="n">photoImageView</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">height</span> <span class="o">&&</span>
<span class="n">_image</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span> <span class="o"><=</span> <span class="nb">self</span><span class="p">.</span><span class="n">photoImageView</span><span class="p">.</span><span class="n">bounds</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">photoImageView</span> <span class="nl">setContentMode</span><span class="p">:</span><span class="n">UIViewContentModeCenter</span><span class="p">];</span>
<span class="p">}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 1</span>
<span class="bp">UIImage</span> <span class="o">*</span><span class="n">overlayImage</span> <span class="o">=</span> <span class="p">[</span><span class="nb">self</span> <span class="nl">faceOverlayImageFromImage</span><span class="p">:</span><span class="n">_image</span><span class="p">];</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 2</span>
<span class="p">[</span><span class="nb">self</span> <span class="nl">fadeInNewImage</span><span class="p">:</span><span class="n">overlayImage</span><span class="p">];</span> <span class="c1">// 3</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>下面来说明上面的新代码所做的事:</p>
<ol>
<li>你首先将工作从主线程移到全局线程。因为这是一个 <code>dispatch_async()</code> ,Block 会被异步地提交,意味着调用线程地执行将会继续。这就使得 <code>viewDidLoad</code> 更早地在主线程完成,让加载过程感觉起来更加快速。同时,一个人脸检测过程会启动并将在稍后完成。</li>
<li>在这里,人脸检测过程完成,并生成了一个新的图像。既然你要使用此新图像更新你的 <code>UIImageView</code> ,那么你就添加一个新的 Block 到主线程。记住——你必须总是在主线程访问 UIKit 的类。</li>
<li>最后,你用 <code>fadeInNewImage:</code> 更新 UI ,它执行一个淡入过程切换到新的曲棍球眼睛图像。</li>
</ol>
<p>编译并运行你的应用;选择一个图像然后你会注意到视图控制器加载明显变快,曲棍球眼睛稍微在之后就加上了。这给应用带来了不错的效果,和之前的显示差别巨大。</p>
<p>进一步,如果你试着加载一个超大的图像,应用不会在加载视图控制器上“挂住”,这就使得应用具有很好伸缩性。</p>
<p>正如之前提到的, <code>dispatch_async</code> 添加一个 Block 到队列就立即返回了。任务会在之后由 GCD 决定执行。当你需要在后台执行一个基于网络或 CPU 紧张的任务时就使用 <code>dispatch_async</code> ,这样就不会阻塞当前线程。</p>
<p>下面是一个关于在 <code>dispatch_async</code> 上如何以及何时使用不同的队列类型的快速指导:</p>
<ul>
<li>自定义串行队列:当你想串行执行后台任务并追踪它时就是一个好选择。这消除了资源争用,因为你知道一次只有一个任务在执行。注意若你需要来自某个方法的数据,你必须内联另一个 Block 来找回它或考虑使用 <code>dispatch_sync</code>。</li>
<li>主队列(串行):这是在一个并发队列上完成任务后更新 UI 的共同选择。要这样做,你将在一个 Block 内部编写另一个 Block 。以及,如果你在主队列调用 <code>dispatch_async</code> 到主队列,你能确保这个新任务将在当前方法完成后的某个时间执行。</li>
<li>并发队列:这是在后台执行非 UI 工作的共同选择。</li>
</ul>
<h2>使用 dispatch_after 延后工作</h2>
<p>稍微考虑一下应用的 UX 。是否用户第一次打开应用时会困惑于不知道做什么?你是这样吗? :]</p>
<p>如果用户的 <code>PhotoManager</code> 里还没有任何照片,那么显示一个提示会是个好主意!然而,你同样要考虑用户的眼睛会如何在主屏幕上浏览:如果你太快的显示一个提示,他们的眼睛还徘徊在视图的其它部分上,他们很可能会错过它。</p>
<p>显示提示之前延迟一秒钟就足够捕捉到用户的注意,他们此时已经第一次看过了应用。</p>
<p>添加如下代码到到 PhotoCollectionViewController.m 中 showOrHideNavPrompt 的废止实现里:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">showOrHideNavPrompt</span>
<span class="p">{</span>
<span class="n">NSUInteger</span> <span class="n">count</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">]</span> <span class="n">photos</span><span class="p">].</span><span class="n">count</span><span class="p">;</span>
<span class="kt">double</span> <span class="n">delayInSeconds</span> <span class="o">=</span> <span class="mf">1.0</span><span class="p">;</span>
<span class="kt">dispatch_time_t</span> <span class="n">popTime</span> <span class="o">=</span> <span class="n">dispatch_time</span><span class="p">(</span><span class="n">DISPATCH_TIME_NOW</span><span class="p">,</span> <span class="p">(</span><span class="kt">int64_t</span><span class="p">)(</span><span class="n">delayInSeconds</span> <span class="o">*</span> <span class="n">NSEC_PER_SEC</span><span class="p">));</span> <span class="c1">// 1 </span>
<span class="n">dispatch_after</span><span class="p">(</span><span class="n">popTime</span><span class="p">,</span> <span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">(</span><span class="kt">void</span><span class="p">){</span> <span class="c1">// 2 </span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">count</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">navigationItem</span> <span class="nl">setPrompt</span><span class="p">:</span><span class="s">@"Add photos with faces to Googlyify them!"</span><span class="p">];</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="p">[</span><span class="nb">self</span><span class="p">.</span><span class="n">navigationItem</span> <span class="nl">setPrompt</span><span class="p">:</span><span class="nb">nil</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>showOrHideNavPrompt 在 viewDidLoad 中执行,以及 UICollectionView 被重新加载的任何时候。按照注释数字顺序看看:</p>
<ol>
<li>你声明了一个变量指定要延迟的时长。</li>
<li>然后等待 <code>delayInSeconds</code> 给定的时长,再异步地添加一个 Block 到主线程。</li>
</ol>
<p>编译并运行应用。应该有一个轻微地延迟,这有助于抓住用户的注意力并展示所要做的事情。</p>
<p><code>dispatch_after</code> 工作起来就像一个延迟版的 <code>dispatch_async</code> 。你依然不能控制实际的执行时间,且一旦 <code>dispatch_after</code> 返回也就不能再取消它。</p>
<p>不知道何时适合使用 <code>dispatch_after</code> ?</p>
<ul>
<li>自定义串行队列:在一个自定义串行队列上使用 <code>dispatch_after</code> 要小心。你最好坚持使用主队列。</li>
<li>主队列(串行):是使用 <code>dispatch_after</code> 的好选择;Xcode 提供了一个不错的自动完成模版。</li>
<li>并发队列:在并发队列上使用 <code>dispatch_after</code> 也要小心;你会这样做就比较罕见。还是在主队列做这些操作吧。</li>
</ul>
<h2>让你的单例线程安全</h2>
<p>单例,不论喜欢还是讨厌,它们在 iOS 上的流行情况就像网上的猫。 :]</p>
<p>一个常见的担忧是它们常常不是线程安全的。这个担忧十分合理,基于它们的用途:单例常常被多个控制器同时访问。</p>
<p>单例的线程担忧范围从初始化开始,到信息的读和写。<code>PhotoManager</code> 类被实现为单例——它在目前的状态下就会被这些问题所困扰。要看看事情如何很快地失去控制,你将在单例实例上创建一个控制好的竞态条件。</p>
<p>导航到 <code>PhotoManager.m</code> 并找到 <code>sharedManager</code> ;它看起来如下:</p>
<div class="highlight"><pre><code><span class="p">+</span> <span class="p">(</span><span class="kt">instancetype</span><span class="p">)</span><span class="nf">sharedManager</span>
<span class="p">{</span>
<span class="k">static</span> <span class="n">PhotoManager</span> <span class="o">*</span><span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sharedPhotoManager</span><span class="p">)</span> <span class="p">{</span>
<span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_photosArray</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">sharedPhotoManager</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>当前状态下,代码相当简单;你创建了一个单例并初始化一个叫做 <code>photosArray</code> 的 <code>NSMutableArray</code> 属性。</p>
<p>然而,<code>if</code> 条件分支不是<a href="http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1#Terminology">线程安全</a>的;如果你多次调用这个方法,有一个可能性是在某个线程(就叫它线程A)上进入 <code>if</code> 语句块并可能在 <code>sharedPhotoManager</code> 被分配内存前发生一个上下文切换。然后另一个线程(线程B)可能进入 <code>if</code> ,分配单例实例的内存,然后退出。</p>
<p>当系统上下文切换回线程A,你会分配另外一个单例实例的内存,然后退出。在那个时间点,你有了两个单例的实例——很明显这不是你想要的(译者注:这还能叫单例吗?)!</p>
<p>要强制这个(竞态)条件发生,替换 <code>PhotoManager.m</code> 中的 <code>sharedManager</code> 为下面的实现:</p>
<div class="highlight"><pre><code><span class="p">+</span> <span class="p">(</span><span class="kt">instancetype</span><span class="p">)</span><span class="nf">sharedManager</span>
<span class="p">{</span>
<span class="k">static</span> <span class="n">PhotoManager</span> <span class="o">*</span><span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sharedPhotoManager</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="bp">NSThread</span> <span class="nl">sleepForTimeInterval</span><span class="p">:</span><span class="mi">2</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"Singleton has memory address at: %@"</span><span class="p">,</span> <span class="n">sharedPhotoManager</span><span class="p">);</span>
<span class="p">[</span><span class="bp">NSThread</span> <span class="nl">sleepForTimeInterval</span><span class="p">:</span><span class="mi">2</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_photosArray</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">sharedPhotoManager</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>上面的代码中你用 <code>NSThread 的 sleepForTimeInterval:</code> 类方法来强制发生一个上下文切换。</p>
<p>打开 <code>AppDelegate.m</code> 并添加如下代码到 <code>application:didFinishLaunchingWithOptions:</code> 的最开始处:</p>
<div class="highlight"><pre><code><span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="p">[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">];</span>
<span class="p">});</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="p">[</span><span class="n">PhotoManager</span> <span class="n">sharedManager</span><span class="p">];</span>
<span class="p">});</span>
</code></pre></div>
<p>这里创建了多个异步并发调用来实例化单例,然后引发上面描述的竞态条件。</p>
<p>编译并运行项目;查看控制台输出,你会看到多个单例被实例化,如下所示:</p>
<p><img src="http://cdn2.raywenderlich.com/wp-content/uploads/2014/01/NSLog-Race-Condition-700x90.png" alt="NSLog-Race-Condition"></p>
<p>注意到这里有好几行显示着不同地址的单例实例。这明显违背了单例的目的,对吧?:]</p>
<p>这个输出向你展示了临界区被执行多次,而它只应该执行一次。现在,固然是你自己强制这样的状况发生,但你可以想像一下这个状况会怎样在无意间发生。</p>
<blockquote><p>注意:基于其它你无法控制的系统事件,NSLog 的数量有时会显示多个。线程问题极其难以调试,因为它们往往难以重现。</p>
</blockquote>
<p>要纠正这个状况,实例化代码应该只执行一次,并阻塞其它实例在 <code>if</code> 条件的临界区运行。这刚好就是 <code>dispatch_once</code> 能做的事。</p>
<p>在单例初始化方法中用 <code>dispatch_once</code> 取代 <code>if</code> 条件判断,如下所示:</p>
<div class="highlight"><pre><code><span class="p">+</span> <span class="p">(</span><span class="kt">instancetype</span><span class="p">)</span><span class="nf">sharedManager</span>
<span class="p">{</span>
<span class="k">static</span> <span class="n">PhotoManager</span> <span class="o">*</span><span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">dispatch_once_t</span> <span class="n">onceToken</span><span class="p">;</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">onceToken</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="p">[</span><span class="bp">NSThread</span> <span class="nl">sleepForTimeInterval</span><span class="p">:</span><span class="mi">2</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"Singleton has memory address at: %@"</span><span class="p">,</span> <span class="n">sharedPhotoManager</span><span class="p">);</span>
<span class="p">[</span><span class="bp">NSThread</span> <span class="nl">sleepForTimeInterval</span><span class="p">:</span><span class="mi">2</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_photosArray</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="p">});</span>
<span class="k">return</span> <span class="n">sharedPhotoManager</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>编译并运行你的应用;查看控制台输出,你会看到有且仅有一个单例的实例——这就是你对单例的期望!:]</p>
<p>现在你已经明白了防止竞态条件的重要性,从 <code>AppDelegate.m</code> 中移除 <code>dispatch_async</code> 语句,并用下面的实现替换 <code>PhotoManager</code> 单例的初始化:</p>
<div class="highlight"><pre><code><span class="p">+</span> <span class="p">(</span><span class="kt">instancetype</span><span class="p">)</span><span class="nf">sharedManager</span>
<span class="p">{</span>
<span class="k">static</span> <span class="n">PhotoManager</span> <span class="o">*</span><span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">dispatch_once_t</span> <span class="n">onceToken</span><span class="p">;</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">onceToken</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_photosArray</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="p">});</span>
<span class="k">return</span> <span class="n">sharedPhotoManager</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p><code>dispatch_once()</code> 以线程安全的方式执行且仅执行其代码块一次。试图访问临界区(即传递给 <code>dispatch_once</code> 的代码)的不同的线程会在临界区已有一个线程的情况下被阻塞,直到临界区完成为止。</p>
<p><img src="http://cdn3.raywenderlich.com/wp-content/uploads/2014/01/Highlander_dispatch_once-480x274.png" alt="Highlander_dispatch_once"></p>
<p>需要记住的是,这只是让访问共享实例线程安全。它绝对没有让类本身线程安全。类中可能还有其它竞态条件,例如任何操纵内部数据的情况。这些需要用其它方式来保证线程安全,例如同步访问数据,你将在下面几个小节看到。</p>
<h2>处理读者与写者问题</h2>
<p>线程安全实例不是处理单例时的唯一问题。如果单例属性表示一个可变对象,那么你就需要考虑是否那个对象自身线程安全。</p>
<p>如果问题中的这个对象是一个 Foundation 容器类,那么答案是——“很可能不安全”!Apple 维护一个<a href="https://developer.apple.com/library/mac/documentation/cocoa/conceptual/multithreading/ThreadSafetySummary/ThreadSafetySummary.html">有用且有些心寒的列表</a>,众多的 Foundation 类都不是线程安全的。 <code>NSMutableArray</code>,已用于你的单例,正在那个列表里休息。</p>
<p>虽然许多线程可以同时读取 <code>NSMutableArray</code> 的一个实例而不会产生问题,但当一个线程正在读取时让另外一个线程修改数组就是不安全的。你的单例在目前的状况下不能预防这种情况的发生。</p>
<p>要分析这个问题,看看 <code>PhotoManager.m</code> 中的 <code>addPhoto:</code>,转载如下:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">addPhoto:</span><span class="p">(</span><span class="n">Photo</span> <span class="o">*</span><span class="p">)</span><span class="nv">photo</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">photo</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="n">_photosArray</span> <span class="nl">addObject</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="p">[</span><span class="nb">self</span> <span class="n">postContentAddedNotification</span><span class="p">];</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>这是一个<code>写</code>方法,它修改一个私有可变数组对象。</p>
<p>现在看看 <code>photos</code> ,转载如下:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="bp">NSArray</span> <span class="o">*</span><span class="p">)</span><span class="nf">photos</span>
<span class="p">{</span>
<span class="k">return</span> <span class="p">[</span><span class="bp">NSArray</span> <span class="nl">arrayWithArray</span><span class="p">:</span><span class="n">_photosArray</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>这是所谓的<code>读</code>方法,它读取可变数组。它为调用者生成一个不可变的拷贝,防止调用者不当地改变数组,但这不能提供任何保护来对抗当一个线程调用读方法 <code>photos</code> 的同时另一个线程调用写方法 <code>addPhoto:</code> 。</p>
<p>这就是软件开发中经典的<code>读者写者问题</code>。GCD 通过用 <code>dispatch barriers</code> 创建一个<code>读者写者锁</code> 提供了一个优雅的解决方案。</p>
<p>Dispatch barriers 是一组函数,在并发队列上工作时扮演一个串行式的瓶颈。使用 GCD 的障碍(barrier)API 确保提交的 Block 在那个特定时间上是指定队列上唯一被执行的条目。这就意味着所有的先于调度障碍提交到队列的条目必能在这个 Block 执行前完成。</p>
<p>当这个 Block 的时机到达,调度障碍执行这个 Block 并确保在那个时间里队列不会执行任何其它 Block 。一旦完成,队列就返回到它默认的实现状态。 GCD 提供了同步和异步两种障碍函数。</p>
<p>下图显示了障碍函数对多个异步队列的影响:</p>
<p><img src="http://cdn1.raywenderlich.com/wp-content/uploads/2014/01/Dispatch-Barrier-480x272.png" alt="Dispatch-Barrier"></p>
<p>注意到正常部分的操作就如同一个正常的并发队列。但当障碍执行时,它本质上就如同一个串行队列。也就是,障碍是唯一在执行的事物。在障碍完成后,队列回到一个正常并发队列的样子。</p>
<p>下面是你何时会——和不会——使用障碍函数的情况:</p>
<ul>
<li>自定义串行队列:一个很坏的选择;障碍不会有任何帮助,因为不管怎样,一个串行队列一次都只执行一个操作。</li>
<li>全局并发队列:要小心;这可能不是最好的主意,因为其它系统可能在使用队列而且你不能垄断它们只为你自己的目的。</li>
<li>自定义并发队列:这对于原子或临界区代码来说是极佳的选择。任何你在设置或实例化的需要线程安全的事物都是使用障碍的最佳候选。</li>
</ul>
<p>由于上面唯一像样的选择是自定义并发队列,你将创建一个你自己的队列去处理你的障碍函数并分开读和写函数。且这个并发队列将允许多个多操作同时进行。</p>
<p>打开 <code>PhotoManager.m</code>,添加如下私有属性到类扩展中:</p>
<div class="highlight"><pre><code><span class="k">@interface</span> <span class="nc">PhotoManager</span> <span class="p">()</span>
<span class="k">@property</span> <span class="p">(</span><span class="k">nonatomic</span><span class="p">,</span><span class="k">strong</span><span class="p">,</span><span class="k">readonly</span><span class="p">)</span> <span class="bp">NSMutableArray</span> <span class="o">*</span><span class="n">photosArray</span><span class="p">;</span>
<span class="k">@property</span> <span class="p">(</span><span class="k">nonatomic</span><span class="p">,</span> <span class="k">strong</span><span class="p">)</span> <span class="kt">dispatch_queue_t</span> <span class="n">concurrentPhotoQueue</span><span class="p">;</span> <span class="c1">///< Add this</span>
<span class="k">@end</span>
</code></pre></div>
<p>找到 <code>addPhoto:</code> 并用下面的实现替换它:</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">addPhoto:</span><span class="p">(</span><span class="n">Photo</span> <span class="o">*</span><span class="p">)</span><span class="nv">photo</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">photo</span><span class="p">)</span> <span class="p">{</span> <span class="c1">// 1</span>
<span class="n">dispatch_barrier_async</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">concurrentPhotoQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 2 </span>
<span class="p">[</span><span class="n">_photosArray</span> <span class="nl">addObject</span><span class="p">:</span><span class="n">photo</span><span class="p">];</span> <span class="c1">// 3</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 4</span>
<span class="p">[</span><span class="nb">self</span> <span class="n">postContentAddedNotification</span><span class="p">];</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>你新写的函数是这样工作的:</p>
<ol>
<li>在执行下面所有的工作前检查是否有合法的相片。</li>
<li>添加写操作到你的自定义队列。当临界区在稍后执行时,这将是你队列中唯一执行的条目。</li>
<li>这是添加对象到数组的实际代码。由于它是一个障碍 Block ,这个 Block 永远不会同时和其它 Block 一起在 concurrentPhotoQueue 中执行。</li>
<li>最后你发送一个通知说明完成了添加图片。这个通知将在主线程被发送因为它将会做一些 UI 工作,所以在此为了通知,你异步地调度另一个任务到主线程。</li>
</ol>
<p>这就处理了写操作,但你还需要实现 <code>photos</code> 读方法并实例化 <code>concurrentPhotoQueue</code> 。</p>
<p>在写者打扰的情况下,要确保线程安全,你需要在 <code>concurrentPhotoQueue</code> 队列上执行读操作。既然你需要从函数返回,你就不能异步调度到队列,因为那样在读者函数返回之前不一定运行。</p>
<p>在这种情况下,<code>dispatch_sync</code> 就是一个绝好的候选。</p>
<p><code>dispatch_sync()</code> 同步地提交工作并在返回前等待它完成。使用 <code>dispatch_sync</code> 跟踪你的调度障碍工作,或者当你需要等待操作完成后才能使用 Block 处理过的数据。如果你使用第二种情况做事,你将不时看到一个 <code>__block</code> 变量写在 <code>dispatch_sync</code> 范围之外,以便返回时在 <code>dispatch_sync</code> 使用处理过的对象。</p>
<p>但你需要很小心。想像如果你调用 <code>dispatch_sync</code> 并放在你已运行着的当前队列。这会导致死锁,因为调用会一直等待直到 Block 完成,但 Block 不能完成(它甚至不会开始!),直到当前已经存在的任务完成,而当前任务无法完成!这将迫使你自觉于你正从哪个队列调用——以及你正在传递进入哪个队列。</p>
<p>下面是一个快速总览,关于在何时以及何处使用 <code>dispatch_sync</code> :</p>
<ul>
<li>自定义串行队列:在这个状况下要非常小心!如果你正运行在一个队列并调用 <code>dispatch_sync</code> 放在同一个队列,那你就百分百地创建了一个死锁。</li>
<li>主队列(串行):同上面的理由一样,必须非常小心!这个状况同样有潜在的导致死锁的情况。</li>
<li>并发队列:这才是做同步工作的好选择,不论是通过调度障碍,或者需要等待一个任务完成才能执行进一步处理的情况。</li>
</ul>
<p>继续在 <code>PhotoManager.m</code> 上工作,用下面的实现替换 <code>photos</code> :</p>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="bp">NSArray</span> <span class="o">*</span><span class="p">)</span><span class="nf">photos</span>
<span class="p">{</span>
<span class="k">__block</span> <span class="bp">NSArray</span> <span class="o">*</span><span class="n">array</span><span class="p">;</span> <span class="c1">// 1</span>
<span class="n">dispatch_sync</span><span class="p">(</span><span class="nb">self</span><span class="p">.</span><span class="n">concurrentPhotoQueue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span> <span class="c1">// 2</span>
<span class="n">array</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSArray</span> <span class="nl">arrayWithArray</span><span class="p">:</span><span class="n">_photosArray</span><span class="p">];</span> <span class="c1">// 3</span>
<span class="p">});</span>
<span class="k">return</span> <span class="n">array</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>这就是你的读函数。按顺序看看编过号的注释,有这些:</p>
<ol>
<li><code>__block</code> 关键字允许对象在 Block 内可变。没有它,<code>array</code> 在 Block 内部就只是只读的,你的代码甚至不能通过编译。</li>
<li>在 <code>concurrentPhotoQueue</code> 上同步调度来执行读操作。</li>
<li>将相片数组存储在 <code>array</code> 内并返回它。</li>
</ol>
<p>最后,你需要实例化你的 <code>concurrentPhotoQueue</code> 属性。修改 <code>sharedManager</code> 以便像下面这样初始化队列:</p>
<div class="highlight"><pre><code><span class="p">+</span> <span class="p">(</span><span class="kt">instancetype</span><span class="p">)</span><span class="nf">sharedManager</span>
<span class="p">{</span>
<span class="k">static</span> <span class="n">PhotoManager</span> <span class="o">*</span><span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="k">static</span> <span class="kt">dispatch_once_t</span> <span class="n">onceToken</span><span class="p">;</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">onceToken</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="n">sharedPhotoManager</span> <span class="o">=</span> <span class="p">[[</span><span class="n">PhotoManager</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_photosArray</span> <span class="o">=</span> <span class="p">[</span><span class="bp">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="c1">// ADD THIS:</span>
<span class="n">sharedPhotoManager</span><span class="o">-></span><span class="n">_concurrentPhotoQueue</span> <span class="o">=</span> <span class="n">dispatch_queue_create</span><span class="p">(</span><span class="s">"com.selander.GooglyPuff.photoQueue"</span><span class="p">,</span>
<span class="n">DISPATCH_QUEUE_CONCURRENT</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">return</span> <span class="n">sharedPhotoManager</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>这里使用 <code>dispatch_queue_create</code> 初始化 <code>concurrentPhotoQueue</code> 为一个并发队列。第一个参数是反向DNS样式命名惯例;确保它是描述性的,将有助于调试。第二个参数指定你的队列是串行还是并发。</p>
<blockquote><p>注意:当你在网上搜索例子时,你会经常看人们传递 <code>0</code> 或者 <code>NULL</code> 给 <code>dispatch_queue_create</code> 的第二个参数。这是一个创建串行队列的过时方式;明确你的参数总是更好。</p>
</blockquote>
<p>恭喜——你的 <code>PhotoManager</code> 单例现在是线程安全的了。不论你在何处或怎样读或写你的照片,你都有这样的自信,即它将以安全的方式完成,不会出现任何惊吓。</p>
<h2>A Visual Review of Queueing 队列的虚拟回顾</h2>
<p>依然没有 100% 地掌握 GCD 的要领?确保你可以使用 GCD 函数轻松地创建简单的例子,使用断点和 <code>NSLog</code> 语句保证自己明白当下发生的情况。</p>
<p>我在下面提供了两个 GIF动画来帮助你巩固对 <code>dispatch_async</code> 和 <code>dispatch_sync</code> 的理解。包含在每个 GIF 中的代码可以提供视觉辅助;仔细注意 GIF 左边显示代码断点的每一步,以及右边相关队列的状态。</p>
<h3>dispatch_sync 回顾</h3>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="n">dispatch_sync</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"First Log"</span><span class="p">);</span>
<span class="p">});</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"Second Log"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p><img src="http://cdn1.raywenderlich.com/wp-content/uploads/2014/01/dispatch_sync_in_action.gif" alt="dispatch_sync_in_action"></p>
<p>下面是图中几个步骤的说明:</p>
<ol>
<li>主队列一路按顺序执行任务——接着是一个实例化 <code>UIViewController</code> 的任务,其中包含了 <code>viewDidLoad</code> 。</li>
<li><code>viewDidLoad</code> 在主线程执行。</li>
<li>主线程目前在 <code>viewDidLoad</code> 内,正要到达 <code>dispatch_sync</code> 。</li>
<li><code>dispatch_sync</code> Block 被添加到一个全局队列中,将在稍后执行。进程将在主线程挂起直到该 Block 完成。同时,全局队列并发处理任务;要记得 Block 在全局队列中将按照 FIFO 顺序出列,但可以并发执行。</li>
<li>全局队列处理 <code>dispatch_sync</code> Block 加入之前已经出现在队列中的任务。</li>
<li>终于,轮到 <code>dispatch_sync</code> Block 。</li>
<li>这个 Block 完成,因此主线程上的任务可以恢复。</li>
<li><code>viewDidLoad</code> 方法完成,主队列继续处理其他任务。</li>
</ol>
<p><code>dispatch_sync</code> 添加任务到一个队列并等待直到任务完成。<code>dispatch_async</code> 做类似的事情,但不同之处是它不会等待任务的完成,而是立即继续“调用线程”的其它任务。</p>
<h3>dispatch_async 回顾</h3>
<div class="highlight"><pre><code><span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="nb">super</span> <span class="n">viewDidLoad</span><span class="p">];</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_HIGH</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"First Log"</span><span class="p">);</span>
<span class="p">});</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">@"Second Log"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p><img src="http://cdn1.raywenderlich.com/wp-content/uploads/2014/01/dispatch_async_in_action.gif" alt="dispatch_async_in_action"></p>
<ol>
<li>主队列一路按顺序执行任务——接着是一个实例化 <code>UIViewController</code> 的任务,其中包含了 <code>viewDidLoad</code> 。</li>
<li><code>viewDidLoad</code> 在主线程执行。</li>
<li>主线程目前在 <code>viewDidLoad</code> 内,正要到达 <code>dispatch_async</code> 。</li>
<li><code>dispatch_async</code> Block 被添加到一个全局队列中,将在稍后执行。</li>
<li><code>viewDidLoad</code> 在添加 <code>dispatch_async</code> 到全局队列后继续进行,主线程把注意力转向剩下的任务。同时,全局队列并发地处理它未完成地任务。记住 Block 在全局队列中将按照 FIFO 顺序出列,但可以并发执行。</li>
<li>添加到 <code>dispatch_async</code> 的代码块开始执行。</li>
<li><code>dispatch_async</code> Block 完成,两个 <code>NSLog</code> 语句将它们的输出放在控制台上。</li>
</ol>
<p>在这个特定的实例中,第二个 <code>NSLog</code> 语句执行,跟着是第一个 <code>NSLog</code> 语句。并不总是这样——着取决于给定时刻硬件正在做的事情,而且你无法控制或知晓哪个语句会先执行。“第一个” <code>NSLog</code> 在某些调用情况下会第一个执行。</p>
<h2>下一步怎么走?</h2>
<p>在本教程中,你学习了如何让你的代码线程安全,以及在执行 CPU 密集型任务时如何保持主线程的响应性。</p>
<p>你可以下载<a href="http://cdn2.raywenderlich.com/wp-content/uploads/2014/01/GooglyPuff_End_1.zip"> GooglyPuff 项目</a>,它包含了目前所有本教程中编写的实现。在本教程的<a href="https://github.com/nixzhu/dev-blog/blob/master/2014-05-14-grand-central-dispatch-in-depth-part-2.md">第二部分</a>,你将继续改进这个项目。</p>
<p>如果你计划优化你自己的应用,那你应该用 <code>Instruments</code> 中的 <code>Time Profile</code> 模版分析你的工作。对这个工具的使用超出了本教程的范围,你可以看看 <a href="http://www.raywenderlich.com/23037/how-to-use-instruments-in-xcode">如何使用Instruments</a> 来得到一个很好的概述。</p>
<p>同时请确保在真实设备上分析,而在模拟器上测试会对程序速度产生非常不准确的印象。</p>
<p>在教程的下一部分,你将更加深入到 GCD 的 API 中,做一些更 Cool 的东西。</p>
GCD 深入理解:第一部分
如何定义和区分高级软件开发工程师
https://www.zruibin.cn/article/ru_he_ding_yi_he_qu_fen_gao_ji_ruan_jian_kai_fa_gong_cheng_s.html
2015-10-16 09:30
2015-10-16 09:30
<p>原文出处:<a href="http://www.infoq.com/cn/news/2015/06/Scrum-software-engineer/" target="blank">如何定义和区分高级软件开发工程师</a></p>
<p>在软件开发领域,高级开发工程师通常是指那些编写代码超过3年的人。这些人可能会被放到领导的位置,但经常会产生非常糟糕的结果。Matt Briggs是一名高级开发工程师兼Scrum管理员。他认为,单纯使用年限来划分开发人员存在问题,两个同样具有10年开发经验的开发人员可能大不相同。近日,他发表了一篇博文,根据开发者所能发挥的作用划分软件开发工程师的成长阶段。</p>
<h3>初级开发工程师</h3>
<p>初级开发工程师通常是指那些刚刚结束学生生涯的开发者。他们以为自己什么都懂,但是面临问题时却又一筹莫展。他们不熟悉用到的工具,也不了解当前代码库。因此,他们需要监督,需要大量的培训和指导,否则可能几年过去了,他们仍然是初级开发工程师。</p>
<p>一名优秀的初级开发工程师应该能够快速完成他人分配的工作,并且保证质量。</p>
<h3>中级开发工程师</h3>
<p>中级开发工程师不像初级开发工程师那样只专注于代码,他们开始通过试验、文献和与其他程序员的讨论寻找构建系统的正确方式,也就是说他们会学习软件构建理论。</p>
<p>一名优秀的中级开发工程师不需要监督。他们可以自己提出代码设计的问题,并在设计讨论中发挥重要的作用。他们也是开发团队的主力。但是,他们在遵循“设计模式”和“领域驱动设计”等理论方法设计系统时,可能会出现过度设计的情况。因此,有必要对他们进行进一步的指导和更高层次的监督。</p>
<p>Briggs指出,绝大多数的高级软件开发工程师和团队负责人实际上都是中级开发工程师,只是大部分人都没有意识到这一点。</p>
<h3>高级开发工程师</h3>
<p>高级开发工程师抛弃了支配中级开发工程师的复杂性,追求简单至上。他们不再按照知识划分开发者,而是了解每个人的优势和不足。在理论运用方面,他们重视“上下文”,而不是一味地追求“正确方式”。他们知道,构建优秀的软件,唯一的方式是改造理论方法,适应客户、代码库、团队、工具和组织的需求,在设计模式、库、框架和流程之间寻找平衡。</p>
<p>高级开发工程师更多地为别人考虑,了解组织和客户如何工作,知道他们的价值所在。他们从来不会说“这不是我的工作”。他们的工作是提供问题解决方案,总是考虑他们的工作会为组织和客户带来什么价值,而不是他们会有多大的工作量。</p>
<p>中级开发工程师会钻研一些令人厌烦的工作,但高级开发工程师会退一步,看看是什么导致了这样的工作。他们会评估修复问题根本原因的成本,从而决定是直接修复,还是先让系统运行起来,后续再修复。</p>
<p>高级开发工程师清楚地知道,他们的主要作用是让团队变得更好。同时,他们也深知,领导不是权力,而是授权,不是命令,而是服务。</p>
<p>Briggs指出,如果团队没有高级开发工程师担任领导角色,那么项目注定要失败。高级开发工程师是唯一有资格选择技术和平台的人,因此,从项目开始的第一天起就应该有一个这样的人。</p>
<p>Briggs承认,这种划分方式过分简单,却也可以提供一些有用的信息。他建议,企业在招聘时要考虑团队和组织的人才构成。</p>
资深开发与普通开发的区别
CMake入门实战
https://www.zruibin.cn/article/cmake_ru_men_shi_zhan.html
2015-07-11 13:21
2015-10-13 20:15
<p><a href="http://www.hahack.com/codes/cmake/" target="_blank" rel="nofollow">http://www.hahack.com/codes/cmake/</a></p>
<h2>什么是 CMake</h2>
<blockquote><p>All problems in computer science can be solved by another level of indirection.
David Wheeler</p>
</blockquote>
<p style="margin-top: 0px; color: rgb(51, 51, 51); font-family: "Open Sans", "Helvetica Neue", Helvetica, "Microsoft YaHei", "WenQuanYi Micro Hei", Arial, sans-serif; font-size: 17px; line-height: 20px; white-space: normal; background-color: rgb(255, 255, 255);">你或许听过好几种 Make 工具,例如<a href="http://www.hahack.com/wiki/tools-makefile.html" rel="nofollow">GNU Make</a>,QT 的<a href="http://qt-project.org/doc/qt-4.8/qmake-manual.html" target="_blank" rel="nofollow">qmake</a>,微软的<a href="http://msdn.microsoft.com/en-us/library/ms930369.aspx" target="_blank" rel="nofollow">MS nmake</a>,BSD Make(<a href="http://www.freebsd.org/doc/en/books/pmake/" target="_blank" rel="nofollow">pmake</a>),<a href="http://makepp.sourceforge.net/" target="_blank" rel="nofollow">Makepp</a>,等等。这些 Make 工具遵循着不同的规范和标准,所执行的 Makefile 格式也千差万别。这样就带来了一个严峻的问题:如果软件想跨平台,必须要保证能够在不同平台编译。而如果使用上面的 Make 工具,就得为每一种标准写一次 Makefile ,这将是一件让人抓狂的工作。</p>
<p style="margin-top: 0px; color: rgb(51, 51, 51); font-family: "Open Sans", "Helvetica Neue", Helvetica, "Microsoft YaHei", "WenQuanYi Micro Hei", Arial, sans-serif; font-size: 17px; line-height: 20px; white-space: normal; background-color: rgb(255, 255, 255);">CMake<span style="display: block; float: right; width: 0px;"></span></span>就是针对上面问题所设计的工具:它首先允许开发者编写一种平台无关的 CMakeList.txt 文件来定制整个编译流程,然后再根据目标用户的平台进一步生成所需的本地化 Makefile 和工程文件,如 Unix 的 Makefile 或 Windows 的 Visual Studio 工程。从而做到“Write once, run everywhere”。显然,CMake 是一个比上述几种 make 更高级的编译配置工具。一些使用 CMake 作为项目架构系统的知名开源项目有<a href="http://www.vtk.org/" target="_blank" rel="nofollow">VTK</a>、<a href="http://www.itk.org/" target="_blank" rel="nofollow">ITK</a>、<a href="http://kde.org/" target="_blank" rel="nofollow">KDE</a>、<a href="http://www.opencv.org.cn/opencvdoc/2.3.2/html/modules/core/doc/intro.html" target="_blank" rel="nofollow">OpenCV</a>、<a href="http://www.openscenegraph.org/" target="_blank" rel="nofollow">OSG</a>等<a href="http://www.hahack.com/codes/cmake/#fn1" rel="nofollow">1</a>。</p><p><br/></p>
<h3>在 linux 平台下使用 CMake 生成 Makefile 并编译的流程如下:</h3>
<p>编写 CMake 配置文件 CMakeLists.txt 。</p>
<p>执行命令 cmake PATH 或者 ccmake PATH 生成 Makefile 1
1ccmake 和 cmake 的区别在于前者提供了一个交互式的界面。
。其中, PATH 是 CMakeLists.txt 所在的目录。</p>
<h4>使用 make 命令进行编译。</h4>
<p>本文将从实例入手,一步步讲解 CMake 的常见用法,文中所有的实例代码可以在这里找到。如果你读完仍觉得意犹未尽,可以继续学习我在文章末尾提供的其他资源。
入门案例:单个源文件</p>
<h5>本节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo1" target="_blank" rel="nofollow">Demo1</a>。</h5>
<p>对于简单的项目,只需要写几行代码就可以了。例如,假设现在我们的项目中只有一个源文件 main.cc ,该程序的用途是计算一个数的指数幂。</p>
<div class="highlight"><pre><code><span class="cp">#include </span>
<span class="cp">#include </span>
<span class="cm">/**</span>
<span class="cm"> * power - Calculate the power of number.</span>
<span class="cm"> * @param base: Base value.</span>
<span class="cm"> * @param exponent: Exponent value.</span>
<span class="cm"> *</span>
<span class="cm"> * @return base raised to the power exponent.</span>
<span class="cm"> */</span>
<span class="kt">double</span> <span class="nf">power</span><span class="p">(</span><span class="kt">double</span> <span class="n">base</span><span class="p">,</span> <span class="kt">int</span> <span class="n">exponent</span><span class="p">)</span>
<span class="p">{</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="n">base</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">exponent</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">){</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">result</span> <span class="o">*</span> <span class="n">base</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o"><</span> <span class="mi">3</span><span class="p">){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Usage: %s base exponent </span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">double</span> <span class="n">base</span> <span class="o">=</span> <span class="n">atof</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="kt">int</span> <span class="n">exponent</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">power</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%g ^ %d is %g</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h4>编写 CMakeLists.txt</h4>
<p>首先编写 CMakeLists.txt 文件,并保存在与 main.cc 源文件同个目录下:</p>
<div class="highlight"><pre><code><span class="cp"># CMake 最低版本号要求</span>
<span class="n">cmake_minimum_required</span> <span class="p">(</span><span class="n">VERSION</span> <span class="mf">2.8</span><span class="p">)</span>
<span class="cp"># 项目信息</span>
<span class="n">project</span> <span class="p">(</span><span class="n">Demo1</span><span class="p">)</span>
<span class="cp"># 指定生成目标</span>
<span class="n">add_executable</span><span class="p">(</span><span class="n">Demo</span> <span class="n">main</span><span class="p">.</span><span class="n">cc</span><span class="p">)</span>
</code></pre></div>
<p>CMakeLists.txt 的语法比较简单,由命令、注释和空格组成,其中命令是不区分大小写的。符号 # 后面的内容被认为是注释。命令由命令名称、小括号和参数组成,参数之间使用空格进行间隔。
对于上面的 CMakeLists.txt 文件,依次出现了几个命令:</p>
<p>cmake_minimum_required:指定运行此配置文件所需的 CMake 的最低版本;</p>
<p>project:参数是 main,该命令表示项目的名称是 main 。</p>
<p>add_executable: 将名为 main.cc 的源文件编译成一个名称为 Demo 的可执行文件。</p>
<h4>编译项目</h4>
<p>之后,在当前目录执行 cmake . ,得到 Makefile 后再使用 make 命令编译得到 Demo1 可执行文件。</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo1</span><span class="p">]</span><span class="err">$</span> <span class="n">cmake</span> <span class="p">.</span>
<span class="o">--</span> <span class="n">The</span> <span class="n">C</span> <span class="n">compiler</span> <span class="n">identification</span> <span class="n">is</span> <span class="n">GNU</span> <span class="mf">4.8.2</span>
<span class="o">--</span> <span class="n">The</span> <span class="n">CXX</span> <span class="n">compiler</span> <span class="n">identification</span> <span class="n">is</span> <span class="n">GNU</span> <span class="mf">4.8.2</span>
<span class="o">--</span> <span class="n">Check</span> <span class="k">for</span> <span class="n">working</span> <span class="n">C</span> <span class="nl">compiler</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">cc</span>
<span class="o">--</span> <span class="n">Check</span> <span class="k">for</span> <span class="n">working</span> <span class="n">C</span> <span class="nl">compiler</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">cc</span> <span class="o">--</span> <span class="n">works</span>
<span class="o">--</span> <span class="n">Detecting</span> <span class="n">C</span> <span class="n">compiler</span> <span class="n">ABI</span> <span class="n">info</span>
<span class="o">--</span> <span class="n">Detecting</span> <span class="n">C</span> <span class="n">compiler</span> <span class="n">ABI</span> <span class="n">info</span> <span class="o">-</span> <span class="n">done</span>
<span class="o">--</span> <span class="n">Check</span> <span class="k">for</span> <span class="n">working</span> <span class="n">CXX</span> <span class="nl">compiler</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">c</span><span class="o">++</span>
<span class="o">--</span> <span class="n">Check</span> <span class="k">for</span> <span class="n">working</span> <span class="n">CXX</span> <span class="nl">compiler</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">c</span><span class="o">++</span> <span class="o">--</span> <span class="n">works</span>
<span class="o">--</span> <span class="n">Detecting</span> <span class="n">CXX</span> <span class="n">compiler</span> <span class="n">ABI</span> <span class="n">info</span>
<span class="o">--</span> <span class="n">Detecting</span> <span class="n">CXX</span> <span class="n">compiler</span> <span class="n">ABI</span> <span class="n">info</span> <span class="o">-</span> <span class="n">done</span>
<span class="o">--</span> <span class="n">Configuring</span> <span class="n">done</span>
<span class="o">--</span> <span class="n">Generating</span> <span class="n">done</span>
<span class="o">--</span> <span class="n">Build</span> <span class="n">files</span> <span class="n">have</span> <span class="n">been</span> <span class="n">written</span> <span class="nl">to</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo1</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo1</span><span class="p">]</span><span class="err">$</span> <span class="n">make</span>
<span class="n">Scanning</span> <span class="n">dependencies</span> <span class="n">of</span> <span class="n">target</span> <span class="n">Demo</span>
<span class="p">[</span><span class="mi">100</span><span class="o">%</span><span class="p">]</span> <span class="n">Building</span> <span class="n">C</span> <span class="n">object</span> <span class="n">CMakeFiles</span><span class="o">/</span><span class="n">Demo</span><span class="p">.</span><span class="n">dir</span><span class="o">/</span><span class="n">main</span><span class="p">.</span><span class="n">cc</span><span class="p">.</span><span class="n">o</span>
<span class="n">Linking</span> <span class="n">C</span> <span class="n">executable</span> <span class="n">Demo</span>
<span class="p">[</span><span class="mi">100</span><span class="o">%</span><span class="p">]</span> <span class="n">Built</span> <span class="n">target</span> <span class="n">Demo</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo1</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo</span> <span class="mi">5</span> <span class="mi">4</span>
<span class="mi">5</span> <span class="o">^</span> <span class="mi">4</span> <span class="n">is</span> <span class="mi">625</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo1</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo</span> <span class="mi">7</span> <span class="mi">3</span>
<span class="mi">7</span> <span class="o">^</span> <span class="mi">3</span> <span class="n">is</span> <span class="mi">343</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo1</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo</span> <span class="mi">2</span> <span class="mi">10</span>
<span class="mi">2</span> <span class="o">^</span> <span class="mi">10</span> <span class="n">is</span> <span class="mi">1024</span>
</code></pre></div>
<p>多个源文件</p>
<h4>同一目录,多个源文件</h4>
<h5>本小节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo2" target="_blank" rel="nofollow">Demo2</a>。</h5>
<p>上面的例子只有单个源文件。现在假如把 power 函数单独写进一个名为 MathFunctions.c 的源文件里,使得这个工程变成如下的形式:</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">Demo2</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">main</span><span class="p">.</span><span class="n">cc</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">cc</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">h</span>
</code></pre></div>
<p>这个时候,CMakeLists.txt 可以改成如下的形式:</p>
<div class="highlight"><pre><code><span class="cp"># CMake 最低版本号要求</span>
<span class="n">cmake_minimum_required</span> <span class="p">(</span><span class="n">VERSION</span> <span class="mf">2.8</span><span class="p">)</span>
<span class="cp"># 项目信息</span>
<span class="n">project</span> <span class="p">(</span><span class="n">Demo2</span><span class="p">)</span>
<span class="cp"># 指定生成目标</span>
<span class="n">add_executable</span><span class="p">(</span><span class="n">Demo</span> <span class="n">main</span><span class="p">.</span><span class="n">cc</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">cc</span><span class="p">)</span>
</code></pre></div>
<p>唯一的改动只是在 add_executable 命令中增加了一个 MathFunctions.cc 源文件。这样写当然没什么问题,但是如果源文件很多,把所有源文件的名字都加进去将是一件烦人的工作。更省事的方法是使用 aux_source_directory 命令,该命令会查找指定目录下的所有源文件,然后将结果存进指定变量名。其语法如下:</p>
<div class="highlight"><pre><code><span class="n">aux_source_directory</span><span class="p">()</span>
</code></pre></div>
<p>因此,可以修改 CMakeLists.txt 如下:</p>
<div class="highlight"><pre><code><span class="cp"># CMake 最低版本号要求</span>
<span class="n">cmake_minimum_required</span> <span class="p">(</span><span class="n">VERSION</span> <span class="mf">2.8</span><span class="p">)</span>
<span class="cp"># 项目信息</span>
<span class="n">project</span> <span class="p">(</span><span class="n">Demo2</span><span class="p">)</span>
<span class="cp"># 查找当前目录下的所有源文件</span>
<span class="cp"># 并将名称保存到 DIR_SRCS 变量</span>
<span class="n">aux_source_directory</span><span class="p">(.</span> <span class="n">DIR_SRCS</span><span class="p">)</span>
<span class="cp"># 指定生成目标</span>
<span class="n">add_executable</span><span class="p">(</span><span class="n">Demo</span> <span class="err">$</span><span class="p">{</span><span class="n">DIR_SRCS</span><span class="p">})</span>
</code></pre></div>
<p>这样,CMake 会将当前目录所有源文件的文件名赋值给变量 DIR_SRCS ,再指示变量 DIR_SRCS 中的源文件需要编译成一个名称为 Demo 的可执行文件。
多个目录,多个源文件</p>
<h5>本小节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo3" target="_blank" rel="nofollow">Demo3</a>。</h5>
<p>现在进一步将 MathFunctions.h 和 MathFunctions.cc 文件移动到 math 目录下。</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">Demo3</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">main</span><span class="p">.</span><span class="n">cc</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">math</span><span class="o">/</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">cc</span>
<span class="o">|</span>
<span class="o">+---</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">h</span>
</code></pre></div>
<p>对于这种情况,需要分别在项目根目录 Demo3 和 math 目录里各编写一个 CMakeLists.txt 文件。为了方便,我们可以先将 math 目录里的文件编译成静态库再由 main 函数调用。
根目录中的 CMakeLists.txt :</p>
<div class="highlight"><pre><code><span class="cp"># CMake 最低版本号要求</span>
<span class="n">cmake_minimum_required</span> <span class="p">(</span><span class="n">VERSION</span> <span class="mf">2.8</span><span class="p">)</span>
<span class="cp"># 项目信息</span>
<span class="n">project</span> <span class="p">(</span><span class="n">Demo3</span><span class="p">)</span>
<span class="cp"># 查找当前目录下的所有源文件</span>
<span class="cp"># 并将名称保存到 DIR_SRCS 变量</span>
<span class="n">aux_source_directory</span><span class="p">(.</span> <span class="n">DIR_SRCS</span><span class="p">)</span>
<span class="cp"># 添加 math 子目录</span>
<span class="n">add_subdirectory</span><span class="p">(</span><span class="n">math</span><span class="p">)</span>
<span class="cp"># 指定生成目标 </span>
<span class="n">add_executable</span><span class="p">(</span><span class="n">Demo</span> <span class="n">main</span><span class="p">.</span><span class="n">cc</span><span class="p">)</span>
<span class="cp"># 添加链接库</span>
<span class="n">target_link_libraries</span><span class="p">(</span><span class="n">Demo</span> <span class="n">MathFunctions</span><span class="p">)</span>
</code></pre></div>
<p>该文件添加了下面的内容: 第3行,使用命令 add_subdirectory 指明本项目包含一个子目录 math,这样 math 目录下的 CMakeLists.txt 文件和源代码也会被处理 。第6行,使用命令 target_link_libraries 指明可执行文件 main 需要连接一个名为 MathFunctions 的链接库 。
子目录中的 CMakeLists.txt:</p>
<div class="highlight"><pre><code><span class="cp"># 查找当前目录下的所有源文件</span>
<span class="cp"># 并将名称保存到 DIR_LIB_SRCS 变量</span>
<span class="n">aux_source_directory</span><span class="p">(.</span> <span class="n">DIR_LIB_SRCS</span><span class="p">)</span>
<span class="cp"># 生成链接库</span>
<span class="n">add_library</span> <span class="p">(</span><span class="n">MathFunctions</span> <span class="err">$</span><span class="p">{</span><span class="n">DIR_LIB_SRCS</span><span class="p">})</span>
</code></pre></div>
<p>在该文件中使用命令 add_library 将 src 目录中的源文件编译为静态链接库。
自定义编译选项</p>
<h5>本节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo4" target="_blank" rel="nofollow">Demo4</a>。</h5>
<p>CMake 允许为项目增加编译选项,从而可以根据用户的环境和需求选择最合适的编译方案。
例如,可以将 MathFunctions 库设为一个可选的库,如果该选项为 ON ,就使用该库定义的数学函数来进行运算。否则就调用标准库中的数学函数库。
修改 CMakeLists 文件</p>
<p>我们要做的第一步是在顶层的 CMakeLists.txt 文件中添加该选项:</p>
<div class="highlight"><pre><code><span class="cp"># CMake 最低版本号要求</span>
<span class="n">cmake_minimum_required</span> <span class="p">(</span><span class="n">VERSION</span> <span class="mf">2.8</span><span class="p">)</span>
<span class="cp"># 项目信息</span>
<span class="n">project</span> <span class="p">(</span><span class="n">Demo4</span><span class="p">)</span>
<span class="cp"># 加入一个配置头文件,用于处理 CMake 对源码的设置</span>
<span class="n">configure_file</span> <span class="p">(</span>
<span class="s">"${PROJECT_SOURCE_DIR}/config.h.in"</span>
<span class="s">"${PROJECT_BINARY_DIR}/config.h"</span>
<span class="p">)</span>
<span class="cp"># 是否使用自己的 MathFunctions 库</span>
<span class="n">option</span> <span class="p">(</span><span class="n">USE_MYMATH</span>
<span class="s">"Use provided math implementation"</span> <span class="n">ON</span><span class="p">)</span>
<span class="cp"># 是否加入 MathFunctions 库</span>
<span class="k">if</span> <span class="p">(</span><span class="n">USE_MYMATH</span><span class="p">)</span>
<span class="n">include_directories</span> <span class="p">(</span><span class="s">"${PROJECT_SOURCE_DIR}/math"</span><span class="p">)</span>
<span class="n">add_subdirectory</span> <span class="p">(</span><span class="n">math</span><span class="p">)</span>
<span class="n">set</span> <span class="p">(</span><span class="n">EXTRA_LIBS</span> <span class="err">$</span><span class="p">{</span><span class="n">EXTRA_LIBS</span><span class="p">}</span> <span class="n">MathFunctions</span><span class="p">)</span>
<span class="n">endif</span> <span class="p">(</span><span class="n">USE_MYMATH</span><span class="p">)</span>
<span class="cp"># 查找当前目录下的所有源文件</span>
<span class="cp"># 并将名称保存到 DIR_SRCS 变量</span>
<span class="n">aux_source_directory</span><span class="p">(.</span> <span class="n">DIR_SRCS</span><span class="p">)</span>
<span class="cp"># 指定生成目标</span>
<span class="n">add_executable</span><span class="p">(</span><span class="n">Demo</span> <span class="err">$</span><span class="p">{</span><span class="n">DIR_SRCS</span><span class="p">})</span>
<span class="n">target_link_libraries</span> <span class="p">(</span><span class="n">Demo</span> <span class="err">$</span><span class="p">{</span><span class="n">EXTRA_LIBS</span><span class="p">})</span>
</code></pre></div>
<p>其中:</p>
<p>第7行的 configure_file 命令用于加入一个配置头文件 config.h ,这个文件由 CMake 从 config.h.in 生成,通过这样的机制,将可以通过预定义一些参数和变量来控制代码的生成。</p>
<p>第13行的 option 命令添加了一个 USE_MYMATH 选项,并且默认值为 ON 。</p>
<p>第17行根据 USE_MYMATH 变量的值来决定是否使用我们自己编写的 MathFunctions 库。</p>
<p>修改 main.cc 文件</p>
<p>之后修改 main.cc 文件,让其根据 USE_MYMATH 的预定义值来决定是否调用标准库还是 MathFunctions 库:</p>
<div class="highlight"><pre><code><span class="cp">#include </span>
<span class="cp">#include </span>
<span class="cp">#include "config.h"</span>
<span class="cp">#ifdef USE_MYMATH</span>
<span class="cp">#include "math/MathFunctions.h"</span>
<span class="cp">#else</span>
<span class="cp">#include </span>
<span class="cp">#endif</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o"><</span> <span class="mi">3</span><span class="p">){</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Usage: %s base exponent </span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">double</span> <span class="n">base</span> <span class="o">=</span> <span class="n">atof</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="kt">int</span> <span class="n">exponent</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
<span class="cp">#ifdef USE_MYMATH</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use our own Math library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">power</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#else</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use the standard library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pow</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#endif</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%g ^ %d is %g</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h4>编写 config.h.in 文件</h4>
<p>上面的程序值得注意的是第2行,这里引用了一个 config.h 文件,这个文件预定义了 USE_MYMATH 的值。但我们并不直接编写这个文件,为了方便从 CMakeLists.txt 中导入配置,我们编写一个 config.h.in 文件,内容如下:</p>
<div class="highlight"><pre><code><span class="cp">#cmakedefine USE_MYMATH</span>
</code></pre></div>
<p>这样 CMake 会自动根据 CMakeLists 配置文件中的设置自动生成 config.h 文件。
编译项目</p>
<p>现在编译一下这个项目,为了便于交互式的选择该变量的值,可以使用 ccmake 命令 2
2也可以使用 cmake -i 命令,该命令会提供一个会话式的交互式配置界面。</p>
<p><img src="http://static.oschina.net/uploads/img/201507/11133724_OAoz.png" width="853" height="493" style="cursor: pointer;"></p>
<h3>CMake的交互式配置界面</h3>
<p>从中可以找到刚刚定义的 USE_MYMATH 选项,按键盘的方向键可以在不同的选项窗口间跳转,按下 enter 键可以修改该选项。修改完成后可以按下 c 选项完成配置,之后再按 g 键确认生成 Makefile 。ccmake 的其他操作可以参考窗口下方给出的指令提示。
我们可以试试分别将 USE_MYMATH 设为 ON 和 OFF 得到的结果:
USE_MYMATH 为 ON</p>
<h5>运行结果:</h5>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo4</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo</span>
<span class="n">Now</span> <span class="n">we</span> <span class="n">use</span> <span class="n">our</span> <span class="n">own</span> <span class="n">MathFunctions</span> <span class="n">library</span><span class="p">.</span>
<span class="mi">7</span> <span class="o">^</span> <span class="mi">3</span> <span class="o">=</span> <span class="mf">343.000000</span>
<span class="mi">10</span> <span class="o">^</span> <span class="mi">5</span> <span class="o">=</span> <span class="mf">100000.000000</span>
<span class="mi">2</span> <span class="o">^</span> <span class="mi">10</span> <span class="o">=</span> <span class="mf">1024.000000</span>
</code></pre></div>
<p>此时 config.h 的内容为:</p>
<div class="highlight"><pre><code><span class="cp">#define USE_MYMATH</span>
</code></pre></div>
<p>USE_MYMATH 为 OFF</p>
<h5>运行结果:</h5>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo4</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo</span>
<span class="n">Now</span> <span class="n">we</span> <span class="n">use</span> <span class="n">the</span> <span class="n">standard</span> <span class="n">library</span><span class="p">.</span>
<span class="mi">7</span> <span class="o">^</span> <span class="mi">3</span> <span class="o">=</span> <span class="mf">343.000000</span>
<span class="mi">10</span> <span class="o">^</span> <span class="mi">5</span> <span class="o">=</span> <span class="mf">100000.000000</span>
<span class="mi">2</span> <span class="o">^</span> <span class="mi">10</span> <span class="o">=</span> <span class="mf">1024.000000</span>
</code></pre></div>
<p>此时 config.h 的内容为:</p>
<div class="highlight"><pre><code><span class="cm">/* #undef USE_MYMATH */</span>
</code></pre></div>
<h5>安装和测试</h5>
<h5>本节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo5" target="_blank" rel="nofollow">Demo5</a>。</h5>
<p>CMake 也可以指定安装规则,以及添加测试。这两个功能分别可以通过在产生 Makefile 后使用 make install 和 make test 来执行。在以前的 GNU Makefile 里,你可能需要为此编写 install 和 test 两个伪目标和相应的规则,但在 CMake 里,这样的工作同样只需要简单的调用几条命令。
定制安装规则</p>
<p>首先先在 math/CMakeLists.txt 文件里添加下面两行:</p>
<div class="highlight"><pre><code><span class="cp"># 指定 MathFunctions 库的安装路径</span>
<span class="n">install</span> <span class="p">(</span><span class="n">TARGETS</span> <span class="n">MathFunctions</span> <span class="n">DESTINATION</span> <span class="n">bin</span><span class="p">)</span>
<span class="n">install</span> <span class="p">(</span><span class="n">FILES</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">h</span> <span class="n">DESTINATION</span> <span class="n">include</span><span class="p">)</span>
</code></pre></div>
<p>指明 MathFunctions 库的安装路径。之后同样修改根目录的 CMakeLists 文件,在末尾添加下面几行:</p>
<div class="highlight"><pre><code><span class="cp"># 指定安装路径</span>
<span class="n">install</span> <span class="p">(</span><span class="n">TARGETS</span> <span class="n">Demo</span> <span class="n">DESTINATION</span> <span class="n">bin</span><span class="p">)</span>
<span class="n">install</span> <span class="p">(</span><span class="n">FILES</span> <span class="s">"${PROJECT_BINARY_DIR}/config.h"</span>
<span class="n">DESTINATION</span> <span class="n">include</span><span class="p">)</span>
</code></pre></div>
<p>通过上面的定制,生成的 Demo 文件和 MathFunctions 函数库 libMathFunctions.o 文件将会被复制到 /usr/local/bin 中,而 MathFunctions.h 和生成的 config.h 文件则会被复制到 /usr/local/include 中。我们可以验证一下3
3顺带一提的是,这里的 /usr/local/ 是默认安装到的根目录,可以通过修改 CMAKE_INSTALL_PREFIX 变量的值来指定这些文件应该拷贝到哪个根目录。</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo5</span><span class="p">]</span><span class="err">$</span> <span class="n">sudo</span> <span class="n">make</span> <span class="n">install</span>
<span class="p">[</span> <span class="mi">50</span><span class="o">%</span><span class="p">]</span> <span class="n">Built</span> <span class="n">target</span> <span class="n">MathFunctions</span>
<span class="p">[</span><span class="mi">100</span><span class="o">%</span><span class="p">]</span> <span class="n">Built</span> <span class="n">target</span> <span class="n">Demo</span>
<span class="n">Install</span> <span class="n">the</span> <span class="n">project</span><span class="p">...</span>
<span class="o">--</span> <span class="n">Install</span> <span class="nl">configuration</span><span class="p">:</span> <span class="s">""</span>
<span class="o">--</span> <span class="nl">Installing</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">Demo</span>
<span class="o">--</span> <span class="nl">Installing</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">include</span><span class="o">/</span><span class="n">config</span><span class="p">.</span><span class="n">h</span>
<span class="o">--</span> <span class="nl">Installing</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">libMathFunctions</span><span class="p">.</span><span class="n">a</span>
<span class="o">--</span> <span class="n">Up</span><span class="o">-</span><span class="n">to</span><span class="o">-</span><span class="nl">date</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">include</span><span class="o">/</span><span class="n">MathFunctions</span><span class="p">.</span><span class="n">h</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo5</span><span class="p">]</span><span class="err">$</span> <span class="n">ls</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span>
<span class="n">Demo</span> <span class="n">libMathFunctions</span><span class="p">.</span><span class="n">a</span>
<span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo5</span><span class="p">]</span><span class="err">$</span> <span class="n">ls</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">include</span>
<span class="n">config</span><span class="p">.</span><span class="n">h</span> <span class="n">MathFunctions</span><span class="p">.</span><span class="n">h</span>
</code></pre></div>
<h4>为工程添加测试</h4>
<p>添加测试同样很简单。CMake 提供了一个称为 CTest 的测试工具。我们要做的只是在项目根目录的 CMakeLists 文件中调用一系列的 add_test 命令。</p>
<div class="highlight"><pre><code><span class="cp"># 启用测试</span>
<span class="n">enable_testing</span><span class="p">()</span>
<span class="cp"># 测试程序是否成功运行</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_run</span> <span class="n">Demo</span> <span class="mi">5</span> <span class="mi">2</span><span class="p">)</span>
<span class="cp"># 测试帮助信息是否可以正常提示</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_usage</span> <span class="n">Demo</span><span class="p">)</span>
<span class="n">set_tests_properties</span> <span class="p">(</span><span class="n">test_usage</span>
<span class="n">PROPERTIES</span> <span class="n">PASS_REGULAR_EXPRESSION</span> <span class="s">"Usage: .* base exponent"</span><span class="p">)</span>
<span class="cp"># 测试 5 的平方</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_5_2</span> <span class="n">Demo</span> <span class="mi">5</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">set_tests_properties</span> <span class="p">(</span><span class="n">test_5_2</span>
<span class="n">PROPERTIES</span> <span class="n">PASS_REGULAR_EXPRESSION</span> <span class="s">"is 25"</span><span class="p">)</span>
<span class="cp"># 测试 10 的 5 次方</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_10_5</span> <span class="n">Demo</span> <span class="mi">10</span> <span class="mi">5</span><span class="p">)</span>
<span class="n">set_tests_properties</span> <span class="p">(</span><span class="n">test_10_5</span>
<span class="n">PROPERTIES</span> <span class="n">PASS_REGULAR_EXPRESSION</span> <span class="s">"is 100000"</span><span class="p">)</span>
<span class="cp"># 测试 2 的 10 次方</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_2_10</span> <span class="n">Demo</span> <span class="mi">2</span> <span class="mi">10</span><span class="p">)</span>
<span class="n">set_tests_properties</span> <span class="p">(</span><span class="n">test_2_10</span>
<span class="n">PROPERTIES</span> <span class="n">PASS_REGULAR_EXPRESSION</span> <span class="s">"is 1024"</span><span class="p">)</span>
</code></pre></div>
<p>上面的代码包含了四个测试。第一个测试 test_run 用来测试程序是否成功运行并返回 0 值。剩下的三个测试分别用来测试 5 的 平方、10 的 5 次方、2 的 10 次方是否都能得到正确的结果。其中 PASS_REGULAR_EXPRESSION 用来测试输出是否包含后面跟着的字符串。
让我们看看测试的结果:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo5</span><span class="p">]</span><span class="err">$</span> <span class="n">make</span> <span class="n">test</span>
<span class="n">Running</span> <span class="n">tests</span><span class="p">...</span>
<span class="n">Test</span> <span class="n">project</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo5</span>
<span class="n">Start</span> <span class="mi">1</span><span class="o">:</span> <span class="n">test_run</span>
<span class="mi">1</span><span class="o">/</span><span class="mi">4</span> <span class="n">Test</span> <span class="err">#</span><span class="mi">1</span><span class="o">:</span> <span class="n">test_run</span> <span class="p">.........................</span> <span class="n">Passed</span> <span class="mf">0.00</span> <span class="n">sec</span>
<span class="n">Start</span> <span class="mi">2</span><span class="o">:</span> <span class="n">test_5_2</span>
<span class="mi">2</span><span class="o">/</span><span class="mi">4</span> <span class="n">Test</span> <span class="err">#</span><span class="mi">2</span><span class="o">:</span> <span class="n">test_5_2</span> <span class="p">.........................</span> <span class="n">Passed</span> <span class="mf">0.00</span> <span class="n">sec</span>
<span class="n">Start</span> <span class="mi">3</span><span class="o">:</span> <span class="n">test_10_5</span>
<span class="mi">3</span><span class="o">/</span><span class="mi">4</span> <span class="n">Test</span> <span class="err">#</span><span class="mi">3</span><span class="o">:</span> <span class="n">test_10_5</span> <span class="p">........................</span> <span class="n">Passed</span> <span class="mf">0.00</span> <span class="n">sec</span>
<span class="n">Start</span> <span class="mi">4</span><span class="o">:</span> <span class="n">test_2_10</span>
<span class="mi">4</span><span class="o">/</span><span class="mi">4</span> <span class="n">Test</span> <span class="err">#</span><span class="mi">4</span><span class="o">:</span> <span class="n">test_2_10</span> <span class="p">........................</span> <span class="n">Passed</span> <span class="mf">0.00</span> <span class="n">sec</span>
<span class="mi">100</span><span class="o">%</span> <span class="n">tests</span> <span class="n">passed</span><span class="p">,</span> <span class="mi">0</span> <span class="n">tests</span> <span class="n">failed</span> <span class="n">out</span> <span class="n">of</span> <span class="mi">4</span>
<span class="n">Total</span> <span class="n">Test</span> <span class="n">time</span> <span class="p">(</span><span class="n">real</span><span class="p">)</span> <span class="o">=</span> <span class="mf">0.01</span> <span class="n">sec</span>
</code></pre></div>
<p>如果要测试更多的输入数据,像上面那样一个个写测试用例未免太繁琐。这时可以通过编写宏来实现:</p>
<div class="highlight"><pre><code><span class="cp"># 定义一个宏,用来简化测试工作</span>
<span class="n">macro</span> <span class="p">(</span><span class="n">do_test</span> <span class="n">arg1</span> <span class="n">arg2</span> <span class="n">result</span><span class="p">)</span>
<span class="n">add_test</span> <span class="p">(</span><span class="n">test_</span><span class="err">$</span><span class="p">{</span><span class="n">arg1</span><span class="p">}</span><span class="n">_</span><span class="err">$</span><span class="p">{</span><span class="n">arg2</span><span class="p">}</span> <span class="n">Demo</span> <span class="err">$</span><span class="p">{</span><span class="n">arg1</span><span class="p">}</span> <span class="err">$</span><span class="p">{</span><span class="n">arg2</span><span class="p">})</span>
<span class="n">set_tests_properties</span> <span class="p">(</span><span class="n">test_</span><span class="err">$</span><span class="p">{</span><span class="n">arg1</span><span class="p">}</span><span class="n">_</span><span class="err">$</span><span class="p">{</span><span class="n">arg2</span><span class="p">}</span>
<span class="n">PROPERTIES</span> <span class="n">PASS_REGULAR_EXPRESSION</span> <span class="err">$</span><span class="p">{</span><span class="n">result</span><span class="p">})</span>
<span class="n">endmacro</span> <span class="p">(</span><span class="n">do_test</span><span class="p">)</span>
<span class="cp"># 使用该宏进行一系列的数据测试</span>
<span class="n">do_test</span> <span class="p">(</span><span class="mi">5</span> <span class="mi">2</span> <span class="s">"is 25"</span><span class="p">)</span>
<span class="n">do_test</span> <span class="p">(</span><span class="mi">10</span> <span class="mi">5</span> <span class="s">"is 100000"</span><span class="p">)</span>
<span class="n">do_test</span> <span class="p">(</span><span class="mi">2</span> <span class="mi">10</span> <span class="s">"is 1024"</span><span class="p">)</span>
</code></pre></div>
<p>关于 CTest 的更详细的用法可以通过 man 1 ctest 参考 CTest 的文档。
支持 gdb</p>
<p>让 CMake 支持 gdb 的设置也很容易,只需要指定 Debug 模式下开启 -g 选项:</p>
<div class="highlight"><pre><code><span class="n">set</span><span class="p">(</span><span class="n">CMAKE_BUILD_TYPE</span> <span class="s">"Debug"</span><span class="p">)</span>
<span class="n">set</span><span class="p">(</span><span class="n">CMAKE_CXX_FLAGS_DEBUG</span> <span class="s">"$ENV{CXXFLAGS} -O0 -Wall -g -ggdb"</span><span class="p">)</span>
<span class="n">set</span><span class="p">(</span><span class="n">CMAKE_CXX_FLAGS_RELEASE</span> <span class="s">"$ENV{CXXFLAGS} -O3 -Wall"</span><span class="p">)</span>
</code></pre></div>
<p>之后可以直接对生成的程序使用 gdb 来调试。
添加环境检查</p>
<h5>本节对应的源代码所在目录:<a href="https://gitcafe.com/wzpan/cmake-demo/tree/master/Demo6" target="_blank" rel="nofollow">Demo6</a>。</h5>
<p>有时候可能要对系统环境做点检查,例如要使用一个平台相关的特性的时候。在这个例子中,我们检查系统是否自带 pow 函数。如果带有 pow 函数,就使用它;否则使用我们定义的 power 函数。
添加 CheckFunctionExists 宏</p>
<p>首先在顶层 CMakeLists 文件中添加 CheckFunctionExists.cmake 宏,并调用 check_function_exists 命令测试链接器是否能够在链接阶段找到 pow 函数。</p>
<div class="highlight"><pre><code><span class="cp"># 检查系统是否支持 pow 函数</span>
<span class="n">include</span> <span class="p">(</span><span class="err">$</span><span class="p">{</span><span class="n">CMAKE_ROOT</span><span class="p">}</span><span class="o">/</span><span class="n">Modules</span><span class="o">/</span><span class="n">CheckFunctionExists</span><span class="p">.</span><span class="n">cmake</span><span class="p">)</span>
<span class="n">check_function_exists</span> <span class="p">(</span><span class="n">pow</span> <span class="n">HAVE_POW</span><span class="p">)</span>
</code></pre></div>
<p>将上面这段代码放在 configure_file 命令前。
预定义相关宏变量</p>
<p>接下来修改 config.h.in 文件,预定义相关的宏变量。</p>
<div class="highlight"><pre><code><span class="c1">// does the platform provide pow function?</span>
<span class="cp">#cmakedefine HAVE_POW</span>
</code></pre></div>
<p>在代码中使用宏和函数</p>
<p>最后一步是修改 main.cc ,在代码中使用宏和函数:</p>
<div class="highlight"><pre><code><span class="cp">#ifdef HAVE_POW</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use the standard library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pow</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#else</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use our own Math library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">power</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#endif</span>
</code></pre></div>
<h4>添加版本号</h4>
<h5>本节对应的源代码所在目录:Demo7。</h5>
<p>给项目添加和维护版本号是一个好习惯,这样有利于用户了解每个版本的维护情况,并及时了解当前所用的版本是否过时,或是否可能出现不兼容的情况。
首先修改顶层 CMakeLists 文件,在 project 命令之后加入如下两行:</p>
<div class="highlight"><pre><code><span class="n">set</span> <span class="p">(</span><span class="n">Demo_VERSION_MAJOR</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">set</span> <span class="p">(</span><span class="n">Demo_VERSION_MINOR</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div>
<p>分别指定当前的项目的主版本号和副版本号。
之后,为了在代码中获取版本信息,我们可以修改 config.h.in 文件,添加两个预定义变量:</p>
<div class="highlight"><pre><code><span class="c1">// the configured options and settings for Tutorial</span>
<span class="cp">#define Demo_VERSION_MAJOR @Demo_VERSION_MAJOR@</span>
<span class="cp">#define Demo_VERSION_MINOR @Demo_VERSION_MINOR@</span>
</code></pre></div>
<p>这样就可以直接在代码中打印版本信息了:</p>
<div class="highlight"><pre><code><span class="cp">#include <stdio.h></span>
<span class="cp">#include <stdlib.h></span>
<span class="cp">#include <math.h></span>
<span class="cp">#include "config.h"</span>
<span class="cp">#include "math/MathFunctions.h"</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">argc</span> <span class="o"><</span> <span class="mi">3</span><span class="p">){</span>
<span class="c1">// print version info</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s Version %d.%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
<span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
<span class="n">Demo_VERSION_MAJOR</span><span class="p">,</span>
<span class="n">Demo_VERSION_MINOR</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Usage: %s base exponent </span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">argv</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
<span class="kt">double</span> <span class="n">base</span> <span class="o">=</span> <span class="n">atof</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="kt">int</span> <span class="n">exponent</span> <span class="o">=</span> <span class="n">atoi</span><span class="p">(</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]);</span>
<span class="cp">#if defined (HAVE_POW)</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use the standard library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">pow</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#else</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Now we use our own Math library. </span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="kt">double</span> <span class="n">result</span> <span class="o">=</span> <span class="n">power</span><span class="p">(</span><span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">);</span>
<span class="cp">#endif</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%g ^ %d is %g</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">base</span><span class="p">,</span> <span class="n">exponent</span><span class="p">,</span> <span class="n">result</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h4>生成安装包</h4>
<h5>本节对应的源代码所在目录:Demo8。</h5>
<p>本节将学习如何配置生成各种平台上的安装包,包括二进制安装包和源码安装包。为了完成这个任务,我们需要用到 CPack ,它同样也是由 CMake 提供的一个工具,专门用于打包。
首先在顶层的 CMakeLists.txt 文件尾部添加下面几行:</p>
<div class="highlight"><pre><code><span class="cp"># 构建一个 CPack 安装包</span>
<span class="n">include</span> <span class="p">(</span><span class="n">InstallRequiredSystemLibraries</span><span class="p">)</span>
<span class="n">set</span> <span class="p">(</span><span class="n">CPACK_RESOURCE_FILE_LICENSE</span>
<span class="s">"${CMAKE_CURRENT_SOURCE_DIR}/License.txt"</span><span class="p">)</span>
<span class="n">set</span> <span class="p">(</span><span class="n">CPACK_PACKAGE_VERSION_MAJOR</span> <span class="s">"${Demo_VERSION_MAJOR}"</span><span class="p">)</span>
<span class="n">set</span> <span class="p">(</span><span class="n">CPACK_PACKAGE_VERSION_MINOR</span> <span class="s">"${Demo_VERSION_MINOR}"</span><span class="p">)</span>
<span class="n">include</span> <span class="p">(</span><span class="n">CPack</span><span class="p">)</span>
</code></pre></div>
<p>上面的代码做了以下几个工作:
导入 InstallRequiredSystemLibraries 模块,以便之后导入 CPack 模块;</p>
<p>设置一些 CPack 相关变量,包括版权信息和版本信息,其中版本信息用了上一节定义的版本号;</p>
<p>导入 CPack 模块。</p>
<p>接下来的工作是像往常一样构建工程,并执行 cpack 命令。
生成二进制安装包:</p>
<div class="highlight"><pre><code><span class="n">cpack</span> <span class="o">-</span><span class="n">C</span> <span class="n">CPackConfig</span><span class="p">.</span><span class="n">cmake</span>
</code></pre></div>
<p>生成源码安装包</p>
<div class="highlight"><pre><code><span class="n">cpack</span> <span class="o">-</span><span class="n">C</span> <span class="n">CPackSourceConfig</span><span class="p">.</span><span class="n">cmake</span>
</code></pre></div>
<p>我们可以试一下。在生成项目后,执行 cpack -C CPackConfig.cmake 命令:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo8</span><span class="p">]</span><span class="err">$</span> <span class="n">cpack</span> <span class="o">-</span><span class="n">C</span> <span class="n">CPackSourceConfig</span><span class="p">.</span><span class="n">cmake</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span> <span class="n">using</span> <span class="n">STGZ</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Install</span> <span class="n">projects</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Run</span> <span class="n">preinstall</span> <span class="n">target</span> <span class="k">for</span><span class="o">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Install</span> <span class="nl">project</span><span class="p">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="nl">package</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo8</span><span class="o">/</span><span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">sh</span> <span class="n">generated</span><span class="p">.</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span> <span class="n">using</span> <span class="n">TGZ</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Install</span> <span class="n">projects</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Run</span> <span class="n">preinstall</span> <span class="n">target</span> <span class="k">for</span><span class="o">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Install</span> <span class="nl">project</span><span class="p">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="nl">package</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo8</span><span class="o">/</span><span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span> <span class="n">generated</span><span class="p">.</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span> <span class="n">using</span> <span class="n">TZ</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Install</span> <span class="n">projects</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Run</span> <span class="n">preinstall</span> <span class="n">target</span> <span class="k">for</span><span class="o">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="n">Install</span> <span class="nl">project</span><span class="p">:</span> <span class="n">Demo8</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="n">Create</span> <span class="n">package</span>
<span class="nl">CPack</span><span class="p">:</span> <span class="o">-</span> <span class="nl">package</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo8</span><span class="o">/</span><span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">Z</span> <span class="n">generated</span><span class="p">.</span>
</code></pre></div>
<p>此时会在该目录下创建 3 个不同格式的二进制包文件:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo8</span><span class="p">]</span><span class="err">$</span> <span class="n">ls</span> <span class="n">Demo8</span><span class="o">-*</span>
<span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">sh</span> <span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span> <span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">Z</span>
</code></pre></div>
<p>这 3 个二进制包文件所包含的内容是完全相同的。我们可以执行其中一个。此时会出现一个由 CPack 自动生成的交互式安装界面:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo8</span><span class="p">]</span><span class="err">$</span> <span class="n">sh</span> <span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="p">.</span><span class="n">sh</span>
<span class="n">Demo8</span> <span class="n">Installer</span> <span class="nl">Version</span><span class="p">:</span> <span class="mf">1.0.1</span><span class="p">,</span> <span class="n">Copyright</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="n">Humanity</span>
<span class="n">This</span> <span class="n">is</span> <span class="n">a</span> <span class="n">self</span><span class="o">-</span><span class="n">extracting</span> <span class="n">archive</span><span class="p">.</span>
<span class="n">The</span> <span class="n">archive</span> <span class="n">will</span> <span class="n">be</span> <span class="n">extracted</span> <span class="nl">to</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo8</span>
<span class="n">If</span> <span class="n">you</span> <span class="n">want</span> <span class="n">to</span> <span class="n">stop</span> <span class="n">extracting</span><span class="p">,</span> <span class="n">please</span> <span class="n">press</span> <span class="o"><</span><span class="n">ctrl</span><span class="o">-</span><span class="n">C</span><span class="o">></span><span class="p">.</span>
<span class="n">The</span> <span class="n">MIT</span> <span class="n">License</span> <span class="p">(</span><span class="n">MIT</span><span class="p">)</span>
<span class="n">Copyright</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2013</span> <span class="n">Joseph</span> <span class="n">Pan</span><span class="p">(</span><span class="nl">http</span><span class="p">:</span><span class="c1">//hahack.com)</span>
<span class="n">Permission</span> <span class="n">is</span> <span class="n">hereby</span> <span class="n">granted</span><span class="p">,</span> <span class="n">free</span> <span class="n">of</span> <span class="n">charge</span><span class="p">,</span> <span class="n">to</span> <span class="n">any</span> <span class="n">person</span> <span class="n">obtaining</span> <span class="n">a</span> <span class="n">copy</span> <span class="n">of</span>
<span class="n">this</span> <span class="n">software</span> <span class="n">and</span> <span class="n">associated</span> <span class="n">documentation</span> <span class="n">files</span> <span class="p">(</span><span class="n">the</span> <span class="s">"Software"</span><span class="p">),</span> <span class="n">to</span> <span class="n">deal</span> <span class="n">in</span>
<span class="n">the</span> <span class="n">Software</span> <span class="n">without</span> <span class="n">restriction</span><span class="p">,</span> <span class="n">including</span> <span class="n">without</span> <span class="n">limitation</span> <span class="n">the</span> <span class="n">rights</span> <span class="n">to</span>
<span class="n">use</span><span class="p">,</span> <span class="n">copy</span><span class="p">,</span> <span class="n">modify</span><span class="p">,</span> <span class="n">merge</span><span class="p">,</span> <span class="n">publish</span><span class="p">,</span> <span class="n">distribute</span><span class="p">,</span> <span class="n">sublicense</span><span class="p">,</span> <span class="n">and</span><span class="o">/</span><span class="n">or</span> <span class="n">sell</span> <span class="n">copies</span> <span class="n">of</span>
<span class="n">the</span> <span class="n">Software</span><span class="p">,</span> <span class="n">and</span> <span class="n">to</span> <span class="n">permit</span> <span class="n">persons</span> <span class="n">to</span> <span class="n">whom</span> <span class="n">the</span> <span class="n">Software</span> <span class="n">is</span> <span class="n">furnished</span> <span class="n">to</span> <span class="k">do</span> <span class="n">so</span><span class="p">,</span>
<span class="n">subject</span> <span class="n">to</span> <span class="n">the</span> <span class="n">following</span> <span class="nl">conditions</span><span class="p">:</span>
<span class="n">The</span> <span class="n">above</span> <span class="n">copyright</span> <span class="n">notice</span> <span class="n">and</span> <span class="n">this</span> <span class="n">permission</span> <span class="n">notice</span> <span class="n">shall</span> <span class="n">be</span> <span class="n">included</span> <span class="n">in</span> <span class="n">all</span>
<span class="n">copies</span> <span class="n">or</span> <span class="n">substantial</span> <span class="n">portions</span> <span class="n">of</span> <span class="n">the</span> <span class="n">Software</span><span class="p">.</span>
<span class="n">THE</span> <span class="n">SOFTWARE</span> <span class="n">IS</span> <span class="n">PROVIDED</span> <span class="s">"AS IS"</span><span class="p">,</span> <span class="n">WITHOUT</span> <span class="n">WARRANTY</span> <span class="n">OF</span> <span class="n">ANY</span> <span class="n">KIND</span><span class="p">,</span> <span class="n">EXPRESS</span> <span class="n">OR</span>
<span class="n">IMPLIED</span><span class="p">,</span> <span class="n">INCLUDING</span> <span class="n">BUT</span> <span class="n">NOT</span> <span class="n">LIMITED</span> <span class="n">TO</span> <span class="n">THE</span> <span class="n">WARRANTIES</span> <span class="n">OF</span> <span class="n">MERCHANTABILITY</span><span class="p">,</span> <span class="n">FITNESS</span>
<span class="n">FOR</span> <span class="n">A</span> <span class="n">PARTICULAR</span> <span class="n">PURPOSE</span> <span class="n">AND</span> <span class="n">NONINFRINGEMENT</span><span class="p">.</span> <span class="n">IN</span> <span class="n">NO</span> <span class="n">EVENT</span> <span class="n">SHALL</span> <span class="n">THE</span> <span class="n">AUTHORS</span> <span class="n">OR</span>
<span class="n">COPYRIGHT</span> <span class="n">HOLDERS</span> <span class="n">BE</span> <span class="n">LIABLE</span> <span class="n">FOR</span> <span class="n">ANY</span> <span class="n">CLAIM</span><span class="p">,</span> <span class="n">DAMAGES</span> <span class="n">OR</span> <span class="n">OTHER</span> <span class="n">LIABILITY</span><span class="p">,</span> <span class="n">WHETHER</span>
<span class="n">IN</span> <span class="n">AN</span> <span class="n">ACTION</span> <span class="n">OF</span> <span class="n">CONTRACT</span><span class="p">,</span> <span class="n">TORT</span> <span class="n">OR</span> <span class="n">OTHERWISE</span><span class="p">,</span> <span class="n">ARISING</span> <span class="n">FROM</span><span class="p">,</span> <span class="n">OUT</span> <span class="n">OF</span> <span class="n">OR</span> <span class="n">IN</span>
<span class="n">CONNECTION</span> <span class="n">WITH</span> <span class="n">THE</span> <span class="n">SOFTWARE</span> <span class="n">OR</span> <span class="n">THE</span> <span class="n">USE</span> <span class="n">OR</span> <span class="n">OTHER</span> <span class="n">DEALINGS</span> <span class="n">IN</span> <span class="n">THE</span> <span class="n">SOFTWARE</span><span class="p">.</span>
<span class="n">Do</span> <span class="n">you</span> <span class="n">accept</span> <span class="n">the</span> <span class="n">license</span><span class="o">?</span> <span class="p">[</span><span class="n">yN</span><span class="p">]</span><span class="o">:</span>
<span class="n">y</span>
<span class="n">By</span> <span class="k">default</span> <span class="n">the</span> <span class="n">Demo8</span> <span class="n">will</span> <span class="n">be</span> <span class="n">installed</span> <span class="nl">in</span><span class="p">:</span>
<span class="s">"/home/ehome/Documents/programming/C/power/Demo8/Demo8-1.0.1-Linux"</span>
<span class="n">Do</span> <span class="n">you</span> <span class="n">want</span> <span class="n">to</span> <span class="n">include</span> <span class="n">the</span> <span class="n">subdirectory</span> <span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="o">?</span>
<span class="n">Saying</span> <span class="n">no</span> <span class="n">will</span> <span class="n">install</span> <span class="nl">in</span><span class="p">:</span> <span class="s">"/home/ehome/Documents/programming/C/power/Demo8"</span> <span class="p">[</span><span class="n">Yn</span><span class="p">]</span><span class="o">:</span>
<span class="n">y</span>
<span class="n">Using</span> <span class="n">target</span> <span class="nl">directory</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">ehome</span><span class="o">/</span><span class="n">Documents</span><span class="o">/</span><span class="n">programming</span><span class="o">/</span><span class="n">C</span><span class="o">/</span><span class="n">power</span><span class="o">/</span><span class="n">Demo8</span><span class="o">/</span><span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span>
<span class="n">Extracting</span><span class="p">,</span> <span class="n">please</span> <span class="n">wait</span><span class="p">...</span>
<span class="n">Unpacking</span> <span class="n">finished</span> <span class="n">successfully</span>
</code></pre></div>
<p>完成后提示安装到了 Demo8-1.0.1-Linux 子目录中,我们可以进去执行该程序:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">ehome</span><span class="err">@</span><span class="n">xman</span> <span class="n">Demo8</span><span class="p">]</span><span class="err">$</span> <span class="p">.</span><span class="o">/</span><span class="n">Demo8</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">-</span><span class="n">Linux</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">Demo</span> <span class="mi">5</span> <span class="mi">2</span>
<span class="n">Now</span> <span class="n">we</span> <span class="n">use</span> <span class="n">our</span> <span class="n">own</span> <span class="n">Math</span> <span class="n">library</span><span class="p">.</span>
<span class="mi">5</span> <span class="o">^</span> <span class="mi">2</span> <span class="n">is</span> <span class="mi">25</span>
</code></pre></div>
<p>关于 CPack 的更详细的用法可以通过 man 1 cpack 参考 CPack 的文档。
将其他平台的项目迁移到 CMake</p>
<p>CMake 可以很轻松地构建出在适合各个平台执行的工程环境。而如果当前的工程环境不是 CMake ,而是基于某个特定的平台,是否可以迁移到 CMake 呢?答案是可能的。下面针对几个常用的平台,列出了它们对应的迁移方案。</p>
<h3>autotools</h3>
<p><a href="https://projects.kde.org/projects/kde/kdesdk/kde-dev-scripts/repository/revisions/master/changes/cmake-utils/scripts/am2cmake" target="_blank" rel="nofollow">am2cmake</a> 可以将 autotools 系的项目转换到 CMake,这个工具的一个成功案例是 KDE 。</p>
<p><a href="http://emanuelgreisen.dk/stuff/kdevelop_am2cmake.php.tgz" target="_blank" rel="nofollow">Alternative Automake2CMake</a> 可以转换使用 automake 的 KDevelop 工程项目。</p>
<p><a href="http://www.cmake.org/Wiki/GccXmlAutoConfHints" target="_blank" rel="nofollow">Converting autoconf tests</a></p>
<h3>qmake</h3>
<p><a href="http://www.cmake.org/Wiki/CMake:ConvertFromQmake" target="_blank" rel="nofollow">qmake converter</a> 可以转换使用 QT 的 qmake 的工程。</p>
<h3>Visual Studio</h3>
<p><a href="http://vcproj2cmake.sf.net/" target="_blank" rel="nofollow">vcproj2cmake.rb</a> 可以根据 Visual Studio 的工程文件(后缀名是 .vcproj 或 .vcxproj)生成 CMakeLists.txt 文件。</p>
<p><a href="http://nberserk.blogspot.com/2010/11/converting-vc-projectsvcproj-to.html" target="_blank" rel="nofollow">vcproj2cmake.ps1</a> vcproj2cmake 的 PowerShell 版本。</p>
<p><a href="http://sourceforge.net/projects/folders4cmake/" target="_blank" rel="nofollow">folders4cmake</a> 根据 Visual Studio 项目文件生成相应的 “source_group” 信息,这些信息可以很方便的在 CMake 脚本中使用。支持 Visual Studio 9/10 工程文件。</p>
<h3>CMakeLists.txt 自动推导</h3>
<p><a href="http://websvn.kde.org/trunk/KDE/kdesdk/cmake/scripts/" target="_blank" rel="nofollow">gencmake</a> 根据现有文件推导 CMakeLists.txt 文件。</p>
<p><a href="http://www.vanvelzensoftware.com/postnuke/index.php?name=Downloads&req=viewdownload&cid=7" target="_blank" rel="nofollow">CMakeListGenerator</a>应用一套文件和目录分析创建出完整的 CMakeLists.txt 文件。仅支持 Win32 平台。</p>
<h2>相关链接</h2>
<ol>
<li><p><a href="http://www.cmake.org/" target="_blank" rel="nofollow">官方主页</a></p></li>
<li><p><a href="http://www.cmake.org/cmake/help/cmake2.4docs.html" target="_blank" rel="nofollow">官方文档</a></p></li>
<li><p><a href="http://www.cmake.org/cmake/help/cmake_tutorial.html" target="_blank" rel="nofollow">官方教程</a></p></li>
<li><p><a href="http://www.cmake.org/Wiki/CMake#Basic_CMakeLists.txt_from-scratch-generator" target="_blank" rel="nofollow">Wiki</a></p></li>
<li><p><a href="http://www.cmake.org/Wiki/CMake_FAQ" target="_blank" rel="nofollow">FAQ</a></p></li>
<li><p><a href="http://www.cmake.org/Bug" target="_blank" rel="nofollow">bug tracker</a></p></li>
<li><p>邮件列表:</p></li>
<ul>
<li><p><a href="http://dir.gmane.org/gmane.comp.programming.tools.cmake.user" target="_blank" rel="nofollow">cmake on Gmane</a></p></li>
<li><p><a href="http://www.mail-archive.com/cmake@cmake.org/" target="_blank" rel="nofollow">http://www.mail-archive.com/cmake@cmake.org/</a></p></li>
<li><p><a href="http://www.mail-archive.com/cmake@cmake.org/" target="_blank" rel="nofollow">http://marc.info/?l=cmake</a></p></li>
</ul>
<li><p>其他推荐文章</p></li>
<ul>
<li><p><a href="http://www.ibm.com/developerworks/cn/linux/l-cn-cmake/" target="_blank" rel="nofollow">在 linux 下使用 CMake 构建应用程序</a></p></li>
<li><p><a href="http://www.cppblog.com/skyscribe/archive/2009/12/14/103208.aspx" target="_blank" rel="nofollow">cmake的一些小经验</a></p></li>
<li><p><a href="http://www.kitware.com/media/archive/kitware_quarterly0107.pdf" target="_blank" rel="nofollow">Packaging Software with CPack</a></p></li>
<li><p><a href="http://www.youtube.com/watch?v=CLvZTyji_Uw" target="_blank" rel="nofollow">视频教程: 《Getting Started with CMake》</a></p></li>
</ul>
</ol><h4>类似工具</h4>
<p><a href="http://scons.org/" target="_blank" rel="nofollow">SCons</a>:Eric S. Raymond、Timothee Besset、Zed A. Shaw 等大神力荐的项目架构工具。和 CMake 的最大区别是使用 Python 作为执行脚本。</p>
<p><a href="http://www.cmake.org/Wiki/CMake_Projects" target="_blank" rel="nofollow">这个页面</a>详细罗列了使用 CMake 的知名项目↩</p>
<p>make install
将hello直接安装到/usr/bin目录,也可以通过make install
DESTDIR=/tmp/test将他安装在
/tmp/test/usr/bin目录,打包时这个方式经常被使用。</p>
<hr>
<p><a href="http://www.cnblogs.com/weinyzhou/archive/2013/02/21/2970288.html" rel="nofollow">[编译环境] pkg-config for mac 安装</a></p>
<p>由于大部分的开源工程都需要用到pkg-config,因此今天在这讲解一下pkg-config for mac 安装过程.</p>
<p>1.检测环境是否已安装pkg-config</p>
<p>再命令行中输入: pkg-config 若未安装,则提示命令未找到.</p>
<p>2.安装pkg-config</p>
<div class="highlight"><pre><code><span class="n">curl</span> <span class="nl">http</span><span class="p">:</span><span class="c1">//pkgconfig.freedesktop.org/releases/pkg-config-0.28.tar.gz -o pkg-config-0.28.tar.gz</span>
<span class="n">tar</span> <span class="o">-</span><span class="n">xf</span> <span class="n">pkg</span><span class="o">-</span><span class="n">config</span><span class="o">-</span><span class="mf">0.28</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span>
<span class="n">cd</span> <span class="n">pkg</span><span class="o">-</span><span class="n">config</span><span class="o">-</span><span class="mf">0.28</span>
<span class="p">.</span><span class="o">/</span><span class="n">configure</span> <span class="o">--</span><span class="n">with</span><span class="o">-</span><span class="n">internal</span><span class="o">-</span><span class="n">glib</span>
<span class="n">make</span>
<span class="n">sudo</span> <span class="n">make</span> <span class="n">install</span>
</code></pre></div>
<p>到此安装完成</p>
CMake入门实战 pkg-config for mac 安装
灵气
https://www.zruibin.cn/article/ling_qi.html
2015-06-08 01:07
2015-06-08 01:07
<p>时光会带人长大,每个二五八万的懵懂青年,在某一天都会变成有车、有房、有钱、有媳妇、有老公、有子女的成熟社会人,问题是,然后呢?然后你想要做一个什么样的人?有钱、有权、能给子女的未来疏通好所有关系的好爸爸?还是擅长去世界各地血拼购物的时尚女性?还是混迹在公司里高不成低不就有一份工作就OK,反正家里有个好老公就行小少妇?生活的洪流将我们卷入新的阶段,开始面对新的生活状态与关系,此时,我们还能为一份小礼物而激动吗?还能在大马路上看见彼此而惊喜吗?还能为生活中的每一次变化而充满信心吗?很多人说,这叫成熟,是褪去年少轻狂的成熟,可我总觉得,这是灵气没有了…</p>
<p>七堇年笔下的时光,是骄傲的校花,十年后在巷子口开了美发店,每天讲着柴米油盐站在巷口喊老公回家吃饭;是优秀的学霸,当上了小白脸被富婆包养随叫随到随上床;是邻桌的富二代,在一夜之间变成阶下囚,自己的孩子8抬不起头走路,连7说话都变得小心翼翼,是考试最后一名的差生,终于在有钱后买下了老国企厂子的地,盖上了昂贵的商品房。</p>
<p>十年前,16岁的我,世界里只有上课学习与偶尔的篮球!</p>
<p>十年后,26岁的我,世界里只有工作,努力活下去,努力过得比别人好一点!</p>
<p>但,我最终所要的灵气呢?梦想呢?十年后,你会变成谁,过得怎样?</p>
<p>不要让未来的你,讨厌现在的自己!</p>
灵气
iOS 简单的动画自定义方法(旋转、移动、闪烁等)与 如何让view只响应一个按钮 与 让自己开发的iOS App允许用户通过itunes共享文件
https://www.zruibin.cn/article/ios_jian_dan_de_dong_hua_zi_ding_yi_fang_fa_(_xuan_zhuan_yi_.html
2015-04-14 14:30
2015-10-13 19:34
<p><a href="http://www.cnblogs.com/kenshincui/p/3972100.html" target="blank">iOS核心动画</a></p>
<div class="highlight"><pre><code><span class="cp">#define kDegreesToRadian(x) (M_PI * (x) / 180.0)</span>
<span class="cp">#define kRadianToDegrees(radian) (radian*180.0)/(M_PI)</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">viewDidLoad</span>
<span class="p">{</span>
<span class="p">[</span><span class="n">superviewDidLoad</span><span class="p">];</span>
<span class="n">self</span><span class="p">.</span><span class="n">title</span> <span class="o">=</span> <span class="err">@</span><span class="s">"测试动画"</span><span class="p">;</span>
<span class="n">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIColorlightGrayColor</span><span class="p">];</span>
<span class="n">myTest1</span> <span class="o">=</span> <span class="p">[[</span><span class="n">UILabelalloc</span><span class="p">]</span><span class="nl">initWithFrame</span><span class="p">:</span><span class="n">CGRectMake</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">100</span><span class="p">,</span> <span class="mi">60</span><span class="p">,</span> <span class="mi">40</span><span class="p">)];</span>
<span class="n">myTest1</span><span class="p">.</span><span class="n">backgroundColor</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIColorblueColor</span><span class="p">];</span>
<span class="n">myTest1</span><span class="p">.</span><span class="n">textAlignment</span> <span class="o">=</span> <span class="n">NSTextAlignmentCenter</span><span class="p">;</span>
<span class="n">myTest1</span><span class="p">.</span><span class="n">text</span> <span class="o">=</span> <span class="err">@</span><span class="s">"Test..."</span><span class="p">;</span>
<span class="n">myTest1</span><span class="p">.</span><span class="n">textColor</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIColorwhiteColor</span><span class="p">];</span>
<span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="nl">viewaddSubview</span><span class="p">:</span><span class="n">myTest1</span><span class="p">];</span>
<span class="c1">//闪烁效果。</span>
<span class="c1">// [myTest1.layer addAnimation:[self opacityForever_Animation:0.5] forKey:nil];</span>
<span class="c1">///移动的动画。</span>
<span class="c1">// [myTest1.layer addAnimation:[self moveX:1.0f X:[NSNumber numberWithFloat:200.0f]] forKey:nil];</span>
<span class="c1">//缩放效果。</span>
<span class="c1">// [myTest1.layer addAnimation:[self scale:[NSNumber numberWithFloat:1.0f] orgin:[NSNumber numberWithFloat:3.0f] durTimes:2.0f Rep:MAXFLOAT] forKey:nil];</span>
<span class="c1">//组合动画。</span>
<span class="c1">// NSArray *myArray = [NSArray arrayWithObjects:[self opacityForever_Animation:0.5],[self moveX:1.0f X:[NSNumber numberWithFloat:200.0f]],[self scale:[NSNumber numberWithFloat:1.0f] orgin:[NSNumber numberWithFloat:3.0f] durTimes:2.0f Rep:MAXFLOAT], nil];</span>
<span class="c1">// [myTest1.layer addAnimation:[self groupAnimation:myArray durTimes:3.0f Rep:MAXFLOAT] forKey:nil];</span>
<span class="c1">//路径动画。</span>
<span class="c1">// CGMutablePathRef myPah = CGPathCreateMutable();</span>
<span class="c1">// CGPathMoveToPoint(myPah, nil,30, 77);</span>
<span class="c1">// CGPathAddCurveToPoint(myPah, nil, 50, 50, 60, 200, 200, 200);//这里的是控制点。</span>
<span class="c1">// [myTest1.layer addAnimation:[self keyframeAnimation:myPah durTimes:5 Rep:MAXFLOAT] forKey:nil];</span>
<span class="c1">//旋转动画。</span>
<span class="p">[</span><span class="n">myTest1</span><span class="p">.</span><span class="nl">layeraddAnimation</span><span class="p">:[</span><span class="nl">selfrotation</span><span class="p">:</span><span class="mi">2</span><span class="nl">degree</span><span class="p">:</span><span class="n">kRadianToDegrees</span><span class="p">(</span><span class="mi">90</span><span class="p">)</span><span class="nl">direction</span><span class="p">:</span><span class="mi">1</span><span class="nl">repeatCount</span><span class="p">:</span><span class="n">MAXFLOAT</span><span class="p">]</span> <span class="nl">forKey</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="p">}</span>
<span class="cp">#pragma mark === 永久闪烁的动画 ======</span>
<span class="o">-</span><span class="p">(</span><span class="n">CABasicACnimation</span> <span class="o">*</span><span class="p">)</span><span class="nl">opacityForever_Animation</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">time</span>
<span class="p">{</span>
<span class="n">CABasicAnimation</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CABasicAnimationanimationWithKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"opacity"</span><span class="p">];</span><span class="c1">//必须写opacity才行。</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fromValue</span> <span class="o">=</span> <span class="p">[</span><span class="nl">NSNumbernumberWithFloat</span><span class="p">:</span><span class="mf">1.0f</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">toValue</span> <span class="o">=</span> <span class="p">[</span><span class="nl">NSNumbernumberWithFloat</span><span class="p">:</span><span class="mf">0.0f</span><span class="p">];</span><span class="c1">//这是透明度。</span>
<span class="n">animation</span><span class="p">.</span><span class="n">autoreverses</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">MAXFLOAT</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">timingFunction</span><span class="o">=</span><span class="p">[</span><span class="nl">CAMediaTimingFunctionfunctionWithName</span><span class="p">:</span><span class="n">kCAMediaTimingFunctionEaseIn</span><span class="p">];</span><span class="c1">///没有的话是均匀的动画。</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">#pragma mark =====横向、纵向移动===========</span>
<span class="o">-</span><span class="p">(</span><span class="n">CABasicAnimation</span> <span class="o">*</span><span class="p">)</span><span class="nl">moveX</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">time</span> <span class="nl">X</span><span class="p">:(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">x</span>
<span class="p">{</span>
<span class="n">CABasicAnimation</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CABasicAnimationanimationWithKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"transform.translation.x"</span><span class="p">];</span><span class="c1">///.y的话就向下移动。</span>
<span class="n">animation</span><span class="p">.</span><span class="n">toValue</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span><span class="c1">//yes的话,又返回原位置了。</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">MAXFLOAT</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">#pragma mark =====缩放-=============</span>
<span class="o">-</span><span class="p">(</span><span class="n">CABasicAnimation</span> <span class="o">*</span><span class="p">)</span><span class="nl">scale</span><span class="p">:(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">Multiple</span> <span class="nl">orgin</span><span class="p">:(</span><span class="n">NSNumber</span> <span class="o">*</span><span class="p">)</span><span class="n">orginMultiple</span> <span class="nl">durTimes</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">time</span> <span class="nl">Rep</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">repertTimes</span>
<span class="p">{</span>
<span class="n">CABasicAnimation</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CABasicAnimationanimationWithKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"transform.scale"</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fromValue</span> <span class="o">=</span> <span class="n">Multiple</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">toValue</span> <span class="o">=</span> <span class="n">orginMultiple</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">autoreverses</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">repertTimes</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="p">;</span><span class="c1">//不设置时候的话,有一个默认的缩放时间.</span>
<span class="n">animation</span><span class="p">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">#pragma mark =====组合动画-=============</span>
<span class="o">-</span><span class="p">(</span><span class="n">CAAnimationGroup</span> <span class="o">*</span><span class="p">)</span><span class="nl">groupAnimation</span><span class="p">:(</span><span class="n">NSArray</span> <span class="o">*</span><span class="p">)</span><span class="n">animationAry</span> <span class="nl">durTimes</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">time</span> <span class="nl">Rep</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">repeatTimes</span>
<span class="p">{</span>
<span class="n">CAAnimationGroup</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="n">CAAnimationGroupanimation</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">animations</span> <span class="o">=</span> <span class="n">animationAry</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">repeatTimes</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">#pragma mark =====路径动画-=============</span>
<span class="o">-</span><span class="p">(</span><span class="n">CAKeyframeAnimation</span> <span class="o">*</span><span class="p">)</span><span class="nl">keyframeAnimation</span><span class="p">:(</span><span class="n">CGMutablePathRef</span><span class="p">)</span><span class="n">path</span> <span class="nl">durTimes</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">time</span> <span class="nl">Rep</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">repeatTimes</span>
<span class="p">{</span>
<span class="n">CAKeyframeAnimation</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CAKeyframeAnimationanimationWithKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"position"</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">path</span> <span class="o">=</span> <span class="n">path</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">removedOnCompletion</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">timingFunction</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CAMediaTimingFunctionfunctionWithName</span><span class="p">:</span><span class="n">kCAMediaTimingFunctionEaseIn</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">autoreverses</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">time</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">repeatTimes</span><span class="p">;</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
<span class="cp">#pragma mark ====旋转动画======</span>
<span class="o">-</span><span class="p">(</span><span class="n">CABasicAnimation</span> <span class="o">*</span><span class="p">)</span><span class="nl">rotation</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">dur</span> <span class="nl">degree</span><span class="p">:(</span><span class="kt">float</span><span class="p">)</span><span class="n">degree</span> <span class="nl">direction</span><span class="p">:(</span><span class="kt">int</span><span class="p">)</span><span class="n">direction</span> <span class="nl">repeatCount</span><span class="p">:(</span><span class="kt">int</span><span class="p">)</span><span class="n">repeatCount</span>
<span class="p">{</span>
<span class="n">CATransform3D</span> <span class="n">rotationTransform</span> <span class="o">=</span> <span class="n">CATransform3DMakeRotation</span><span class="p">(</span><span class="n">degree</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">direction</span><span class="p">);</span>
<span class="n">CABasicAnimation</span> <span class="o">*</span><span class="n">animation</span> <span class="o">=</span> <span class="p">[</span><span class="nl">CABasicAnimationanimationWithKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"transform"</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">toValue</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSValue</span> <span class="nl">valueWithCATransform3D</span><span class="p">:</span><span class="n">rotationTransform</span><span class="p">];</span>
<span class="n">animation</span><span class="p">.</span><span class="n">duration</span> <span class="o">=</span> <span class="n">dur</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">autoreverses</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">cumulative</span> <span class="o">=</span> <span class="n">NO</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">fillMode</span> <span class="o">=</span> <span class="n">kCAFillModeForwards</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">repeatCount</span> <span class="o">=</span> <span class="n">repeatCount</span><span class="p">;</span>
<span class="n">animation</span><span class="p">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">self</span><span class="p">;</span>
<span class="k">return</span> <span class="n">animation</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p><br/><hr><br/></p>
<h3>如何让view只响应一个按钮</h3>
<p>一些应用经常会在一个view上放多个按钮,比如登录页面,就会有注册与登录按钮。理论上讲注册和登录按钮是不能同时按下的,但是如果你同时按下这两个按钮,这两个东东都会显示响应高亮状态,其结果就不可预知了。这种情况经常会被测试童鞋当成bug如何让view只响应一个按钮 - 杨叫兽 - 青青子衿 悠悠我心。</p>
<p>其实UIView类属性有个exclusiveTouch属性,表示是否该view响应触摸是排他的。默认的设置是NO,即不排他。如果想让按钮排他响应,只需将按钮的exclusiveTouch设置为YES即可。</p>
<p>实际编程中我一般会在viewDidLoad方法中对self.view中的subview进行遍历,如果是按钮对象就将它的exclusiveTouch属性设为YES。</p>
<p>示例代码:</p>
<div class="highlight"><pre><code><span class="k">for</span> <span class="p">(</span><span class="n">UIView</span> <span class="o">*</span><span class="n">subview</span> <span class="n">in</span> <span class="n">self</span><span class="p">.</span><span class="n">view</span><span class="p">.</span><span class="n">subviews</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">([</span><span class="n">subview</span> <span class="nl">isKindOfClass</span><span class="p">:[</span><span class="n">UIButton</span> <span class="n">class</span><span class="p">]])</span> <span class="p">{</span>
<span class="n">subview</span><span class="p">.</span><span class="n">exclusiveTouch</span> <span class="o">=</span> <span class="n">YES</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>另外说明:手势识别会忽略exclusiveTouch设置。详见苹果官方例子:SimpleGestureRecognizers。</p>
<p><br/><hr><br/></p>
<h3>让自己开发的iOS App允许用户通过itunes共享文件</h3>
<p>如果我们想在自己开发的应用中,支持这个服务,就需要在应用程序的配置文件也就是plist文件中</p>
<p>添加Application supports iTunes file sharing,设置为YES。</p>
iOS 简单的动画自定义方法(旋转、移动、闪烁等)与 如何让view只响应一个按钮 与 让自己开发的iOS App允许用户通过itunes共享文件
你的精力分配决定你是什么层次
https://www.zruibin.cn/article/ni_de_jing_li_fen_pei_jue_ding_ni_shi_shi_yao_ceng_ci.html
2015-01-19 22:46
2015-01-19 22:46
<p>你的精力分配决定你是什么层次。一哥们儿爱看莱昂纳多。今天他网上和别人吵了一架,吵架原因是莱昂纳多到底有没有资格拿奥斯卡。吵了两个小时,结果就是不得不晚上加班完成任务。夜宵吃饭找我吐槽,倒不是吐槽奥斯卡和对立影迷,而是觉得自己好没用,因为他觉得自己浪费了太多时间在没有价值没有意义的争论上,自己却把正事耽误了。哥们问自问自答:「你说我是不是贱?也是我不没啥大事做,要是一分钟几百万,真没时间为了这点事花费一下午。」</p>
<p>听某哲学系女老师讲课,讨论到女孩为什么不能做纯家庭主妇。在她看来,经济独立性,性别自尊等倒不是最大的原因,她认为最重要的原因是「女人不能与社会脱节」。因为女人一旦脱节社会,「你的世界就只有那个房子和那个男人」,这样很多“鸡毛蒜皮”小事你都会觉得是大事,于是乎会因为很多「鸡毛蒜皮」事情和丈夫吵起来,因为你的注意力并没有更重要的事情,例如基本的社会交流和工作任务,可以被转移。</p>
<p>之前在网上看到一个让人心酸的故事。就是一个毕业了几年的女孩因为叫的牛肉面里面的肉少和老板争执起来。结果哭了。哭的原因不是因为牛肉的多少,而是如她所说:「这不是我想要的生活。」女孩毕业之后打拼几年,谁想毕业之后还在因为碗里的几块牛肉和别人争执,细细想来,如果她单位时间价值够高,有更重要的事情可以做,他是不会将精力放在讨价还价上的。她的那两排泪,是对自己现在状态和过往经历的一种否定和哭诉。</p>
<p>经济学有个概念,叫「机会成本」。「机会成本」是指为了得到某种东西而所要放弃另一些东西的最大价值。简单打个比方,比方说你就一百块钱,能吃一顿饭,也能看一场电影,你去看电影了,你的机会成本就是这顿饭。又如,你周末两天可以用来打
Dota 也可以用来看《论语》,你去看《论语》了,打Dota 及其快乐就是你的机会成本。</p>
<p>换句话说,我做的事情价值多少,是由我放弃的事情反映出来的,而我放弃的事情,也是由我做的事情的价值反映的。价值这东西不好说,因人而异,哲学命题我水平有限讨论不来,往往因人而异。但是生活经验和道德直觉告诉我,对于个人,一个人相关价值是可以从他的抉择中判断出来。同样的资源你怎么分,同样的抉择你怎么选,将一个人的层次或者说特质表露无疑。</p>
<p>北大出家的柳智宇,在他那里,出家的价值起码大于 MIT 的全奖 offer
;陶渊明不为五斗米折腰,在他那里,个人尊严的价值起码大于官爵和工资;革命年代的英雄们为了共和国的进步牺牲,在他们那里,民族的独立和解放高于个人幸福甚至生命;最近查处的一批贪污腐败分子,在他们那里,个人的享乐高于责任、党性、纪律和民众福祉。</p>
<p>总之,什么样的人,价值如何的人或者事,可以从他的个人选择中判断出来:你放弃了做什么而选择了做什么。你的心中孰轻孰重,孰优孰劣,在你实际行动的诠释下,一切的言语都是很苍白无力的:你做了什么,你就是什么,值什么。</p>
<p>如果你为了一块糖和好朋友大打出手,你俩的友谊和你的好朋友就值这块糖;如果你为了电影票钱和女朋友斤斤计较导致分开,你俩的爱情和你的爱人就值这几百块钱;如果你因为一个廉价花瓶碎了,打得孩子再也不敢自由玩耍,你孩子的好奇心也就值这个花瓶;如果你因为几次加班,就跟领导上司大发脾气吵得不可开交,你的前途也就值这几次加班费。</p>
<p>如果你放弃骄傲和任性也要挽回你亲爱的女朋友,你的女朋友对你来说价值就高于你的自负和倔强;如果你放弃享乐和纵欲,坚持努力和进步,你对成功的追求和渴望的价值就高于你对纯粹欲望快感刺激的多巴胺;如果你倾家荡产也要救你患病的亲人,你的亲人对你来说价值就高于你的一切财富;如果你为了理想放弃了KKR 的高工资而去创业卖糖葫芦,你的理想对你来说就高于雄厚的年薪。</p>
<p>如此,我们的精力分配,一定程度上反映着我们的层次。我们如果为了吴彦祖还是黄晓明帅和别人争执一个下午,那么与其说明我们「心胸格局小」之类的道德定位,毋宁说我们的一个下午时间也就值这点娱乐圈的争论;我们如果为了地铁上让座不让座跟别人吵了起来,骂了起来,与其说我们「素质低」之类的修为判断,毋宁说我们个人形象也就值两站地的地铁。做什么价码的事,就是什么价码的人;为了什么价码的人和事怄气或执拗,就配什么价码的苦难和荣耀。</p>
你的精力分配决定你是什么层次
FFmpeg and iOS and VLC
https://www.zruibin.cn/article/ffmpegandiosandvlc.html
2014-12-22 23:32
2015-10-13 19:41
<p><a href="http://blog.csdn.net/leixiaohua1020/article/details/15811977" target="blank">http://blog.csdn.net/leixiaohua1020/article/details/15811977</a></p>
<p><a href="http://blog.csdn.net/column/details/ffmpeg-devel.html" target="blank">http://blog.csdn.net/column/details/ffmpeg-devel.html</a></p>
<h2>linux下编译ffmpeg</h2>
<p>1、下载ffmpeg。</p>
<p>下载网址:<a href="http://www.ffmpeg.org/download.html">http://www.ffmpeg.org/download.html</a></p>
<p>2、解压缩</p>
<div class="highlight"><pre><code><span class="n">tar</span> <span class="o">-</span><span class="n">zxvf</span> <span class="n">ffmpeg</span><span class="o">-</span><span class="mf">2.0.1</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span>
</code></pre></div>
<p>3、配置,生成Makefile</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">configure</span> <span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">shared</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">yasm</span> <span class="o">--</span><span class="n">prefix</span><span class="o">=/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span> <span class="p">(</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="err">为你要安装的目录</span><span class="p">)</span>
</code></pre></div>
<p>如果执行结果不对,可以根据提示信息,并查看帮助,解决问题</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">configure</span> <span class="o">--</span><span class="n">help</span>
</code></pre></div>
<p>4、编译安装</p>
<div class="highlight"><pre><code><span class="n">make</span>
<span class="n">make</span> <span class="n">install</span>
</code></pre></div>
<p>5、安装之后在/usr/local/ffmpeg会看到有三个目录</p>
<div class="highlight"><pre><code><span class="n">bin</span> <span class="err">执行文件目录</span>
<span class="n">lib</span> <span class="err">静态,动态链接库目录</span>
<span class="n">include</span> <span class="err">编程用到的头文件</span>
</code></pre></div>
<p>为了防止执行程序找不到库文件,
可以将/usr/local/ffmpeg/lib目录设置到LD_LIBRARY_PATH环境变量,
或者查看/usr/local/ffmpeg/lib下所有的链接,并在/usr/lib下建立同样的链接。如下。</p>
<div class="highlight"><pre><code><span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavcodec</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavcodec</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavdevice</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavdevice</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavfilter</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavfilter</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavformat</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavformat</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavutil</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavutil</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswresample</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswresample</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswscale</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswscale</span><span class="p">.</span><span class="n">so</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavcodec</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavcodec</span><span class="p">.</span><span class="n">so</span><span class="mf">.55</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavdevice</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavdevice</span><span class="p">.</span><span class="n">so</span><span class="mf">.55</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavfilter</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavfilter</span><span class="p">.</span><span class="n">so</span><span class="mf">.3</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavformat</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavformat</span><span class="p">.</span><span class="n">so</span><span class="mf">.55</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavutil</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libavutil</span><span class="p">.</span><span class="n">so</span><span class="mf">.52</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswresample</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswresample</span><span class="p">.</span><span class="n">so</span><span class="mf">.0</span>
<span class="n">ln</span> <span class="o">-</span><span class="n">s</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswscale</span><span class="p">.</span><span class="n">so</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">libswscale</span><span class="p">.</span><span class="n">so</span><span class="mf">.2</span>
</code></pre></div>
<p>6、编译测试程序</p>
<div class="highlight"><pre><code><span class="n">gcc</span> <span class="o">-</span><span class="n">o</span> <span class="n">ffmpegtest</span> <span class="n">ffmpegtest</span><span class="p">.</span><span class="n">c</span> <span class="o">-</span><span class="n">I</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">include</span> <span class="o">-</span><span class="n">L</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">ffmpeg</span><span class="o">/</span><span class="n">lib</span> <span class="o">-</span><span class="n">lavformat</span> <span class="o">-</span><span class="n">lavcodec</span> <span class="o">-</span><span class="n">lavtuil</span>
</code></pre></div>
<p>7、执行程序</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">ffmpegtest</span>
</code></pre></div>
<p>或直接执行/usr/local/ffmpeg/lib目录下的./ffmpeg进行测试。</p>
<hr>
<p><a href="http://www.cnblogs.com/smileEvday/archive/2013/11/21/ffmpeg.html" target="blank">http://www.cnblogs.com/smileEvday/archive/2013/11/21/ffmpeg.html</a></p>
<p><a href="https://github.com/kewlbear/FFmpeg-iOS-build-script" target="blank">https://github.com/kewlbear/FFmpeg-iOS-build-script</a></p>
<h3>iOS: FFmpeg编译和使用问题总结</h3>
<p> 折磨了我近一周多时间的FFmpeg库编译问题终于解决了,必须得把这一段时间来遇到过的坑全写出来。如果急着解决问题,编译最新版本的FFmpeg库请直接看第二部分,编译较老版本(0.7)的FFmpeg库请直接跳至第七部分,那里有你想要的编译脚本,但别忘了抽空看看全文。</p>
<p> </p>
<h4>一、背景</h4>
<p> 网上有很多FFmpeg编译配置的资料,大部分都是关于FFmpeg最新的版本(2.0)的,我一开始也想着编写一个2.0版本的,可以放到接手的那个项目中,发现各种问题(无法快进,没有声音),再看一下代码一堆警告,原因很简单,使用的FFMpeg库太新了,很多接口变动了。由于手上没有多少信息,不知道那个项目使用的是哪个版本的FFmpeg库,一点点找,终于知道原来使用的是0.7.x的。找到目标版本的FFmpeg本以为万事大吉了,后来才发现原来这才是坑的开始,有历经一系列磨难,最后终于把编译问题解决了。</p>
<p> </p>
<h4>二、FFmpeg最新版本的库编译</h4>
<p> FFmpeg最新版本的应该是2.1的,历史版本详见<a href="http://www.ffmpeg.org/releases/,在这个网站上我们可以下到所有历史版本的库。FFmpeg是一个跨平台的用C语言写成的库,包含了编码,解码,色彩空间转换等库。编译需要用到命令行,对于我们这些没搞过后台或者linux开发的脚本知识欠缺的人来说的确算是一个挑战。庆幸的是现在网络这么方便,不会做问Google,很快就找到了一个在xcode5下一键编译FFmpeg库的脚本。这个脚本是个老外写的,真心强大,从下载到编译到构建最后的Fat库一气呵成。">http://www.ffmpeg.org/releases/,在这个网站上我们可以下到所有历史版本的库。FFmpeg是一个跨平台的用C语言写成的库,包含了编码,解码,色彩空间转换等库。编译需要用到命令行,对于我们这些没搞过后台或者linux开发的脚本知识欠缺的人来说的确算是一个挑战。庆幸的是现在网络这么方便,不会做问Google,很快就找到了一个在xcode5下一键编译FFmpeg库的脚本。这个脚本是个老外写的,真心强大,从下载到编译到构建最后的Fat库一气呵成。</a></p>
<p> 脚本地址: <a href="https://gist.github.com/m1entus/6983547">https://gist.github.com/m1entus/6983547</a></p>
<p> 运行这个脚本需要依赖一个库Perl写的脚本,搜了一下网上目前编译FFmpeg库的帖子基本都会提到这个脚本,脚本地址如下: <a href="https://github.com/mansr/gas-preprocessor。">https://github.com/mansr/gas-preprocessor。</a></p>
<p> 下载完这两个脚本后,编译FFmpeg库的准备工作就基本完成了,接着依次执行下面几步:</p>
<p> 1、拷贝gas-preprocessor.pl文件到 /usr/bin目录下。</p>
<p> 2、修改gas-preprocessor.pl文件的权限</p>
<p> 注:需要有读,写和执行的权限。具体操作为,首先在命令行下进入/usr/bin目录,然后执行chmod命令,如下图所示:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164924_E74A.png" alt="" style="cursor: pointer;"></p>
<p> 3、切换build-ffmpeg.sh脚本的目录下,使用命令sh build-ffmpeg.sh 运行该脚本即可。</p>
<p> 注: 1) build-ffmpeg.sh脚本的父目录的名字不能包括空格,否则可能导致构建失败。</p>
<p> 2) build-ffmpeg.sh脚本中可以配置编译的FFMpeg版本,以及使用iOS SDK的版本,如下图所示:
<img src="http://static.oschina.net/uploads/img/201504/22164924_CMc6.png" alt="" style="cursor: pointer;"></p>
<p> 该脚本中默认采用的FFmpeg是2.0版本,使用iOS 7.0的SDK编译,c语言编译器采用clang,应用中可以根据实际项目需要选中不同的FFmpeg和iOS SDK版本。</p>
<p> 根据上面的步骤看来,编译工作也没有什么复杂的,为什么我会说踩了很多坑呢?这个问题我会一点点儿解释。</p>
<h4>三、编译较早期版本的FFmpeg本库</h4>
<p> 第二部分中我们介绍了一个牛逼的脚本,一键编译,这给我们造成了一种错觉,FFmpeg编译不过如此吗!如果我们尝试一下把脚本中的VERSION变成0.7试试,运行脚本,发现编译报错。如下图所示:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164924_ky9C.png" alt="" style="cursor: pointer;"></p>
<p> 提示位置选项--disable-iconv,根据提示我们输入./configure查看所有可用选项。命令行下切换到实际的FFmpeg源码目录下,查看帮助如下图:
<img src="http://static.oschina.net/uploads/img/201504/22164925_ZzNx.png" alt="" style="cursor: pointer;"></p>
<p> 我们可以看到很多选项,英语不难,就是有些选项描述的太简洁了,所以实际使用时如果不确定的话,我们可以去问问google。</p>
<p> 好了回过头来看看这个configure文件到底有什么作用呢?</p>
<h5>1、裁剪</h5>
<p> 我们知道FFmpeg库是一个非常庞大的库,包括编码,解码以及流媒体的支持等,如果不做裁剪全部编译进来的话,最后生成的静态库会很大。实际使用中我们可能只想用到解码(例如播放器),因此我们可以使用相关选项指定编译时禁用编码部分。当然我们还可以做进一步的裁剪,例如只打开部分常用格式的解码,禁用掉其他的解码,这样编译出来的静态库将会更小。</p>
<p>要想裁剪,我们的先知道有哪些部分,使用下面的命令可以查看FFMpeg库支持的组件列表。</p>
<div class="highlight"><pre><code><span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">decoders</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">decoders</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">encoders</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">encoders</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">hwaccels</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">hardware</span> <span class="n">accelerators</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">muxers</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">muxers</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">demuxers</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">demuxers</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">parsers</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">parsers</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">protocols</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">protocols</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">bsfs</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">bitstream</span> <span class="n">filters</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">indevs</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">input</span> <span class="n">devices</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">outdevs</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">output</span> <span class="n">devices</span>
<span class="o">--</span><span class="n">list</span><span class="o">-</span><span class="n">filters</span> <span class="n">show</span> <span class="n">all</span> <span class="n">available</span> <span class="n">filters</span>
</code></pre></div>
<p>我们可以根据实际需要把不用的部分都禁用掉,这样编译快,包也会比较小,常用的裁剪选项如下:</p>
<div class="highlight"><pre><code><span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">doc</span> <span class="k">do</span> <span class="n">not</span> <span class="n">build</span> <span class="n">documentation</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffmpeg</span> <span class="n">disable</span> <span class="n">ffmpeg</span> <span class="n">build</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffplay</span> <span class="n">disable</span> <span class="n">ffplay</span> <span class="n">build</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffserver</span> <span class="n">disable</span> <span class="n">ffserver</span> <span class="n">build</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">network</span> <span class="n">disable</span> <span class="n">network</span> <span class="n">support</span> <span class="p">[</span><span class="n">no</span><span class="p">]</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">encoder</span><span class="o">=</span><span class="n">NAME</span> <span class="n">disable</span> <span class="n">encoder</span> <span class="n">NAME</span>
<span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">encoder</span><span class="o">=</span><span class="n">NAME</span> <span class="n">enable</span> <span class="n">encoder</span> <span class="n">NAME</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">encoders</span> <span class="n">disable</span> <span class="n">all</span> <span class="n">encoders</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">decoder</span><span class="o">=</span><span class="n">NAME</span> <span class="n">disable</span> <span class="n">decoder</span> <span class="n">NAME</span>
<span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">decoder</span><span class="o">=</span><span class="n">NAME</span> <span class="n">enable</span> <span class="n">decoder</span> <span class="n">NAME</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">decoders</span> <span class="n">disable</span> <span class="n">all</span> <span class="n">decoders</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">hwaccel</span><span class="o">=</span><span class="n">NAME</span> <span class="n">disable</span> <span class="n">hwaccel</span>
</code></pre></div>
<p>举个例子,如果我们需要做一款本地视频播放器,那么我们可以使用如下配置:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164925_srXm.png" alt="" style="cursor: pointer;"></p>
<p> 当然你还可以根据帮助列表进行更细粒度的裁剪,例如只支持哪几种格式的解码等等。</p>
<h5>2、指定编译环境</h5>
<p> FFMpeg作为一个跨平台的库,不同的平台,不同的人的计算机上编译器的路径都可能不尽相同,所以我们需要为编译脚本指定编译器的路径。同事我们还可以指定其他编译选项,如是否交叉编译,目标平台系统,CPU架构,需要依赖的其他库的路径已经指定是否禁用汇编优化等。</p>
<div class="highlight"><pre><code><span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">cross</span><span class="o">-</span><span class="n">compile</span> <span class="n">assume</span> <span class="n">a</span> <span class="n">cross</span><span class="o">-</span><span class="n">compiler</span> <span class="n">is</span> <span class="n">used</span>
<span class="o">--</span><span class="n">sysroot</span><span class="o">=</span><span class="n">PATH</span> <span class="n">root</span> <span class="n">of</span> <span class="n">cross</span><span class="o">-</span><span class="n">build</span> <span class="n">tree</span>
<span class="o">--</span><span class="n">sysinclude</span><span class="o">=</span><span class="n">PATH</span> <span class="n">location</span> <span class="n">of</span> <span class="n">cross</span><span class="o">-</span><span class="n">build</span> <span class="n">system</span> <span class="n">headers</span>
<span class="o">--</span><span class="n">target</span><span class="o">-</span><span class="n">os</span><span class="o">=</span><span class="n">OS</span> <span class="n">compiler</span> <span class="n">targets</span> <span class="n">OS</span> <span class="p">[]</span>
<span class="o">--</span><span class="n">cc</span><span class="o">=</span><span class="n">CC</span> <span class="n">use</span> <span class="n">C</span> <span class="n">compiler</span> <span class="n">CC</span> <span class="p">[</span><span class="n">gcc</span><span class="p">]</span>
<span class="o">--</span><span class="n">extra</span><span class="o">-</span><span class="n">cflags</span><span class="o">=</span><span class="n">ECFLAGS</span> <span class="n">add</span> <span class="n">ECFLAGS</span> <span class="n">to</span> <span class="n">CFLAGS</span> <span class="p">[]</span>
<span class="o">--</span><span class="n">extra</span><span class="o">-</span><span class="n">ldflags</span><span class="o">=</span><span class="n">ELDFLAGS</span> <span class="n">add</span> <span class="n">ELDFLAGS</span> <span class="n">to</span> <span class="n">LDFLAGS</span> <span class="p">[]</span>
<span class="o">--</span><span class="n">arch</span><span class="o">=</span><span class="n">ARCH</span> <span class="n">select</span> <span class="n">architecture</span> <span class="p">[]</span>
<span class="o">--</span><span class="n">cpu</span><span class="o">=</span><span class="n">CPU</span> <span class="n">select</span> <span class="n">the</span> <span class="n">minimum</span> <span class="n">required</span> <span class="n">CPU</span> <span class="p">(</span><span class="n">affects</span>
<span class="n">instruction</span> <span class="n">selection</span><span class="p">,</span> <span class="n">may</span> <span class="n">crash</span> <span class="n">on</span> <span class="n">older</span> <span class="n">CPUs</span><span class="p">)</span>
<span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">asm</span> <span class="n">disable</span> <span class="n">all</span> <span class="n">assembler</span> <span class="n">optimizations</span>
</code></pre></div>
<p>sysroot即iOS SDK的路径,注意编译真机版本的库时需要使用iPhoneOS.platform中SDK的路径,编译模拟器版本的库使用iPhoneSimulator.platform中SDK的路径。target-os填写darwin(苹果系统的内核),arch可以根据具体的情况添加i386(模拟器),armv6,armv7等。cpu根据具体类型可填写cortex-a8,cortox-a9,i386等。 </p>
<h5>3、指定静态库的安装路径</h5>
<p> 指定执行make install命令时编译好的静态库和相关头文件拷贝到的位置,即FFmpeg库编译后输出的路径。通常我们只需要设置“--prefix=PREFIX”选项即可。例如我们需要将最后生成静态库的路径指向“build/armv7”下,则设置--prefix="build/armv7";</p>
<p> </p>
<h4>四、FFmpeg0.7版本库一键编译脚本</h4>
<p> 通过第三部分的介绍,相信我们应该对FFmpeg的配置都有了一个初步的认识,我们再回到第三部分开始时我们运行build-ffmpeg.sh的碰到的问题,经过查看configure的帮助,我们发现0.7这个版本的FFmpeg库却是没有"--disable-iconv"选项。这个牛逼的脚本是针对当前较新的FFmpeg库写的,在低版本中没有一些配置选项也是正常。</p>
<p> 下面给出经过修改后的脚本,脚本中对原先的脚本进行了精简,去掉了下载部分的代码。</p>
<div class="highlight"><pre><code><span class="cp">#!/bin/sh</span>
<span class="cp">########################################################################</span>
<span class="cp">##################### copyright by smileEvday ##########################</span>
<span class="cp">##################### smileEvday.cnblogs.com ###########################</span>
<span class="cp">########################################################################</span>
<span class="cp"># FFMpeg,SDK版本号</span>
<span class="n">VERSION</span><span class="o">=</span><span class="s">"0.7.4"</span><span class="n">SDKVERSION</span><span class="o">=</span><span class="s">"6.1"</span><span class="err">#最低支持的</span><span class="n">SDK</span><span class="err">版本号</span>
<span class="n">MINSDKVERSION</span><span class="o">=</span><span class="s">"5.0"</span><span class="err">#</span> <span class="err">源文件路径</span>
<span class="n">SRCDIR</span><span class="o">=</span><span class="err">$</span><span class="p">(</span><span class="n">pwd</span><span class="p">)</span>
<span class="n">BUILDDIR</span><span class="o">=</span><span class="s">"${SRCDIR}/build"</span><span class="n">mkdir</span> <span class="o">-</span><span class="n">p</span> <span class="err">$</span><span class="n">BUILDDIR</span>
<span class="cp"># 获取xcode开发环境安装路径</span>
<span class="n">DEVELOPER</span><span class="o">=</span><span class="err">`</span><span class="n">xcode</span><span class="o">-</span><span class="n">select</span> <span class="o">-</span><span class="n">print</span><span class="o">-</span><span class="n">path</span><span class="err">`</span>
<span class="cp"># 要编译的架构列表</span>
<span class="n">ARCHS</span><span class="o">=</span><span class="s">"armv7 armv7s i386"</span><span class="k">for</span> <span class="n">ARCH</span> <span class="n">in</span> <span class="err">$</span><span class="p">{</span><span class="n">ARCHS</span><span class="p">}</span><span class="k">do</span>
<span class="k">if</span> <span class="p">[</span> <span class="s">"${ARCH}"</span> <span class="o">==</span> <span class="s">"i386"</span> <span class="p">];</span>
<span class="n">then</span>
<span class="n">PLATFORM</span><span class="o">=</span><span class="s">"iPhoneSimulator"</span>
<span class="n">EXTRA_CFLAGS</span><span class="o">=</span><span class="s">"-arch i386"</span>
<span class="n">EXTRA_LDFLAGS</span><span class="o">=</span><span class="s">"-arch i386 -mfpu=neon"</span>
<span class="n">EXTRA_CONFIG</span><span class="o">=</span><span class="s">"--arch=i386 --cpu=i386"</span>
<span class="k">else</span>
<span class="n">PLATFORM</span><span class="o">=</span><span class="s">"iPhoneOS"</span>
<span class="n">EXTRA_CFLAGS</span><span class="o">=</span><span class="s">"-arch ${ARCH} -mfloat-abi=softfp"</span>
<span class="n">EXTRA_LDFLAGS</span><span class="o">=</span><span class="s">"-arch ${ARCH} -mfpu=neon -mfloat-abi=softfp"</span>
<span class="n">EXTRA_CONFIG</span><span class="o">=</span><span class="s">"--arch=arm --cpu=cortex-a9 --disable-armv5te"</span>
<span class="n">fi</span>
<span class="n">make</span> <span class="n">clean</span>
<span class="cp"># you can do any clip here </span>
<span class="p">.</span><span class="o">/</span><span class="n">configure</span> <span class="o">--</span><span class="n">prefix</span><span class="o">=</span><span class="s">"${BUILDDIR}/${ARCH}"</span> <span class="err">\</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">doc</span> <span class="err">\</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffmpeg</span> <span class="err">\</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffplay</span> <span class="err">\</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">ffserver</span> <span class="err">\</span> <span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">cross</span><span class="o">-</span><span class="n">compile</span> <span class="err">\</span> <span class="o">--</span><span class="n">enable</span><span class="o">-</span><span class="n">pic</span> <span class="err">\</span> <span class="o">--</span><span class="n">disable</span><span class="o">-</span><span class="n">asm</span> <span class="err">\</span> <span class="o">--</span><span class="n">target</span><span class="o">-</span><span class="n">os</span><span class="o">=</span><span class="n">darwin</span> \
<span class="err">$</span><span class="p">{</span><span class="n">EXTRA_CONFIG</span><span class="p">}</span> <span class="err">\</span> <span class="o">--</span><span class="n">cc</span><span class="o">=</span><span class="s">"${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer/usr/bin/gcc"</span> <span class="err">\</span> <span class="o">--</span><span class="n">as</span><span class="o">=</span><span class="s">"/usr/bin/gas-preprocessor.pl"</span> <span class="err">\</span> <span class="o">--</span><span class="n">sysroot</span><span class="o">=</span><span class="s">"${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer/SDKs/${PLATFORM}${SDKVERSION}.sdk"</span> <span class="err">\</span> <span class="o">--</span><span class="n">extra</span><span class="o">-</span><span class="n">cflags</span><span class="o">=</span><span class="s">"-miphoneos-version-min=${MINSDKVERSION} ${EXTRA_CFLAGS}"</span> <span class="err">\</span> <span class="o">--</span><span class="n">extra</span><span class="o">-</span><span class="n">ldflags</span><span class="o">=</span><span class="s">"-miphoneos-version-min=${MINSDKVERSION} ${EXTRA_LDFLAGS} -isysroot ${DEVELOPER}/Platforms/${PLATFORM}.platform/Developer/SDKs/${PLATFORM}${SDKVERSION}.sdk"</span>
<span class="n">make</span> <span class="o">&&</span> <span class="n">make</span> <span class="n">install</span> <span class="o">&&</span> <span class="n">make</span> <span class="n">clean</span>
<span class="n">done</span>
<span class="cp">########################################################################################################################</span>
<span class="cp">##################################################### 生成fat库 #########################################################</span>
<span class="cp">########################################################################################################################</span>
<span class="n">mkdir</span> <span class="o">-</span><span class="n">p</span> <span class="err">$</span><span class="p">{</span><span class="n">BUILDDIR</span><span class="p">}</span><span class="o">/</span><span class="n">universal</span><span class="o">/</span><span class="n">lib</span>
<span class="n">cd</span> <span class="err">$</span><span class="p">{</span><span class="n">BUILDDIR</span><span class="p">}</span><span class="o">/</span><span class="n">armv7</span><span class="o">/</span><span class="n">libfor</span> <span class="n">file</span> <span class="n">in</span> <span class="o">*</span><span class="p">.</span><span class="n">adocd</span> <span class="err">$</span><span class="p">{</span><span class="n">SRCDIR</span><span class="p">}</span><span class="o">/</span><span class="n">build</span>
<span class="n">xcrun</span> <span class="o">-</span><span class="n">sdk</span> <span class="n">iphoneos</span> <span class="n">lipo</span> <span class="o">-</span><span class="n">output</span> <span class="n">universal</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="err">$</span><span class="n">file</span> <span class="o">-</span><span class="n">create</span> <span class="o">-</span><span class="n">arch</span> <span class="n">armv7</span> <span class="n">armv7</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="err">$</span><span class="n">file</span> <span class="o">-</span><span class="n">arch</span> <span class="n">armv7s</span> <span class="n">armv7s</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="err">$</span><span class="n">file</span> <span class="o">-</span><span class="n">arch</span> <span class="n">i386</span> <span class="n">i386</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="err">$</span><span class="n">file</span>
<span class="n">echo</span> <span class="s">"Universal $file created."</span><span class="n">done</span>
<span class="n">cp</span> <span class="o">-</span><span class="n">r</span> <span class="err">$</span><span class="p">{</span><span class="n">BUILDDIR</span><span class="p">}</span><span class="o">/</span><span class="n">armv7</span><span class="o">/</span><span class="n">include</span> <span class="err">$</span><span class="p">{</span><span class="n">BUILDDIR</span><span class="p">}</span><span class="o">/</span><span class="n">universal</span><span class="o">/</span><span class="n">echo</span> <span class="s">"Done."</span>
</code></pre></div>
<p>注:由于FFmpeg库比较陈旧,该脚本使用xcode4.6下,编译器为GCC,采用6.1的SDK进行编译。如果你的机器上装的同事安装了xcode4.x和xcode5的话,可以在命令行下使用如下命令切换当前的默认编译环境为xcode4.6即可:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164925_zFtD.png" alt="" style="cursor: pointer;"></p>
<p> 设置好xcode的编译环境以后,只需要将该脚本拷贝到FFMpeg源文件路径下运行即可一键生成armv7,armv7s,i386以及合成后的全平台库。</p>
<h4>五、如何使用以及编译链接中可能遇到的问题</h4>
<p> 第四部分中我们对build-ffmpeg.sh的脚本进行了修改和精简后得到了build-ffmpeg0.7.sh,我们只需要运行该脚本就可以一键完成FFmpeg 0.7版本库的编译工作了。编译后我们得到的是lib目录(包含所有生成的静态库)以及include目录(包含相应的头文件),使用时我们只需要将这些文件添加到工程中即可。</p>
<p> 问题到这里似乎就全部解决了,如果顺利的话,恭喜你,你可以直接使用了。</p>
<p> 如果你跟我一样的"不幸"的话,可能还会遇到一些其他问题。下面是我遇到的问题及解决办法:</p>
<h5>1、time.h重复问题</h5>
<p> 我们知道一般静态库都是搭配头文件使用的,要在项目里面使用FFmpeg库,我们出了需要在xcode的build phases中添加静态库以外,还需要导入该库对应的头文件。FFmpeg库对应的头文件有很多,通常会采用设置header search path的方式来导入头文件,这样做有两个好处: 第一可以避免对我们的工程结构造成干扰。第二可以在一定程序上降低头文件冲突。</p>
<p> time.h冲突的问题就是属于头文件冲突,系统的标准库中有time.h文件,FFmpeg应该是在1.1之后也加入了一个time.h文件,路径为libavutil/time.h。所以如果你使用的是FFmpeg1.1之后的版本,那么在使用中就可能会碰到头文件冲突的问题。解决这个问题,网上流传一个方法是修改FFmpeg库中time.h文件的名字,我觉得这太麻烦了,而且也容易出错。后来查看FFmpeg源码的时候偶然发现它自身内部引用这个time.h的时候都有带一层父目录,如#include "libavutil/time.h"。因此想是不是通过指定头文件搜索路径就可以解决这个问题。</p>
<p> 打开工程设置页面,搜索header search path如下图所示:
<img src="http://static.oschina.net/uploads/img/201504/22164925_vPBX.png" alt="" style="cursor: pointer;"></p>
<p> 如果你的FFmpeg库正好是放在当前的路径下,且为了偷懒设置了递归包含头文件的话,那么你很可能就会遇到time.h冲突的问题。因为xcode工程默认的设置是优先查找用户路径,编译时FFmpeg中libavutil下的time.h就会优先被链接,从而导致不会再链接系统time.h文件,最终导致编译失败。</p>
<p> 解决这个问题有两个办法:</p>
<p> a、取消掉Header Search Paths中的递归引用。</p>
<p> b、设置Always Search User Paths为NO。</p>
<h5>2、gcc c compiletest error问题</h5>
<p>
xcode5下面编译FFmpeg都采用clang,同样也会遇到类似问题。这个问题通常出现在配置文件错误的情况下,一般都是gcc路径错误,当然也可能是其他编译参数错误问题。</p>
<p> 出现这个问题我们应该首先检查gcc的路径是否正确,如果确认了指定路径上存在gcc程序,但是还是报错的,我们再去检查当前要编译的平台和指定的gcc路径是否一致,如果你使用iPhoneOS.platform下面的gcc去编译i386平台的库那肯定是不会测试通过的。</p>
<h5>3、C compiler test failed问题</h5>
<p> 编译i386版本的FFmpeg库和armv版本库可能用到的参数不尽相同,例如我遇到这个问题,我的编译选项中有一项如下:</p>
<p> --extra-cflags="-arch i386 -mfloat-abi=softfp -miphoneos-version-min=5.0"</p>
<p> 在我确认其他参数(如cpu,arch)都正确的情况下,依然提示我们“C compiler test failed.” 后面紧跟着一句查看config.log你可以得到更详细的信息,于是打开该文件,你可以在最开始的地方看到你的配置语句,如果是用脚本,这块儿会显示最终解释后(替换参数为真实值)的配置语句。然后紧跟着一堆具体的配置,通常哭啼的错误信息会在该文件的最末尾。我遇到的问题的信息如下:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164925_2Y9i.png" alt="" style="cursor: pointer;"></p>
<p> 看到标红的这个区域了没有,提示“-mfloat-abi=softfp”选项不支持,删掉该选项后,在运行时配置就通过了。其他配置问题,都可以通过查看config.log来获取更详细的错误信息。</p>
<h5>4、由于未导入libbz动态库的问题</h5>
<p> 如果导入FFmpeg库了,并且配置了头文件搜索路径,遇到"Undefined symbols for architecture armv7s: _BZ2_bzDecompressInit",如下图所示:</p>
<p><img src="http://static.oschina.net/uploads/img/201504/22164925_93eg.png" alt="" style="cursor: pointer;"></p>
<p> 这个问题是由于没有导入“libbz2.1.0.dylib”库的原因,导入库即可解决该问题。</p>
<h5>5、libavcodec/audioconvert.h头文件缺失问题</h5>
<p> 不知道为什么执行make install的时候libavcodec中的audioconvert.h怎么没有拷贝到include目录下的libavcodec中去,查看发现原来libavutil目录下已经有一个audioconvert.h了。解决这个问题只需要从FFmpeg库的libavcodec中拷贝audioconvert.h头文件到include的libavcodec目录中即可解决。</p>
<h4>六、杂谈</h4>
<p> 感谢我所遭遇的"不幸",如果当时接受的项目使用的最新版本的FFmpeg库,我可能就直接运行一下那个牛逼的脚本,然后一切就可以顺顺利利。如果真是那样的话,我可能也就不会花时间去学习基本的脚本知识,去了解FFmpeg库的相关配置,这样的结果就是下次当我中奖遇到FFmpeg库编译链接等问题时,只能束手无策。</p>
<p> 说了这么多,当我们使用一个技术的时候,不应该仅仅停留在会用的层次,花点儿时间了解一下背后的原理会更让你对该技术有个更深的理解,多学,多看,多思考,最终会有有所收获的。</p>
<h4>七、编译脚本及参考资料</h4>
<h5>1、编译脚本 </h5>
<p> gas-preprocessor脚本地址: <a href="" target="blank"></a><a href="https://github.com/mansr/gas-preprocessor ">https://github.com/mansr/gas-preprocessor </a></p>
<p> FFmpeg 2.x一键化编译脚本: <a href="" target="blank"></a><a href="https://gist.github.com/m1entus/6983547">https://gist.github.com/m1entus/6983547</a></p>
<p> FFmpeg0.7一键化编译脚本: <a href="" target="blank"></a><a href="https://gist.github.com/smileEvday/7565260">https://gist.github.com/smileEvday/7565260</a></p>
<h5>2、参考资料</h5>
<p>模拟器与真机下ffmpeg的编译方法(总结版)</p>
<p><a href="http://www.cocoachina.com/iphonedev/toolthain/2011/1020/3395.html" target="blank">http://www.cocoachina.com/iphonedev/toolthain/2011/1020/3395.html</a>
</p>
<p>编译在ios4.3中使用的ffmpeg库(转)</p>
<p><a href="http://www.cocoachina.com/bbs/simple/?t70887.html" target="blank">http://www.cocoachina.com/bbs/simple/?t70887.html</a>
</p>
<p>Installing ffmpeg ios libraries armv7, armv7s, i386 and universal on Mac with 10.8</p>
<p><a href="http://stackoverflow.com/questions/18003034/installing-ffmpeg-ios-libraries-armv7-armv7s-i386-and-universal-on-mac-with-10/19370679#19370679" target="blank">http://stackoverflow.com/questions/18003034/installing-ffmpeg-ios-libraries-armv7-armv7s-i386-and-universal-on-mac-with-10/19370679#19370679</a>
</p>
<p><br/><hr><br/></p>
<h3>VLC</h3>
<p><a href="https://wiki.videolan.org/iOSCompile" target="blank">https://wiki.videolan.org/iOSCompile</a></p>
<p><a href="https://wiki.videolan.org/VLCKit/" target="blank">https://wiki.videolan.org/VLCKit/</a></p>
<p><a href="http://www.cnblogs.com/zjjcy/p/3858759.html" target="blank">http://www.cnblogs.com/zjjcy/p/3858759.html</a></p>
<p><a href="http://www.cnblogs.com/luxiaoxun/p/3462153.html" target="blank">http://www.cnblogs.com/luxiaoxun/p/3462153.html</a></p>
FFmpeg and iOS and VLC
GDB基本命令和技巧
https://www.zruibin.cn/article/gdb_ji_ben_ming_ling_he_ji_qiao.html
2014-12-18 01:26
2015-10-13 19:18
<h3>关于GDB</h3>
<p>对于大多数Cocoa程序员来说,最常用的debugger莫过于Xcode自带的调试工具了。而实际上,它正是gdb的一个图形化包装。相对于gdb,图形化带来了很多便利,但同时也缺少了一些重要功能。而且在某些情况下,gdb反而更加方便。因此,学习gdb,了解一下幕后的实质,也是有必要的。</p>
<p>gdb可以通过终端运行,也可以在Xcode的控制台调用命令。本文将通过终端讲述一些gdb的基本命令和技巧。</p>
<p>首先,我们来看一个例子:</p>
<div class="highlight"><pre><code><span class="cp">#import <Foundation/Foundation.h> </span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">NSAutoreleasePool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSAutoreleasePool</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"Hello, world!"</span><span class="p">);</span>
<span class="p">[</span><span class="n">pool</span> <span class="n">release</span><span class="p">];</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>我们把文件命名为test.m,然后编译:</p>
<div class="highlight"><pre><code><span class="n">gcc</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">framework</span> <span class="n">Foundation</span> <span class="n">test</span><span class="p">.</span><span class="n">m</span>
</code></pre></div>
<p>准备工作已经完成。现在我们可以开始调试了。只要把要调试的文件名作为参数,启动gdb:</p>
<div class="highlight"><pre><code><span class="n">gdb</span> <span class="n">a</span><span class="p">.</span><span class="n">out</span>
</code></pre></div>
<p>gdb启动后会输出很多法律声明之类的信息。无视它们,最后我们看到一个提示:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
</code></pre></div>
<p>成功!现在debugger和刚才编译好的程序都被装载了。不过,现在程序还没有开始运行。因为gdb在程序开始前把它暂停了,好让我们有机会设置调试参数。这次我们不需要做特别设置,所以马上开始运行吧:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">Reading</span> <span class="n">symbols</span> <span class="k">for</span> <span class="n">shared</span> <span class="n">libraries</span> <span class="p">.</span><span class="o">++++</span><span class="p">.......................</span> <span class="n">done</span>
<span class="mi">2011</span><span class="o">-</span><span class="mo">06</span><span class="o">-</span><span class="mi">16</span> <span class="mi">20</span><span class="o">:</span><span class="mi">28</span><span class="o">:</span><span class="mf">53.658</span> <span class="n">a</span><span class="p">.</span><span class="n">out</span><span class="p">[</span><span class="mi">2946</span><span class="o">:</span><span class="n">a0f</span><span class="p">]</span> <span class="n">Hello</span><span class="p">,</span> <span class="n">world</span><span class="o">!</span>
<span class="n">Program</span> <span class="n">exited</span> <span class="n">normally</span><span class="p">.</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
</code></pre></div>
<p>糟糕,程序竟然exited normally了(==|||)。这可不行,我们得让他崩溃才行。所以我们给这个小程序添加一个bug:</p>
<div class="highlight"><pre><code><span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">"Hello, world! x = %@"</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
</code></pre></div>
<p>nice。这样一来程序就会漂亮地崩溃了:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">Reading</span> <span class="n">symbols</span> <span class="k">for</span> <span class="n">shared</span> <span class="n">libraries</span> <span class="p">.</span><span class="o">++++</span><span class="p">.......................</span> <span class="n">done</span>
<span class="n">Program</span> <span class="n">received</span> <span class="n">signal</span> <span class="n">EXC_BAD_ACCESS</span><span class="p">,</span> <span class="n">Could</span> <span class="n">not</span> <span class="n">access</span> <span class="n">memory</span><span class="p">.</span>
<span class="nl">Reason</span><span class="p">:</span> <span class="mi">13</span> <span class="n">at</span> <span class="nl">address</span><span class="p">:</span> <span class="mh">0x0000000000000000</span>
<span class="mh">0x00007fff84f1011c</span> <span class="n">in</span> <span class="n">objc_msgSend</span> <span class="p">()</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
</code></pre></div>
<p>如果我们是在shell中直接运行的程序,在崩溃后就会回到shell。不过现在我们是通过gdb运行的,所以现在并没有跳出。gdb暂停了我们的程序,但依然使之驻留在内存中,让我们有机会做调试。</p>
<p>首先,我们想知道具体是哪里导致了程序崩溃。gdb已经通过刚才的输出告知了我们: 函数objc_msgSend就是祸之根源。但是这个信息并不足够,因为这个objc_msgSend是objc运行时库中的函数。我们并不知道它是怎么调用的。我们关注的是我们自己的代码。
要知道这一点,我们需要得到当前进程的函数调用栈的情况,以此回溯找到我们自己的方法。这时我们需要用到backtrace命令,一般简写为bt:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">bt</span>
<span class="cp">#0 0x00007fff84f1011c in objc_msgSend () </span>
<span class="cp">#1 0x00007fff864ff30b in _CFStringAppendFormatAndArgumentsAux () </span>
<span class="cp">#2 0x00007fff864ff27d in _CFStringCreateWithFormatAndArgumentsAux () </span>
<span class="cp">#3 0x00007fff8657e9ef in _CFLogvEx () </span>
<span class="cp">#4 0x00007fff87beab3e in NSLogv () </span>
<span class="cp">#5 0x00007fff87beaad6 in NSLog () </span>
<span class="cp">#6 0x0000000100000ed7 in main () at test.m:10</span>
</code></pre></div>
<p>现在我们可以看到,程序在test.m的第10行,调用NSLog方法时崩溃了。接下来我们想看一下这次调用的详细信息。这时我们要用到up命令。up命令可以在栈的各层之间跳转。本例中,我们的代码main是#6:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">up</span> <span class="mi">6</span>
<span class="cp">#6 0x0000000100000ed7 in main () at test.m:10 </span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">"Hello, world! x = %@"</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
</code></pre></div>
<p>这回不仅是函数名,连出错的那行代码也打印出来了。但是,我们还可以使用list(简写为l)命令,打印出更多信息:
ps: 如果需要回到栈列表。可以使用down命令。</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">l</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">NSAutoreleasePool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSAutoreleasePool</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">"Hello, world! x = %@"</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
<span class="p">[</span><span class="n">pool</span> <span class="n">release</span><span class="p">];</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>啊,整个代码都被列出来了。虽然我们用编辑器打开test.m文件然后找到第10行也可以打到同样效果,但显然没有上面的方法更有效率。(当然没有Xcode自带的那个快就是了)</p>
<p>好了,现在我们再来看看这个bug(虽然是我们自己弄出来的)。很明显,在格式化字符串前少加了一个@。我们改正它,并重新运行一遍程序:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">Reading</span> <span class="n">symbols</span> <span class="k">for</span> <span class="n">shared</span> <span class="n">libraries</span> <span class="p">.</span><span class="o">++++</span><span class="p">.......................</span> <span class="n">done</span>
<span class="n">Program</span> <span class="n">received</span> <span class="n">signal</span> <span class="n">EXC_BAD_ACCESS</span><span class="p">,</span> <span class="n">Could</span> <span class="n">not</span> <span class="n">access</span> <span class="n">memory</span><span class="p">.</span>
<span class="nl">Reason</span><span class="p">:</span> <span class="n">KERN_INVALID_ADDRESS</span> <span class="n">at</span> <span class="nl">address</span><span class="p">:</span> <span class="mh">0x000000000000002a</span>
<span class="mh">0x00007fff84f102b3</span> <span class="n">in</span> <span class="n">objc_msgSend_fixup</span> <span class="p">()</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">bt</span>
<span class="cp">#0 0x00007fff84f102b3 in objc_msgSend_fixup () </span>
<span class="cp">#1 0x0000000000000000 in ?? ()</span>
</code></pre></div>
<p>啊咧,程序还是崩溃了。更杯具的是,栈信息没有显示出这个objc_msgSend_fixup方法是从哪里调用的。这样我们就没法用上面的方法找到目标代码了。这时,我们只好请出一个debugger最常用的功能:断点。</p>
<p>在gdb中,设置断点通过break命令实现。它可以简写为b。有两种方法可以确定断点的位置:传入一个已定义的符号,或是直接地通过一个file:line对设置位置。
现在让我们在main函数的开始处设置一个断点:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="n">test</span><span class="p">.</span><span class="nl">m</span><span class="p">:</span><span class="mi">8</span>
<span class="n">Breakpoint</span> <span class="mi">1</span> <span class="n">at</span> <span class="mh">0x100000e8f</span><span class="o">:</span> <span class="n">file</span> <span class="n">test</span><span class="p">.</span><span class="n">m</span><span class="p">,</span> <span class="n">line</span> <span class="mf">8.</span>
</code></pre></div>
<p>debugger给了我们一个回应,告诉我们断点设置成功了,而且这个断点的标号是1。断点的标号很有用,可以用来给断点排序&停用&启用&删除等。不过我们现在不需要理会,我们只是接着运行程序:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span>
<span class="n">The</span> <span class="n">program</span> <span class="n">being</span> <span class="n">debugged</span> <span class="n">has</span> <span class="n">been</span> <span class="n">started</span> <span class="n">already</span><span class="p">.</span>
<span class="n">Start</span> <span class="n">it</span> <span class="n">from</span> <span class="n">the</span> <span class="n">beginning</span><span class="o">?</span> <span class="p">(</span><span class="n">y</span> <span class="n">or</span> <span class="n">n</span><span class="p">)</span> <span class="n">y</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">Breakpoint</span> <span class="mi">1</span><span class="p">,</span> <span class="n">main</span> <span class="p">(</span><span class="n">argc</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span> <span class="n">argv</span><span class="o">=</span><span class="mh">0x7fff5fbff628</span><span class="p">)</span> <span class="n">at</span> <span class="n">test</span><span class="p">.</span><span class="nl">m</span><span class="p">:</span><span class="mi">8</span>
<span class="mi">8</span> <span class="n">NSAutoreleasePool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSAutoreleasePool</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
</code></pre></div>
<p>debugger在在我们期望的地方停下了。现在我们使用next(简写n)命令单步调试程序,看看它到底是在哪一行崩溃的:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">n</span>
<span class="kt">int</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">42</span><span class="p">;</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"Hello, world! x = %@"</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span>
<span class="n">Program</span> <span class="n">received</span> <span class="n">signal</span> <span class="n">EXC_BAD_ACCESS</span><span class="p">,</span> <span class="n">Could</span> <span class="n">not</span> <span class="n">access</span> <span class="n">memory</span><span class="p">.</span>
<span class="nl">Reason</span><span class="p">:</span> <span class="n">KERN_INVALID_ADDRESS</span> <span class="n">at</span> <span class="nl">address</span><span class="p">:</span> <span class="mh">0x000000000000002a</span>
<span class="mh">0x00007fff84f102b3</span> <span class="n">in</span> <span class="n">objc_msgSend_fixup</span> <span class="p">()</span>
</code></pre></div>
<p>值得注意的是,我只键入了一次n命令,随后直接敲了2次回车。这样做的原因是gdb把任何空输入当作最近一次输入命令的重复。所以这里相当于输入了3次n。</p>
<p>现在我们可以看到,崩溃之处依然是NSLog。原因嘛,当然是在格式化输出的地方用%@表示int型变量x了。我们仔细看一下输出信息:崩溃原因是错误地访问了0x000000000000002a这个地址。而2a的十进制表示正是42--我们为x赋的值。编译器把它当作地址了。</p>
<h3>输出数值</h3>
<p>一个很重要的调试方法是输出表达式和变量的值。在gdb中,这是通过print命令完成的。</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="n">x</span>
<span class="err">$</span><span class="mi">1</span> <span class="o">=</span> <span class="mi">42</span>
</code></pre></div>
<p>在print命令后追加/format可以格式化输出。/format是一个gdb的格式化字符串,比较有用的格式化字符有 x:十进制数; c:字符; a:地址等。</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span><span class="o">/</span><span class="n">x</span> <span class="n">x</span>
<span class="err">$</span><span class="mi">2</span> <span class="o">=</span> <span class="mh">0x2a</span>
</code></pre></div>
<p>print-object方法(简写为po)用来输出obj-c中的对象。它的工作原理是,向被调用的对象发送名为debugDescription的消息。它和常见的description消息很像。</p>
<p>举例来说,让我们输出一下autorelease pool:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">po</span> <span class="n">pool</span>
<span class="o"><</span><span class="nl">NSAutoreleasePool</span><span class="p">:</span> <span class="mh">0x10010e820</span><span class="o">></span>
</code></pre></div>
<p>这个命令不仅仅可以输出显式定义的对象,也可以输出表达式的结果。这次我们测试一下nsobject中debugDescription的方法签名:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">po</span> <span class="p">[</span><span class="n">NSObject</span> <span class="nl">instanceMethodSignatureForSelector</span><span class="p">:</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">debugDescription</span><span class="p">)]</span>
<span class="o"><</span><span class="nl">NSMethodSignature</span><span class="p">:</span> <span class="mh">0x10010f320</span><span class="o">></span>
<span class="n">number</span> <span class="n">of</span> <span class="n">arguments</span> <span class="o">=</span> <span class="mi">2</span>
<span class="n">frame</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">224</span>
<span class="n">is</span> <span class="n">special</span> <span class="k">struct</span> <span class="k">return</span><span class="o">?</span> <span class="n">NO</span>
<span class="k">return</span> <span class="nl">value</span><span class="p">:</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span>
<span class="n">type</span> <span class="n">encoding</span> <span class="p">(</span><span class="err">@</span><span class="p">)</span> <span class="sc">'@'</span>
<span class="n">flags</span> <span class="p">{</span><span class="n">isObject</span><span class="p">}</span>
<span class="n">modifiers</span> <span class="p">{}</span>
<span class="n">frame</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">offset</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span> <span class="n">size</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">}</span>
<span class="n">memory</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">}</span>
<span class="n">argument</span> <span class="mi">0</span><span class="o">:</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span>
<span class="n">type</span> <span class="n">encoding</span> <span class="p">(</span><span class="err">@</span><span class="p">)</span> <span class="sc">'@'</span>
<span class="n">flags</span> <span class="p">{</span><span class="n">isObject</span><span class="p">}</span>
<span class="n">modifiers</span> <span class="p">{}</span>
<span class="n">frame</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">offset</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span> <span class="n">size</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">}</span>
<span class="n">memory</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">}</span>
<span class="n">argument</span> <span class="mi">1</span><span class="o">:</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span> <span class="o">--------</span>
<span class="n">type</span> <span class="n">encoding</span> <span class="p">(</span><span class="o">:</span><span class="p">)</span> <span class="sc">':'</span>
<span class="n">flags</span> <span class="p">{}</span>
<span class="n">modifiers</span> <span class="p">{}</span>
<span class="n">frame</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span> <span class="n">offset</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">,</span> <span class="n">size</span> <span class="n">adjust</span> <span class="o">=</span> <span class="mi">0</span><span class="p">}</span>
<span class="n">memory</span> <span class="p">{</span><span class="n">offset</span> <span class="o">=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">size</span> <span class="o">=</span> <span class="mi">8</span><span class="p">}</span>
</code></pre></div>
<p>是不是很方便。但是要注意,gdb也许会不能识别NSObject这样的类名。这时我们就要使用一些小技巧,比如说用NSClassFromString来获得类名:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">po</span> <span class="p">[</span><span class="n">NSClassFromString</span><span class="p">(</span><span class="err">@</span><span class="s">"NSObject"</span><span class="p">)</span> <span class="nl">instanceMethodSignatureForSelector</span><span class="p">:</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">debugDescription</span><span class="p">)]</span>
</code></pre></div>
<p>返回值是对象的表达式可以用po命令输出结果,那么返回值是基本类型的方法又怎样呢?显然,它们是可以用p命令输出的。但是要小心,因为gdb并不能自动识别出返回值的类型。所以我们在输出前要显式地转换一下:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">[</span><span class="n">NSObject</span> <span class="nl">instancesRespondToSelector</span><span class="p">:</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">doesNotExist</span><span class="p">)]</span>
<span class="n">Unable</span> <span class="n">to</span> <span class="n">call</span> <span class="n">function</span> <span class="s">"objc_msgSend"</span> <span class="n">at</span> <span class="mh">0x7fff84f100f4</span><span class="o">:</span> <span class="n">no</span> <span class="k">return</span> <span class="n">type</span> <span class="n">information</span> <span class="n">available</span><span class="p">.</span>
<span class="n">To</span> <span class="n">call</span> <span class="n">this</span> <span class="n">function</span> <span class="n">anyway</span><span class="p">,</span> <span class="n">you</span> <span class="n">can</span> <span class="n">cast</span> <span class="n">the</span> <span class="k">return</span> <span class="n">type</span> <span class="n">explicitly</span> <span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">g</span><span class="p">.</span> <span class="err">'</span><span class="n">print</span> <span class="p">(</span><span class="kt">float</span><span class="p">)</span> <span class="n">fabs</span> <span class="p">(</span><span class="mf">3.0</span><span class="p">)</span><span class="err">'</span><span class="p">)</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">char</span><span class="p">)[</span><span class="n">NSObject</span> <span class="nl">instancesRespondToSelector</span><span class="p">:</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">doesNotExist</span><span class="p">)]</span>
<span class="err">$</span><span class="mi">5</span> <span class="o">=</span> <span class="mi">0</span> <span class="err">'</span><span class="mo">00</span><span class="err">'</span>
</code></pre></div>
<p>你也许发现了,doesNotExist方法的返回值是BOOL,而我们做的转换却是char。这是因为gdb也不能识别那些用typedef定义的类型。不仅仅是你定义的,即使是Cocoa框架里定义的也不行。</p>
<p>你也许已经注意到,在用p进行输出的时侯,输出值前面会有一个类似"$1="的前缀。它们是gdb变量。它们可以在后面的表达式中使用,来指代它后面的值。在下面的例子里,我们开辟了一块内存,将其置零,然后释放。在这个过程中,我们使用了gdb变量,这样就不用一遍遍地复制粘贴地址了。</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span><span class="n">malloc</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
<span class="err">$</span><span class="mi">6</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x100105ab0</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">bzero</span><span class="p">(</span><span class="err">$</span><span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span class="err">$</span><span class="mi">7</span> <span class="o">=</span> <span class="kt">void</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="o">*</span><span class="err">$</span><span class="mi">6</span>
<span class="err">$</span><span class="mi">8</span> <span class="o">=</span> <span class="mi">0</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">free</span><span class="p">(</span><span class="err">$</span><span class="mi">6</span><span class="p">)</span>
<span class="err">$</span><span class="mi">9</span> <span class="o">=</span> <span class="kt">void</span>
</code></pre></div>
<p>我们也想把这个技巧用到对象上,但不幸的是po命令并不会把它的返回值存储到变量里。所以我们在得到一个新的对象时必须先使用p命令:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)[[</span><span class="n">NSObject</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">]</span>
<span class="err">$</span><span class="mi">10</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span> <span class="mh">0x100105950</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">po</span> <span class="err">$</span><span class="mi">10</span>
<span class="o"><</span><span class="nl">NSObject</span><span class="p">:</span> <span class="mh">0x100105950</span><span class="o">></span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">long</span><span class="p">)[</span><span class="err">$</span><span class="mi">10</span> <span class="n">retainCount</span><span class="p">]</span>
<span class="err">$</span><span class="mi">11</span> <span class="o">=</span> <span class="mi">1</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">p</span> <span class="p">(</span><span class="kt">void</span><span class="p">)[</span><span class="err">$</span><span class="mi">10</span> <span class="n">release</span><span class="p">]</span>
<span class="err">$</span><span class="mi">12</span> <span class="o">=</span> <span class="kt">void</span>
</code></pre></div>
<h3>检查内存</h3>
<p>有些时候,仅仅输出一个数值还不能帮助我们查找出错误。我们需要一次性地打印出一整块内存来窥视全局。这时候我们就需要使用x命令。</p>
<p>x命令的格式是x/format address。其中address很简单,它通常是指向一块内存的表达式。但是format的语法就有点复杂了。它由三个部分组成:</p>
<p>第一个是要显示的块的数量;第二个是显示格式(如x代表16进制,d代表十进制,c代表字符);第三个是每个块的大小。值得注意的是第三部分,即块大小是用字符对应的。用b, h, w, g 分别表示1, 2, 4, 8 bytes。举例来说,用十六进制方式,打印从ptr开始的4个4-byte块应该这样写:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">x</span><span class="o">/</span><span class="mi">4</span><span class="n">xw</span> <span class="n">ptr</span>
</code></pre></div>
<p>接下来举一个比较实际的例子。我们看一下NSObject类的内容:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">x</span><span class="o">/</span><span class="mi">4</span><span class="n">xg</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)[</span><span class="n">NSObject</span> <span class="n">class</span><span class="p">]</span>
<span class="mh">0x7fff70adb468</span> <span class="o"><</span><span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="o">>:</span> <span class="mh">0x00007fff70adb440</span> <span class="mh">0x0000000000000000</span>
<span class="mh">0x7fff70adb478</span> <span class="o"><</span><span class="n">OBJC_CLASS_</span><span class="err">$</span><span class="n">_NSObject</span><span class="o">+</span><span class="mi">16</span><span class="o">>:</span> <span class="mh">0x0000000100105ac0</span> <span class="mh">0x0000000100104ac0</span>
</code></pre></div>
<p>接下来再看看一个NSObject实例的内容:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">x</span><span class="o">/</span><span class="mi">1</span><span class="n">xg</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)[</span><span class="n">NSObject</span> <span class="n">new</span><span class="p">]</span>
<span class="mh">0x100105ba0</span><span class="o">:</span> <span class="mh">0x00007fff70adb468</span>
</code></pre></div>
<p>现在我们看到,在实例开头引用了类的地址。</p>
<h3>设置变量</h3>
<p>有时,查看数值程度的能力还是稍弱了一点,我们还想能够修改变量。这也很简单,只需要使用set命令:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">set</span> <span class="n">x</span> <span class="o">=</span> <span class="mi">43</span>
</code></pre></div>
<p>我们可以用任意表达式给一个变量赋值。比如说新创建一个对象然后赋值:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">set</span> <span class="n">obj</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)[[</span><span class="n">NSObject</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">]</span>
</code></pre></div>
<h3>断点</h3>
<p>我们可以在程序的某个位置设置断点,这样当程序运行到那里的时候就会暂停,而把控制权转移给调试器。就像之前提到的,我们用break命令来设置断点。下面详细地列出了如何设置断点的目标:</p>
<p>SymbolName: 为断点指定一个函数名。这样断点就会设置在该函数上。
file.c:1234: 把断点设置在指定文件的一行。
-[ClassName method:name:]: 把断点设置在objc的方法上。用+代表类方法。
*0xdeadbeef: 在内存的指定位置设置断点。这不是很常用,一般在没有源码的调试时使用。</p>
<p>断点可以用enable命令和disable命令来切换到使用和停用状态,也可以通过delete命令彻底删除。想要查看现有断点的话,使用info breakpoints命令(可以简写成info b,或是i b)。</p>
<p>另外,我们也可以用if命令,把断点升级成条件断点。顾名思义,条件断点只会在设定的条件成真时起作用。举例来说,下面的语句为MyMethod添加了一个条件断点,它只在参数等于5的时候有效:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="o">-</span><span class="p">[</span><span class="n">Class</span> <span class="nl">myMethod</span><span class="p">:]</span> <span class="k">if</span> <span class="n">parameter</span> <span class="o">==</span> <span class="mi">5</span>
</code></pre></div>
<p>最后,在断点上可以附加gdb命令。这样,当断点中断时,附带的命令会自动执行。附加命令使用commands breakpointnumber。这时gdb就会进入断点指令输入状态。</p>
<p>断点指令就是一个以end结尾的标准gdb指令序列。举个例子,我们想在每次NSLog被调用时输出栈信息:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="n">NSLog</span>
<span class="n">Breakpoint</span> <span class="mi">4</span> <span class="n">at</span> <span class="mh">0x7fff87beaa62</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">commands</span>
<span class="n">Type</span> <span class="n">commands</span> <span class="k">for</span> <span class="n">when</span> <span class="n">breakpoint</span> <span class="mi">4</span> <span class="n">is</span> <span class="n">hit</span><span class="p">,</span> <span class="n">one</span> <span class="n">per</span> <span class="n">line</span><span class="p">.</span>
<span class="n">End</span> <span class="n">with</span> <span class="n">a</span> <span class="n">line</span> <span class="n">saying</span> <span class="n">just</span> <span class="s">"end"</span><span class="p">.</span>
<span class="o">></span><span class="n">bt</span>
<span class="o">></span><span class="n">end</span>
</code></pre></div>
<p>这很好理解,只有一点需要提一下:如果commands命令是作用在刚设置的断点上的话,那么就可以省略断点序号。</p>
<p>有些时候,我们希望调试器输出一些信息,但是并不想中断程序运行。这实际上也可以通过追加指令实现。我们只需要在指令的最后增加continue指令就行了。在下面的例子里,我们在断点中断后打印栈信息和参数信息,随后继续运行:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">b</span> <span class="o">-</span><span class="p">[</span><span class="n">Class</span> <span class="nl">myMethod</span><span class="p">:]</span>
<span class="n">Breakpoint</span> <span class="mi">5</span> <span class="n">at</span> <span class="mh">0x7fff864f1404</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">commands</span>
<span class="n">Type</span> <span class="n">commands</span> <span class="k">for</span> <span class="n">when</span> <span class="n">breakpoint</span> <span class="mi">5</span> <span class="n">is</span> <span class="n">hit</span><span class="p">,</span> <span class="n">one</span> <span class="n">per</span> <span class="n">line</span><span class="p">.</span>
<span class="n">End</span> <span class="n">with</span> <span class="n">a</span> <span class="n">line</span> <span class="n">saying</span> <span class="n">just</span> <span class="s">"end"</span><span class="p">.</span>
<span class="o">></span><span class="n">bt</span>
<span class="o">></span><span class="n">p</span> <span class="n">parameter</span>
<span class="o">></span><span class="k">continue</span>
<span class="o">></span><span class="n">end</span>
</code></pre></div>
<p>最后一个奇特的运用是return命令。它和c中的同名命令一样,都用来跳出当前函数。如果设置了参数,这参数会作为函数的返回值。</p>
<p>比如说,我们可以用这个技巧屏蔽掉NSLog函数:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">commands</span>
<span class="n">Type</span> <span class="n">commands</span> <span class="k">for</span> <span class="n">when</span> <span class="n">breakpoint</span> <span class="mi">6</span> <span class="n">is</span> <span class="n">hit</span><span class="p">,</span> <span class="n">one</span> <span class="n">per</span> <span class="n">line</span><span class="p">.</span>
<span class="n">End</span> <span class="n">with</span> <span class="n">a</span> <span class="n">line</span> <span class="n">saying</span> <span class="n">just</span> <span class="s">"end"</span><span class="p">.</span>
<span class="o">></span><span class="k">return</span>
<span class="o">></span><span class="k">continue</span>
<span class="o">></span><span class="n">end</span>
</code></pre></div>
<p>有一点需要提醒:虽然上述的技巧很有用,但同时它会带来副作用。例如上面屏蔽NSLog的技巧会严重拖慢程序的运行速度。因为每次断点中断,都会使控制权转移到debugger一边,然后运行命令。这些跨进程的操作很耗时间。</p>
<p>有时候也许看不出来,但当执行的断点变多,或是你在诸如objc_msgSend这样的方法上添加了条件断点,那么也许你的程序会一直运行到天荒地老。</p>
<h3>无源码时的参数</h3>
<p>有时我们需要在没有代码的地方调试。比如说,我们在用xcode调试时,经常会发现程序在Cocoa框架里的某个地方崩溃了。我们需要找到到底是在哪里出错了。这种时候,一个可行的方法就是查看崩溃处的参数,看看到底发生了什么。</p>
<p>这是一篇很好的文章,它讲解了在不同的体系结构下,参数是如何存储的。不过它并没有讲到ARM(= =)。所幸ARM的存储很简单,参数只是按顺序被存储在$r0, $r1, $r2, $r3寄存器里。记住,在所有通过寄存器传递参数的体系结构里(i386不是),只有在函数开头的一小段里,寄存器里存的才是参数。因为在程序进行的过程中,它们随时都可能被其他变量替换掉。</p>
<p>举例来说,我们可以打印出传给NSLog的参数:</p>
<div class="highlight"><pre><code><span class="n">Breakpoint</span> <span class="mi">2</span><span class="p">,</span> <span class="mh">0x00007fff87beaa62</span> <span class="n">in</span> <span class="n">NSLog</span> <span class="p">()</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">po</span> <span class="err">$</span><span class="n">rdi</span>
<span class="n">Hello</span><span class="p">,</span> <span class="n">world</span><span class="o">!</span>
</code></pre></div>
<p>这里有个很常见的技巧:如果我们想给NSLog添加断点来巡查崩溃,就可以根据输出内容设置一下判断,让debugger不至于在每次NSLog时都中断:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="k">break</span> <span class="n">NSLog</span> <span class="k">if</span> <span class="p">(</span><span class="kt">char</span><span class="p">)[</span><span class="err">$</span><span class="n">rdi</span> <span class="nl">hasPrefix</span><span class="p">:</span> <span class="err">@</span><span class="s">"crashing"</span><span class="p">]</span>
</code></pre></div>
<p>记住,方法的前两个参数是self和_cmd。所以我们的参数应该从$rdx(x86_64)或$rd2(ARM)开始计算。</p>
<h3>异常</h3>
<p>异常会被运行时方法objc_exception_throw抛出。在这个方法里设置断点是很重要的。原因有两点:</p>
<ol>
<li>抛出异常,通常是程序出现严重错误的信号。 </li>
<li>被抛出的异常通常会被对应的代码捕获。如果你不在这里设置断点的话,就只能获得异常被捕获之后的信息,而不知道它到底是在哪里被抛出的。 </li>
</ol>
<p>如果你设置了断点,程序就会在异常被抛出的时候停止。这样你就有机会查看栈信息,知道具体是哪里抛出了异常。</p>
<p>为异常设置断点的方法也很简单,因为要抛出的异常是objc_exception_throw方法的唯一一个参数,所以我们可以用上一小节提到的方法来完成它。</p>
<h3>线程</h3>
<p>现在,多线程代码随处可见。知道如何调试多线程程序也越来越重要。以下一段代码启动了几个后台运行的线程:</p>
<div class="highlight"><pre><code><span class="n">dispatch_apply</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">x</span><span class="p">){</span>
<span class="n">sleep</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
<span class="p">});</span>
</code></pre></div>
<p>运行debugger,在程序睡眠的时候用Control-C杀掉它:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">Reading</span> <span class="n">symbols</span> <span class="k">for</span> <span class="n">shared</span> <span class="n">libraries</span> <span class="p">.</span><span class="o">+++</span><span class="p">........................</span> <span class="n">done</span>
<span class="o">^</span><span class="n">C</span>
<span class="n">Program</span> <span class="n">received</span> <span class="n">signal</span> <span class="n">SIGINT</span><span class="p">,</span> <span class="n">Interrupt</span><span class="p">.</span>
<span class="mh">0x00007fff88c6ff8a</span> <span class="n">in</span> <span class="n">__semwait_signal</span> <span class="p">()</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">bt</span>
<span class="cp">#0 0x00007fff88c6ff8a in __semwait_signal () </span>
<span class="cp">#1 0x00007fff88c6fe19 in nanosleep () </span>
<span class="cp">#2 0x00007fff88cbcdf0 in sleep () </span>
<span class="cp">#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12 </span>
<span class="cp">#4 0x00007fff88cbbbc8 in _dispatch_apply2 () </span>
<span class="cp">#5 0x00007fff88cb31e5 in dispatch_apply_f () </span>
<span class="cp">#6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11</span>
</code></pre></div>
<p>和我们想的一样,我们输出了一个线程的信息。但是,另外两个后台运行的线程在哪里?我们可以用info threads命令获取所有线程的列表:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">info</span> <span class="n">threads</span>
<span class="mi">3</span> <span class="s">"com.apple.root.default-priorit"</span> <span class="mh">0x00007fff88c6ff8a</span> <span class="n">in</span> <span class="n">__semwait_signal</span> <span class="p">()</span>
<span class="mi">2</span> <span class="s">"com.apple.root.default-priorit"</span> <span class="mh">0x00007fff88c6ff8a</span> <span class="n">in</span> <span class="n">__semwait_signal</span> <span class="p">()</span>
<span class="o">*</span> <span class="mi">1</span> <span class="s">"com.apple.root.default-priorit"</span> <span class="mh">0x00007fff88c6ff8a</span> <span class="n">in</span> <span class="n">__semwait_signal</span> <span class="p">()</span>
</code></pre></div>
<p>线程1前面有个星号,这表示它是现在活动中的线程。现在我们切换到线程2:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="kr">thread</span> <span class="mi">2</span>
<span class="p">[</span><span class="n">Switching</span> <span class="n">to</span> <span class="kr">thread</span> <span class="mi">2</span> <span class="p">(</span><span class="n">process</span> <span class="mi">4794</span><span class="p">),</span> <span class="s">"com.apple.root.default-priority"</span><span class="p">]</span>
<span class="mh">0x00007fff88c6ff8a</span> <span class="n">in</span> <span class="n">__semwait_signal</span> <span class="p">()</span>
<span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">bt</span>
<span class="cp">#0 0x00007fff88c6ff8a in __semwait_signal () </span>
<span class="cp">#1 0x00007fff88c6fe19 in nanosleep () </span>
<span class="cp">#2 0x00007fff88cbcdf0 in sleep () </span>
<span class="cp">#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12 </span>
<span class="cp">#4 0x00007fff88cbbbc8 in _dispatch_apply2 () </span>
<span class="cp">#5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () </span>
<span class="cp">#6 0x00007fff88c4f128 in _pthread_wqthread () </span>
<span class="cp">#7 0x00007fff88c4efc5 in start_wqthread ()</span>
</code></pre></div>
<p>现在我们输出了线程2的信息。然后时线程3……是不是觉得这种方法效率太低了?我们只有3个线程,但如果有300个呢?幸好,gdb提供了thread apply all backtrace命令(简写为t a a bt),用来列出所有线程的详细信息。</p>
<p>Thread 3 (process 4794):</p>
<div class="highlight"><pre><code><span class="cp">#0 0x00007fff88c6ff8a in __semwait_signal () </span>
<span class="cp">#1 0x00007fff88c6fe19 in nanosleep () </span>
<span class="cp">#2 0x00007fff88cbcdf0 in sleep () </span>
<span class="cp">#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=2) at test.m:12 </span>
<span class="cp">#4 0x00007fff88cbbbc8 in _dispatch_apply2 () </span>
<span class="cp">#5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () </span>
<span class="cp">#6 0x00007fff88c4f128 in _pthread_wqthread () </span>
<span class="cp">#7 0x00007fff88c4efc5 in start_wqthread ()</span>
</code></pre></div>
<p>Thread 2 (process 4794):</p>
<div class="highlight"><pre><code><span class="cp">#0 0x00007fff88c6ff8a in __semwait_signal () </span>
<span class="cp">#1 0x00007fff88c6fe19 in nanosleep () </span>
<span class="cp">#2 0x00007fff88cbcdf0 in sleep () </span>
<span class="cp">#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=1) at test.m:12 </span>
<span class="cp">#4 0x00007fff88cbbbc8 in _dispatch_apply2 () </span>
<span class="cp">#5 0x00007fff88c4f7f1 in _dispatch_worker_thread2 () </span>
<span class="cp">#6 0x00007fff88c4f128 in _pthread_wqthread () </span>
<span class="cp">#7 0x00007fff88c4efc5 in start_wqthread ()</span>
</code></pre></div>
<p>Thread 1 (process 4794):</p>
<div class="highlight"><pre><code><span class="cp">#0 0x00007fff88c6ff8a in __semwait_signal () </span>
<span class="cp">#1 0x00007fff88c6fe19 in nanosleep () </span>
<span class="cp">#2 0x00007fff88cbcdf0 in sleep () </span>
<span class="cp">#3 0x0000000100000ea7 in __main_block_invoke_1 (.block_descriptor=0x1000010a0, x=0) at test.m:12 </span>
<span class="cp">#4 0x00007fff88cbbbc8 in _dispatch_apply2 () </span>
<span class="cp">#5 0x00007fff88cb31e5 in dispatch_apply_f () </span>
<span class="cp">#6 0x0000000100000e6a in main (argc=1, argv=0x7fff5fbff628) at test.m:11</span>
</code></pre></div>
<p>现在我们可以方便地查看整个程序中的线程了。如果想要更彻底地观察某个线程,只需要用thread命令切换到该线程,然后使用各种已经学过的gdb命令。</p>
<h3>控制台参数和环境变量</h3>
<p>在用gdb调试带参数的程序时会遇到一个疑惑,即程序的参数究竟怎么输入:</p>
<div class="highlight"><pre><code><span class="err">$</span> <span class="n">gdb</span> <span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">echo</span> <span class="n">hello</span> <span class="n">world</span>
<span class="n">Excess</span> <span class="n">command</span> <span class="n">line</span> <span class="n">arguments</span> <span class="n">ignored</span><span class="p">.</span> <span class="p">(</span><span class="n">world</span><span class="p">)</span>
<span class="p">[...]</span>
<span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">mikeash</span><span class="o">/</span><span class="n">shell</span><span class="o">/</span><span class="nl">hello</span><span class="p">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span>
</code></pre></div>
<p>如上,把参数直接缀在后面显然是不对的。因为这样它们会被解释成gdb的参数,而不是要调试程序的参数。运行结果也证明了这一点,gdb把hello和world都解释成了要运行的程序名。</p>
<p>解决方法也很简单,即,在gdb启动之后,执行run命令的同时输入参数:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">run</span> <span class="n">hello</span> <span class="n">world</span>
<span class="n">Starting</span> <span class="nl">program</span><span class="p">:</span> <span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">echo</span> <span class="n">hello</span> <span class="n">world</span>
<span class="n">Reading</span> <span class="n">symbols</span> <span class="k">for</span> <span class="n">shared</span> <span class="n">libraries</span> <span class="o">+</span><span class="p">.</span> <span class="n">done</span>
<span class="n">hello</span> <span class="n">world</span>
</code></pre></div>
<p>环境变量可以在启动gdb之前预先在shell中载入,通常情况下这么做也没有问题。但是,如果你操纵的环境变量会对每个程序都造成严重影响的话,这就不是一个好主意了。在这种情况下,我们用set env命令,做针对于目标程序的修改:</p>
<div class="highlight"><pre><code><span class="p">(</span><span class="n">gdb</span><span class="p">)</span> <span class="n">set</span> <span class="n">env</span> <span class="n">DYLD_INSERT_LIBRARIES</span> <span class="o">/</span><span class="n">gdb</span><span class="o">/</span><span class="n">crashes</span><span class="o">/</span><span class="k">if</span><span class="o">/</span><span class="n">this</span><span class="o">/</span><span class="n">is</span><span class="o">/</span><span class="n">inserted</span><span class="p">.</span><span class="n">dylib</span>
</code></pre></div>
gdb基本命令和技巧
iOS Block、NSThread、NSOperation、GCD
https://www.zruibin.cn/article/iosblocknsthreadnsoperationgcd.html
2014-12-16 15:57
2015-10-13 19:14
<h2>Block</h2>
<p>======ARC & Block======</p>
<p>ARC和Block合用的时候是最容易出问题的了。</p>
<ol>
<li>Blocks 作为property的时候要使用copy</li>
</ol>
<div class="highlight"><pre><code><span class="err">@</span><span class="n">property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">copy</span><span class="p">)</span> <span class="n">SomeBlockType</span> <span class="n">someBlock</span><span class="p">;</span>
</code></pre></div>
<ol>
<li>防止retain cycle造成内存泄漏。下面是一种比较好的作法。</li>
</ol>
<div class="highlight"><pre><code><span class="n">SomeObjectClass</span> <span class="o">*</span><span class="n">someObject</span> <span class="o">=</span> <span class="p">...</span>
<span class="n">__weak</span> <span class="n">SomeObjectClass</span> <span class="o">*</span><span class="n">weakSomeObject</span> <span class="o">=</span> <span class="n">someObject</span><span class="p">;</span>
<span class="n">someObject</span><span class="p">.</span><span class="n">completionHandler</span> <span class="o">=</span> <span class="o">^</span><span class="p">{</span>
<span class="n">SomeObjectClass</span> <span class="o">*</span><span class="n">strongSomeObject</span> <span class="o">=</span> <span class="n">weakSomeObject</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">strongSomeObject</span> <span class="o">==</span> <span class="n">nil</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// The original someObject doesn't exist anymore.</span>
<span class="c1">// Ignore, notify or otherwise handle this case.</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
<span class="c1">// okay, NOW we can do something with someObject</span>
<span class="p">[</span><span class="n">strongSomeObject</span> <span class="n">someMethod</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>当然,如果Block不会被赋值给SomeObjectClass的property就不需要先定义一个weak对象。</p>
<p>相信大家为解决告警,又会得到一个比较圆满的解决方案,见下:</p>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="n">IBAction</span><span class="p">)</span><span class="nl">onTest</span><span class="p">:(</span><span class="n">id</span><span class="p">)</span><span class="n">sender</span>
<span class="p">{</span>
<span class="n">BlockDemo</span> <span class="o">*</span><span class="n">demo</span> <span class="o">=</span> <span class="p">[[</span><span class="n">BlockDemo</span> <span class="n">alloc</span><span class="p">]</span><span class="n">init</span><span class="p">];</span>
<span class="n">__weak</span> <span class="n">typeof</span><span class="p">(</span><span class="n">BlockDemo</span><span class="p">)</span> <span class="o">*</span><span class="n">weakDemo</span> <span class="o">=</span> <span class="n">demo</span><span class="p">;</span>
<span class="p">[</span><span class="n">demo</span> <span class="nl">setExecuteFinished</span><span class="p">:</span><span class="o">^</span><span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">weakDemo</span><span class="p">.</span><span class="n">resultCode</span> <span class="o">==</span> <span class="mi">200</span><span class="p">)</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"call back ok."</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}];</span>
<span class="p">[</span><span class="n">demo</span> <span class="n">executeTest</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>这样写,即去除了告警又保证了block的运行。</p>
<p><a href="http://www.cnblogs.com/sunfrog/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/">http://www.cnblogs.com/sunfrog/tag/%E5%A4%9A%E7%BA%BF%E7%A8%8B/</a></p>
<p><a href="http://www.cocoachina.com/ios/20150731/12819.html">http://www.cocoachina.com/ios/20150731/12819.html</a></p>
<p><br><hr><br></p>
<h2>多线程之Thread</h2>
<p>每个iOS应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。
iOS支持多个层次的多线程编程,层次越高的抽象程度越高,使用也越方便,也是苹果最推荐使用的方法。</p>
<div class="highlight"><pre><code><span class="err">下面根据抽象层次从低到高依次列出</span><span class="n">iOS</span><span class="err">所支持的多线程编程方法:</span>
<span class="mf">1.</span><span class="n">Thread</span> <span class="err">:是三种方法里面相对轻量级的,但需要管理线程的生命周期、同步、加锁问题,这会导致一定的性能开销。</span>
<span class="mf">2.</span><span class="n">Cocoa</span> <span class="n">Operations</span><span class="err">:是基于</span><span class="n">OC</span><span class="err">实现的,</span><span class="n">NSOperation</span><span class="err">以面向对象的方式封装了需要执行的操作,不必关心线程管理、同步等问题。</span><span class="n">NSOperation</span><span class="err">是一个抽象基类,</span><span class="n">iOS</span><span class="err">提供了两种默认实现:</span><span class="n">NSInvocationOperation</span><span class="err">和</span><span class="n">NSBlockOperation</span><span class="err">,当然也可以自定义</span><span class="n">NSOperation</span><span class="err">。</span>
<span class="mf">3.</span><span class="n">Grand</span> <span class="n">Central</span> <span class="n">Dispatch</span><span class="p">(</span><span class="err">简称</span><span class="n">GCD</span><span class="err">,</span><span class="n">iOS4</span><span class="err">才开始支持</span><span class="p">)</span><span class="err">:提供了一些新特性、运行库来支持多核并行编程,它的关注点更高:如何在多个</span><span class="n">cpu</span><span class="err">上提升效率。</span>
</code></pre></div>
<div class="highlight"><pre><code><span class="c1">//************ 一:初始化 **************//</span>
<span class="c1">// 1 . 动态方法</span>
<span class="n">NSThread</span> <span class="o">*</span><span class="kr">thread</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSThread</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithTarget</span><span class="p">:</span><span class="n">self</span> <span class="nl">selector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">object</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="kr">thread</span><span class="p">.</span><span class="n">threadPriority</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">[</span><span class="kr">thread</span> <span class="n">start</span><span class="p">];</span>
<span class="c1">// 2. 静态方法</span>
<span class="p">[</span><span class="n">NSThread</span> <span class="nl">detachNewThreadSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">toTarget</span><span class="p">:</span><span class="n">self</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="c1">// 调用完毕后,会马上创建并开启新线程</span>
<span class="c1">// 3. 隐式的创建线程</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">performSelectorInBackground</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="c1">//************ 二:获取当前线程 **************//</span>
<span class="n">NSThread</span> <span class="o">*</span><span class="n">currentTh</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">];</span>
<span class="c1">//************ 三:获取主线程 **************//</span>
<span class="n">NSThread</span> <span class="o">*</span><span class="n">main</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">mainThread</span><span class="p">];</span>
<span class="c1">//************ 四、暂停当前线程 **************//</span>
<span class="c1">// 暂停2s</span>
<span class="p">[</span><span class="n">NSThread</span> <span class="nl">sleepForTimeInterval</span><span class="p">:</span><span class="mi">2</span><span class="p">];</span>
<span class="n">NSDate</span> <span class="o">*</span><span class="n">date</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSDate</span> <span class="nl">dateWithTimeInterval</span><span class="p">:</span><span class="mi">2</span> <span class="nl">sinceDate</span><span class="p">:[</span><span class="n">NSDate</span> <span class="n">date</span><span class="p">]];</span>
<span class="p">[</span><span class="n">NSThread</span> <span class="nl">sleepUntilDate</span><span class="p">:</span><span class="n">date</span><span class="p">];</span>
<span class="c1">//************ 五、线程间的通信 **************//</span>
<span class="c1">//1.在指定线程上执行操作</span>
<span class="n">NSThread</span> <span class="o">*</span><span class="n">currentThread</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">];</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">performSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">onThread</span><span class="p">:</span><span class="n">currentThread</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">nil</span> <span class="nl">waitUntilDone</span><span class="p">:</span><span class="n">NO</span><span class="p">];</span>
<span class="c1">//2.在主线程上执行操作</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">performSelectorOnMainThread</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">nil</span> <span class="nl">waitUntilDone</span><span class="p">:</span><span class="n">YES</span><span class="p">];</span>
<span class="c1">//3.在当前线程执行操作</span>
<span class="p">[</span><span class="n">self</span> <span class="nl">performSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">runOne</span><span class="p">)</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="c1">//************ 六、优缺点**************//</span>
<span class="c1">// 1.优点:NSThread比其他两种多线程方案较轻量级,更直观地控制线程对象</span>
<span class="c1">// 2.缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销</span>
</code></pre></div>
<h3>多线程之NSOperation</h3>
<h4>一、NSOperation</h4>
<p>1.简介
NSOperation实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作。
NSOperation本身是抽象基类,因此必须使用它的子类,使用NSOperation子类的方式有2种:
1> Foundation框架提供了两个具体子类直接供我们使用:NSInvocationOperation和NSBlockOperation
2> 自定义子类继承NSOperation,实现内部相应的方法</p>
<p>2.执行操作
NSOperation调用start方法即可开始执行操作,NSOperation对象默认按同步方式执行,也就是在调用start方法的那个线程中直接执行。NSOperation对象的isConcurrent方法会告诉我们这个操作相对于调用start方法的线程,是同步还是异步执行。isConcurrent方法默认返回NO,表示操作与调用线程同步执行</p>
<p>3.取消操作
operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">operation</span> <span class="n">cancel</span><span class="p">];</span>
</code></pre></div>
<p>4.监听操作的执行
如果我们想在一个NSOperation执行完毕后做一些事情,就调用NSOperation的setCompletionBlock方法来设置想做的事情</p>
<div class="highlight"><pre><code><span class="n">operation</span><span class="p">.</span><span class="n">completionBlock</span> <span class="o">=</span> <span class="o">^</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"执行完毕"</span><span class="p">);</span>
<span class="p">};</span>
</code></pre></div>
<p>或者</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">operation</span> <span class="nl">setCompletionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"执行完毕"</span><span class="p">);</span>
<span class="p">}];</span>
</code></pre></div>
<h4>二、NSInvocationOperation</h4>
<p>1.简介
基于一个对象和selector来创建操作。如果你已经有现有的方法来执行需要的任务,就可以使用这个类</p>
<p>2.创建并执行操作</p>
<div class="highlight"><pre><code><span class="c1">// 这个操作是:调用self的run方法 </span>
<span class="n">NSInvocationOperation</span> <span class="o">*</span><span class="n">operation</span> <span class="o">=</span> <span class="p">[[</span><span class="n">NSInvocationOperation</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithTarget</span><span class="p">:</span><span class="n">self</span> <span class="nl">selector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">run</span><span class="p">)</span> <span class="nl">object</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="c1">// 开始执行任务(同步执行) </span>
<span class="p">[</span><span class="n">operation</span> <span class="n">start</span><span class="p">];</span>
</code></pre></div>
<h4>三、NSBlockOperation</h4>
<p>1.简介</p>
<div class="highlight"><pre><code><span class="err">能够并发地执行一个或多个</span><span class="n">block</span><span class="err">对象,所有相关的</span><span class="n">block</span><span class="err">都执行完之后</span><span class="p">,</span><span class="err">操作才算完成</span>
</code></pre></div>
<p>2.创建并执行操作</p>
<div class="highlight"><pre><code><span class="n">NSBlockOperation</span> <span class="o">*</span><span class="n">operation</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSBlockOperation</span> <span class="nl">blockOperationWithBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(){</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"执行了一个新的操作,线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">}];</span>
<span class="c1">// 开始执行任务(这里还是同步执行) </span>
<span class="p">[</span><span class="n">operation</span> <span class="n">start</span><span class="p">];</span>
</code></pre></div>
<p>3.通过addExecutionBlock方法添加block操作</p>
<div class="highlight"><pre><code><span class="n">NSBlockOperation</span> <span class="o">*</span><span class="n">operation</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSBlockOperation</span> <span class="nl">blockOperationWithBlock</span><span class="p">:</span><span class="o">^</span><span class="p">(){</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"执行第1次操作,线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">}];</span>
<span class="p">[</span><span class="n">operation</span> <span class="nl">addExecutionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"又执行了1个新的操作,线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">}];</span>
<span class="p">[</span><span class="n">operation</span> <span class="nl">addExecutionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"又执行了1个新的操作,线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">}];</span>
<span class="p">[</span><span class="n">operation</span> <span class="nl">addExecutionBlock</span><span class="p">:</span><span class="o">^</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"又执行了1个新的操作,线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">}];</span>
<span class="c1">// 开始执行任务 </span>
<span class="p">[</span><span class="n">operation</span> <span class="n">start</span><span class="p">];</span>
</code></pre></div>
<p>打印信息如下:</p>
<div class="highlight"><pre><code><span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">02</span> <span class="mi">21</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">46.102</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">4602</span><span class="o">:</span><span class="n">c07</span><span class="p">]</span> <span class="err">又执行了</span><span class="mi">1</span><span class="err">个新的操作,线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x7121d50</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">1</span><span class="p">}</span>
<span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">02</span> <span class="mi">21</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">46.102</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">4602</span><span class="o">:</span><span class="mf">3f</span><span class="mo">03</span><span class="p">]</span> <span class="err">又执行了</span><span class="mi">1</span><span class="err">个新的操作,线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x742e1d0</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">5</span><span class="p">}</span>
<span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">02</span> <span class="mi">21</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">46.102</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">4602</span><span class="o">:</span><span class="mi">1</span><span class="n">b03</span><span class="p">]</span> <span class="err">执行第</span><span class="mi">1</span><span class="err">次操作,线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x742de50</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">3</span><span class="p">}</span>
<span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">02</span> <span class="mi">21</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">46.102</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">4602</span><span class="o">:</span><span class="mi">1303</span><span class="p">]</span> <span class="err">又执行了</span><span class="mi">1</span><span class="err">个新的操作,线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x7157bf0</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">4</span><span class="p">}</span>
</code></pre></div>
<p>可以看出,这4个block是并发执行的,也就是在不同线程中执行的,num属性可以看成是线程的id</p>
<h4>四、自定义NSOperation</h4>
<p>1.简介</p>
<p>如果NSInvocationOperation和NSBlockOperation对象不能满足需求, 你可以直接继承NSOperation, 并添加任何你想要的行为。继承所需的工作量主要取决于你要实现非并发还是并发的NSOperation。定义非并发的NSOperation要简单许多,只需要重载-(void)main这个方法,在这个方法里面执行主任务,并正确地响应取消事件; 对于并发NSOperation, 你必须重写NSOperation的多个基本方法进行实现(这里暂时先介绍非并发的NSOperation)</p>
<p>2.非并发的NSOperation
比如叫做DownloadOperation,用来下载图片</p>
<p>1> 继承NSOperation,重写main方法,执行主任务
DownloadOperation.h</p>
<div class="highlight"><pre><code><span class="cp">#import <Foundation/Foundation.h> </span>
<span class="err">@</span><span class="n">protocol</span> <span class="n">DownloadOperationDelegate</span><span class="p">;</span>
<span class="err">@</span><span class="n">interface</span> <span class="nl">DownloadOperation</span> <span class="p">:</span> <span class="n">NSOperation</span>
<span class="c1">// 图片的url路径 </span>
<span class="err">@</span><span class="n">property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">copy</span><span class="p">)</span> <span class="n">NSString</span> <span class="o">*</span><span class="n">imageUrl</span><span class="p">;</span>
<span class="c1">// 代理 </span>
<span class="err">@</span><span class="n">property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">retain</span><span class="p">)</span> <span class="n">id</span><span class="o"><</span><span class="n">DownloadOperationDelegate</span><span class="o">></span> <span class="n">delegate</span><span class="p">;</span>
<span class="o">-</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="nl">initWithUrl</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">url</span> <span class="nl">delegate</span><span class="p">:(</span><span class="n">id</span><span class="o"><</span><span class="n">DownloadOperationDelegate</span><span class="o">></span><span class="p">)</span><span class="n">delegate</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
<span class="c1">// 图片下载的协议 </span>
<span class="err">@</span><span class="n">protocol</span> <span class="n">DownloadOperationDelegate</span> <span class="o"><</span><span class="n">NSObject</span><span class="o">></span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">downloadFinishWithImage</span><span class="p">:(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="n">image</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
</code></pre></div>
<p>DownloadOperation.m</p>
<div class="highlight"><pre><code><span class="cp">#import "DownloadOperation.h" </span>
<span class="err">@</span><span class="n">implementation</span> <span class="n">DownloadOperation</span>
<span class="err">@</span><span class="n">synthesize</span> <span class="n">delegate</span> <span class="o">=</span> <span class="n">_delegate</span><span class="p">;</span>
<span class="err">@</span><span class="n">synthesize</span> <span class="n">imageUrl</span> <span class="o">=</span> <span class="n">_imageUrl</span><span class="p">;</span>
<span class="c1">// 初始化 </span>
<span class="o">-</span> <span class="p">(</span><span class="n">id</span><span class="p">)</span><span class="nl">initWithUrl</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">url</span> <span class="nl">delegate</span><span class="p">:(</span><span class="n">id</span><span class="o"><</span><span class="n">DownloadOperationDelegate</span><span class="o">></span><span class="p">)</span><span class="n">delegate</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span> <span class="o">=</span> <span class="p">[</span><span class="n">super</span> <span class="n">init</span><span class="p">])</span> <span class="p">{</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageUrl</span> <span class="o">=</span> <span class="n">url</span><span class="p">;</span>
<span class="n">self</span><span class="p">.</span><span class="n">delegate</span> <span class="o">=</span> <span class="n">delegate</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 释放内存 </span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">dealloc</span> <span class="p">{</span>
<span class="p">[</span><span class="n">super</span> <span class="n">dealloc</span><span class="p">];</span>
<span class="p">[</span><span class="n">_delegate</span> <span class="n">release</span><span class="p">];</span>
<span class="p">[</span><span class="n">_imageUrl</span> <span class="n">release</span><span class="p">];</span>
<span class="p">}</span>
<span class="c1">// 执行主任务 </span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">main</span> <span class="p">{</span>
<span class="c1">// 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池 </span>
<span class="err">@</span><span class="n">autoreleasepool</span> <span class="p">{</span>
<span class="c1">// .... </span>
<span class="p">}</span>
<span class="p">}</span>
<span class="err">@</span><span class="n">end</span>
</code></pre></div>
<p>2> 正确响应取消事件</p>
<p>operation开始执行之后,会一直执行任务直到完成,或者显式地取消操作。取消可能发生在任何时候,甚至在operation执行之前。尽管NSOperation提供了一个方法,让应用取消一个操作,但是识别出取消事件则是我们自己的事情。如果operation直接终止, 可能无法回收所有已分配的内存或资源。因此operation对象需要检测取消事件,并优雅地退出执行</p>
<p>NSOperation对象需要定期地调用isCancelled方法检测操作是否已经被取消,如果返回YES(表示已取消),则立即退出执行。不管是自定义NSOperation子类,还是使用系统提供的两个具体子类,都需要支持取消。isCancelled方法本身非常轻量,可以频繁地调用而不产生大的性能损失</p>
<p>以下地方可能需要调用isCancelled:</p>
<ul>
<li>在执行任何实际的工作之前</li>
<li>在循环的每次迭代过程中,如果每个迭代相对较长可能需要调用多次</li>
<li>代码中相对比较容易中止操作的任何地方</li>
<li>DownloadOperation的main方法实现如下</li>
</ul>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">main</span> <span class="p">{</span>
<span class="c1">// 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池 </span>
<span class="err">@</span><span class="n">autoreleasepool</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
<span class="c1">// 获取图片数据 </span>
<span class="n">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">self</span><span class="p">.</span><span class="n">imageUrl</span><span class="p">];</span>
<span class="n">NSData</span> <span class="o">*</span><span class="n">imageData</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL</span><span class="p">:</span><span class="n">url</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">nil</span><span class="p">;</span>
<span class="n">imageData</span> <span class="o">=</span> <span class="n">nil</span><span class="p">;</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// 初始化图片 </span>
<span class="n">UIImage</span> <span class="o">*</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nl">imageWithData</span><span class="p">:</span><span class="n">imageData</span><span class="p">];</span>
<span class="k">if</span> <span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">isCancelled</span><span class="p">)</span> <span class="p">{</span>
<span class="n">image</span> <span class="o">=</span> <span class="n">nil</span><span class="p">;</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">([</span><span class="n">self</span><span class="p">.</span><span class="n">delegate</span> <span class="nl">respondsToSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">downloadFinishWithImage</span><span class="p">:)])</span> <span class="p">{</span>
<span class="c1">// 把图片数据传回到主线程 </span>
<span class="p">[(</span><span class="n">NSObject</span> <span class="o">*</span><span class="p">)</span><span class="n">self</span><span class="p">.</span><span class="n">delegate</span> <span class="nl">performSelectorOnMainThread</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">downloadFinishWithImage</span><span class="p">:)</span> <span class="nl">withObject</span><span class="p">:</span><span class="n">image</span> <span class="nl">waitUntilDone</span><span class="p">:</span><span class="n">NO</span><span class="p">];</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h3>GCD</h3>
<h4>一、简介</h4>
<p>在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决方案。GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器。GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理</p>
<h4>二、调度队列(dispath queue)</h4>
<p>1.GCD的一个重要概念是队列,它的核心理念:将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。</p>
<p>2.系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue。也可以创建自己的dispath queue,而且可以创建任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。</p>
<p>3.dispatch queue按先进先出的顺序,串行或并发地执行任务</p>
<p>1> serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务
2> concurrent dispatch queue则尽可能多地启动任务并发执行</p>
<h4>三、创建和管理dispatch queue</h4>
<p>1.获得全局并发Dispatch Queue (concurrent dispatch queue)</p>
<p>1> 并发dispatch queue可以同时并行地执行多个任务,不过并发queue仍然按先进先出的顺序来启动任务。并发queue会在之前的任务完成之前就出列下一个任务并开始执行。并发queue同时执行的任务数量会根据应用和系统动态变化,各种因素包括:可用核数量、其它进程正在执行的工作数量、其它串行dispatch queue中优先任务的数量等.</p>
<p>2> 系统给每个应用提供三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。你不需要显式地创建这些queue,使用dispatch_get_global_queue函数来获取这三个queue:</p>
<div class="highlight"><pre><code><span class="c1">// 获取默认优先级的全局并发dispatch queue </span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</code></pre></div>
<p>第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可</p>
<p>3> 虽然dispatch queue是引用计数的对象,但你不需要retain和release全局并发queue。因为这些queue对应用是全局的,retain和release调用会被忽略。你也不需要存储这三个queue的引用,每次都直接调用dispatch_get_global_queue获得queue就行了。</p>
<p>2.创建串行Dispatch Queue (serial dispatch queue)</p>
<p>1> 应用的任务需要按特定顺序执行时,就需要使用串行Dispatch Queue,串行queue每次只能执行一个任务。你可以使用串行queue来替代锁,保护共享资源 或可变的数据结构。和锁不一样的是,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁</p>
<p>2> 你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue</p>
<p>3> 利用dispatch_queue_create函数创建串行queue,两个参数分别是queue名和一组queue属性</p>
<div class="highlight"><pre><code><span class="kt">dispatch_queue_t</span> <span class="n">queue</span><span class="p">;</span>
<span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_queue_create</span><span class="p">(</span><span class="s">"cn.itcast.queue"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</code></pre></div>
<p>3.运行时获得公共Queue</p>
<p>GCD提供了函数让应用访问几个公共dispatch queue:</p>
<p>1> 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。</p>
<p>2> 使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue</p>
<p>3> 使用dispatch_get_global_queue来获得共享的并发queue</p>
<p>4.Dispatch Queue的内存管理</p>
<p>1> Dispatch Queue和其它dispatch对象(还有dispatch source)都是引用计数的数据类型。当你创建一个串行dispatch queue时,初始引用计数为 1,你可以使用dispatch_retain和dispatch_release函数来增加和减少引用计数。当引用计数到达 0 时,系统会异步地销毁这个queue</p>
<p>2> 对dispatch对象(如dispatch queue)retain和release 是很重要的,确保它们被使用时能够保留在内存中。和OC对象一样,通用的规则是如果使用一个传递过来的queue,你应该在使用前retain,使用完之后release</p>
<p>3> 你不需要retain或release全局dispatch queue,包括全局并发dispatch queue和main dispatch queue</p>
<p>4> 即使你实现的是自动垃圾收集的应用,也需要retain和release创建的dispatch queue和其它dispatch对象。GCD 不支持垃圾收集模型来回收内存</p>
<h4>四、添加任务到queue</h4>
<p>要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务,也。一旦进入到queue,queue会负责尽快地执行你的任务。一般可以用一个block来封装任务内容。</p>
<p>1.添加单个任务到queue</p>
<p>1> 异步添加任务</p>
<p>你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件</p>
<p>2> 同步添加任务</p>
<p>少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。</p>
<p>3> 代码演示</p>
<div class="highlight"><pre><code><span class="c1">// 调用前,查看下当前线程 </span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"当前调用线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="c1">// 创建一个串行queue </span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_queue_create</span><span class="p">(</span><span class="s">"cn.itcast.queue"</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"开启了一个异步任务,当前线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">});</span>
<span class="n">dispatch_sync</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"开启了一个同步任务,当前线程:%@"</span><span class="p">,</span> <span class="p">[</span><span class="n">NSThread</span> <span class="n">currentThread</span><span class="p">]);</span>
<span class="p">});</span>
<span class="c1">// 销毁队列 </span>
<span class="n">dispatch_release</span><span class="p">(</span><span class="n">queue</span><span class="p">);</span>
</code></pre></div>
<p>打印信息:</p>
<div class="highlight"><pre><code><span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">03</span> <span class="mi">09</span><span class="o">:</span><span class="mo">03</span><span class="o">:</span><span class="mf">37.348</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">6491</span><span class="o">:</span><span class="n">c07</span><span class="p">]</span> <span class="err">当前调用线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x714fa80</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">1</span><span class="p">}</span>
<span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">03</span> <span class="mi">09</span><span class="o">:</span><span class="mo">03</span><span class="o">:</span><span class="mf">37.349</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">6491</span><span class="o">:</span><span class="mf">1e03</span><span class="p">]</span> <span class="err">开启了一个异步任务,当前线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x74520a0</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">3</span><span class="p">}</span>
<span class="mi">2013</span><span class="o">-</span><span class="mo">02</span><span class="o">-</span><span class="mo">03</span> <span class="mi">09</span><span class="o">:</span><span class="mo">03</span><span class="o">:</span><span class="mf">37.350</span> <span class="kr">thread</span><span class="p">[</span><span class="mi">6491</span><span class="o">:</span><span class="n">c07</span><span class="p">]</span> <span class="err">开启了一个同步任务,当前线程:</span><span class="o"><</span><span class="nl">NSThread</span><span class="p">:</span> <span class="mh">0x714fa80</span><span class="o">></span><span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="p">(</span><span class="n">null</span><span class="p">),</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">1</span><span class="p">}</span>
</code></pre></div>
<p>2.并发地执行循环迭代</p>
<p>如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。
例如下面的for循环:</p>
<div class="highlight"><pre><code><span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">count</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d "</span><span class="p">,</span><span class="n">i</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>1> 如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,你可以调用dispatch_apply或dispatch_apply_f函数来替换循环。这两个函数为每次循环迭代将指定的block或函数提交到queue。当dispatch到并发 queue时,就有可能同时执行多个循环迭代。用dispatch_apply或dispatch_apply_f时你可以指定串行或并发 queue。并发queue允许同时执行多个循环迭代,而串行queue就没太大必要使用了。</p>
<p>下面代码使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1</p>
<div class="highlight"><pre><code><span class="c1">// 获得全局并发queue </span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="kt">size_t</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="n">dispatch_apply</span><span class="p">(</span><span class="n">count</span><span class="p">,</span> <span class="n">queue</span><span class="p">,</span> <span class="o">^</span><span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%zd "</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// 销毁队列 </span>
<span class="n">dispatch_release</span><span class="p">(</span><span class="n">queue</span><span class="p">);</span>
</code></pre></div>
<p>打印信息:</p>
<div class="highlight"><pre><code><span class="mi">1</span> <span class="mi">2</span> <span class="mi">0</span> <span class="mi">3</span> <span class="mi">4</span> <span class="mi">5</span> <span class="mi">6</span> <span class="mi">7</span> <span class="mi">8</span> <span class="mi">9</span>
</code></pre></div>
<p>可以看出,这些迭代是并发执行的</p>
<p>和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。</p>
<p>3.在主线程中执行任务</p>
<p>1> GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(由CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。</p>
<p>2> 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行</p>
<p>3> 代码实现,比如异步下载图片后,回到主线程显示图片</p>
<div class="highlight"><pre><code><span class="c1">// 异步下载图片 </span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="err">@</span><span class="s">"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"</span><span class="p">];</span>
<span class="n">UIImage</span> <span class="o">*</span><span class="n">image</span> <span class="o">=</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nl">imageWithData</span><span class="p">:[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL</span><span class="p">:</span><span class="n">url</span><span class="p">]];</span>
<span class="c1">// 回到主线程显示图片 </span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageView</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div>
<p>4.任务中使用Objective-C对象</p>
<p>GCD支持Cocoa内存管理机制,因此可以在提交到queue的block中自由地使用Objective-C对象。每个dispatch queue维护自己的autorelease pool确保释放autorelease对象,但是queue不保证这些对象实际释放的时间。如果应用消耗大量内存,并且创建大量autorelease对象,你需要创建自己的autorelease pool,用来及时地释放不再使用的对象。</p>
<h4>五、暂停和继续queue</h4>
<p>我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。</p>
<h4>六、Dispatch Group的使用</h4>
<p>假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现</p>
<div class="highlight"><pre><code><span class="c1">// 根据url获取UIImage </span>
<span class="o">-</span> <span class="p">(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="nl">imageWithURLString</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">urlString</span> <span class="p">{</span>
<span class="n">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">urlString</span><span class="p">];</span>
<span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL</span><span class="p">:</span><span class="n">url</span><span class="p">];</span>
<span class="k">return</span> <span class="p">[</span><span class="n">UIImage</span> <span class="nl">imageWithData</span><span class="p">:</span><span class="n">data</span><span class="p">];</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">downloadImages</span> <span class="p">{</span>
<span class="c1">// 异步下载图片 </span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 下载第一张图片 </span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">url1</span> <span class="o">=</span> <span class="err">@</span><span class="s">"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"</span><span class="p">;</span>
<span class="n">UIImage</span> <span class="o">*</span><span class="n">image1</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">imageWithURLString</span><span class="p">:</span><span class="n">url1</span><span class="p">];</span>
<span class="c1">// 下载第二张图片 </span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">url2</span> <span class="o">=</span> <span class="err">@</span><span class="s">"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg"</span><span class="p">;</span>
<span class="n">UIImage</span> <span class="o">*</span><span class="n">image2</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">imageWithURLString</span><span class="p">:</span><span class="n">url2</span><span class="p">];</span>
<span class="c1">// 回到主线程显示图片 </span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageView1</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image1</span><span class="p">;</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageView2</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image2</span><span class="p">;</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:</p>
<p>我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。
下面用Dispatch Group优化上面的代码:</p>
<div class="highlight"><pre><code><span class="c1">// 根据url获取UIImage </span>
<span class="o">-</span> <span class="p">(</span><span class="n">UIImage</span> <span class="o">*</span><span class="p">)</span><span class="nl">imageWithURLString</span><span class="p">:(</span><span class="n">NSString</span> <span class="o">*</span><span class="p">)</span><span class="n">urlString</span> <span class="p">{</span>
<span class="n">NSURL</span> <span class="o">*</span><span class="n">url</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSURL</span> <span class="nl">URLWithString</span><span class="p">:</span><span class="n">urlString</span><span class="p">];</span>
<span class="n">NSData</span> <span class="o">*</span><span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSData</span> <span class="nl">dataWithContentsOfURL</span><span class="p">:</span><span class="n">url</span><span class="p">];</span>
<span class="c1">// 这里并没有自动释放UIImage对象 </span>
<span class="k">return</span> <span class="p">[[</span><span class="n">UIImage</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithData</span><span class="p">:</span><span class="n">data</span><span class="p">];</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">downloadImages</span> <span class="p">{</span>
<span class="kt">dispatch_queue_t</span> <span class="n">queue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="c1">// 异步下载图片 </span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 创建一个组 </span>
<span class="kt">dispatch_group_t</span> <span class="n">group</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">();</span>
<span class="n">__block</span> <span class="n">UIImage</span> <span class="o">*</span><span class="n">image1</span> <span class="o">=</span> <span class="n">nil</span><span class="p">;</span>
<span class="n">__block</span> <span class="n">UIImage</span> <span class="o">*</span><span class="n">image2</span> <span class="o">=</span> <span class="n">nil</span><span class="p">;</span>
<span class="c1">// 关联一个任务到group </span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 下载第一张图片 </span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">url1</span> <span class="o">=</span> <span class="err">@</span><span class="s">"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"</span><span class="p">;</span>
<span class="n">image1</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">imageWithURLString</span><span class="p">:</span><span class="n">url1</span><span class="p">];</span>
<span class="p">});</span>
<span class="c1">// 关联一个任务到group </span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// 下载第一张图片 </span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">url2</span> <span class="o">=</span> <span class="err">@</span><span class="s">"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg"</span><span class="p">;</span>
<span class="n">image2</span> <span class="o">=</span> <span class="p">[</span><span class="n">self</span> <span class="nl">imageWithURLString</span><span class="p">:</span><span class="n">url2</span><span class="p">];</span>
<span class="p">});</span>
<span class="c1">// 等待组中的任务执行完毕,回到主线程执行block回调 </span>
<span class="n">dispatch_group_notify</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageView1</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image1</span><span class="p">;</span>
<span class="n">self</span><span class="p">.</span><span class="n">imageView2</span><span class="p">.</span><span class="n">image</span> <span class="o">=</span> <span class="n">image2</span><span class="p">;</span>
<span class="c1">// 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁 </span>
<span class="c1">// 在这里释放图片资源 </span>
<span class="p">[</span><span class="n">image1</span> <span class="n">release</span><span class="p">];</span>
<span class="p">[</span><span class="n">image2</span> <span class="n">release</span><span class="p">];</span>
<span class="p">});</span>
<span class="c1">// 释放group </span>
<span class="n">dispatch_release</span><span class="p">(</span><span class="n">group</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div>
<p>dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行</p>
iOS Block、NSThread、NSOperation、GCD
iOS开发中KVO的内部实现
https://www.zruibin.cn/article/ios_kai_fa_zhong_kvo_de_nei_bu_shi_xian.html
2014-10-31 12:13
2015-10-13 19:00
<p><a href="http://blog.csdn.net/wzzvictory/article/details/9674431" target="_blank" rel="nofollow">http://blog.csdn.net/wzzvictory/article/details/9674431</a></p>
<p>KVO是实现Cocoa Bindings的基础,它提供了一种方法,当某个属性改变时,相应的objects会被通知到。在其他语言中,这种观察者模式通常需要单独实现,而在Objective-C中,通常无须增加额外代码即可使用。</p>
<h4>概览</h4>
<p>这是怎么实现的呢?其实这都是通过Objective-C强大的运行时(runtime)实现的。当你第一次观察某个object 时,runtime会创建一个新的继承原先class的subclass。在这个新的class中,它重写了所有被观察的key,然后将object的isa指针指向新创建的class(这个指针告诉Objective-C运行时某个object到底是哪种类型的object)。所以object神奇地变成了新的子类的实例。</p>
<p>这些被重写的方法实现了如何通知观察者们。当改变一个key时,会触发setKey方法,但这个方法被重写了,并且在内部添加了发送通知机制。(当然也可以不走setXXX方法,比如直接修改iVar,但不推荐这么做)。</p>
<p>有意思的是:苹果不希望这个机制暴露在外部。除了setters,这个动态生成的子类同时也重写了-class方法,依旧返回原先的class!如果不仔细看的话,被KVO过的object看起来和原先的object没什么两样。</p>
<h4>深入探究</h4>
<p>下面来看看这些是如何实现的。我写了个程序来演示隐藏在KVO背后的机制。</p>
<div class="highlight"><pre><code><span class="c1">// gcc -o kvoexplorer -framework Foundation kvoexplorer.m </span>
<span class="cp">#import <Foundation/Foundation.h> </span>
<span class="cp">#import <objc/runtime.h> </span>
<span class="err">@</span><span class="n">interface</span> <span class="nl">TestClass</span> <span class="p">:</span> <span class="n">NSObject</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">y</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">z</span><span class="p">;</span>
<span class="p">}</span>
<span class="err">@</span><span class="n">property</span> <span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
<span class="err">@</span><span class="n">property</span> <span class="kt">int</span> <span class="n">y</span><span class="p">;</span>
<span class="err">@</span><span class="n">property</span> <span class="kt">int</span> <span class="n">z</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
<span class="err">@</span><span class="n">implementation</span> <span class="n">TestClass</span>
<span class="err">@</span><span class="n">synthesize</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
<span class="k">static</span> <span class="n">NSArray</span> <span class="o">*</span><span class="n">ClassMethodNames</span><span class="p">(</span><span class="n">Class</span> <span class="n">c</span><span class="p">)</span> <span class="p">{</span>
<span class="n">NSMutableArray</span> <span class="o">*</span><span class="n">array</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSMutableArray</span> <span class="n">array</span><span class="p">];</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">methodCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">Method</span> <span class="o">*</span><span class="n">methodList</span> <span class="o">=</span> <span class="n">class_copyMethodList</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="o">&</span><span class="n">methodCount</span><span class="p">);</span>
<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">methodCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">[</span><span class="n">array</span> <span class="nl">addObject</span><span class="p">:</span> <span class="n">NSStringFromSelector</span><span class="p">(</span><span class="n">method_getName</span><span class="p">(</span><span class="n">methodList</span><span class="p">[</span><span class="n">i</span><span class="p">]))];</span>
<span class="n">free</span><span class="p">(</span><span class="n">methodList</span><span class="p">);</span>
<span class="k">return</span> <span class="n">array</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">static</span> <span class="kt">void</span> <span class="n">PrintDescription</span><span class="p">(</span><span class="n">NSString</span> <span class="o">*</span><span class="n">name</span><span class="p">,</span> <span class="n">id</span> <span class="n">obj</span><span class="p">)</span> <span class="p">{</span>
<span class="n">NSString</span> <span class="o">*</span><span class="n">str</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSString</span> <span class="nl">stringWithFormat</span><span class="p">:</span><span class="err">@</span><span class="s">"%@: %@</span><span class="se">\n\t</span><span class="s">NSObject class %s</span><span class="se">\n\t</span><span class="s">libobjc class %s</span><span class="se">\n\t</span><span class="s">implements methods <%@>"</span><span class="p">,</span>
<span class="n">name</span><span class="p">,</span> <span class="n">obj</span><span class="p">,</span> <span class="n">class_getName</span><span class="p">([</span><span class="n">obj</span> <span class="n">class</span><span class="p">]),</span>
<span class="n">class_getName</span><span class="p">(</span><span class="n">obj</span><span class="o">-></span><span class="n">isa</span><span class="p">),</span>
<span class="p">[</span><span class="n">ClassMethodNames</span><span class="p">(</span><span class="n">obj</span><span class="o">-></span><span class="n">isa</span><span class="p">)</span> <span class="nl">componentsJoinedByString</span><span class="p">:</span><span class="err">@</span><span class="s">", "</span><span class="p">]];</span> <span class="n">printf</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">[</span><span class="n">str</span> <span class="n">UTF8String</span><span class="p">]);</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="n">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="p">[</span><span class="n">NSAutoreleasePool</span> <span class="n">new</span><span class="p">];</span>
<span class="n">TestClass</span> <span class="o">*</span><span class="n">x</span> <span class="o">=</span> <span class="p">[[</span><span class="n">TestClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">TestClass</span> <span class="o">*</span><span class="n">y</span> <span class="o">=</span> <span class="p">[[</span><span class="n">TestClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">TestClass</span> <span class="o">*</span><span class="n">xy</span> <span class="o">=</span> <span class="p">[[</span><span class="n">TestClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">TestClass</span> <span class="o">*</span><span class="n">control</span> <span class="o">=</span> <span class="p">[[</span><span class="n">TestClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="p">[</span><span class="n">x</span> <span class="nl">addObserver</span><span class="p">:</span><span class="n">x</span> <span class="nl">forKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"x"</span> <span class="nl">options</span><span class="p">:</span><span class="mi">0</span> <span class="nl">context</span><span class="p">:</span><span class="nb">NULL</span><span class="p">];</span>
<span class="p">[</span><span class="n">xy</span> <span class="nl">addObserver</span><span class="p">:</span><span class="n">xy</span> <span class="nl">forKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"x"</span> <span class="nl">options</span><span class="p">:</span><span class="mi">0</span> <span class="nl">context</span><span class="p">:</span><span class="nb">NULL</span><span class="p">];</span>
<span class="p">[</span><span class="n">y</span> <span class="nl">addObserver</span><span class="p">:</span><span class="n">y</span> <span class="nl">forKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"y"</span> <span class="nl">options</span><span class="p">:</span><span class="mi">0</span> <span class="nl">context</span><span class="p">:</span><span class="nb">NULL</span><span class="p">];</span>
<span class="p">[</span><span class="n">xy</span> <span class="nl">addObserver</span><span class="p">:</span><span class="n">xy</span> <span class="nl">forKeyPath</span><span class="p">:</span><span class="err">@</span><span class="s">"y"</span> <span class="nl">options</span><span class="p">:</span><span class="mi">0</span> <span class="nl">context</span><span class="p">:</span><span class="nb">NULL</span><span class="p">];</span>
<span class="n">PrintDescription</span><span class="p">(</span><span class="err">@</span><span class="s">"control"</span><span class="p">,</span> <span class="n">control</span><span class="p">);</span>
<span class="n">PrintDescription</span><span class="p">(</span><span class="err">@</span><span class="s">"x"</span><span class="p">,</span> <span class="n">x</span><span class="p">);</span>
<span class="n">PrintDescription</span><span class="p">(</span><span class="err">@</span><span class="s">"y"</span><span class="p">,</span> <span class="n">y</span><span class="p">);</span>
<span class="n">PrintDescription</span><span class="p">(</span><span class="err">@</span><span class="s">"xy"</span><span class="p">,</span> <span class="n">xy</span><span class="p">);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Using NSObject methods, normal setX: is %p, overridden setX: is %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="p">[</span><span class="n">control</span> <span class="nl">methodForSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">setX</span><span class="p">:)],</span>
<span class="p">[</span><span class="n">x</span> <span class="nl">methodForSelector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">setX</span><span class="p">:)]);</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"Using libobjc functions, normal setX: is %p, overridden setX: is %p</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">method_getImplementation</span><span class="p">(</span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="n">object_getClass</span><span class="p">(</span><span class="n">control</span><span class="p">),</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">setX</span><span class="p">:))),</span> <span class="n">method_getImplementation</span><span class="p">(</span><span class="n">class_getInstanceMethod</span><span class="p">(</span><span class="n">object_getClass</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="nl">setX</span><span class="p">:))));</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>我们从头到尾细细看来。</p>
<p>首先定义了一个TestClass的类,它有3个属性。</p>
<p>然后定义了一些方便调试的方法。ClassMethodNames使用Objective-C运行时方法来遍历一个class,得到方法列表。注意,这些方法不包括父类的方法。PrintDescription打印object的所有信息,包括class信息(包括-class和通过运行时得到的class),以及这个class实现的方法。</p>
<p>然后创建了4个TestClass实例,每一个都使用了不同的观察方式。x实例有一个观察者观察xkey,y, xy也类似。为了做比较,zkey没有观察者。最后control实例没有任何观察者。</p>
<p>然后打印出4个objects的description。</p>
<p>之后继续打印被重写的setter内存地址,以及未被重写的setter的内存地址做比较。这里做了两次,是因为-methodForSelector:没能得到重写的方法。KVO试图掩盖它实际上创建了一个新的subclass这个事实!但是使用运行时的方法就原形毕露了。</p>
<p>运行代码</p>
<p>看看这段代码的输出</p>
<div class="highlight"><pre><code><span class="nl">control</span><span class="p">:</span> <span class="o"><</span><span class="nl">TestClass</span><span class="p">:</span> <span class="mh">0x104b20</span><span class="o">></span>
<span class="n">NSObject</span> <span class="n">class</span> <span class="n">TestClass</span>
<span class="n">libobjc</span> <span class="n">class</span> <span class="n">TestClass</span>
<span class="n">implements</span> <span class="n">methods</span> <span class="o"><</span><span class="nl">setX</span><span class="p">:,</span> <span class="n">x</span><span class="p">,</span> <span class="nl">setY</span><span class="p">:,</span> <span class="n">y</span><span class="p">,</span> <span class="nl">setZ</span><span class="p">:,</span> <span class="n">z</span><span class="o">></span> <span class="nl">x</span><span class="p">:</span> <span class="o"><</span><span class="nl">TestClass</span><span class="p">:</span> <span class="mh">0x103280</span><span class="o">></span>
<span class="n">NSObject</span> <span class="n">class</span> <span class="n">TestClass</span>
<span class="n">libobjc</span> <span class="n">class</span> <span class="n">NSKVONotifying_TestClass</span>
<span class="n">implements</span> <span class="n">methods</span> <span class="o"><</span><span class="nl">setY</span><span class="p">:,</span> <span class="nl">setX</span><span class="p">:,</span> <span class="n">class</span><span class="p">,</span> <span class="n">dealloc</span><span class="p">,</span> <span class="n">_isKVOA</span><span class="o">></span> <span class="nl">y</span><span class="p">:</span> <span class="o"><</span><span class="nl">TestClass</span><span class="p">:</span> <span class="mh">0x104b00</span><span class="o">></span> <span class="n">NSObject</span> <span class="n">class</span> <span class="n">TestClass</span>
<span class="n">libobjc</span> <span class="n">class</span> <span class="n">NSKVONotifying_TestClass</span>
<span class="n">implements</span> <span class="n">methods</span> <span class="o"><</span><span class="nl">setY</span><span class="p">:,</span> <span class="nl">setX</span><span class="p">:,</span> <span class="n">class</span><span class="p">,</span> <span class="n">dealloc</span><span class="p">,</span> <span class="n">_isKVOA</span><span class="o">></span> <span class="nl">xy</span><span class="p">:</span> <span class="o"><</span><span class="nl">TestClass</span><span class="p">:</span> <span class="mh">0x104b10</span><span class="o">></span> <span class="n">NSObject</span> <span class="n">class</span> <span class="n">TestClass</span>
<span class="n">libobjc</span> <span class="n">class</span> <span class="n">NSKVONotifying_TestClass</span>
<span class="n">implements</span> <span class="n">methods</span> <span class="o"><</span><span class="nl">setY</span><span class="p">:,</span> <span class="nl">setX</span><span class="p">:,</span> <span class="n">class</span><span class="p">,</span> <span class="n">dealloc</span><span class="p">,</span> <span class="n">_isKVOA</span><span class="o">></span> <span class="n">Using</span> <span class="n">NSObject</span> <span class="n">methods</span><span class="p">,</span> <span class="n">normal</span> <span class="nl">setX</span><span class="p">:</span> <span class="n">is</span> <span class="mh">0x195e</span><span class="p">,</span> <span class="n">overridden</span> <span class="nl">setX</span><span class="p">:</span> <span class="n">is</span> <span class="mh">0x195e</span> <span class="n">Using</span> <span class="n">libobjc</span> <span class="n">functions</span><span class="p">,</span> <span class="n">normal</span> <span class="nl">setX</span><span class="p">:</span> <span class="n">is</span> <span class="mh">0x195e</span><span class="p">,</span> <span class="n">overridden</span> <span class="nl">setX</span><span class="p">:</span> <span class="n">is</span> <span class="mh">0x96a1a550</span>
</code></pre></div>
<p>首先,它输出了controlobject,没有任何问题,它的class是TestClass,并且实现了6个set/get方法。</p>
<p>然后是3个被观察的objects。注意-class仍然显示的是TestClass,使用object_getClass显示了这个object的真面目:它是NSKVONotifying_TestClass的一个实例。这个NSKVONotifying_TestClass就是动态生成的subclass!</p>
<p>注意,它是如何实现这两个被观察的setters的。你会发现,它很聪明,没有重写-setZ:,虽然它也是个 setter,因为它没有被观察。同时注意到,3个实例对应的是同一个class,也就是说两个setters都被重写了,尽管其中的两个实例只观察了一 个属性。这会带来一点效率上的问题,因为即使没有被观察的property也会走被重写的setter,但苹果显然觉得这比分开生成动态的 subclass更好,我也觉得这是个正确的选择。</p>
<p>你会看到3个其他的方法。有之前提到过的被重写的-class方法,假装自己还是原来的class。还有-dealloc方法处理一些收尾工作。还有一个_isKVOA方法,看起来像是一个私有方法。</p>
<p>接下来,我们输出-setX:的实现。使用-methodForSelector:返回的是相同的值。因为-setX:已经在子类被重写了,这也就意味着methodForSelector:在内部实现中使用了-class,于是得到了错误的结果。</p>
<p>最后我们通过运行时得到了不同的输出结果。</p>
<p>作为一个优秀的探索者,我们进入debugger来看看这第二个方法的实现到底是怎样的:</p>
<p>(gdb) print (IMP)0x96a1a550 $1 = (IMP) 0x96a1a550 <_NSSetIntValueAndNotify>
看起来是一个内部方法,对Foundation使用nm -a得到一个完整的私有方法列表:</p>
<div class="highlight"><pre><code><span class="mo">0013</span><span class="n">df80</span> <span class="n">t</span> <span class="n">__NSSetBoolValueAndNotify</span> <span class="mo">000</span><span class="n">a0480</span> <span class="n">t</span> <span class="n">__NSSetCharValueAndNotify</span>
<span class="mf">0013e120</span> <span class="n">t</span> <span class="n">__NSSetDoubleValueAndNotify</span> <span class="mf">0013e1</span><span class="n">f0</span> <span class="n">t</span> <span class="n">__NSSetFloatValueAndNotify</span>
<span class="mf">000e3550</span> <span class="n">t</span> <span class="n">__NSSetIntValueAndNotify</span> <span class="mf">0013e390</span> <span class="n">t</span> <span class="n">__NSSetLongLongValueAndNotify</span>
<span class="mf">0013e2</span><span class="n">c0</span> <span class="n">t</span> <span class="n">__NSSetLongValueAndNotify</span> <span class="mo">000</span><span class="mi">89</span><span class="n">df0</span> <span class="n">t</span> <span class="n">__NSSetObjectValueAndNotify</span>
<span class="mf">0013e6</span><span class="n">f0</span> <span class="n">t</span> <span class="n">__NSSetPointValueAndNotify</span> <span class="mf">0013e7</span><span class="n">d0</span> <span class="n">t</span> <span class="n">__NSSetRangeValueAndNotify</span>
<span class="mf">0013e8</span><span class="n">b0</span> <span class="n">t</span> <span class="n">__NSSetRectValueAndNotify</span> <span class="mf">0013e550</span> <span class="n">t</span> <span class="n">__NSSetShortValueAndNotify</span>
<span class="mo">000</span><span class="mi">8</span><span class="n">ab20</span> <span class="n">t</span> <span class="n">__NSSetSizeValueAndNotify</span> <span class="mf">0013e050</span> <span class="n">t</span>
<span class="n">__NSSetUnsignedCharValueAndNotify</span> <span class="mf">0009f</span><span class="n">cd0</span> <span class="n">t</span> <span class="n">__NSSetUnsignedIntValueAndNotify</span>
<span class="mf">0013e470</span> <span class="n">t</span> <span class="n">__NSSetUnsignedLongLongValueAndNotify</span> <span class="mf">0009f</span><span class="n">c00</span> <span class="n">t</span>
<span class="n">__NSSetUnsignedLongValueAndNotify</span> <span class="mf">0013e620</span> <span class="n">t</span> <span class="n">__NSSetUnsignedShortValueAndNotify</span>
</code></pre></div>
<p>这个列表也能发现一些有趣的东西。比如苹果为每一种primitive type都写了对应的实现。Objective-C的object会用到的其实只有__NSSetObjectValueAndNotify,但需要一整套来对应剩下的,而且看起来也没有实现完全,比如long dobule或_Bool都没有。甚至没有为通用指针类型(generic pointer type)提供方法。所以,不在这个方法列表里的属性其实是不支持KVO的。</p>
<p>KVO是一个很强大的工具,有时候过于强大了,尤其是有了自动触发通知机制。现在你知道它内部是怎么实现的了,这些知识或许能帮助你更好地使用它,或在它出错时更方便调试。</p>
<p>如果你打算使用KVO,或许可以看一下我的另一篇文章Key-Value Observing Done Right</p>
iOS开发中KVO的内部实现
iOS查找错误日志与性能优化系列
https://www.zruibin.cn/article/ios_cha_zhao_cuo_wu_ri_zhi_yu_xing_neng_you_hua_xi_lie.html
2014-10-31 12:04
2015-10-23 11:29
<h3>第一种方法:</h3>
<div class="highlight"><pre><code><span class="n">dwarfdump</span> <span class="o">--</span><span class="n">uuid</span> <span class="n">xx</span><span class="p">.</span><span class="n">app</span><span class="p">.</span><span class="n">dSYM</span> <span class="err">用来得到</span><span class="n">app</span><span class="err">的</span><span class="n">UUID</span><span class="err">。</span>
<span class="n">dwarfdump</span> <span class="o">--</span><span class="n">lookup</span> <span class="mh">0x9d70</span> <span class="o">-</span><span class="n">arch</span> <span class="n">armv7</span> <span class="n">xx</span><span class="p">.</span><span class="n">app</span><span class="p">.</span><span class="n">dSYM</span> <span class="err">使错误的日志能看懂,把相应的内存地址对应到正确的地方。</span>
</code></pre></div>
<p>如果一开始dwarfdump命令不能用的话,要先装Command Line Tools,这个在设置里面能下载(cmd+“,”打开设置)。另外还必须在进入.DSYM所在文件夹。</p>
<h3>第二种方法:</h3>
<div class="highlight"><pre><code><span class="n">atos</span> <span class="o">-</span><span class="n">o</span> <span class="n">YourApp</span><span class="p">.</span><span class="n">app</span><span class="p">.</span><span class="n">dSYM</span><span class="o">/</span><span class="n">Contents</span><span class="o">/</span><span class="n">Resources</span><span class="o">/</span><span class="n">DWARF</span><span class="o">/</span><span class="n">YourApp</span> <span class="mh">0x00062867</span>
</code></pre></div>
<p>在XCODE编译项目之后,会在app旁看见一个同名的dSYM文件.
他是一个编译的中转文件,简单说就是debug的symbols包含在这个文件中.</p>
<p>他有什么作用? 当release的版本 crash的时候,会有一个日志文件,包含出错的内存地址, 使用symbolicatecrash工具能够把日志和dSYM文件转换成可以阅读的log信息,也就是将内存地址,转换成程序里的函数或变量和所属于的 文件名.</p>
<p>有一篇详细的blog讲了这个过程</p>
<p><a href="http://www.anoshkin.net/blog/2008/09/09/iphone-crash-logs/">http://www.anoshkin.net/blog/2008/09/09/iphone-crash-logs/</a></p>
<p>将类似</p>
<div class="highlight"><pre><code><span class="n">Thread</span> <span class="mi">0</span> <span class="nl">Crashed</span><span class="p">:</span>
<span class="mi">0</span> <span class="n">libobjc</span><span class="p">.</span><span class="n">A</span><span class="p">.</span><span class="n">dylib</span> <span class="mi">0</span><span class="err">×</span><span class="mi">300</span><span class="n">c87ec</span> <span class="mi">0</span><span class="err">×</span><span class="mi">300</span><span class="n">bb000</span> <span class="o">+</span> <span class="mi">55276</span>
<span class="mi">1</span> <span class="n">MobileLines</span> <span class="mi">0</span><span class="err">×</span><span class="mo">00006434</span> <span class="mi">0</span><span class="err">×</span><span class="mi">1000</span> <span class="o">+</span> <span class="mi">21556</span>
<span class="mi">2</span> <span class="n">MobileLines</span> <span class="mi">0</span><span class="err">×</span><span class="mo">000064</span><span class="n">c2</span> <span class="mi">0</span><span class="err">×</span><span class="mi">1000</span> <span class="o">+</span> <span class="mi">21698</span>
<span class="mi">3</span> <span class="n">UIKit</span> <span class="mi">0</span><span class="err">×</span><span class="mi">30</span><span class="n">a740ac</span> <span class="mi">0</span><span class="err">×</span><span class="mi">30</span><span class="n">a54000</span> <span class="o">+</span> <span class="mi">131244</span>
</code></pre></div>
<p>的log信息转换成</p>
<div class="highlight"><pre><code><span class="n">Thread</span> <span class="mi">0</span> <span class="nl">Crashed</span><span class="p">:</span>
<span class="mi">0</span> <span class="n">libobjc</span><span class="p">.</span><span class="n">A</span><span class="p">.</span><span class="n">dylib</span> <span class="mi">0</span><span class="err">×</span><span class="mi">300</span><span class="n">c87ec</span> <span class="n">objc_msgSend</span> <span class="o">+</span> <span class="mi">20</span>
<span class="mi">1</span> <span class="n">MobileLines</span> <span class="mi">0</span><span class="err">×</span><span class="mo">00006434</span> <span class="o">-</span><span class="p">[</span><span class="n">BoardView</span> <span class="nl">setSelectedPiece</span><span class="p">:]</span> <span class="p">(</span><span class="n">BoardView</span><span class="p">.</span><span class="nl">m</span><span class="p">:</span><span class="mi">321</span><span class="p">)</span>
<span class="mi">2</span> <span class="n">MobileLines</span> <span class="mi">0</span><span class="err">×</span><span class="mo">000064</span><span class="n">c2</span> <span class="o">-</span><span class="p">[</span><span class="n">BoardView</span> <span class="nl">touchesBegan</span><span class="p">:</span><span class="nl">withEvent</span><span class="p">:]</span> <span class="p">(</span><span class="n">BoardView</span><span class="p">.</span><span class="nl">m</span><span class="p">:</span><span class="mi">349</span><span class="p">)</span>
<span class="mi">3</span> <span class="n">UIKit</span> <span class="mi">0</span><span class="err">×</span><span class="mi">30</span><span class="n">a740ac</span> <span class="o">-</span><span class="p">[</span><span class="n">UIWindow</span> <span class="nl">sendEvent</span><span class="p">:]</span> <span class="o">+</span> <span class="mi">264</span>
</code></pre></div>
<p>的有用信息.</p>
<p>工具symbolicatecrash隐藏在/Developer/Platforms/iPhoneOS.platform/Developer /Library/Xcode/Plug-ins/iPhoneRemoteDevice.xcodeplugin/Contents/Resources/symbolicatecrash</p>
<p>所以一般复制到/usr/local/bin/ 成为命令行直接调用</p>
<div class="highlight"><pre><code><span class="err">$</span> <span class="n">sudo</span> <span class="n">cp</span> <span class="o">/</span><span class="n">Developer</span><span class="o">/</span><span class="n">Platforms</span><span class="o">/</span><span class="n">iPhoneOS</span><span class="p">.</span><span class="n">platform</span><span class="o">/</span><span class="n">Developer</span><span class="o">/</span><span class="n">Library</span><span class="o">/</span><span class="n">Xcode</span><span class="o">/</span><span class="n">Plug</span><span class="o">-</span><span class="n">ins</span><span class="o">/</span><span class="n">iPhoneRemoteDevice</span><span class="p">.</span><span class="n">xcodeplugin</span><span class="o">/</span><span class="n">Contents</span><span class="o">/</span><span class="n">Resources</span><span class="o">/</span><span class="n">symbolicatecrash</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span>
</code></pre></div>
<div class="highlight"><pre><code><span class="n">export</span> <span class="n">DEVELOPER_DIR</span><span class="o">=</span><span class="s">"/Applications/Xcode.app/Contents/Developer"</span>
<span class="n">find</span> <span class="o">/</span><span class="n">Applications</span><span class="o">/</span><span class="n">Xcode</span><span class="p">.</span><span class="n">app</span> <span class="o">-</span><span class="n">name</span> <span class="n">symbolicatecrash</span> <span class="o">-</span><span class="n">type</span> <span class="n">f</span>
</code></pre></div>
<p>这个时候运行</p>
<div class="highlight"><pre><code><span class="err">$</span> <span class="n">symbolicatecrash</span> <span class="o">-</span><span class="n">h</span>
</code></pre></div>
<p>就能看见帮助信息了.</p>
<p>这个时候,问题又来了..每次编译后的dsym文件都要手动保存一次,很是麻烦.</p>
<p>于是有人写了一个脚本,自动在编译后保存该文件.
请参考:<a href="http://www.cimgf.com/2009/12/23/automatically-save-the-dsym-files/" target="blank">http://www.cimgf.com/2009/12/23/automatically-save-the-dsym-files/</a></p>
<p>脚本我复制过来在下面</p>
<div class="highlight"><pre><code><span class="cp">#!/bin/sh</span>
<span class="k">if</span> <span class="p">[</span> <span class="s">"$BUILD_STYLE"</span> <span class="o">==</span> <span class="s">"Debug"</span> <span class="p">];</span> <span class="n">then</span>
<span class="n">echo</span> <span class="err">“</span><span class="n">Skipping</span> <span class="n">debug</span><span class="err">”</span>
<span class="n">exit</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">fi</span>
<span class="k">if</span> <span class="p">[</span> <span class="s">"$EFFECTIVE_PLATFORM_NAME"</span> <span class="o">==</span> <span class="s">"-iphonesimulator"</span> <span class="p">];</span> <span class="n">then</span>
<span class="n">echo</span> <span class="err">“</span><span class="n">Skipping</span> <span class="n">simulator</span> <span class="n">build</span><span class="err">”</span>
<span class="n">exit</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">fi</span>
<span class="n">SRC_PATH</span><span class="o">=</span><span class="err">$</span><span class="p">{</span><span class="n">DWARF_DSYM_FOLDER_PATH</span><span class="p">}</span><span class="o">/</span><span class="err">$</span><span class="p">{</span><span class="n">DWARF_DSYM_FILE_NAME</span><span class="p">}</span>
<span class="n">RELATIVE_DEST_PATH</span><span class="o">=</span><span class="n">dSYM</span><span class="o">/</span><span class="err">$</span><span class="p">{</span><span class="n">EXECUTABLE_NAME</span><span class="p">}.</span><span class="err">$</span><span class="p">(</span><span class="n">date</span> <span class="o">+%</span><span class="n">Y</span><span class="o">%</span><span class="n">m</span><span class="o">%</span><span class="n">d</span><span class="o">%</span><span class="n">H</span><span class="o">%</span><span class="n">M</span><span class="o">%</span><span class="n">S</span><span class="p">).</span><span class="n">app</span><span class="p">.</span><span class="n">dSYM</span>
<span class="n">DEST_PATH</span><span class="o">=</span><span class="err">$</span><span class="p">{</span><span class="n">PROJECT_DIR</span><span class="p">}</span><span class="o">/</span><span class="err">$</span><span class="p">{</span><span class="n">RELATIVE_DEST_PATH</span><span class="p">}</span>
<span class="n">echo</span> <span class="err">“</span><span class="n">moving</span> <span class="err">$</span><span class="p">{</span><span class="n">SRC_PATH</span><span class="p">}</span> <span class="n">to</span> <span class="err">$</span><span class="p">{</span><span class="n">DEST_PATH</span><span class="p">}</span><span class="err">”</span>
<span class="n">mv</span> <span class="err">“$</span><span class="p">{</span><span class="n">SRC_PATH</span><span class="p">}</span><span class="err">”</span> <span class="err">“$</span><span class="p">{</span><span class="n">DEST_PATH</span><span class="p">}</span><span class="err">”</span>
<span class="k">if</span> <span class="p">[</span> <span class="o">-</span><span class="n">f</span> <span class="s">".git/config"</span> <span class="p">];</span> <span class="n">then</span>
<span class="n">git</span> <span class="n">add</span> <span class="err">“$</span><span class="p">{</span><span class="n">RELATIVE_DEST_PATH</span><span class="p">}</span><span class="err">”</span>
<span class="n">git</span> <span class="n">commit</span> <span class="o">-</span><span class="n">m</span> <span class="err">“</span><span class="n">Added</span> <span class="n">dSYM</span> <span class="n">file</span> <span class="k">for</span> <span class="err">$</span><span class="p">{</span><span class="n">BUILD_STYLE</span><span class="p">}</span> <span class="n">build</span><span class="err">”</span> <span class="err">“$</span><span class="p">{</span><span class="n">RELATIVE_DEST_PATH</span><span class="p">}</span><span class="err">”</span>
<span class="n">fi</span>
</code></pre></div>
<hr>
<h3>一:性能优化策略</h3>
<p>这一系列文章是我的读书笔记,整理一下,也算是温故而知新。</p>
<p>性能问题的处理流程</p>
<p>发现/重现问题</p>
<p>利用工具剖析</p>
<p>形成假设</p>
<p>改进代码和设计</p>
<p>iOS应用性能问题处理流程</p>
<p>在以上的四个步骤中循环反复,直到问题解决。</p>
<p>Profile!不要猜!</p>
<p>性能优化的主要策略:</p>
<p>不要做无用功:不要在启动时花几百ms来做logging,不要为同样的数据做多次查询</p>
<p>试图重用:对于创建过程昂贵的对象,要重用而不是重新创建</p>
<p>Table View的cell</p>
<p>Date/Number的formatter</p>
<p>正则表达式</p>
<p>SQLite语句</p>
<p>使用更快的方式设计、编程:选择正确的集合对象和算法来进行编程、选择适合的数据存储格式(plist、SQLite)、优化SQLite查询语句</p>
<p>事先做优化</p>
<p>对于昂贵的计算,要进行事先计算。iCal中的重复事件,是预先计算出来的,并保存到数据库中。</p>
<p>事先计算并缓存一些对象,可能会占用大量的内存。注意不要将这些对象声明为static并常驻内存。</p>
<p>事后做优化:异步加载、懒加载</p>
<p>为伸缩性而做优化:当数据有10条、100条、1000条甚至更多的时候,应用程序的性能不应该对应的呈数量级式的增长,否则无法使用。</p>
<p>说起来惭愧,我真的很少遇到性能问题。以前假设中的性能问题,很多是根本不存在的。事前计划也杜绝了不了性能问题的产生,所以不如暂时忘记它吧。当然对于一些常识性的提高性能的设计,仍然是必须的。</p>
<h3>二:iOS应用启动速度优化</h3>
<p>很多app的开发者都不重视app的启动速度,这对于碎片化使用情景的用户来说,简直是灾难。</p>
<p>iOS应用的启动速度</p>
<p>应用启动时,会播放一个放大的动画。iPhone上是400ms,iPad上是500ms。最理想的启动速度是,在播放完动画后,用户就可以使用。</p>
<p>如果应用启动过慢,用户就会放弃使用,甚至永远都不再回来。抛开代码不谈,如果抱着PC端游和单机游戏的思维,在游戏启动时强加公司Logo,启动动画,并且用户不可跳过,也会使用户的成功使用率大大降低。</p>
<p>iOS系统的“看门狗"</p>
<p>为了防止一个应用占用过多的系统资源,开发iOS的苹果工程师门设计了一个“看门狗”的机制。在不同的场景下,“看门狗”会监测应用的性能。如果超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程。开发者们在crashlog里面,会看到诸如0x8badf00d这样的错误代码(“看门狗”吃了坏的食物,它很不高兴)。</p>
<p>场景 “看门狗”超时时间</p>
<p>启动 20秒</p>
<p>恢复运行 10秒</p>
<p>悬挂进程 10秒</p>
<p>退出应用 6秒</p>
<p>后台运行 10分钟</p>
<p>值得注意的是,Xcode在Debug的时候,会禁止“看门狗”。</p>
<p>如何测试启动时间</p>
<p>两种方法:一种使用NSLog,另外一种使用Time Profiler。</p>
<p>使用NSLog</p>
<div class="highlight"><pre><code><span class="n">CFAbsoluteTime</span> <span class="n">StartTime</span><span class="p">;</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span> <span class="p">{</span>
<span class="n">StartTime</span> <span class="o">=</span> <span class="n">CFAbsoluteTimeGetCurrent</span><span class="p">();</span>
<span class="c1">// ...</span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nl">applicationDidFinishLaunching</span><span class="p">:(</span><span class="n">UIApplication</span> <span class="o">*</span><span class="p">)</span><span class="n">app</span> <span class="p">{</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="n">NSLog</span><span class="p">(</span><span class="err">@</span><span class="s">"Launched in %f sec"</span><span class="p">,</span> <span class="n">CFAbsoluteTimeGetCurrent</span><span class="p">()</span> <span class="o">-</span> <span class="n">StartTime</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div>
<p>使用Time Profiler</p>
<p>Instruments->Time Profiler</p>
<p>Profile你的app</p>
<p>切换到CPU strategy view,找到你的app启动的第一帧</p>
<p>搜索-[UIApplication _reportAppLaunchFinished]</p>
<p>找到包含-[UIApplication _reportAppLaunchFinished]的最后一帧,即可计算出启动时间</p>
<p>iOS App启动过程</p>
<p>链接并加载Framework和static lib</p>
<p>UIKit初始化</p>
<p>应用程序callback</p>
<p>第一个Core Animation transaction</p>
<p>链接并加载Framework及static lib时需要注意:</p>
<p>每个Framework都会增加启动时间和占用的内存</p>
<p>不必要的Framework,不要链接</p>
<p>必要的Framework,不要票房为Optional</p>
<p>只在使用在Deployment Target之后发布的Framework时,才使用Optional(比如你的Deployment Target是iOS 3.0,需要链接StoreKit的时候)</p>
<p>避免创建全局的C++对象</p>
<p>初始化UIKit时需要注意:</p>
<p>字体、状态栏、user defaults、main nib会被初始化</p>
<p>保持main nib尽可能的小</p>
<p>User defaults本质上是一个plist文件,保存的数据是同时被反序列化的,不要在user defaults里面保存图片等大数据</p>
<p>应用程序的回调:</p>
<p>application:willFinishLaunchingWithOptions:</p>
<p>恢复应用程序的状态</p>
<p>application:didFinishLaunchingWithOptions:</p>
<p>我一直认为设计的本质是折衷。当你为了100ms的启动速度优化欢欣不已,而无视那长达10秒的启动动画时,应该想想究竟什么是应该做的。做正确的事情比把事情做好更重要。</p>
<p>三:事件处理-拯救主线程</p>
<p>用户经常评论app的一个用词是“卡顿”,很大的因素是因为主线程被占用了。用户的事件是在主线程被处理的,包括点击、滚动、加速计、Proximity Sensor。</p>
<p>为了保证事件的平滑处理,需要进行如下优化:</p>
<p>最小化主线程的CPU占用</p>
<p>将工作“搬离”主线程</p>
<p>不要阻塞主线程</p>
<p>最小化主线程的CPU占用</p>
<p>前面两篇文章,我们接触到了Time Profiler。使用它可以剖析不同线程的CPU使用情况,并给出调用堆栈的CPU时间占用百分比。如果app“卡顿”,并且在Time Profiler的结果可以找到明确的高占用堆栈,你需要把它优化掉。</p>
<p>将工作“搬离”主线程 - 隐式并发</p>
<p>为了得到更流畅的交互体验,iOS已经帮我们做了很多事情,Android就没有这么好运了。iOS将以下这些事情搬离了主线程:</p>
<p>View和layer的动画(动画绘制前的计算,而不是drawing过程)</p>
<p>Layer的组合计算(drawing后的叠加)</p>
<p>PNG的解码(是的,你没看错;而且利用了CPU的多核心)</p>
<p>注意滚动(Scrolling)不是一个动画,而是在Main Run Loop中不断接收事件并且处理。</p>
<p>将工作“搬离”主线程 - 显式并发</p>
<p>这里是需要开发者们搞定的部分。磁盘、网络等I/O会阻塞线程,不要把它们放到主线程里。常用的技术有:</p>
<p>Grand Central Dispatch(GCD)</p>
<p>NSOperationQueue</p>
<p>NSThread</p>
<p>iOS 4.0后,易用的GCD技术被广泛使用。例如:</p>
<div class="highlight"><pre><code><span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span>
<span class="o">^</span><span class="p">{</span>
<span class="c1">// do something in background</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">dispatch_get_main_queue</span><span class="p">(),</span> <span class="o">^</span><span class="p">{</span>
<span class="c1">// do something on main thread</span>
<span class="p">});</span>
<span class="p">});</span>
</code></pre></div>
<h3>GCD的陷阱</h3>
<p>GCD其实就是线程,只不过提供了一个更高层次的抽象。过多的线程一定会带来性能损失,因此GCD设计了一个最高允许的线程值(对开发者透明,不用管到底有多少)。那么如何解决这个问题呢?</p>
<p>将队列串行化</p>
<p>使用Dispatch sources</p>
<p>使用带有限制的NSOperationQueue</p>
<p>使用Cocoa Touch提供的异步方法</p>
<p>另外一个陷阱是线程安全:</p>
<p>UIKit必须要在主线程使用,除了UIGraphics,UIBezierPath,UIImage</p>
<p>大多数CG、CA、Foundation的类,不是线程安全的</p>
<p>如果你使用了ojbc runtime来进行introspection,由于它是thread safe的,可能会导致竞争</p>
<p>此外,iOS 4.3添加了DISPATCH_QUEUE_PRIORITY_BACKGROUND,它拥有非常低的优先级。这个优先级只用于不太关心完成时间的真正的后台任务,如果要表示较低的优先级,你通常需要的是DISPATCH_QUEUE_PRIORITY_LOW。</p>
<p>不要阻塞主线程</p>
<p>即使占用了很少的CPU时间(如果你在Time Profiler中看到这些的数据),也可能会阻塞主线程。磁盘、网络、Lock、dispatch_sync以及向其它进程/线程发送消息都会阻塞主线程。Time Profiler只能检测出占用CPU过多的堆栈,但检测不了这些IO的问题。</p>
<p>大多数的阻塞事件,都会伴随着一个系统调用,如:</p>
<p>read/write - 读写文件</p>
<p>send/recv - 收发网络数据</p>
<p>psynch_mutex_wait - 获得锁</p>
<p>mach_msg - IPC</p>
<p>System Trace这个Instrumentor,记录了所有的系统调用,以及每次调用的等待时间。如果你在System Trace里面发现了CPU Time很低,但Wait Time很高的调用,说明在主线程处理I/O已经严重损害了app的性能。</p>
<p>保证主线程的低CPU占用,将I/O移至其它线程,可以大大地提高主线程对交互事件的处理能力。我建议开发者朋友们写代码的时候,除非是以前遇到过的问题,都没有必要假设问题存在。80%的优化都是不必要的。</p>
<p><br/><hr><br/></p>
<p>iOS应用是非常注重用户体验的,不光是要求界面设计合理美观,也要求各种UI的反应灵敏,我相信大家对那种一拖就卡卡卡的 TableView 应用没什么好印象。还记得12306么,那个速度,相信大家都受不了。为了提高 iOS 的运行速度,下面我将抛砖引玉介绍一些我实践过的用来提供iOS程序运行效率的方法,与大家分享,希望能得到更多的反馈和建议。</p>
<p>1,计算代码运行时间:相信数据,不要太相信感觉。不过要注意模拟器和真机的差异。</p>
<p>最简单的工具就是 NSDate,但精度不是太好。</p>
<p>NSDate* tmpStartData = [[NSDate date] retain]; //You code here... double deltaTime = [[NSDate date] timeIntervalSinceDate:tmpStartData]; NSLog(@">>>>>>>>>>cost time = %f", deltaTime);
或者将运行代码放到如下方法的 block 参数中,然后返回所运行的时间:</p>
<div class="highlight"><pre><code><span class="cp">#import <mach/mach_time.h> </span>
<span class="c1">// for mach_absolute_time() and friends </span>
<span class="n">CGFloat</span> <span class="nf">BNRTimeBlock</span> <span class="p">(</span><span class="kt">void</span> <span class="p">(</span><span class="o">^</span><span class="n">block</span><span class="p">)(</span><span class="kt">void</span><span class="p">))</span> <span class="p">{</span>
<span class="kt">mach_timebase_info_data_t</span> <span class="n">info</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">mach_timebase_info</span><span class="p">(</span><span class="o">&</span><span class="n">info</span><span class="p">)</span> <span class="o">!=</span> <span class="n">KERN_SUCCESS</span><span class="p">)</span> <span class="k">return</span> <span class="o">-</span><span class="mf">1.0</span><span class="p">;</span>
<span class="kt">uint64_t</span> <span class="n">start</span> <span class="o">=</span> <span class="n">mach_absolute_time</span> <span class="p">();</span>
<span class="n">block</span> <span class="p">();</span>
<span class="kt">uint64_t</span> <span class="n">end</span> <span class="o">=</span> <span class="n">mach_absolute_time</span> <span class="p">();</span>
<span class="kt">uint64_t</span> <span class="n">elapsed</span> <span class="o">=</span> <span class="n">end</span> <span class="o">-</span> <span class="n">start</span><span class="p">;</span>
<span class="kt">uint64_t</span> <span class="n">nanos</span> <span class="o">=</span> <span class="n">elapsed</span> <span class="o">*</span> <span class="n">info</span><span class="p">.</span><span class="n">numer</span> <span class="o">/</span> <span class="n">info</span><span class="p">.</span><span class="n">denom</span><span class="p">;</span>
<span class="k">return</span> <span class="p">(</span><span class="n">CGFloat</span><span class="p">)</span><span class="n">nanos</span> <span class="o">/</span> <span class="n">NSEC_PER_SEC</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>2,善用性能分析工具。</p>
<p>XCode 自带了很多强大的分析工具,包括静态 Analyze 工具,以及运行时 Profile 工具。
<img width="497" height="223" alt="" src="http://static.oschina.net/uploads/img/201410/31120932_NNBL.jpg" style="cursor: pointer;"></p>
<p>3,关于图片</p>
<p>优先使用[UIImage imageNamed:@""];</p>
<p>与[[UIImage alloc] initWithContentsOfFile:] 和 [UIImage alloc [initWithData:]] 相比,[UIImage imageNamed:]有着更好的效率,这是因为 iOS 会自带 cache 通过 [UIImage imageNamed:] 载入的图像,但该方法有一个缺点,那就是只能载入应用程序 bundle 中的图像,像网络下载的图像就无能无力了。我习惯的做法是自定义一个 ImageCache 类,自己来 cache 图像。</p>
<p>尽量不要使用全屏大小的背景图片;使用 gradient 图片来取代硬编码的 gradient;gradient 图片应当尽可能窄,然后将之拉伸运用到实际场合中去。</p>
<p>4,对于结构复杂的 View,使用 drawRect 自绘而不是从 nib 中载入。</p>
<p>5,对于 TableView,重用 cell;减少 cell 初始化的工作量,延迟装载;定制复杂 cell 时,使用 drawRect 自绘;Cache 尽可能多的东西,包括 cell 高度;尽可能让 cell 不透明;避免使用图像特性,比如 gradients。</p>
<p>6,在线程中使用 autoreleasepool。</p>
<p>7,将一些不太重要的任务放在 idle 时运行。</p>
<div class="highlight"><pre><code><span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">idleNotificationMethod</span> <span class="p">{</span>
<span class="c1">// do something here </span>
<span class="p">}</span>
<span class="o">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="n">registerForIdleNotification</span> <span class="p">{</span>
<span class="p">[[</span><span class="n">NSNotificationCenter</span> <span class="n">defaultCenter</span><span class="p">]</span> <span class="nl">addObserver</span><span class="p">:</span><span class="n">self</span> <span class="nl">selector</span><span class="p">:</span><span class="err">@</span><span class="n">selector</span><span class="p">(</span><span class="n">idleNotificationMethod</span><span class="p">)</span> <span class="nl">name</span><span class="p">:</span><span class="err">@</span><span class="s">"IdleNotification"</span> <span class="nl">object</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span> <span class="n">NSNotification</span> <span class="o">*</span><span class="n">notification</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSNotification</span> <span class="nl">notificationWithName</span><span class="p">:</span><span class="err">@</span><span class="s">"IdleNotification"</span> <span class="nl">object</span><span class="p">:</span><span class="n">nil</span><span class="p">];</span>
<span class="p">[[</span><span class="n">NSNotificationQueue</span> <span class="n">defaultQueue</span><span class="p">]</span> <span class="nl">enqueueNotification</span><span class="p">:</span><span class="n">notification</span> <span class="nl">postingStyle</span><span class="p">:</span><span class="n">NSPostWhenIdle</span><span class="p">];</span>
<span class="p">}</span>
</code></pre></div>
<p>8,不要在 viewWillAppear 中做费时的操作。</p>
<p>viewWillAppear: 在 view 显示之前被调用,出于效率考虑,在这个方法中不要处理复杂费时的事情;只应该在这个方法设置 view 的显示属性之类的简单事情,比如背景色,字体等。要不然,用户会明显感觉到 view 显示迟钝。</p>
<p>9,使用多线程来延迟加载资源。比如常见的 TableViewCell 中的网络图像显示,先使用一个默认图像,然后开启线程下载网络图像,当图像下载完成之后,再替换默认图像。</p>
<p>10,关于后台任务</p>
<p>系统进入 background 之后,一般只有10分钟的运行时间,因此有很多值得注意的事项:</p>
<p>a) 尽量减少内存的使用。当内存不足时,iOS将kill那些消耗内存最多的 App。</p>
<p>b) 释放所有的共享资源,比如 Calendar 与 Address book。当应用程序进入后台时,如果它还在使用或没有释放共享资源,iOS会立即kill掉该应用程序。</p>
<p>c) 正确处理App生命周期事件。当进入后台时,应该保持应用程序数据,以便回到前台时能够恢复。当进入 inactive 状态时,应该暂停当前的业务流。iOS运行App在后台运行的时间有限,因此后台代码不应该执行非常耗时的任务,可能的话就使用多线程。当进入后台 时,iOS会保存当前App的一个快照,以便之后在合适的时候(装载view和数据时)呈现给用户以提高用户体验,因此在进入后台时,应该避免在屏幕上呈 现用户信息,以免泄露用户个人资料。</p>
<p>d) 不要更新UI或者执行大量消耗CPU或电池的代码。进入后台之后,不应该执行不必要的任务,不要执行 OpenGL ES 调用,应取消 Bonjour 相关的服务,正确处理网络链接失败,避免更新 UI,清除所有的警告或其他弹出对话框。</p>
<p>e) 保证后台代码的执行工作正常,注意处理异常。</p>
<p>f) 在后台时正确响应系统变化。 如: 设备旋转消息UIDeviceOrientationDidChangeNotification ,重要的时间变化(新的一天开始或时区变化)UIApplicationSignificantTimeChangeNotification ,电池变化UIDeviceBatteryLevelDidChangeNotification 和 UIDeviceBatteryStateDidChangeNotification,用户默认设置变化 NSUserDefaultsDidChangeNotification,本地化语言变化 NSCurrentLocaleDidChangeNotification 等。</p>
<p>11,如果关键代码使用 C/C++/asm 效率更高就使用 C/C++/asm。</p>
<p>12,如果一个方法在一个循环次数非常多的循环中使用,在进入循环前使用 methodForSelector 获取该方法 IMP,然后在循环体中直接使用该 IMP。</p>
<p>13,关于内存释放</p>
<p>在 didReceiveMemoryWarning 中释放内存,比如cache 的图像,view 等,并记得调用 [supper didReceiveMemoryWarning]。清理函数 didReceiveMemoryWarning, viewDidUnload 和 dealloc 都是在方法结尾处调用 supper 的方法。</p>
<p>14,提高 APP 加载速度</p>
<p>避免使用静态初始化,包括静态c++对象,加载时会运行的代码,如+(void) load{} ,会造成在Main函数之前运行额外的代码。</p>
<p>16,利用 cache 空间换时间。cache 是一种常见的空间换时间的提供性能的收到,可以用在相当多的场合。</p>
<p>尽量 cache 那些可重复利用的对象,比如 table cell,date/number formatters,正则表达式,sqlite语句等。</p>
<p>17,关于数据库</p>
<p>缓存经常用到的 sqlite 语句;优化数据库查询语句,用sqlite3_trace和sqlite3_profile来查找性能差的语句;如果可能的话,缓存查询结果缓。</p>
<p>在使用 sqlite_prepare会将SQL查询编译成字节码,要使用bind,重用那些已经prepared的语句。</p>
iOS查找错误日志与性能优化系列
iOS GCD使用指南(swift)
https://www.zruibin.cn/article/iosgcd_shi_yong_zhi_nan_swift.html
2014-10-30 15:49
2015-10-13 17:53
<p>Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。</p>
<h3>Dispatch Queue</h3>
<p>Dispatch Queue是用来执行任务的队列,是GCD中最基本的元素之一。
Dispatch Queue分为两种:
Serial Dispatch Queue,按添加进队列的顺序(先进先出)一个接一个的执行</p>
<p>Concurrent Dispatch Queue,并发执行队列里的任务</p>
<p>简而言之,Serial Dispatch Queue只使用了一个线程,Concurrent Dispatch Queue使用了多个线程(具体使用了多少个,由系统决定)。</p>
<p>可以通过两种方式来获得Dispatch Queue,第一种方式是自己创建一个:</p>
<p>let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)</p>
<p>第一个参数是队列的名称,一般是使用倒序的全域名。虽然可以不给队列指定一个名称,但是有名称的队列可以让我们在遇到问题时更好调试;</p>
<p>当第二个参数为nil时返回Serial Dispatch Queue,如上面那个例子,当指定为DISPATCH_QUEUE_CONCURRENT时返回Concurrent Dispatch Queue。</p>
<p>需要注意一点,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Queue将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放,如下:</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="nl">myQueue</span><span class="p">:</span> <span class="kt">dispatch_queue_t</span> <span class="o">=</span> <span class="n">dispatch_queue_create</span><span class="p">(</span><span class="s">"com.xxx"</span><span class="p">,</span> <span class="n">nil</span><span class="p">)</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">myQueue</span><span class="p">,</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"in Block"</span><span class="p">)</span>
<span class="p">})</span>
<span class="n">dispatch_release</span><span class="p">(</span><span class="n">myQueue</span><span class="p">)</span>
</code></pre></div>
<p>以上是通过手动创建的方式来获取Dispatch Queue,第二种方式是直接获取系统提供的Dispatch Queue。
要获取的Dispatch Queue无非就是两种类型:
Main Dispatch Queue</p>
<h4>Global Dispatch Queue / Concurrent Dispatch Queue</h4>
<p>一般只在需要更新UI时我们才获取Main Dispatch Queue,其他情况下用Global Dispatch Queue就满足需求了:</p>
<div class="highlight"><pre><code><span class="c1">//获取Main Dispatch Queue</span>
<span class="n">let</span> <span class="n">mainQueue</span> <span class="o">=</span> <span class="n">dispatch_get_main_queue</span><span class="p">()</span>
<span class="c1">//获取Global Dispatch Queue</span>
<span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div>
<p>得到的Global Dispatch Queue实际上是一个Concurrent Dispatch Queue,Main Dispatch Queue实际上就是Serial Dispatch Queue(并且只有一个)。</p>
<p>获取Global Dispatch Queue的时候可以指定优先级,可以根据自己的实际情况来决定使用哪种优先级。</p>
<p>一般情况下,我们通过第二种方式获取Dispatch Queue就行了。</p>
<h4>dispatch_after</h4>
<p>dispatch_after能让我们添加进队列的任务延时执行,比如想让一个Block在10秒后执行:</p>
<div class="highlight"><pre><code><span class="n">var</span> <span class="n">time</span> <span class="o">=</span> <span class="n">dispatch_time</span><span class="p">(</span><span class="n">DISPATCH_TIME_NOW</span><span class="p">,</span> <span class="p">(</span><span class="n">Int64</span><span class="p">)(</span><span class="mi">10</span> <span class="o">*</span> <span class="n">NSEC_PER_SEC</span><span class="p">))</span>
<span class="n">dispatch_after</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"在10秒后执行"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>NSEC_PER_SEC表示的是秒数,它还提供了NSEC_PER_MSEC表示毫秒。</p>
<p>上面这句dispatch_after的真正含义是在10秒后把任务添加进队列中,并不是表示在10秒后执行,大部分情况该函数能达到我们的预期,只有在对时间要求非常精准的情况下才可能会出现问题。</p>
<p>获取一个dispatch_time_t类型的值可以通过两种方式来获取,以上是第一种方式,即通过dispatch_time函数,另一种是通过dispatch_walltime函数来获取,dispatch_walltime需要使用一个timespec的结构体来得到dispatch_time_t。通常dispatch_time用于计算相对时间,dispatch_walltime用于计算绝对时间,我写了一个把NSDate转成dispatch_time_t的Swift方法:</p>
<div class="highlight"><pre><code><span class="n">func</span> <span class="n">getDispatchTimeByDate</span><span class="p">(</span><span class="nl">date</span><span class="p">:</span> <span class="n">NSDate</span><span class="p">)</span> <span class="o">-></span> <span class="kt">dispatch_time_t</span> <span class="p">{</span>
<span class="n">let</span> <span class="n">interval</span> <span class="o">=</span> <span class="n">date</span><span class="p">.</span><span class="n">timeIntervalSince1970</span>
<span class="n">var</span> <span class="n">second</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="n">let</span> <span class="n">subsecond</span> <span class="o">=</span> <span class="n">modf</span><span class="p">(</span><span class="n">interval</span><span class="p">,</span> <span class="o">&</span><span class="n">second</span><span class="p">)</span>
<span class="n">var</span> <span class="n">time</span> <span class="o">=</span> <span class="n">timespec</span><span class="p">(</span><span class="nl">tv_sec</span><span class="p">:</span> <span class="kt">__darwin_time_t</span><span class="p">(</span><span class="n">second</span><span class="p">),</span> <span class="nl">tv_nsec</span><span class="p">:</span> <span class="p">(</span><span class="n">Int</span><span class="p">)(</span><span class="n">subsecond</span> <span class="o">*</span> <span class="p">(</span><span class="n">Double</span><span class="p">)(</span><span class="n">NSEC_PER_SEC</span><span class="p">)))</span>
<span class="k">return</span> <span class="n">dispatch_walltime</span><span class="p">(</span><span class="o">&</span><span class="n">time</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>这个方法接收一个NSDate对象,然后把NSDate转成dispatch_walltime需要的timespec结构体,最后再把dispatch_time_t返回,同样是在10秒后执行,之前的代码在调用部分需要修改成:</p>
<div class="highlight"><pre><code><span class="n">var</span> <span class="n">time</span> <span class="o">=</span> <span class="n">getDispatchTimeByDate</span><span class="p">(</span><span class="n">NSDate</span><span class="p">(</span><span class="nl">timeIntervalSinceNow</span><span class="p">:</span> <span class="mi">10</span><span class="p">))</span>
<span class="n">dispatch_after</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"在10秒后执行"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>这就是通过绝对时间来使用dispatch_after的例子。</p>
<h4>dispatch_group</h4>
<p>可能经常会有这样一种情况:我们现在有3个Block要执行,我们不在乎它们执行的顺序,我们只希望在这3个Block执行完之后再执行某个操作。这个时候就需要使用dispatch_group了:</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">let</span> <span class="n">group</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">()</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"1"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"2"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"3"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dispatch_group_notify</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"completed"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>输出的顺序与添加进队列的顺序无关,因为队列是Concurrent Dispatch Queue,但“completed”的输出一定是在最后的:</p>
<div class="highlight"><pre><code><span class="n">completed</span>
</code></pre></div>
<p>除了使用dispatch_group_notify函数可以得到最后执行完的通知外,还可以使用</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">let</span> <span class="n">group</span> <span class="o">=</span> <span class="n">dispatch_group_create</span><span class="p">()</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"1"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"2"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">dispatch_group_async</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">println</span><span class="p">(</span><span class="s">"3"</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">//使用dispatch_group_wait函数</span>
<span class="n">dispatch_group_wait</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">DISPATCH_TIME_FOREVER</span><span class="p">)</span>
<span class="n">println</span><span class="p">(</span><span class="s">"completed"</span><span class="p">)</span>
</code></pre></div>
<p>需要注意的是,dispatch_group_wait实际上会使当前的线程处于等待的状态,也就是说如果是在主线程执行dispatch_group_wait,在上面的Block执行完之前,主线程会处于卡死的状态。可以注意到dispatch_group_wait的第二个参数是指定超时的时间,如果指定为DISPATCH_TIME_FOREVER(如上面这个例子)则表示会永久等待,直到上面的Block全部执行完,除此之外,还可以指定为具体的等待时间,根据dispatch_group_wait的返回值来判断是上面block执行完了还是等待超时了。</p>
<p>最后,同之前创建dispatch_queue一样,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Group将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。</p>
<h4>dispatch_barrier_async</h4>
<p>dispatch_barrier_async就如同它的名字一样,在队列执行的任务中增加“栅栏”,在增加“栅栏”之前已经开始执行的block将会继续执行,当dispatch_barrier_async开始执行的时候其他的block处于等待状态,dispatch_barrier_async的任务执行完后,其后的block才会执行。我们简单的写个例子,假设这个例子有读文件和写文件的部分:</p>
<div class="highlight"><pre><code><span class="n">func</span> <span class="nf">writeFile</span><span class="p">()</span> <span class="p">{</span>
<span class="n">NSUserDefaults</span><span class="p">.</span><span class="n">standardUserDefaults</span><span class="p">().</span><span class="n">setInteger</span><span class="p">(</span><span class="mi">7</span><span class="p">,</span> <span class="nl">forKey</span><span class="p">:</span> <span class="s">"Integer_Key"</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">func</span> <span class="nf">readFile</span><span class="p">(){</span>
<span class="n">print</span><span class="p">(</span><span class="n">NSUserDefaults</span><span class="p">.</span><span class="n">standardUserDefaults</span><span class="p">().</span><span class="n">integerForKey</span><span class="p">(</span><span class="s">"Integer_Key"</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div>
<p>写文件只是在NSUserDefaults写入一个数字7,读只是将这个数字打印出来而已。我们要避免在写文件时候正好有线程来读取,就使用dispatch_barrier_async函数:</p>
<div class="highlight"><pre><code><span class="n">NSUserDefaults</span><span class="p">.</span><span class="n">standardUserDefaults</span><span class="p">().</span><span class="n">setInteger</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="nl">forKey</span><span class="p">:</span> <span class="s">"Integer_Key"</span><span class="p">)</span>
<span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_barrier_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">writeFile</span><span class="p">()</span> <span class="p">;</span> <span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span><span class="n">self</span><span class="p">.</span><span class="n">readFile</span><span class="p">()}</span>
</code></pre></div>
<p>我们先将一个9初始化到NSUserDefaults的Integer_Key中,然后在中间执行dispatch_barrier_async函数,由于这个队列是一个Concurrent Dispatch Queue,能同时并发多少线程是由系统决定的,如果添加dispatch_barrier_async的时候,其他的block(包括上面4个block)还没有开始执行,那么会先执行dispatch_barrier_async里的任务,其他block全部处于等待状态。如果添加dispatch_barrier_async的时候,已经有block在执行了,那么dispatch_barrier_async会等这些block执行完后再执行。</p>
<h4>dispatch_apply</h4>
<p>dispatch_apply会将一个指定的block执行指定的次数。如果要对某个数组中的所有元素执行同样的block的时候,这个函数就显得很有用了,用法很简单,指定执行的次数以及Dispatch Queue,在block回调中会带一个索引,然后就可以根据这个索引来判断当前是对哪个元素进行操作:</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">dispatch_apply</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">print</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">print</span><span class="p">(</span><span class="s">"completed"</span><span class="p">)</span>
</code></pre></div>
<p>由于是Concurrent Dispatch Queue,不能保证哪个索引的元素是先执行的,但是“completed”一定是在最后打印,因为dispatch_apply函数是同步的,执行过程中会使线程在此处等待,所以一般的,我们应该在一个异步线程里使用dispatch_apply函数:</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">,</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">dispatch_apply</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">(</span><span class="n">index</span><span class="p">)</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">print</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">print</span><span class="p">(</span><span class="s">"completed"</span><span class="p">)</span>
<span class="p">})</span>
<span class="n">print</span><span class="p">(</span><span class="s">"在dispatch_apply之前"</span><span class="p">)</span>
</code></pre></div>
<h4>dispatch_suspend / dispatch_resume</h4>
<p>某些情况下,我们可能会想让Dispatch Queue暂时停止一下,然后在某个时刻恢复处理,这时就可以使用dispatch_suspend以及dispatch_resume函数:</p>
<div class="highlight"><pre><code><span class="c1">//暂停</span>
<span class="n">dispatch_suspend</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span>
<span class="c1">//恢复</span>
<span class="n">dispatch_resume</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">)</span>
</code></pre></div>
<p>暂停时,如果已经有block正在执行,那么不会对该block的执行产生影响。dispatch_suspend只会对还未开始执行的block产生影响。</p>
<h4>Dispatch Semaphore</h4>
<p>信号量在多线程开发中被广泛使用,当一个线程在进入一段关键代码之前,线程必须获取一个信号量,一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待前面的线程释放信号量。</p>
<p>信号量的具体做法是:当信号计数大于0时,每条进来的线程使计数减1,直到变为0,变为0后其他的线程将进不来,处于等待状态;执行完任务的线程释放信号,使计数加1,如此循环下去。</p>
<p>下面这个例子中使用了10条线程,但是同时只执行一条,其他的线程处于等待状态:</p>
<div class="highlight"><pre><code><span class="n">let</span> <span class="n">globalQueue</span> <span class="o">=</span> <span class="n">dispatch_get_global_queue</span><span class="p">(</span><span class="n">DISPATCH_QUEUE_PRIORITY_DEFAULT</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="n">let</span> <span class="n">semaphore</span> <span class="o">=</span> <span class="n">dispatch_semaphore_create</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="n">in</span> <span class="mi">0</span> <span class="p">...</span> <span class="mi">9</span> <span class="p">{</span>
<span class="n">dispatch_async</span><span class="p">(</span><span class="n">globalQueue</span><span class="p">,</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">dispatch_semaphore_wait</span><span class="p">(</span><span class="n">semaphore</span><span class="p">,</span> <span class="n">DISPATCH_TIME_FOREVER</span><span class="p">)</span>
<span class="n">let</span> <span class="n">time</span> <span class="o">=</span> <span class="n">dispatch_time</span><span class="p">(</span><span class="n">DISPATCH_TIME_NOW</span><span class="p">,</span> <span class="p">(</span><span class="n">Int64</span><span class="p">)(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">NSEC_PER_SEC</span><span class="p">))</span>
<span class="n">dispatch_after</span><span class="p">(</span><span class="n">time</span><span class="p">,</span> <span class="n">globalQueue</span><span class="p">)</span> <span class="p">{</span> <span class="p">()</span> <span class="o">-></span> <span class="n">Void</span> <span class="n">in</span>
<span class="n">print</span><span class="p">(</span><span class="s">"2秒后执行"</span><span class="p">)</span>
<span class="n">dispatch_semaphore_signal</span><span class="p">(</span><span class="n">semaphore</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
</code></pre></div>
<p>取得信号量的线程在2秒后释放了信息量,相当于是每2秒执行一次。</p>
<p>通过上面的例子可以看到,在GCD中,用dispatch_semaphore_create函数能初始化一个信号量,同时需要指定信号量的初始值;使用dispatch_semaphore_wait函数分配信号量并使计数减1,为0时处于等待状态;使用dispatch_semaphore_signal函数释放信号量,并使计数加1。</p>
<p>另外dispatch_semaphore_wait同样也支持超时,只需要给其第二个参数指定超时的时候即可,同Dispatch Group的dispatch_group_wait函数类似,可以通过返回值来判断。</p>
<p>这个函数也需要注意,如果是在OS X 10.8或iOS 6以及之后版本中使用,Dispatch Semaphore将会由ARC自动管理,如果是在此之前的版本,需要自己手动释放。</p>
<h4>dispatch_once</h4>
<p>dispatch_once函数通常用在单例模式上,它可以保证在程序运行期间某段代码只执行一次,如果我们要通过dispatch_once创建一个单例类,在Swift可以这样:</p>
<div class="highlight"><pre><code><span class="n">class</span> <span class="n">SingletonObject</span> <span class="p">{</span>
<span class="n">class</span> <span class="n">var</span> <span class="nl">sharedInstance</span> <span class="p">:</span> <span class="n">SingletonObject</span> <span class="p">{</span>
<span class="k">struct</span> <span class="n">Static</span> <span class="p">{</span>
<span class="k">static</span> <span class="n">var</span> <span class="nl">onceToken</span> <span class="p">:</span> <span class="kt">dispatch_once_t</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">static</span> <span class="n">var</span> <span class="nl">instance</span> <span class="p">:</span> <span class="n">SingletonObject</span><span class="o">?</span> <span class="o">=</span> <span class="n">nil</span>
<span class="p">}</span>
<span class="n">dispatch_once</span><span class="p">(</span><span class="o">&</span><span class="n">Static</span><span class="p">.</span><span class="n">onceToken</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Static</span><span class="p">.</span><span class="n">instance</span> <span class="o">=</span> <span class="n">SingletonObject</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">Static</span><span class="p">.</span><span class="n">instance</span><span class="o">!</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>这样就能通过GCD的安全机制保证这段代码只执行一次。</p>
Sublime Text相关
https://www.zruibin.cn/article/sublimetext_xiang_guan.html
2014-10-27 13:56
2015-10-13 17:45
<h3>sublime text2怎么修改字体</h3>
<p>选择Preferences下的Setings-User,将如下代码粘贴至大括号里面</p>
<div class="highlight"><pre><code><span class="s">"font_face"</span><span class="o">:</span> <span class="s">"Segoe UI Light"</span><span class="p">,</span>
<span class="s">"font_size"</span><span class="o">:</span> <span class="mf">14.5</span>
</code></pre></div>
<p>第一行意思是修改字体和加不加粗,带上bold代表加粗,去掉代表正常字体,第二行意思是修改字体大小。</p>
<p>然后保存即可,字体就会变了。</p>
<p>sublime text2 配置tab为4个空格</p>
<p>Preference-defalut:</p>
<p>【将Tab键自动替换为4个空格】</p>
<div class="highlight"><pre><code><span class="c1">// The number of spaces a tab is considered equal to</span>
<span class="err">“</span><span class="n">tab_size</span><span class="err">”</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="c1">// Set to true to insert spaces when tab is pressed</span>
<span class="err">“</span><span class="n">translate_tabs_to_spaces</span><span class="err">”</span><span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
</code></pre></div>
<p style="color:red;">/Data/Packages 下各个目录中都有.sublime-build文件可以参考,User目录则为处定义的。另,cmd中第一个参数可以是全路径,也可以是命令行中的命令,如可以是D:/gwin_x86/bin/gcc.exe或gcc,命令行中必须有添加才行,否则为全路径!</p><p><br/><hr><br/></p>
<p>1、安装包控制(Package Control)</p>
<p>打开Sublime Text 2,按快捷键 ctrl+` 或者点击 Tools → Command Palette 调出控制台Console;
将以下代码复制粘贴进命令行后回车:</p>
<div class="highlight"><pre><code><span class="n">import</span> <span class="n">urllib2</span><span class="p">,</span><span class="n">os</span><span class="p">;</span><span class="n">pf</span><span class="o">=</span><span class="err">'</span><span class="n">Package</span> <span class="n">Control</span><span class="p">.</span><span class="n">sublime</span><span class="o">-</span><span class="n">package</span><span class="err">'</span><span class="p">;</span><span class="n">ipp</span><span class="o">=</span><span class="n">sublime</span><span class="p">.</span><span class="n">installed_packages_path</span><span class="p">();</span><span class="n">os</span><span class="p">.</span><span class="n">makedirs</span><span class="p">(</span><span class="n">ipp</span><span class="p">)</span> <span class="k">if</span> <span class="n">not</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">exists</span><span class="p">(</span><span class="n">ipp</span><span class="p">)</span> <span class="k">else</span> <span class="n">None</span><span class="p">;</span><span class="n">open</span><span class="p">(</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">join</span><span class="p">(</span><span class="n">ipp</span><span class="p">,</span><span class="n">pf</span><span class="p">),</span><span class="err">'</span><span class="n">wb</span><span class="err">'</span><span class="p">).</span><span class="n">write</span><span class="p">(</span><span class="n">urllib2</span><span class="p">.</span><span class="n">urlopen</span><span class="p">(</span><span class="err">'</span><span class="nl">http</span><span class="p">:</span><span class="c1">//sublime.wbond.net/'+pf.replace(' ','%20')).read())</span>
</code></pre></div>
<p>重新启动Sublime Text 2,如果在Preferences → Package Settings 中看到 Package Control 这一项,就说明安装成功了。</p>
<p>2、安装Alignment插件</p>
<p>对于喜欢整齐的玛民来说,这不失为一个省事的插件。该插件可以通过上面安装好的 Package Control 来安装:
按ctrl + shift + P调出命令面板;
输入 install 调出 Package Control:Install Package 选项,并回车;
输入Alignment,选中并按回车安装;
重启Sublime Text 2,选中文本并按ctrl + alt + a 就可以进行对齐操作了。</p>
<p>3、安装 Soda 主题</p>
<p>这里的主题不同于针对代码的 color scheme,而是针对Sublime Text 2该软件本身的主题,该主题也可以通过万能的 Package Control 来安装。</p>
<p>按ctrl + shift + P调出命令面板;</p>
<p>输入 install 调出 Package Control:Install Package 选项,并回车;</p>
<p>输入 theme soda 选中后回车即可安装;</p>
<p>安装完之后要激活主题,打开 Preferences → Global Settings – User,加上以下代码保存即可生效:</p>
<div class="highlight"><pre><code><span class="s">"theme"</span><span class="o">:</span> <span class="s">"Soda Light.sublime-theme"</span> <span class="err">或者</span> <span class="s">"theme"</span> <span class="o">:</span> <span class="s">"Soda Dark.sublime-theme"</span>
</code></pre></div>
<p>4、安装cTags插件</p>
<p>首先,从Ctags官网下载压缩包下来,解压到电脑的某个地方,比如“C:\Program Files\ctags”,然后把cTags添加到系统变量里去:</p>
<p>在“我的电脑”右键属性 → 高级 → 环境变量 → 在“系统变量”里找到“Path”,点击“编辑” → 把“;C:\Program Files\ctags”(不包括双引号)复制到最后 → 最后一路“确定”保存。</p>
<p>然后通过 Package Control 来安装 cTags 插件:</p>
<p>按ctrl + shift + P调出命令面板;</p>
<p>输入 install 调出 Package Control:Install Package 选项,并回车;</p>
<p>输入 ctags 选中后回车即可安装。</p>
<p>安装完之后,在项目的当前目录下按ctrl + t, ctrl + r,会生成.tags的文件。当光标停留在某个函数上时,按快捷键 ctrl+t, ctrl+t就可以打开函数所在的文件,并跳转到相应的位置了。</p>
<p>PS:安装这个插件折腾了我蛮久,主要是不知道还要从ctags官网下载压缩包,以及修改系统的变量,后来还是一博友给我发的国外的参考资料才知道要这样配置的。刚开始知道这软件之所以没用是因为没有像eclipse可以追踪函数的功能,后来才知道可以通过安装cTags插件来实现。装上此功能后,就更喜欢用Sublime Text 2了。</p>
<p>5、jsFormat插件</p>
<p>格式化js:选中一段文本,control+alt+f。</p>
<p>6、DocBlockr</p>
<p>在JS函数上方输入/**,然后回车,doc就生成好了非常好用。</p>
<p>7、sublime-jslint</p>
<p>打开一个js文件,control+j,即可输出jsLint检查的结果。打开Packages目录,找到插件目录sublime-jslint,打开sublime-jslint.sublime-settings文件,可以修改jsLint配置,还可以配置文件保存时自动检查等,如:</p>
<p>{ // Path to the jslint jar. // Leave blank to use bundled jar. "jslint_jar": "", // Options pass to jslint. // Jerry Qu注:全部可用配置参考这里,<a href="https://github.com/fbzhong/sublime-jslint/wiki/Available-jslint4java-options">https://github.com/fbzhong/sublime-jslint/wiki/Available-jslint4java-options</a> "jslint_options": "--encoding utf-8 --bitwise --browser --cap --css --devel --debug --evil --forin --fragment --on --sub --white --windows --sloppy", // Ignore errors, regex. "ignore_errors": [ // "Expected an identifier and instead saw "undefined" (a reserved word)" ], // run jslint on save. "run_on_save": false, // debug flag. "debug":false }</p>
<p>8、SideBarEnhancements</p>
<p>推荐通过 Package Control 安装 SideBarEnhancements 这个插件,可以大大加强在侧栏目录树中右键的选项</p>
<p>9、Zen Coding</p>
<p>10、jQuery Package for sublime Text</p>
<p>11、Clipboard History</p>
<p>12、Bracket Highlighter</p>
<p>13、GBK to UTF8</p>
<p>14、Git</p>
<p><span style="color: rgb(255, 0, 0); font-size: 24px;">我用的插件</span></p><p><a href="https://github.com/kenwheeler/brogrammer-theme" target="blank">https://github.com/kenwheeler/brogrammer-theme</a></p>
<p>Package Control</p>
<p>CTags(函数跳转,要安装源码)</p>
<p>Alignment(对齐)</p>
<p>DocBlockr(注释,需在keys-User中改快捷键,内容复制Default的)</p>
<p>Sublime定位文件的功能:在已经打开的文件的代码中右键,点击Reveal in Side Bar</p>
<p><span style="color: rgb(255, 0, 0); font-size: 24px;">主题</span></p>
Brogrammer
<a href="https://github.com/kenwheeler/brogrammer-theme" target="blank">https://github.com/kenwheeler/brogrammer-theme</a>
安装方式按README来做即可
<p><span style="color: rgb(255, 0, 0); font-size: 24px;">我的通用设置</span></p><div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"color_scheme"</span><span class="o">:</span> <span class="s">"Packages/Theme - Brogrammer/brogrammer.tmTheme"</span><span class="p">,</span>
<span class="s">"font_face"</span><span class="o">:</span> <span class="s">"Segoe UI Light"</span><span class="p">,</span>
<span class="s">"font_size"</span><span class="o">:</span> <span class="mi">16</span><span class="p">,</span>
<span class="s">"ignored_packages"</span><span class="o">:</span>
<span class="p">[</span>
<span class="s">"Vintage"</span>
<span class="p">],</span>
<span class="s">"tab_size"</span><span class="o">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="s">"theme"</span><span class="o">:</span> <span class="s">"Brogrammer.sublime-theme"</span><span class="p">,</span>
<span class="s">"translate_tabs_to_spaces"</span><span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
<span class="s">"bold_folder_labels"</span><span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
<span class="s">"show_encoding"</span><span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
<span class="s">"highlight_line"</span><span class="o">:</span> <span class="nb">true</span>
<span class="p">}</span>
<span class="c1">// 字体大小</span>
<span class="s">"font_size"</span><span class="o">:</span> <span class="mi">17</span>
<span class="c1">// 高亮编辑中的那一行</span>
<span class="s">"highlight_line"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// 焦点丢失后自动保存</span>
<span class="s">"save_on_focus_lost"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// 显示当前文件的编码</span>
<span class="s">"show_encoding"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// 保存的时候把无用的空格去掉</span>
<span class="s">"trim_trailing_white_space_on_save"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// Tab转换</span>
<span class="s">"tab_size"</span><span class="o">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="s">"translate_tabs_to_spaces"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// 自动换行</span>
<span class="s">"word_wrap"</span><span class="o">:</span> <span class="nb">false</span>
<span class="c1">// 宽度指导线</span>
<span class="s">"rulers"</span><span class="o">:</span> <span class="p">[</span><span class="mi">80</span><span class="p">]</span>
<span class="c1">// 拼写检查</span>
<span class="s">"spell_check"</span><span class="o">:</span> <span class="nb">false</span>
<span class="c1">// 要不要滚过头</span>
<span class="s">"scroll_past_end"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// Vim模式</span>
<span class="s">"ignored_packages"</span><span class="o">:</span> <span class="p">[</span>
<span class="s">"Vintage"</span>
<span class="p">]</span>
<span class="c1">// 显示Tab、空格</span>
<span class="s">"draw_white_space"</span><span class="o">:</span> <span class="s">"all"</span>
<span class="c1">// 加粗文件夹名称</span>
<span class="s">"bold_folder_labels"</span><span class="o">:</span> <span class="nb">true</span>
<span class="c1">// 显示全路径</span>
<span class="s">"show_full_path"</span><span class="o">:</span> <span class="nb">true</span>
</code></pre></div>
<h3>自定义Build System</h3>
<div class="highlight"><pre><code><span class="c1">//mac node </span>
<span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"/usr/local/bin/node"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">,</span> <span class="s">"$file_base_name"</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"${project_path:${folder}}"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"*.js"</span>
<span class="p">}</span>
</code></pre></div>
<p><br/><hr><br/></p>
<h3>Sublime Text 2中的快捷键不完全翻译版本</h3>
<p><span style="color: rgb(255, 0, 0); font-family: "Microsoft Yahei"; font-size: 20px; line-height: 20px;"><br></span></p><table><tbody><tr><th>快捷键</th><th>功能</th></tr><tr><td>ctrl+shift+n</td><td>打开新Sublime</td></tr><tr><td>ctrl+shift+w</td><td>关闭Sublime,关闭所有打开文件</td></tr><tr><td>ctrl+shift+t</td><td>重新打开最近关闭文件</td></tr><tr><td>ctrl+n</td><td>新建文件</td></tr><tr><td>ctrl+s</td><td>保存</td></tr><tr><td>ctrl+shift+s</td><td>另存为</td></tr><tr><td>ctrl+f4</td><td>关闭文件</td></tr><tr><td>ctrl+w</td><td>关闭</td></tr><tr><td>ctrl+k, ctrl+b</td><td>切换侧边栏显示状态</td></tr><tr><td>f11</td><td>切换全屏状态</td></tr><tr><td>shift+f11</td><td>免打扰模式状态切换</td></tr><tr><td>backspace</td><td>删除左侧</td></tr><tr><td>shift+backspace</td><td>左侧删除</td></tr><tr><td>ctrl+shift+backspace</td><td>左侧全部删除</td></tr><tr><td>delete</td><td>右侧删除</td></tr><tr><td>enter</td><td>插入</td></tr><tr><td>shift+enter</td><td>插入</td></tr><tr><td>ctrl+z</td><td>撤消</td></tr><tr><td>ctrl+shift+z</td><td>重做</td></tr><tr><td>ctrl+y</td><td>重做或重复</td></tr><tr><td>ctrl+u</td><td>软撤消</td></tr><tr><td>ctrl+shift+u</td><td>软重做</td></tr><tr><td>ctrl+shift+v</td><td>粘贴并格式化</td></tr><tr><td>shift+delete</td><td>剪切</td></tr><tr><td>ctrl+insert</td><td>拷贝</td></tr><tr><td>shift+insert</td><td>粘贴</td></tr><tr><td>ctrl+x</td><td>剪切</td></tr><tr><td>ctrl+c</td><td>拷贝</td></tr><tr><td>ctrl+v</td><td>粘贴</td></tr><tr><td>left</td><td>移动</td></tr><tr><td>right</td><td>移动</td></tr><tr><td>up</td><td>移动</td></tr><tr><td>down</td><td>移动</td></tr><tr><td>shift+left</td><td>移动并选择</td></tr><tr><td>shift+right</td><td>移动并选择</td></tr><tr><td>shift+up</td><td>移动并选择</td></tr><tr><td>shift+down</td><td>移动并选择</td></tr><tr><td>ctrl+left</td><td>按\w规则移动(跳跃)</td></tr><tr><td>ctrl+right</td><td>按\w规则移动(跳跃)</td></tr><tr><td>ctrl+shift+left</td><td>按\w规则移动并选择(跳跃)</td></tr><tr><td>ctrl+shift+right</td><td>按\w规则移动并选择(跳跃)</td></tr><tr><td>alt+left</td><td>按单词移动</td></tr><tr><td>alt+right</td><td>按单词移动</td></tr><tr><td>alt+shift+left</td><td>按单词移动并选择</td></tr><tr><td>alt+shift+right</td><td>按单词移动并选择</td></tr><tr><td>ctrl+alt+up</td><td>选择多行进行编辑</td></tr><tr><td>ctrl+alt+down</td><td>选择多行进行编辑</td></tr><tr><td>pageup</td><td>移动</td></tr><tr><td>pagedown</td><td>移动</td></tr><tr><td>shift+pageup</td><td>移动+选择</td></tr><tr><td>shift+pagedown</td><td>移动+选择</td></tr><tr><td>home</td><td>移动到行首</td></tr><tr><td>end</td><td>移动到行尾</td></tr><tr><td>shift+home</td><td>选择到行首</td></tr><tr><td>shift+end</td><td>选择到行尾</td></tr><tr><td>ctrl+home</td><td>移动到页首行头</td></tr><tr><td>ctrl+end</td><td>移动到页尾行尾</td></tr><tr><td>ctrl+shift+home</td><td>选择到页首行头</td></tr><tr><td>ctrl+shift+end</td><td>选择到页尾行尾</td></tr><tr><td>ctrl+up</td><td>滚动行</td></tr><tr><td>ctrl+down</td><td>滚动行</td></tr><tr><td>ctrl+pagedown</td><td>下一视图(视觉位置)</td></tr><tr><td>ctrl+pageup</td><td>前一视图</td></tr><tr><td>ctrl+tab</td><td>栈中下一视图(打开顺序)</td></tr><tr><td>ctrl+shift+tab</td><td>栈中前一视图</td></tr><tr><td>ctrl+a</td><td>全选</td></tr><tr><td>ctrl+shift+l</td><td>选择多行编辑</td></tr><tr><td>escape</td><td>单个选择</td></tr><tr><td>escape</td><td>清除字段</td></tr><tr><td>escape</td><td>清除字段</td></tr><tr><td>escape</td><td>隐藏面板</td></tr><tr><td>escape</td><td>hide overlay</td></tr><tr><td>escape</td><td>hide auto complete</td></tr><tr><td>tab</td><td>insert best completion</td></tr><tr><td>tab</td><td>insert best completion</td></tr><tr><td>tab</td><td>replace completion with next completion</td></tr><tr><td>tab</td><td>reindent</td></tr><tr><td>tab</td><td>indent</td></tr><tr><td>tab</td><td>next field</td></tr><tr><td>tab</td><td>commit completion</td></tr><tr><td>shift+tab</td><td>insert</td></tr><tr><td>shift+tab</td><td>unindent</td></tr><tr><td>shift+tab</td><td>unindent</td></tr><tr><td>shift+tab</td><td>unindent</td></tr><tr><td>shift+tab</td><td>prev field</td></tr><tr><td>ctrl+]</td><td>缩进</td></tr><tr><td>ctrl+[</td><td>不缩进</td></tr><tr><td>insert</td><td>toggle overwrite</td></tr><tr><td>ctrl+l</td><td>选择行,重复可依次增加选择下一行</td></tr><tr><td>ctrl+d</td><td>选择单词,重复可增加选择下一个相同的单词</td></tr><tr><td>ctrl+k, ctrl+d</td><td>find under expand skip</td></tr><tr><td>ctrl+shift+space</td><td>expand selection</td></tr><tr><td>ctrl+shift+m</td><td>expand selection</td></tr><tr><td>ctrl+m</td><td>跳转到对应括号</td></tr><tr><td>ctrl+shift+j</td><td>expand selection</td></tr><tr><td>ctrl+shift+a</td><td>expand selection</td></tr><tr><td>alt+.</td><td>close tag</td></tr><tr><td>ctrl+q</td><td>toggle record macro</td></tr><tr><td>ctrl+shift+q</td><td>run macro</td></tr><tr><td>ctrl+enter</td><td>run macro file</td></tr><tr><td>ctrl+shift+enter</td><td>在当前行前插入新行</td></tr><tr><td>enter</td><td>commit completion</td></tr><tr><td><span style="color: rgb(255, 0, 0);">ctrl+p</span></td><td><span style="color: rgb(255, 0, 0);">搜索项目中的文件</span></td></tr><tr><td><span style="color: rgb(255, 0, 0);">ctrl+shift+p</span></td><td><span style="color: rgb(255, 0, 0);">打开命令面板</span></td></tr><tr><td>ctrl+alt+p</td><td>prompt select project</td></tr><tr><td><span style="color: rgb(255, 0, 0);">ctrl+r</span></td><td><span style="color: rgb(255, 0, 0);">前往Method</span></td></tr><tr><td><span style="color: rgb(255, 0, 0);">ctrl+g</span></td><td><span style="color: rgb(255, 0, 0);">跳转到第几行</span></td></tr><tr><td>ctrl+;</td><td>show overlay</td></tr><tr><td>ctrl+i</td><td>show panel</td></tr><tr><td>ctrl+shift+i</td><td>show panel</td></tr><tr><td>ctrl+f</td><td>查找</td></tr><tr><td>ctrl+h</td><td>查找替换</td></tr><tr><td>ctrl+shift+h</td><td>查找替换下一个</td></tr><tr><td>f3</td><td>下一个匹配项</td></tr><tr><td>shift+f3</td><td>上一个匹配项</td></tr><tr><td>ctrl+f3</td><td>下一个匹配项</td></tr><tr><td>ctrl+shift+f3</td><td>find under prev</td></tr><tr><td>alt+f3</td><td>find all under</td></tr><tr><td>ctrl+e</td><td>slurp find string</td></tr><tr><td>ctrl+shift+e</td><td>slurp replace string</td></tr><tr><td>ctrl+shift+f</td><td>show panel</td></tr><tr><td>f4</td><td>next result</td></tr><tr><td>shift+f4</td><td>prev result</td></tr><tr><td>f6</td><td>toggle setting</td></tr><tr><td>ctrl+f6</td><td>next misspelling</td></tr><tr><td>ctrl+shift+f6</td><td>prev misspelling</td></tr><tr><td>ctrl+shift+up</td><td>swap line up</td></tr><tr><td>ctrl+shift+down</td><td>swap line down</td></tr><tr><td>ctrl+backspace</td><td>delete word</td></tr><tr><td>ctrl+shift+backspace</td><td>run macro file</td></tr><tr><td>ctrl+delete</td><td>delete word</td></tr><tr><td>ctrl+shift+delete</td><td>run macro file</td></tr><tr><td>ctrl+/</td><td>当前行注释状态切换</td></tr><tr><td>ctrl+shift+/</td><td>当前位置注释状态切换</td></tr><tr><td>ctrl+j</td><td>选择标签内容,将后继行附加到行尾</td></tr><tr><td>ctrl+shift+d</td><td>duplicate line</td></tr><tr><td>ctrl+`</td><td>show panel</td></tr><tr><td>ctrl+space</td><td>auto complete</td></tr><tr><td>ctrl+space</td><td>replace completion with auto complete</td></tr><tr><td>ctrl+alt+shift+p</td><td>show scope name</td></tr><tr><td>f7</td><td>build</td></tr><tr><td>ctrl+b</td><td>build</td></tr><tr><td>ctrl+shift+b</td><td>build</td></tr><tr><td>ctrl+break</td><td>exec</td></tr><tr><td>ctrl+t</td><td>transpose</td></tr><tr><td>f9</td><td>行排序</td></tr><tr><td>ctrl+f9</td><td>行排序</td></tr><tr><td>// Auto-pair quotes</td><td><br></td></tr><tr><td>\</td><td>insert snippet</td></tr><tr><td>\</td><td>insert snippet</td></tr><tr><td>\</td><td>move</td></tr><tr><td>backspace</td><td>run macro file</td></tr><tr><td>// Auto-pair single quotes</td><td><br></td></tr><tr><td>"</td><td>insert snippet</td></tr><tr><td>"</td><td>insert snippet</td></tr><tr><td>"</td><td>move</td></tr><tr><td>backspace</td><td>run macro file</td></tr><tr><td>// Auto-pair brackets</td><td><br></td></tr><tr><td>(</td><td>insert snippet</td></tr><tr><td>(</td><td>insert snippet</td></tr><tr><td>)</td><td>move</td></tr><tr><td>backspace</td><td>run macro file</td></tr><tr><td>// Auto-pair square brackets</td><td><br></td></tr><tr><td>[</td><td>insert snippet</td></tr><tr><td>[</td><td>insert snippet</td></tr><tr><td>]</td><td>move</td></tr><tr><td>backspace</td><td>run macro file</td></tr><tr><td>// Auto-pair curly brackets</td><td><br></td></tr><tr><td>{</td><td>insert snippet</td></tr><tr><td>{</td><td>insert snippet</td></tr><tr><td>}</td><td>move</td></tr><tr><td>backspace</td><td>run macro file</td></tr><tr><td>enter</td><td>run macro file</td></tr><tr><td>shift+enter</td><td>run macro file</td></tr><tr><td>ctrl+1</td><td>focus group</td></tr><tr><td>ctrl+2</td><td>focus group</td></tr><tr><td>ctrl+3</td><td>focus group</td></tr><tr><td>ctrl+4</td><td>focus group</td></tr><tr><td>ctrl+shift+1</td><td>move to group</td></tr><tr><td>ctrl+shift+2</td><td>move to group</td></tr><tr><td>ctrl+shift+3</td><td>move to group</td></tr><tr><td>ctrl+shift+4</td><td>move to group</td></tr><tr><td>ctrl+0</td><td>focus side bar</td></tr><tr><td>alt+1</td><td>select by index</td></tr><tr><td>alt+2</td><td>select by index</td></tr><tr><td>alt+3</td><td>select by index</td></tr><tr><td>alt+4</td><td>select by index</td></tr><tr><td>alt+5</td><td>select by index</td></tr><tr><td>alt+6</td><td>select by index</td></tr><tr><td>alt+7</td><td>select by index</td></tr><tr><td>alt+8</td><td>select by index</td></tr><tr><td>alt+9</td><td>select by index</td></tr><tr><td>alt+0</td><td>select by index</td></tr><tr><td>f2</td><td>next bookmark</td></tr><tr><td>shift+f2</td><td>prev bookmark</td></tr><tr><td>ctrl+f2</td><td>标记状态切换</td></tr><tr><td>ctrl+shift+f2</td><td>clear bookmarks</td></tr><tr><td>alt+f2</td><td>select all bookmarks</td></tr><tr><td>ctrl+shift+k</td><td>run macro file</td></tr><tr><td>alt+q</td><td>wrap lines</td></tr><tr><td>ctrl+k, ctrl+u</td><td>upper case</td></tr><tr><td>ctrl+k, ctrl+l</td><td>lower case</td></tr><tr><td>ctrl+k, ctrl+space</td><td>set mark</td></tr><tr><td>ctrl+k, ctrl+a</td><td>select to mark</td></tr><tr><td>ctrl+k, ctrl+w</td><td>delete to mark</td></tr><tr><td>ctrl+k, ctrl+x</td><td>swap with mark</td></tr><tr><td>ctrl+k, ctrl+y</td><td>yank</td></tr><tr><td>ctrl+k, ctrl+k</td><td>run macro file</td></tr><tr><td>ctrl+k, ctrl+backspace</td><td>run macro file</td></tr><tr><td>ctrl+k, ctrl+g</td><td>clear bookmarks</td></tr><tr><td>ctrl+k, ctrl+c</td><td>show at center</td></tr><tr><td>ctrl++</td><td>increase font size</td></tr><tr><td>ctrl+=</td><td>increase font size</td></tr><tr><td>ctrl+keypad plus</td><td>increase font size</td></tr><tr><td>ctrl+-</td><td>decrease font size</td></tr><tr><td>ctrl+keypad minus</td><td>decrease font size</td></tr><tr><td>alt+shift+w</td><td>insert snippet</td></tr><tr><td>ctrl+shift+[</td><td>折叠(代码)</td></tr><tr><td>ctrl+shift+]</td><td>不折叠</td></tr><tr><td>ctrl+k, ctrl+1</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+2</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+3</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+4</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+5</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+6</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+7</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+8</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+9</td><td>按层级折叠(代码),数字是层级数</td></tr><tr><td>ctrl+k, ctrl+0</td><td>unfold all</td></tr><tr><td>ctrl+k, ctrl+j</td><td>unfold all</td></tr><tr><td>ctrl+k, ctrl+t</td><td>fold tag attributes</td></tr><tr><td>context menu</td><td>context menu</td></tr><tr><td>alt+c</td><td>toggle case sensitive</td></tr><tr><td>alt+r</td><td>toggle regex</td></tr><tr><td>alt+w</td><td>toggle whole word</td></tr><tr><td>alt+a</td><td>toggle preserve case</td></tr><tr><td>// 查找面板的按键绑定</td><td><br></td></tr><tr><td>enter</td><td>向后查找</td></tr><tr><td>shift+enter</td><td>向前查找</td></tr><tr><td>alt+enter</td><td>查找全部</td></tr><tr><td>// 替换面板的按键绑定</td><td><br></td></tr><tr><td>enter</td><td>查找下一个</td></tr><tr><td>shift+enter</td><td>查找前一个</td></tr><tr><td>alt+enter</td><td>查找全部</td></tr><tr><td>ctrl+alt+enter</td><td>替换全部</td></tr><tr><td>// Incremental find panel key bindings</td><td><br></td></tr><tr><td>enter</td><td>hide panel</td></tr><tr><td>shift+enter</td><td>find prev</td></tr><tr><td>alt+enter</td><td>find all</td></tr></tbody></table>
<br/><hr><br/>
Build Systems
Build systems run external programs to process your project’s files and print captured output to the output panel. Ultimately, they call subprocess.Popen.
File Format
Build systems use JSON. Here’s an example build system:
```
{
"cmd": ["python", "-u", "$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python"
}
```
<h2>Options<a href="http://sublimetext.info/docs/en/reference/build_systems.html#options" rel="nofollow">¶</a></h2><ul><li><p><span style="background-color: transparent; font-weight: bold;">cmd</span></p></li><li><p style="line-height: 1.4em; margin-top: 0px !important;">Array containing the command to run and its desired arguments.</p><p style="line-height: 1.4em; margin-top: 0px; margin-bottom: 0px; font-weight: bold; padding: 0px;">Note</p><p style="line-height: 1.4em; margin-top: 0px; margin-bottom: 0px; padding: 0px;">Under Windows, GUIs are supressed.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">file_regex</span></p></li><li><p>Optional. Regular expression to capture error output of<span style="background-color: transparent;">cmd</span>.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">line_regex</span></p></li><li><p>Optional. If the<span style="background-color: transparent;">file_regex</span>doesn’t match on the current line, but there’s a<span style="background-color: transparent;">line_regex</span>specified, and it does match the current line, then walk backwards through the buffer until a line matching the file regex is found: use these two matches to determine the file and line to go to.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">selector</span></p></li><li><p>Optional. Used when<strong>Tools | Build System | Automatic</strong>is set to<span style="background-color: transparent;">true</span>. Sublime Text uses this scope selector to find the appropriate build system for the active view.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">working_dir</span></p></li><li><p>Optional. Directory to change the current directory to before running<span style="background-color: transparent;">cmd</span>.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">encoding</span></p></li><li><p>Optional. Output encoding of<span style="background-color: transparent;">cmd</span>. Must be a valid python encoding. Defaults to<span style="background-color: transparent;">utf-8</span>.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">target</span></p></li><li><p>Optional. Sublime Text command to run. Defaults to<span style="background-color: transparent;">exec</span>(<span style="background-color: transparent;">Packages/Default/exec.py</span>).</p></li><li><p><span style="background-color: transparent; font-weight: bold;">env</span></p></li><li><p>Optional. Dictionary of environment variables to be merged with the current process’ that will be passed to<span style="background-color: transparent;">cmd</span>.</p></li><li><p><span style="background-color: transparent; font-weight: bold;">shell</span></p></li><li><p>Optional. If<span style="background-color: transparent;">true</span>,<span style="background-color: transparent;">cmd</span>will be run through the shell (<span style="background-color: transparent;">cmd.exe</span>,<a href="http://sublimetext.info/docs/en/reference/build_systems.html#id1" rel="nofollow">``</a>bash``…).</p></li><li><p><span style="background-color: transparent; font-weight: bold;">path</span></p></li><li><p>Optional. This string will replace the current process’<span style="background-color: transparent;">PATH</span>before calling<span style="background-color: transparent;">cmd</span>. The old<span style="background-color: transparent;">PATH</span>value will be restored after that.</p></li></ul><h3>Capturing Error Output with<span style="background-color: transparent;">file_regex</span><a href="http://sublimetext.info/docs/en/reference/build_systems.html#capturing-error-output-with-file-regex" rel="nofollow"></a></h3><p style="line-height: 1.4em;">The<span style="background-color: transparent;">file_regex</span>option uses a Perl-style regular expression to capture up to four fields of error information from the build program’s output, namely:<em>file name</em>,<em>line number</em>,<em>column number</em>and<em>error message</em>. Use groups in the pattern to capture this information. The<em>file name</em>field and the<em>line number</em>field are required.</p><p style="line-height: 1.4em;">When error information is captured, you can navigate to error instances in your project’s files with<span style="background-color: transparent;">F4</span>and<span style="background-color: transparent;">Shift+F4</span>. If available, the captured<em>error message</em>will be displayed in the status bar.</p><h3>Platform-specific Options<a href="http://sublimetext.info/docs/en/reference/build_systems.html#platform-specific-options" rel="nofollow"></a></h3><p style="line-height: 1.4em;"><span style="background-color: transparent;">windows</span>,<span style="background-color: transparent;">osx</span>and<span style="background-color: transparent;">linux</span>are additional options which override any build system options for the corresponding platform only. Here’s an example:</p><div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"ant"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^ *</span><span class="se">\\</span><span class="s">[javac</span><span class="se">\\</span><span class="s">] (.+):([0-9]+):() (.*)$"</span><span class="p">,</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"${project_path:${folder}}"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.java"</span><span class="p">,</span>
<span class="s">"windows"</span><span class="o">:</span>
<span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"ant.bat"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>In this case, ant.bat will be executed under Windows, while for the other platforms ant will be used instead.</p>
<h4>Variables</h4>
<div class="highlight"><pre><code><span class="err">$</span><span class="n">file</span> <span class="n">The</span> <span class="n">full</span> <span class="n">path</span> <span class="n">to</span> <span class="n">the</span> <span class="n">current</span> <span class="n">file</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span> <span class="n">g</span><span class="p">.,</span> <span class="nl">C</span><span class="p">:</span><span class="err">\</span><span class="n">Files</span><span class="err">\</span><span class="n">Chapter1</span><span class="p">.</span><span class="n">txt</span><span class="p">.</span>
<span class="err">$</span><span class="n">file_path</span> <span class="n">The</span> <span class="n">directory</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">file</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span> <span class="n">g</span><span class="p">.,</span> <span class="nl">C</span><span class="p">:</span><span class="err">\</span><span class="n">Files</span><span class="p">.</span>
<span class="err">$</span><span class="n">file_name</span> <span class="n">The</span> <span class="n">name</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">file</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span> <span class="n">g</span><span class="p">.,</span> <span class="n">Chapter1</span><span class="p">.</span><span class="n">txt</span><span class="p">.</span>
<span class="err">$</span><span class="n">file_extension</span> <span class="n">The</span> <span class="n">extension</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">file</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span> <span class="n">g</span><span class="p">.,</span> <span class="n">txt</span><span class="p">.</span>
<span class="err">$</span><span class="n">file_base_name</span> <span class="n">The</span> <span class="n">name</span> <span class="n">only</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">file</span><span class="p">,</span> <span class="n">e</span><span class="p">.</span> <span class="n">g</span><span class="p">.,</span> <span class="n">Document</span><span class="p">.</span>
<span class="err">$</span><span class="n">packages</span> <span class="n">The</span> <span class="n">full</span> <span class="n">path</span> <span class="n">to</span> <span class="n">the</span> <span class="n">Packages</span> <span class="n">folder</span><span class="p">.</span>
<span class="err">$</span><span class="n">project</span> <span class="n">The</span> <span class="n">full</span> <span class="n">path</span> <span class="n">to</span> <span class="n">the</span> <span class="n">current</span> <span class="n">project</span> <span class="n">file</span><span class="p">.</span>
<span class="err">$</span><span class="n">project_path</span> <span class="n">The</span> <span class="n">directory</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">project</span> <span class="n">file</span><span class="p">.</span>
<span class="err">$</span><span class="n">project_name</span> <span class="n">The</span> <span class="n">name</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">project</span> <span class="n">file</span><span class="p">.</span>
<span class="err">$</span><span class="n">project_extension</span> <span class="n">The</span> <span class="n">extension</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">project</span> <span class="n">file</span><span class="p">.</span>
<span class="err">$</span><span class="n">project_base_name</span> <span class="n">The</span> <span class="n">name</span> <span class="n">only</span> <span class="n">portion</span> <span class="n">of</span> <span class="n">the</span> <span class="n">current</span> <span class="n">project</span> <span class="n">file</span><span class="p">.</span>
</code></pre></div>
<p>Place Holders for Variables¶</p>
<p>Snippet style formatting can be used with these variables, for example:</p>
<p>${project_name:Default}
This will emit the name of the current project if there is one, otherwise Default.</p>
<p>${file/.php/.txt/}
This will emit the full path of the current file, replacing .php with .txt.</p>
<p>Running Build Systems</p>
<p>Select Tools | Build in the Sublime Text menu or press F7.</p>
<p>Troubleshooting Build Systems</p>
<p>External programs used in build systems need to be in your PATH. As a quick test, you can try to run them from the command line first and see whether they work. However, note that your shell’s PATH variable might differ to that seen by Sublime Text due to your shell’s profile. Also, note that you can use the path option in a .build-system to specify additional directories toPATH.</p>
<p>Sublime Text is currently the text editor of choice for a number of developers in the open-source community. It’s sophisticated, has powerful text selection and customization support and also includes a feature not used by many – its build system. In this post, I’d like to take you through the Sublime build system and share build scripts for working with many of the languages and tools we use today.</p>
<p>These will include scripts for Grunt, CoffeeScript, SASS and others.</p>
<h3>Introduction</h3>
<p>Sublime Text build systems can be considered simplistic, but highly customizable. The basic idea is that each type of Build profile is powered by a “.sublime-build” file – a JSON representations of the commands, paths and configuration needed to build a project using a specific tool or set of tools.</p>
<p>Builds can be executed using a keyboard shortcut (Command+B on Mac is the default on Mac or F7 on Windows), via the Tools menu or when a file is saved. If a project is currently open, the build system we last selected (e.g grunt) will be remembered.</p>
<p><img alt="" height="273" src="http://static.oschina.net/uploads/img/201410/27140446_OMed.png" title="Screen Shot 2012-08-17 at 2.33.01 PM" width="510" style="cursor: pointer;"></p>
<p>When Sublime is passed references to external tools/binaries via a “.sublime-build” files, it can execute these applications with any arguments or flags that may be necessary. It is also able to pipe back the output of calling any of these apps using the built-in console in Sublime. Effectively this allows us to easily build projects without the need to leave our editor.</p>
<p><img alt="" height="214" src="http://static.oschina.net/uploads/img/201410/27140447_Jai1.png" title="Screen Shot 2012-08-17 at 2.36.42 PM" width="510" style="cursor: pointer;"></p>
<h4>Adding a custom Build System</h4>
<p>Sublime populates its Tools/Build System menu based on the “.sublime-build” files stored in the Sublime “Packages” directory. Should one need to locate this, it can be found in “~/Library/Application Support/Sublime Text 2/Packages/User” (if using OS X) or the corresponding Packages/User directory on other platforms.</p>
<p><img alt="" height="298" src="http://static.oschina.net/uploads/img/201410/27140448_RbDm.png" title="Screen Shot 2012-08-17 at 2.36.08 PM" width="510" style="cursor: pointer;"></p>
<p>A basic “.sublime-build” file could be represented in key/value form as follows:</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"command"</span><span class="p">,</span> <span class="s">"argument"</span><span class="p">,</span> <span class="s">"--flag"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.js"</span><span class="p">],</span>
<span class="s">"path"</span><span class="o">:</span> <span class="s">"/usr/local/bin"</span><span class="p">,</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"/projects/"</span>
<span class="p">}</span>
</code></pre></div>
<p>Keys supported include:</p>
<p>cmd - An array containing a command to run and its desired arguments and flags. Note that Sublime will search your PATH for any tools listed unless an absolute path has been used to point to them.</p>
<p>selector – An optional string used to locate the best builder to use for the current file scope. This is only relevant if Tools/Build System/Automatic is true.</p>
<p>path – An optional string that replaces your current process’s PATH before calling the commands listed.</p>
<p>working_dir – An optional string defining a directory to switch the current directory to prior to calling any commands.</p>
<p>shell - An optional boolean that defines whether commands should be run through the shell (e.g bash).</p>
<p>file_regex – An optional regular expression used to capture error output from commands.</p>
<p>For a comprehensive list of keys supported in Sublime build scripts, see the unofficial docs.</p>
<p>Build Variables:</p>
<p>In addition, Sublime supports variable substitutions in build files such as $file_path (for the path to the current file) and more. These include:</p>
<p>$file_path – the directory of the current file being viewed</p>
<p>$file_name - only the name portion of the current file (extension included)</p>
<p>$file_base_name - the name portion of the current file (extension excluded)</p>
<p>$project_path - the directory path to the current project</p>
<p>$project_name – the name portion of the current project</p>
<p>A complete list of substitutions supported is also available.</p>
<p>Grouping build tasks</p>
<p>Some developers also like to group together tasks within an external bash script (or equivalent). For example, here’s a simple git-ftp deploy script you can use with Sublime to commit and push your latest changes with git and then upload your latest files to FTP.</p>
<p>Example: Commit, Push And Upload To FTP</p>
<p>deployment.sh:</p>
<h1>!/bin/bash</h1>
<p>git add . && git commit -m "deployment" && git push && git ftp init -u username -p password - ftp://host.example.com/public_html
deployment.sublime-build:</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"deployment"</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"${project_path:${folder}}"</span>
<span class="p">}</span>
</code></pre></div>
<p>If you haven’t used git-ftp before, Alex Fluger has a solid article about using it that may be of interest.</p>
<p>Targeting Platforms:</p>
<p>Sublime build files also support specifying configuration data for specific platforms (namely, OS X, Windows and Linux). Targeting a platform can easily be done by specifying another element in our config with the name of the platform. e.g</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">...</span>
<span class="p">...</span>
<span class="s">"windows"</span><span class="o">:</span>
<span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">...</span>
<span class="p">},</span>
<span class="s">"osx"</span><span class="o">:</span>
<span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">...</span>
<span class="p">},</span>
<span class="s">"linux"</span><span class="o">:</span>
<span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Build files for popular front-end tools</p>
<p>To help you get started, I’ve written a collection of “.sublime-build” files for some of the front-end tools I’m aware web developers are using these days below.</p>
<p>Most of these will function fine without the need to specify path, but if you run into an issue with paths, try including it to your config (e.g "path": "/usr/local/bin").</p>
<h4>grunt:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"grunt"</span><span class="p">,</span> <span class="s">"--no-color"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.js"</span><span class="p">,</span> <span class="s">"source.less"</span><span class="p">,</span> <span class="s">"source.json"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<p>Node Build Script:</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"h5bp"</span><span class="p">,</span> <span class="s">"--no-color"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.js"</span><span class="p">,</span> <span class="s">"source.less"</span><span class="p">,</span> <span class="s">"source.json"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h4>CoffeeScript:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"coffee"</span><span class="p">,</span><span class="s">"-c"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"selector"</span> <span class="o">:</span> <span class="s">"source.coffee"</span>
<span class="p">}</span>
</code></pre></div>
<h4>SASS:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"sass"</span><span class="p">,</span> <span class="s">"--watch"</span><span class="p">,</span> <span class="s">".:."</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"$file_path"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.scss"</span><span class="p">,</span> <span class="s">"source.sass"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<p>Whilst a more verbose version with automatic minification and watch config could be written:</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"sass"</span><span class="p">,</span> <span class="s">"--watch"</span><span class="p">,</span> <span class="s">"sass:stylesheets"</span><span class="p">,</span> <span class="s">"--style"</span><span class="p">,</span> <span class="s">"compressed"</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"$project_path"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.scss"</span><span class="p">,</span> <span class="s">"source.sass"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h4>LESS:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"lessc"</span><span class="p">,</span> <span class="s">"-x"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">,</span> <span class="s">"$file_path/$file_base_name.css"</span><span class="p">,</span> <span class="s">"--verbose"</span><span class="p">],</span>
<span class="s">"shell"</span> <span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.css.less"</span>
<span class="p">}</span>
</code></pre></div>
<h4>Stylus:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"stylus"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"."</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.stylus"</span>
<span class="p">}</span>
</code></pre></div>
<p>(a more comprehensive version of this can be found in the LESS-build-sublimeproject.)</p>
<h4>Jade:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"cmd"</span><span class="p">,</span> <span class="s">"/c"</span><span class="p">,</span> <span class="s">"jade"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.jade"</span>
<span class="p">}</span>
</code></pre></div>
<h4>r.js (RequireJS Optimizer):</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"node"</span><span class="p">,</span> <span class="s">"r.js"</span><span class="p">,</span> <span class="s">"-o"</span><span class="p">,</span> <span class="s">"app.build.js"</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"$project_path"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.js"</span>
<span class="p">}</span>
</code></pre></div>
<h4>UglifyJS:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span> <span class="s">"node"</span><span class="p">,</span> <span class="s">"uglifyjs"</span><span class="p">,</span> <span class="s">"-o"</span><span class="p">,</span> <span class="s">"${file_path}/${file_base_name}.min.js"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.js"</span>
<span class="p">}</span>
</code></pre></div>
<h4>Node (just passing in directly):</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"node"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^[ ]*File </span><span class="se">\"</span><span class="s">(...*?)</span><span class="se">\"</span><span class="s">, line ([0-9]*)"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.js"</span>
<span class="p">}</span>
</code></pre></div>
<h4>Pandoc (Markdown to HTML):</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"pandoc"</span><span class="p">,</span> <span class="s">"-S"</span><span class="p">,</span> <span class="s">"-s"</span><span class="p">,</span> <span class="s">"-f"</span><span class="p">,</span> <span class="s">"markdown"</span><span class="p">,</span> <span class="s">"-t"</span><span class="p">,</span> <span class="s">"html"</span><span class="p">,</span> <span class="s">"-o"</span><span class="p">,</span> <span class="s">"$file_base_name.html"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"text.html.markdown"</span>
<span class="p">}</span>
</code></pre></div>
<p>(and when it’s released, Yeoman):</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"yeoman"</span><span class="p">,</span> <span class="s">"build"</span><span class="p">,</span> <span class="s">"--no-color"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="p">[</span><span class="s">"source.js"</span><span class="p">,</span> <span class="s">"source.scss"</span><span class="p">,</span> <span class="s">"source.sass"</span><span class="p">,</span> <span class="s">"source.html"</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h4>JSHint:</h4>
<p>I imagine most web developers would want to run JSHint from within a broader build process, but if you’d also like to run it standalone via a Sublime build file, the sublime-jshint package has a build file that will work fine on both OS X and Windows.</p>
<p>Build files for specific programming languages</p>
<p>I also thought that while we were looking at build files, it would be useful to demonstrate how these can be used to build/compile with some popular programming languages. These may differ to those included with Sublime by default, but are useful for reference:</p>
<h4>Ruby (using RVM):</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"~/.rvm/bin/rvm-auto-ruby"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^(...*?):([0-9]*):?([0-9]*)"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.ruby"</span>
<span class="p">}</span>
</code></pre></div>
<h4>Python:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"python"</span><span class="p">,</span> <span class="s">"-u"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^[ ]*File </span><span class="se">\"</span><span class="s">(...*?)</span><span class="se">\"</span><span class="s">, line ([0-9]*)"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.python"</span>
<span class="p">}</span>
</code></pre></div>
<h4>PHP:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"/usr/bin/php"</span><span class="p">,</span> <span class="s">"-l"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span> <span class="o"><-</span> <span class="n">Couldn</span><span class="err">'</span><span class="n">t</span> <span class="n">just</span> <span class="n">use</span> <span class="s">"php"</span> <span class="o">?</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^Parse error: .* in (.*?) on line ([0-9]*)"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.php"</span>
<span class="p">}</span>
</code></pre></div>
<h4>Java:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"javac"</span><span class="p">,</span> <span class="s">"$file_name"</span><span class="p">,</span> <span class="s">"&&"</span><span class="p">,</span> <span class="s">"java"</span><span class="p">,</span> <span class="s">"$file_base_name"</span><span class="p">],</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"${project_path:${folder}}"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.java"</span><span class="p">,</span>
<span class="s">"shell"</span><span class="o">:</span> <span class="nb">true</span>
<span class="p">}</span>
</code></pre></div>
<h4>.Net (Windows):</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"%WINDIR%</span><span class="se">\\</span><span class="s">Microsoft.NET</span><span class="se">\\</span><span class="s">Framework</span><span class="se">\\</span><span class="s">v4.0.30319</span><span class="se">\\</span><span class="s">msbuild"</span><span class="p">,</span> <span class="s">"${project_base_name}.sln"</span><span class="p">],</span>
<span class="s">"shell"</span><span class="o">:</span> <span class="nb">true</span><span class="p">,</span>
<span class="s">"working_dir"</span><span class="o">:</span> <span class="s">"${project_path:${folder}}"</span>
<span class="p">}</span>
</code></pre></div>
<h4>C:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"make && ./a.out"</span><span class="p">],</span>
<span class="s">"path"</span><span class="o">:</span> <span class="s">"/usr/bin:/usr/local/bin:..."</span><span class="p">,</span>
<span class="s">"shell"</span><span class="o">:</span> <span class="nb">true</span>
<span class="p">}</span>
</code></pre></div>
<h4>C++ (via g++):</h4>
<p>(Note that we’re also able to specify OS-specific configurations too, as in the below):</p>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"g++"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">,</span> <span class="s">"-o"</span><span class="p">,</span> <span class="s">"$file_base_name"</span><span class="p">,</span> <span class="s">"-I/usr/local/include"</span><span class="p">],</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.c++"</span><span class="p">,</span>
<span class="s">"windows"</span><span class="o">:</span> <span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"cl"</span><span class="p">,</span> <span class="s">"/Fo${file_path}"</span><span class="p">,</span> <span class="s">"/O2"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">]</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h4>Haskell:</h4>
<div class="highlight"><pre><code><span class="p">{</span>
<span class="s">"cmd"</span><span class="o">:</span> <span class="p">[</span><span class="s">"runhaskell"</span><span class="p">,</span> <span class="s">"$file"</span><span class="p">],</span>
<span class="s">"file_regex"</span><span class="o">:</span> <span class="s">"^(...*?):([0-9]*):?([0-9]*)"</span><span class="p">,</span>
<span class="s">"selector"</span><span class="o">:</span> <span class="s">"source.haskell"</span>
<span class="p">}</span>
</code></pre></div>
<p>Conclusions</p>
<p>Sublime build systems are awesome and can help you avoid the need to manually switch between your editor and external build tools regularly. As you’ve hopefully now learned, putting together your own custom build systems is a straight-forward process and I’d recommend trying it out if Sublime happens to be your editor of choice.</p>
Lua Install All Platform
https://www.zruibin.cn/article/luainstallallplatform.html
2014-10-23 18:40
2015-10-13 16:29
<p><a href="http://www.lua.org/" target="blank">Lua</a> is implemented in pure ANSI C and compiles unmodified in all platforms that have an ANSI C compiler. Lua also compiles cleanly as C++.</p>
<p>Lua is very easy to build and install. There are detailed instructions in the package but here is a simple terminal session that downloads the current release of Lua and builds it in Linux:</p>
<div class="highlight"><pre><code><span class="n">curl</span> <span class="o">-</span><span class="n">R</span> <span class="o">-</span><span class="n">O</span> <span class="nl">http</span><span class="p">:</span><span class="c1">//www.lua.org/ftp/lua-5.2.3.tar.gz</span>
<span class="n">tar</span> <span class="n">zxf</span> <span class="n">lua</span><span class="o">-</span><span class="mf">5.2.3</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span>
<span class="n">cd</span> <span class="n">lua</span><span class="o">-</span><span class="mf">5.2.3</span>
<span class="n">make</span> <span class="n">linux</span> <span class="n">test</span>
</code></pre></div>
<p>For Mac OS X, use make macosx test.</p>
<hr>
<p>lua是一个脚本语言,它的编译器非常简单。一般而言,lua在游戏里面使用得比较多。它可以通过类似于脚本的形式把函数的功能串行起来,实现很多不可思议的效果。现在关于lua的资料比较少,主要有两个文档可以介绍一下。一个是云风翻译的lua手册,另外一本就是lua作者编写的《Programming in lua》。可是很多朋友看完了这两本资料之后还是不太清楚该怎么使用。今天乘着有空,可以把自己的一些使用经验来写一写。 我们可以把lua看成是lib库,在使用的时候把这个lib添加到自己的工程里面就可以了。这里介绍的方法是windows编译lua的方法,如果是linux系统请参考其他文档。</p>
<p>(1)下载lua工程,下载地址为<a href="http://www.lua.org/ftp/,可以随便挑选一个版本即可;">http://www.lua.org/ftp/,可以随便挑选一个版本即可;</a></p>
<p>(2)利用vs2005创建一个solution;</p>
<p>(3)在solution中创建两个工程,一个是lualib,一个是lua;</p>
<p>(4)将下载的lua工程解压,同时把src/下面的代码添加到lualib工程中;</p>
<p>(5)将lua.c中的main函数修改为lua_main,将luac.c中的main函数修改为luac_main;</p>
<p>(6)编译lualib工程生成lualib.lib;</p>
<p>(7)在lua工程中添加code.c,同时修改include dir、lib dir,同时在Additional Dependencies中添加lualib.lib;</p>
<p>(8)在code.c中添加代码,内容如下,</p>
<div class="highlight"><pre><code><span class="cp">#include <stdio.h></span>
<span class="cp">#include "lua.h"</span>
<span class="cp">#include "lualib.h"</span>
<span class="cp">#include "lauxlib.h"</span>
<span class="cp">#include "luaconf.h"</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
<span class="n">lua_State</span><span class="o">*</span> <span class="n">L</span> <span class="o">=</span> <span class="n">luaL_newstate</span><span class="p">();</span>
<span class="n">luaL_openlibs</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="n">luaL_dofile</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">"C:/Documents and Settings/fxx/桌面/lua/debug/test.lua"</span><span class="p">);</span>
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>(9)此时,我们还需要创建test.lua文件,内容如下</p>
<div class="highlight"><pre><code><span class="k">function</span> <span class="nf">show</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">b</span> <span class="o">=</span> <span class="p">{}</span>
<span class="kd">local</span> <span class="n">index</span>
<span class="k">for</span> <span class="n">index</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">1</span> <span class="k">do</span>
<span class="nb">print</span><span class="p">(</span><span class="n">index</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">show</span><span class="p">()</span>
</code></pre></div>
<p>(10)编译lua工程,ctrl+F5运行,如果你此时看到了10个打印的数字,那说明lua编译成功了。</p>
<hr>
<p>1、首先安装lua</p>
<p>linux系统</p>
<p>make linux</p>
<p>make install</p>
<p>2、编译</p>
<div class="highlight"><pre><code><span class="n">gcc</span> <span class="o">-</span><span class="n">lm</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">test</span> <span class="n">test</span><span class="p">.</span><span class="n">c</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span> <span class="o">-</span><span class="n">ldl</span>
</code></pre></div>
<p>如果少-ldl,那么编译就会报:</p>
<div class="highlight"><pre><code><span class="n">gcc</span> <span class="o">-</span><span class="n">lm</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">test</span> <span class="n">test</span><span class="p">.</span><span class="n">c</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span>
</code></pre></div>
<div class="highlight"><pre><code><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">loadlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">gctm</span><span class="err">'</span><span class="o">:</span>
<span class="n">loadlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x35</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">dlclose</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">loadlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">ll_loadfunc</span><span class="err">'</span><span class="o">:</span>
<span class="n">loadlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0xc0</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">dlopen</span><span class="err">'</span>
<span class="n">loadlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0xfc</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">dlsym</span><span class="err">'</span>
<span class="n">loadlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x198</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">dlerror</span><span class="err">'</span>
<span class="n">loadlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x1bb</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">dlerror</span><span class="err">'</span>
</code></pre></div>
<p>如果少liblua.a ,就会报如下问题:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">wangbin</span><span class="err">@</span><span class="n">tuan</span> <span class="n">lua</span><span class="p">]</span><span class="err">$</span> <span class="n">gcc</span> <span class="o">-</span><span class="n">lm</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">test</span> <span class="n">test</span><span class="p">.</span><span class="n">c</span> <span class="o">-</span><span class="n">ldl</span>
<span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">ccCT0d24</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">main</span><span class="err">'</span><span class="o">:</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">26</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">luaL_newstate</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">27</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">luaL_openlibs</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">29</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">luaL_loadbufferx</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">29</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">lua_pcallk</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">32</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">lua_tolstring</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">33</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">lua_settop</span><span class="err">'</span>
<span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">wangbin</span><span class="o">/</span><span class="n">work</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">lua</span><span class="o">/</span><span class="n">test</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">36</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">lua_close</span><span class="err">'</span>
<span class="nl">collect2</span><span class="p">:</span> <span class="n">ld</span> <span class="n">returned</span> <span class="mi">1</span> <span class="n">exit</span> <span class="n">status</span>
</code></pre></div>
<p>如果少-lm,那么编译结果如下:</p>
<div class="highlight"><pre><code><span class="p">[</span><span class="n">wangbin</span><span class="err">@</span><span class="n">tuan</span> <span class="n">lua</span><span class="p">]</span><span class="err">$</span> <span class="n">gcc</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">o</span> <span class="n">test</span> <span class="n">test</span><span class="p">.</span><span class="n">c</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span> <span class="o">-</span><span class="n">ldl</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lobject</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">luaO_arith</span><span class="err">'</span><span class="o">:</span>
<span class="n">lobject</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0xdf</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">pow</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lvm</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">luaV_execute</span><span class="err">'</span><span class="o">:</span>
<span class="n">lvm</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x159a</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">pow</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_sin</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x3e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">sin</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_sinh</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x6e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">sinh</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_cos</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x9e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">cos</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_cosh</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0xce</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">cosh</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_tan</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0xfe</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">tan</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_tanh</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x12e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">tanh</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_asin</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x15e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">asin</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_acos</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x18e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">acos</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_atan</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x1be</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">atan</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_atan2</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x1fb</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">atan2</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_fmod</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x2db</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">fmod</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_sqrt</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x391</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">sqrt</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_pow</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x3d3</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">pow</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_log</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x450</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">log</span><span class="err">'</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x460</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">log</span><span class="err">'</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x486</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">log10</span><span class="err">'</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x4aa</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">log</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_log10</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x4de</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">log10</span><span class="err">'</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">liblua</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">lmathlib</span><span class="p">.</span><span class="n">o</span><span class="p">)</span><span class="o">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">`</span><span class="n">math_exp</span><span class="err">'</span><span class="o">:</span>
<span class="n">lmathlib</span><span class="p">.</span><span class="nl">c</span><span class="p">:(.</span><span class="n">text</span><span class="o">+</span><span class="mh">0x50e</span><span class="p">)</span><span class="o">:</span> <span class="n">undefined</span> <span class="n">reference</span> <span class="n">to</span> <span class="err">`</span><span class="n">exp</span><span class="err">'</span>
<span class="nl">collect2</span><span class="p">:</span> <span class="n">ld</span> <span class="n">returned</span> <span class="mi">1</span> <span class="n">exit</span> <span class="n">status</span>
</code></pre></div>
<p>在Ubuntu 14.10下安装Lua 5.2出错的解决</p>
<p>系统环境为 Ubuntu 14.10,下载Lua安装文件。此处下载的版本为 Lua 5.2.3。</p>
<p>将Lua源代码进行解压:</p>
<div class="highlight"><pre><code><span class="n">tar</span> <span class="n">xzvf</span> <span class="n">lua</span><span class="o">-</span><span class="mf">5.2.3</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span>
<span class="n">cd</span> <span class="n">lua</span><span class="o">-</span><span class="mf">5.2.3</span>
</code></pre></div>
<p>使用下面的命令进行编译测试:</p>
<div class="highlight"><pre><code><span class="n">make</span> <span class="n">linux</span> <span class="n">test</span>
</code></pre></div>
<p>如果遇到以下错误信息:</p>
<div class="highlight"><pre><code><span class="n">lua</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">67</span><span class="o">:</span><span class="mi">31</span><span class="o">:</span> <span class="n">fatal</span> <span class="nl">error</span><span class="p">:</span> <span class="n">readline</span><span class="o">/</span><span class="n">readline</span><span class="p">.</span><span class="nl">h</span><span class="p">:</span> <span class="err">没有那个文件或目录</span>
</code></pre></div>
<p>说明缺少libreadline-dev依赖包,使用命令进行安装:</p>
<div class="highlight"><pre><code><span class="n">sudo</span> <span class="n">apt</span><span class="o">-</span><span class="n">get</span> <span class="n">install</span> <span class="n">libreadline</span><span class="o">-</span><span class="n">dev</span>
</code></pre></div>
<p>然后重新进行编译测试,如果不再有错误提示,可进行正式的编译和安装</p>
<div class="highlight"><pre><code><span class="n">make</span> <span class="n">linux</span>
<span class="n">sudo</span> <span class="n">make</span> <span class="n">install</span>
</code></pre></div>
<p>到此,我的Lua已经在Ubuntu 14.10上成功安装。</p>
Lua Install All Platform
OpenCV Install All Platform
https://www.zruibin.cn/article/opencvinstallallplatform.html
2014-10-20 13:24
2015-10-13 16:01
<p>首页 - OpenCV China :图像处理,计算机视觉库,Image Processing, Computer Vision</p>
<p><a href="http://wiki.opencv.org.cn/index.php/%E9%A6%96%E9%A1%B5" target="blank">http://wiki.opencv.org.cn/index.php/%E9%A6%96%E9%A1%B5</a></p>
<h2>Mac os</h2>
<p>一、安装OpenCV for MAC</p>
<ol>
<li>is下载opencv for mac安装源文件,解压缩</li>
<li>安装cmake程序。如果是Homebrew,在终端中输入:“brew install cmake”,自动安装cmake。<a href="http://mxcl.github.com/homebrew/" target="_blank" rel="nofollow">http://mxcl.github.com/homebrew/</a>
把网页最下方的命令拷入terminal即可</li>
<li>进入存放解压后的opencv文件夹,新建一个空的文件夹build,进入该文件夹,编译安装opencv,使用命令如下:</li>
</ol>
<div class="highlight"><pre><code><span class="n">mkdir</span> <span class="n">build</span>
<span class="n">cd</span> <span class="n">build</span>
<span class="n">cmake</span> <span class="o">-</span><span class="n">G</span> <span class="s">"Unix Makefiles"</span> <span class="p">..</span>
<span class="c1">// cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. </span>
<span class="n">make</span>
<span class="n">sudo</span> <span class="n">make</span> <span class="n">install</span>
</code></pre></div>
<p>P.S:如果提示说"</p>
<div class="highlight"><pre><code><span class="n">CMake</span> <span class="nl">Error</span><span class="p">:</span> <span class="n">CMake</span> <span class="n">was</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">find</span> <span class="n">a</span> <span class="n">build</span> <span class="n">program</span> <span class="n">corresponding</span> <span class="n">to</span> <span class="s">"Unix Makefiles"</span><span class="p">.</span>
<span class="n">CMAKE_MAKE_PROGRAM</span> <span class="n">is</span> <span class="n">not</span> <span class="n">set</span><span class="p">.</span> <span class="n">You</span> <span class="n">probably</span> <span class="n">need</span> <span class="n">to</span> <span class="n">select</span> <span class="n">a</span> <span class="n">different</span> <span class="n">build</span> <span class="n">tool</span><span class="p">.</span><span class="s">"</span>
</code></pre></div>
<p>等,那表示Cmake未安装完全,Xcode主页下载Command Line Tool.</p>
<p>安装好的lib文件存放在“/usr/local/lib”文件夹,h文件存放在“/usr/local/include”。
至此,opencv for Mac 安装完毕,参考的网址如下:
<a href="http://tilomitra.com/opencv-on-mac-osx/" target="_blank" rel="nofollow">http://tilomitra.com/opencv-on-mac-osx/</a></p>
<p>修改Header Search Paths为 /usr/local/include/opencv和 /usr/local/include
修改Library Search Paths为/usr/local/lib</p>
<p>---- 2 ----</p>
<h4>1、安装OpenCV</h4>
<p>1)首先下载opencv for mac安装源文件
2)安装cmake程序。
3)进入存放解压后的opencv文件夹,新建一个空的文件夹release,进入该文件夹,编译安装opencv,使用命令如下:</p>
<div class="highlight"><pre><code><span class="n">mkdir</span> <span class="n">release</span>
<span class="n">cd</span> <span class="n">release</span>
<span class="n">cmake</span> <span class="o">-</span><span class="n">G</span> <span class="s">"Unix Makefiles"</span> <span class="p">..</span>
<span class="c1">// cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local .. </span>
<span class="n">make</span>
<span class="n">sudo</span> <span class="n">make</span> <span class="n">install</span>
</code></pre></div>
<p>4)安装好的lib文件存放在“/usr/local/lib”文件夹,h文件存放在“/usr/local/include”。
至此,opencv for Mac 安装完毕</p>
<h4>2、使用CMake编译OpenCV程序</h4>
<p>1)新建源程序文件</p>
<p>新建CMakeLists.txt文件</p>
<p>3)使用CMake编译生成XCode项目,搞定。</p>
<p>3、直接使用XCode开发</p>
<p>1)创建一个空的command line工程。
2)加入测试代码。</p>
<p>3)添加lib文件:右键点击工程名,选择“Add files to..”,在文件选择对话框弹出来时输入“/”,在弹出的路径框中输入:/usr/local/lib,全选该文件夹下的全部dylib文件,添加至工程。</p>
<p>4)添加lib文件查找支持: 点击工程名文件,进入“Build Settings”选项卡,在“Library Search Paths”栏中输入“/usr/local/lib”</p>
<p>5)添加头文件:点击工程名文件,进入“Build Settings”选项卡,在“Header Search Paths”栏中输入:“/usr/local/include /usr/local/include/opencv”</p>
<p>【注意】不管用CMake还是手工创建XCode项目,都要将BuildSetting中的C++ Standard Library 改为libstdc++(GUN C++ standard library),不然会产生编译错误,提示找不到"assert.h"文件</p>
<p>OpenCV2.4.10这个版本有Bug,建议安装新版。</p>
<p>但是新的3.0还不支持安卓,只能用2.4.10。</p>
<p>安装步骤见<a href="http://blogs.wcode.org/2014/10/howto-install-build-and-use-opencv-macosx-10-10/">http://blogs.wcode.org/2014/10/howto-install-build-and-use-opencv-macosx-10-10/</a></p>
<p>期间我遇到了</p>
<div class="highlight"><pre><code><span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">edwalt</span><span class="o">/</span><span class="n">Downloads</span><span class="o">/</span><span class="n">opencv</span><span class="o">/</span><span class="n">opencv</span><span class="o">-</span><span class="mf">2.4.10</span><span class="o">/</span><span class="n">modules</span><span class="o">/</span><span class="n">legacy</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">calibfilter</span><span class="p">.</span><span class="nl">cpp</span><span class="p">:</span><span class="mi">98</span><span class="o">:</span><span class="mi">9</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span><span class="n">comparison</span> <span class="n">of</span> <span class="n">array</span> <span class="err">'</span><span class="n">this</span><span class="o">-></span><span class="n">latestPoints</span><span class="err">'</span> <span class="n">not</span> <span class="n">equal</span> <span class="n">to</span> <span class="n">a</span> <span class="n">null</span> <span class="n">pointer</span> <span class="n">is</span> <span class="n">always</span> <span class="nb">true</span>
<span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="p">,</span><span class="o">-</span><span class="n">Wtautological</span><span class="o">-</span><span class="n">pointer</span><span class="o">-</span><span class="n">compare</span><span class="p">]</span>
<span class="k">if</span> <span class="p">(</span><span class="n">latestPoints</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span>
<span class="o">^~~~~~~~~~~~</span> <span class="o">~~~~</span>
<span class="o">/</span><span class="n">Users</span><span class="o">/</span><span class="n">edwalt</span><span class="o">/</span><span class="n">Downloads</span><span class="o">/</span><span class="n">opencv</span><span class="o">/</span><span class="n">opencv</span><span class="o">-</span><span class="mf">2.4.10</span><span class="o">/</span><span class="n">modules</span><span class="o">/</span><span class="n">legacy</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">calibfilter</span><span class="p">.</span><span class="nl">cpp</span><span class="p">:</span><span class="mi">526</span><span class="o">:</span><span class="mi">9</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span><span class="n">address</span> <span class="n">of</span> <span class="n">array</span> <span class="err">'</span><span class="n">this</span><span class="o">-></span><span class="n">latestCounts</span><span class="err">'</span> <span class="n">will</span> <span class="n">always</span> <span class="n">evaluate</span> <span class="n">to</span> <span class="err">'</span><span class="nb">true</span><span class="err">'</span> <span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="p">,</span><span class="o">-</span><span class="n">Wpointer</span><span class="o">-</span><span class="kt">bool</span><span class="o">-</span><span class="n">conversion</span><span class="p">]</span>
<span class="k">if</span><span class="p">(</span> <span class="n">latestCounts</span> <span class="p">)</span>
<span class="o">~~</span> <span class="o">^~~~~~~~~~~~</span>
<span class="mi">2</span> <span class="n">errors</span> <span class="n">generated</span><span class="p">.</span>
</code></pre></div>
<p>这个问题就是因为一个Bug所致,幸好这问题已经解决。
<a href="https://github.com/Itseez/opencv/commit/35f96d6da76099d80180439c857a4abe5cb17966">https://github.com/Itseez/opencv/commit/35f96d6da76099d80180439c857a4abe5cb17966</a>
打开Finder进入所有文件,搜索calibfilter.cpp。参照此网页内的代码修改此文件。-的就是要删除的,+就是要加上的。保存后继续Terminal输入make编译正常了。</p>
<p><br/><hr><br/></p>
<h2>Linux</h2>
<p>Cmake的安装</p>
<p>OpenCV 2.2以后版本需要使用Cmake生成makefile文件,因此需要先安装cmake。</p>
<p>ubuntu下安装cmake比较简单,</p>
<p>apt-get install cmake</p>
<p>如果觉得自带的版本不符合要求,可以下载安装包。</p>
<p>下载最新版的安装包:</p>
<p><a href="http://www.cmake.org/cmake/resources/software.html" target="blank">http://www.cmake.org/cmake/resources/software.html</a></p>
<p>这里下载已经编译好的,这样只需要解压至需要的目录下即可使用:</p>
<div class="highlight"><pre><code><span class="n">tar</span> <span class="n">zxvf</span> <span class="n">cmake</span><span class="o">-</span><span class="mf">2.8.10.2</span><span class="o">-</span><span class="n">Linux</span><span class="o">-</span><span class="n">i386</span><span class="p">.</span><span class="n">tar</span><span class="p">.</span><span class="n">gz</span> <span class="err">–</span><span class="n">C</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span>
</code></pre></div>
<p>设置环境变量:</p>
<div class="highlight"><pre><code><span class="n">sudo</span> <span class="n">gedit</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">emouse</span><span class="o">/</span><span class="p">.</span><span class="n">bashrc</span>
</code></pre></div>
<p>在打开的文件后添加:</p>
<div class="highlight"><pre><code><span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="err">$</span><span class="nl">PATH</span><span class="p">:</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">cmake</span><span class="o">-</span><span class="mf">2.8.10.2</span><span class="o">-</span><span class="n">Linux</span><span class="o">-</span><span class="n">i386</span><span class="o">/</span><span class="n">bin</span>
</code></pre></div>
<p>查看版本,测试是否安装成功:</p>
<div class="highlight"><pre><code><span class="n">root</span><span class="err">@</span><span class="nl">emouse</span><span class="p">:</span><span class="o">/</span><span class="n">home</span><span class="err">#</span> <span class="n">cmake</span> <span class="o">--</span><span class="n">version</span>
<span class="n">cmake</span> <span class="n">version</span> <span class="mf">2.8.10.2</span>
</code></pre></div>
<p>Ubuntu 下安装 OpenCV</p>
<p>软件环境:</p>
<p>Ubuntu 12.04</p>
<p>OpenCV 2.4.3</p>
<p>Cmake 2.8.10.1</p>
<p>gcc 4.6.3 (系统默认)</p>
<p>1、先安装 libgtk2.0-dev 和 pkg-config,,否则后期编译运行程序会出现类似如下的问题:</p>
<div class="highlight"><pre><code><span class="n">OpenCV</span> <span class="nl">Error</span><span class="p">:</span> <span class="n">Unspecified</span> <span class="n">error</span> <span class="p">(</span><span class="n">The</span> <span class="n">function</span> <span class="n">is</span> <span class="n">not</span> <span class="n">implemented</span><span class="p">.</span> <span class="n">Rebuild</span> <span class="n">the</span> <span class="n">library</span> <span class="n">with</span> <span class="n">Windows</span><span class="p">,</span> <span class="n">GTK</span><span class="o">+</span> <span class="mf">2.</span><span class="n">x</span> <span class="n">or</span> <span class="n">Carbon</span> <span class="n">support</span><span class="p">.</span> <span class="n">If</span> <span class="n">you</span> <span class="n">are</span> <span class="n">on</span> <span class="n">Ubuntu</span> <span class="n">or</span> <span class="n">Debian</span><span class="p">,</span> <span class="n">install</span> <span class="n">libgtk2</span><span class="mf">.0</span><span class="o">-</span><span class="n">dev</span> <span class="n">and</span> <span class="n">pkg</span><span class="o">-</span><span class="n">config</span><span class="p">,</span> <span class="n">then</span> <span class="n">re</span><span class="o">-</span><span class="n">run</span> <span class="n">cmake</span> <span class="n">or</span> <span class="n">configure</span> <span class="n">script</span><span class="p">)</span> <span class="n">in</span> <span class="n">cvNamedWindow</span><span class="p">,</span> <span class="n">file</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">opencv</span><span class="o">/</span><span class="n">OpenCV</span><span class="o">-</span><span class="mf">2.0.0</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">highgui</span><span class="o">/</span><span class="n">window</span><span class="p">.</span><span class="n">cpp</span><span class="p">,</span> <span class="n">line</span> <span class="mi">100</span>
<span class="n">terminate</span> <span class="n">called</span> <span class="n">after</span> <span class="n">throwing</span> <span class="n">an</span> <span class="n">instance</span> <span class="n">of</span> <span class="err">'</span><span class="n">cv</span><span class="o">::</span><span class="n">Exception</span><span class="err">'</span>
</code></pre></div>
<p>通过网络获取安装:</p>
<div class="highlight"><pre><code><span class="cp"># apt-get install libgtk2.0-dev# apt-get install pkg-config</span>
</code></pre></div>
<p>2、下载OpenCV ,文件名:OpenCV-2.4.3.tar.bz2,下载地址:</p>
<p><a href="http://www.opencv.org.cn/index.php/Download" target="blank">http://www.opencv.org.cn/index.php/Download</a></p>
<p>解压:</p>
<div class="highlight"><pre><code><span class="cp">#tar jxvf OpenCV-2.4.3.tar.bz2</span>
</code></pre></div>
<p>得到文件夹 OpenCV-2.4.3</p>
<p>这里新建一个文件夹OpenCV-x86作为PC编译目录。</p>
<p>3、#cmake-gui 打开cmake的gui界面,开始进行配置。</p>
<p>cmake主要用于进行一些配置设定,从而生成用于编译安装的makefile文件,通过界面进行参数的配置和设定,非常直观、方便。在配置中指定源码和编译目录以及生成方式。</p>
<p>按照下图的步骤进行配置:</p>
<p><a href="http://images.cnitblog.com/blog/337520/201302/22220405-92d39b75803a4167ac71dc372aed096b.png" rel="nofollow"><img title="image" alt="image" src="http://static.oschina.net/uploads/img/201410/20132645_1RBq.png" width="701" height="544"></a></p>
<p>点击Finish后cmake即载入默认配置,如下图所示:</p>
<p><a href="http://images.cnitblog.com/blog/337520/201302/22220411-4fbf65d125bd4458b79c4999ee1af90e.png" rel="nofollow"><img title="image" alt="image" src="http://static.oschina.net/uploads/img/201410/20132645_bEYD.png" width="711" height="552"></a></p>
<p>如图所示,窗口的中间部分即配置列表,这里和使用cmake命令直接生成makefile文件一致的,如</p>
<div class="highlight"><pre><code><span class="err">$</span> <span class="n">cmake</span> <span class="o">-</span><span class="n">D</span> <span class="n">CMAKE_BUILD_TYPE</span><span class="o">=</span><span class="n">RELEASE</span> <span class="o">-</span><span class="n">D</span> <span class="n">CMAKE_INSTALL_PREFIX</span><span class="o">=/</span><span class="n">home</span><span class="o">/</span><span class="n">OpenCV</span>
</code></pre></div>
<p>只是这里通过图形界面的方式来进行配置,更加直观方便。</p>
<p>这里指对一个地方进行修改,CMAKE_BUILD_TYPE 值输入RELEASE,其他保持不变,图中蓝色虚线部分显示了默认的安装目录,生成makefile文件最后执行 make install时就会安装到这个目录,这里可以根据个人需求更改。在这里的配置中我勾选了WITH_QT 去掉了WITH_TIFF,其他更多的配置也不清楚,OpenCV中文网站也没找到系统的说明,这里暂时不深究,点击Generate生成配置文件。</p>
<p>进入OpenCV-x86目录可以查看Makefile文件,可以留意文件的生成时间是否和刚才的生成时间一致。</p>
<p>4、接下来在OpenCV-x86 分别执行make和make install即可完成编译安装。</p>
<p>5、安装完成后需要对系统相关环境变量进行配置:</p>
<p>sudo gedit /etc/ld.so.conf.d/opencv.conf
将以下内容添加到最后:</p>
<p>/usr/local/lib</p>
<p>接下来配置库:</p>
<p>sudo ldconfig
更改环境变量:</p>
<p>sudo gedit /etc/bash.bashrc
在文件后添加:</p>
<p>PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig
export PKG_CONFIG_PATH
保存退出,在运行下面的例程之前,需要重新开启终端来使配置生效。</p>
<p>例程测试</p>
<p>拷贝步骤2中解压的的/OpenCV-2.4.3/samples/c 将c文件夹拷贝出来,下面运行一下这里面的一个例程,初步体验下OpenCV。拷贝完成后进入这个文件夹:</p>
<p>chmod +x build_all.sh</p>
<p>./build_all.sh</p>
<p>这样就对例程目录下的源文件进行了编译,这里运行一个人脸检测的程序,下面摘录自本文参考资料3。</p>
<p>Some of the training data for object detection is stored in /usr/local/share/opencv/haarcascades. You need to tell OpenCV which training data to use. I will use one of the frontal face detectors available. Let’s find a face:</p>
<p>终端中运行:</p>
<div class="highlight"><pre><code><span class="p">.</span><span class="o">/</span><span class="n">facedetect</span> <span class="o">--</span><span class="n">cascade</span><span class="o">=</span><span class="s">"/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml"</span> <span class="o">--</span><span class="n">scale</span><span class="o">=</span><span class="mf">1.5</span> <span class="n">lena</span><span class="p">.</span><span class="n">jpg</span>
</code></pre></div>
<p>得到的结果如下图:</p>
<p><a href="http://images.cnitblog.com/blog/337520/201302/22220416-35bf6f36c14548718759c0cd84a471b8.png" rel="nofollow"><img title="image" alt="image" src="http://static.oschina.net/uploads/img/201410/20132645_6A6L.png" width="723" height="556"></a><a href="http://images.cnitblog.com/blog/337520/201302/22220448-28c63501d5ec4fdc9ee1166e69397f93.png" rel="nofollow"><img title="image" alt="image" src="http://static.oschina.net/uploads/img/201410/20132645_6ezY.png" width="514" height="589"></a></p>
<p>到这里基本的就写完了,OpenCV我之前也没有任何基础,这里只是把平台配置起来跑通,后续的工作还有很多,欢迎各位参考。转载请注明<a href="http://emouse.cnblogs.com/" target="blank">http://emouse.cnblogs.com/</a></p>
<p>----- 2 ------</p>
<p>我的系统是Ubuntu 12.04LTS ,下载的OpenCV版本是目前最新的OpenCV 2.4.2</p>
<p>1、准备好源码,可以直接下载,也可以svn弄下来
要准备的东东就是上网下载个Linux版的OpenCV啦,zip格式的。解压到一个地方,我放到机子的地方是/home/star/apps/里面。
如今的目录状态是:/home/star(这是我的用户名啊,和你不一样)/apps(这是我习惯放程序的地方,神码pdf阅读器就是放这的)/OpenCV/(这里就好多OpenCV的文件,下面要在里面cmake的)</p>
<p>2、下载OpenCV所需要的依赖文件
sudo -sH 变成超人
然后狂apt-get install。。。。
具体install什么,就看下面的链接啦,如果是新手的话,建议全部都install啊。。不然就会有最低下的错误,而且弄了好久都不知到怎么回事。
<a href="http://opencv.willowgarage.com/wiki/InstallGuide%20%3A%20Debian" target="blank">http://opencv.willowgarage.com/wiki/InstallGuide%20%3A%20Debian</a></p>
<p>3、编译OpenCV
回到步骤1的那个OpenCV目录,新建一个叫release的文件夹,然后在里面cmake,具体也可参考上面的链接
cd ~/opencv # the directory containing INSTALL, CMakeLists.txt etc. mkdir release cd release cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_PYTHON_SUPPORT=ON -D BUILD_EXAMPLES=ON ..</p>
<p>看到上面的cmake命令没有!!!搞定的话第一小步就完成了</p>
<p>然后就第二小步: make
这一步非常久啊。。。很久很久很久。。。。。。
然后就很轻松的: make install</p>
<p>4、小配置</p>
<p>这一步关乎你的程序能不能找到那些include,和lib。</p>
<p>大家可以参考这篇文章~~很后才找到的。。。为什么没有早点发现呢。。。走了好多冤枉路啊= =</p>
<p><a href="http://www.samontab.com/web/2010/04/installing-opencv-2-1-in-ubuntu/" target="blank">http://www.samontab.com/web/2010/04/installing-opencv-2-1-in-ubuntu/</a></p>
<p>下面的是从这个链接copy的。。。人家图文并茂,都懒得翻译了。。怕翻译错了。。。</p>
<p>Now you have to configure the library. First, open the opencv.conf file with the following code:</p>
<p>1 sudo gedit /etc/ld.so.conf.d/opencv.conf</p>
<p>Add the following line at the end of the file(it may be an empty file, that is ok) and then save it:</p>
<p>1 /usr/local/lib</p>
<p>Run the following code to configure the library:</p>
<p>1 sudo ldconfig</p>
<p>Now you have to open another file:</p>
<p>1 sudo gedit /etc/bash.bashrc</p>
<p>Add these two lines at the end of the file and save it:</p>
<p>1 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig</p>
<p>2 export PKG_CONFIG_PATH</p>
<p>Finally, open a new console, restart the computer or logout and then login again. OpenCV will not work correctly until you do this.</p>
<p>5、写程序!!!
在自己的工作目录里面,新建DisplayImage.cpp
然后从这个地方,copy一下源代码
<a herf="http://docs.opencv.org/doc/tutorials/introduction/linux_gcc_cmake/linux_gcc_cmake.html" target="blank">http://docs.opencv.org/doc/tutorials/introduction/linux_gcc_cmake/linux_gcc_cmake.html</a>
然后就编译了,编译的方法有三种:</p>
<p>1)直接gcc</p>
<p>2)用cmake建makefile然后make一下</p>
<p>3)IDE法,传说有个万能IDE叫eclipse。。。。
第一种,</p>
<p>g++ <code>pkg-config --cflags opencv</code> -o hello hello.cpp <code>pkg-config --libs opencv</code></p>
<p><a href="http://stackoverflow.com/questions/11532963/cant-compile-opencv-in-linux" target="blank">http://stackoverflow.com/questions/11532963/cant-compile-opencv-in-linux</a>
给个链接出来,是要告诉你,libs要放在后面啊。。不然会出错滴~~</p>
<p>第二种,
建一个CMakeLists.txt的东西,输入下面的东西
project( DisplayImage )find_package( OpenCV REQUIRED )add_executable( DisplayImage DisplayImage )target_link_libraries( DisplayImage ${OpenCV_LIBS} )
然后。。。还是看这篇文章。。。<a href="http://docs.opencv.org/doc/tutorials/introduction/linux_gcc_cmake/linux_gcc_cmake.html">http://docs.opencv.org/doc/tutorials/introduction/linux_gcc_cmake/linux_gcc_cmake.html</a></p>
<p>cmake之后,就有个可执行文件,然后就能显示图片啦~~~(怎么又是Lena。。。。)</p>
<p>我遇到的问题:
1、没有编译错误,但运行程序的时候出现下面这个错误
OpenCV ERROR: Unspecified error (The function is not implemented. Rebuild the library with Windows, GTK+ 2.x or Carbon support)
意思是,你木有某些东西的支持,建议你安装什么libgtk2.0(这个忘了是什么),和pkg-config。
然后我就安装了libgtk神马的啊。然后运行了还不行。(请耐心看下去,最下面解决了~~)
有个GG说看这篇文章可以搞定<a href="http://mathiasirwans.blogspot.com/2007/07/my-experience-with-ubuntu-610-opencv-10.html">http://mathiasirwans.blogspot.com/2007/07/my-experience-with-ubuntu-610-opencv-10.html</a>
但是我看了还搞不定。。。
后来深思了出错提示,在安装了上面所说缺少的东西之后,重新cmake+make+make install后,终于搞定了~~~(因为make的过程很痛苦,所以我之前一直回避make。。这次证实我真错了。。。)</p>
<p>2、对视频文件的读写都有问题。应该是ffmepg没设置好
参考这篇文章:<a href="http://www.360doc.com/content/11/0726/10/1217721_135894185.shtml" target="blank">http://www.360doc.com/content/11/0726/10/1217721_135894185.shtml</a></p>
<p>还有这个:<a href="http://www.360doc.com/content/11/0726/10/1217721_135894185.shtml" target="blank">http://www.360doc.com/content/11/0726/10/1217721_135894185.shtml</a></p>
<p><br/><hr><br/></p>
<h2>windows</h2>
<p>安装codeblocks
如果大家没有接触过codeblocks的话,那么请在Google上面搜索一下把,这个可是鼎鼎有名的跨平台的C++的一个集成调试工具,简称(IDE),您也可以访问它的网站codeblocks.org。本文的主要目的,就是向您介绍在windows下面如何在codeblocks里面的使用和配置情况。您只需要下载codeblocks下载地址里面的对应的包就可以了,我这里推荐下载的是那个 codeblocks-8.02mingw-setup.exe 文件,安装此文件以后,MinGW编译器也一并安装完毕了。 注意,codebocks支持多种编译器和多种平台,本文介绍的是windows平台下,在codeblocks里面使用MinGW编译器(一种windows下面的gcc编译器和相关工具的移植版本)。</p>
<p>安装OpenCV(1.0)
这一节的内容,与别的安装教程内容,比如VC6下安装与配置都是一样的。如果您已经安装和配置了OpenCV,那么请直接跳到#使用向导生成codeblocks项目
从<a href="http://www.opencv.org.cn">http://www.opencv.org.cn</a> 下载OpenCV安装程序。假如要将OpenCV安装到C:\Program Files\OpenCV。(下面附图为OpenCV 1.0rc1的安装界面,OpenCV 1.0安装界面与此基本一致。)在安装时选择"将\OpenCV\bin加入系统变量"(Add\OpenCV\bin to the systerm PATH)。
<a href="http://wiki.opencv.org.cn/index.php/Image:Opencv-install-step1.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_ofXp.png" alt="Image:Opencv-install-step1.png" width="503" height="385"></a><a href="http://wiki.opencv.org.cn/index.php/Image:Opencv-install-step2.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_XgM1.png" alt="Image:Opencv-install-step2.png" width="503" height="385"></a><a href="http://wiki.opencv.org.cn/index.php/Image:Opencv-install-step3.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_xFJN.png" alt="Image:Opencv-install-step3.png" width="503" height="385"></a><a href="http://wiki.opencv.org.cn/index.php/Image:Opencv-install-step4.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_BWHy.png" alt="Image:Opencv-install-step4.png" width="503" height="385"></a></p>
<p>配置Windows环境变量
检查C:\Program Files\OpenCV\bin是否已经被加入到环境变量PATH,如果没有,请加入。加入后需要注销当前Windows用户(或重启)后重新登陆才生效。(可以在任务管理器里重启explorer.exe)
<a href="http://wiki.opencv.org.cn/index.php/Image:Path-envirionment-var1.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_5xsb.png" alt="Image:Path-envirionment-var1.png" width="418" height="490"></a><a href="http://wiki.opencv.org.cn/index.php/Image:Path-envirionment-var2.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_LQhj.png" alt="Image:Path-envirionment-var2.png" width="385" height="423"></a></p>
<p>使用向导生成codeblocks项目
首先,使用向导,生成console模式下面的一个项目。如下图所示,注意选择红色框所示的Console application</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv0.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_YFQZ.png" alt="Image:Cb_opencv0.png" width="594" height="440"></a></p>
<p>随便去一个项目的名字,这里我取成 text_opencv</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv1.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_u3Lm.png" alt="Image:Cb_opencv1.png" width="519" height="423"></a></p>
<p>一路选择下一步,就创建完成了。整个项目里面,就一个main.cpp文件。
接着,修改main.cpp的代码如下</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv4.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_uveX.png" alt="Image:Cb_opencv4.png" width="598" height="510"></a></p>
<p>注意,上面的代码,可以从图像文件读入和显示直接copy代码得到。</p>
<p>添加库文件和头文件
完成了代码修改之后,并不能编译和生成exe文件,必须添加必要的头文件的路径和库文件的路径,以便于编译器和连接器找到这些文件。 先右键点击项目的名称,在右键菜单中选择“build options”,如下图所示。 注:若安装OpenCV 2.x,请参考本页页低的“英文的OpenCV wiki 里面的Codeblocks教程”.</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv5.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_aFIW.png" alt="Image:Cb_opencv5.png" width="372" height="437"></a></p>
<p>然后,按照下面的图,添加include路径。</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv6.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_4IFT.png" alt="Image:Cb_opencv6.png" width="707" height="515"></a></p>
<p>再按照下面的图,添加lib库的路径。</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv7.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_alY9.png" alt="Image:Cb_opencv7.png" width="707" height="515"></a></p>
<p>接着,添加库的编译选项,加入 -lhighgui -lcv -lcxcore ,如下图所示。
<a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv8.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_dTNP.png" alt="Image:Cb_opencv8.png" width="707" height="515"></a></p>
<p>大功告成,编译生成exe文件并运行
现在,就可以选择“build”按钮,生成exe文件,并且运行了。当然,可能你并没有设置需要显示的图片。你需要手工copy一个图片到项目的目录,比如 lena.jpg。然后添加到程序的命令行参数,如下图所示。</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv9.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_3TdS.png" alt="Image:Cb_opencv9.png" width="234" height="256"></a><br><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv10.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_Y06Z.png" alt="Image:Cb_opencv10.png" width="283" height="373"></a></p>
<p>以下就是程序运行并且显示的结果</p>
<p><a href="http://wiki.opencv.org.cn/index.php/Image:Cb_opencv2.png" rel="nofollow"><img src="http://static.oschina.net/uploads/img/201410/20132459_22HW.png" alt="Image:Cb_opencv2.png" width="785" height="623"></a></p>
30岁后你会站在哪里?(摘取)
https://www.zruibin.cn/article/30_sui_hou_ni_hui_zhan_zai_na_li_?__zhai_qu_.html
2014-09-27 19:51
2014-09-27 19:51
<p>其实,你一直站在狗笼里</p>
<p>有一次我开车载着儿子在等红绿灯时,看到一位中年男人模样的广告举牌员,站在路口压低帽沿,等红灯车子都停下时,他就把手上的牌子举高。</p>
<p>这时,儿子问,为何同样是成年人,有的站在路口晒太阳?有的站在快餐店柜台?有的却站在百货公司里吹冷气?</p>
<p>我回答,这是很正常的事,每个人想站在哪里,会站在哪里,都是自己的选择。</p>
<p>选择?儿子怔了一下又问,那么,为何路口那位先生不立刻就选择去快餐店上班?或是去百货公司吹冷气?同样是有钱赚啊?</p>
<p>我叹了口气对儿子说,我所说的选择,不是他们现在的选择,而是他们半年前,甚至是三或五年前的选择。</p>
<p>他们现在想站在什么位置,或不得不站在什么位置,都取决于他们在一段时间之前所做的决定,加上本身努力及时间的累积,他们才能站在这个位置,并不是你当下想做什么,就能随心所欲的。</p>
<p>我不知道当时未成年的儿子是否听懂,然而,我发现很多已经出社会,年纪也已经是20几岁的年轻人,似乎完全不懂这个道理。</p>
<p>我看到遇到听到的20几岁年轻人中,99%以上都是每天醉生梦死,活在自己世界的梦中人。</p>
<p>大部分的年轻人,只要有一份工作,就自认为是很不了起的成就,至少他们觉得自己的表现,已经比那些靠爸族或尼特族好太多了。</p>
<p>我曾问他们,自己认为现在是站在什么位置?30岁后又会站在哪里?</p>
<p>他们觉得,不管是什么工作,饮料店店员也好,行政助理也罢,只要他们能赚钱养活自己,就是站在白领阶级的位置。</p>
<p>至于将来30岁后,他们会站在哪里,他们不知道,但他们肯定自己未来的位置,不会比现在的差。</p>
<p>然而,我想对那些20几或30几岁的年轻人说,如果你们安于每月都有薪水领,有地方住有饭吃,偶尔可以聚餐、逛街、唱歌这样的舒适圈,然后到了月底把薪水花光时,就窝在家里吃方便面等月初的薪水入帐。这样的人生,其实,是和站在狗笼里的狗猫没有两样的。</p>
<p>因为,你们和那些被豢养的狗猫一样,是没有自由的。没有财务自由,没有逃出笼外去享受更多人生体验的自由。</p>
<p>如果20几岁或已经过30岁的人,当下没有这样的认知和自觉,5年或10年后,你们还是只能窝在狗笼里,望着笼外的成功者,开著名车住豪宅或拥有高质量和品味的生活,大叹自己八字不好或老天不公平。</p>
<p>其实,你一直是站在狗笼里的梦中人。</p>
<p>这是很残酷的事实,然而,只要你能全然接受这个事实,开始规画自己30岁后想站在哪里,在这个当下,你就不再是被豢养的宠物了。</p>
<p>因为,你早晚会打开关住你的狗笼,成为一个拥有自由的成功人士。</p>
<p>工作经验愈多,起薪就愈低</p>
<p>一位45岁的中年人和一名25岁的小伙子,两人同时应征路口的豪宅举牌员。</p>
<p>建商对他们说,举牌一天的薪资是700元。中年人听完大喊不公,认为自己有3年以上的举牌经验,不该和小伙子领一样的钱,建商应该替自己调涨工资,「至少要有800元吧!」</p>
<p>建商不以为然地指了指两人身后,一个蓬头垢面、看不出年纪的流浪汉说:「论经历,你们谁也不会比他久;至于他的工资,也是700元没变过!」</p>
<p>是否做得愈久,就该领得愈多?</p>
<p>有一篇在网络广泛流传的文章,当中一名想加薪的员工对老板说「我有25年的经验」,而老板响应他的是:「你没有25年的经验,你只是同一个经验用了25年。」</p>
<p>正在阅读这篇文章的各位,其实都该自问:自己是否也像这名员工,做着没有累积性的工作,却期望老板替你加薪?</p>
<p>随着时代改变,企业已经不再是终生雇用制的思维了,就算你在同一间公司中稳稳待上25年,待遇也未必有所变化。不可否认,现在的企业愈来愈倾向于依照能力与贡献度调整职级和薪资,「年资」取胜的做法逐渐成为过去式,做得久也未必能领得多,一切以实力见真章。</p>
<p>究竟从倚老卖老到论功行赏,中间有何区别?</p>
<p>如果你做的始终是短期、派遣等不具累积性的工作,那么对于老板来说,你的替代价值就和门口的管理员差不多。</p>
<p>或许这么讲比较清楚:如果你到一把年纪还在做零技术需求的工作,就难怪你的薪水没有起色。下一份工作的面试考官甚至会怀疑,为何你工作多年,到现在还在当基层人员?</p>
<p>管理学中有一个著名的「彼得原理」,当中提到:一个在目前工作上有出色表现的人,理应能继续向上提升到更高的职位;而如果一个人在同一职位上停滞不前,就表示这个人可能连目前的职务都无法胜任。</p>
<p>依照这个论点,长期停留在基层的你,处境实在令人担忧。</p>
<p>其实,老板宁可你的工作经验没那么多。</p>
<p>根据研究,许多人在转职时还背着前一份工作的「包袱」,这会抵销掉经验带来的优势。专家认为,人们在转换到另一个环境时,其实很难抛掉原先习得的规范和价值;所谓「职业包袱」指的是固化的做事方式和态度,而且工作经验愈多,你所背的包袱就愈沉重。</p>
<p>对于不少雇主来说,这是一个相当尴尬的问题。如果这是不大需要经验也能完成的工作,那何不干脆雇用一个没有经验的新人,再透过训练将他们收为己用呢?</p>
<p>而一个经常换工作的人,求职的心态也容易让人产生怀疑,站在雇主的角度,很难不去认为:这份工作对你来说,是否只是跳板而已?</p>
<p>如果你被预期这份工作不会做很久,那么在策略考虑上,上司当然不敢把重要的任务交付给你。</p>
<p>你的履历,是否像「现代警世录」?</p>
<p>有一种像是「警世录」的履历,会让老板看得心中警铃大响,而老一辈频频摇头。如果你的经历在雇主眼中属于这一种,你的问题可就大了。</p>
<p>在一次聚会上,我听见一位母亲数落她出社会不久的孩子:「不要以为你工作经验很多,你每个工作都只做几个月而已,能有什么经验?而且,那些工作之间毫不相干,这样一点用也没有……」</p>
<p>这名男孩出社会两年多,已经换了五、六个工作,平均不到半年就换一次。最近男孩又辞掉了工作,也不见有任何面试邀约,只是每天闲赋在家,似乎对找工作这件事心灰意冷。他的母亲要我帮忙劝劝他,于是我试着了解男孩对求职环境不满的原因。</p>
<p>「大部分的职缺都是薪水太低、工时太长,而且我有两年经验,为什么要屈就两万多元(新台币)的待遇?」男孩不满地说。</p>
<p>我请他将我当成面试考官,在我面前介绍自己。当我静静听完他介绍自己的履历,我告诉他,以他目前的条件──很遗憾,的确只值两万多元(新台币)的薪水。</p>
<p>「你也许很不服气,但这就是现实。」接着我将他的问题一一点出,过于频繁地更换工作以及彼此间毫无相关的工作内容,是男孩履历中的致命伤。「你的履历应该去芜存菁,删去不重要的部分,尽量把每份工作的时间拉长,展现你产生的价值与影响力,而不是些不相干的琐事。」</p>
<p>我还告诉他,履历反应的是求职者的市场价值,如果这段经历完全讲不出什么实质的内容,那还不如不提。</p>
<p>如果被人看出不断地跳槽和转换工作,对年轻人来说未必是好事,毕竟每种工作都需要花心力适应,每次跳槽都势必造成耗损;而且工作期间过短,容易被贴上「定性不足」、「适应力不佳」的标签。</p>
<p>此外,履历中出现空档,对求职的杀伤力也不小。如果经历并不连贯,势必会被怀疑是遭到资遣或开除。再者,如果原工作只做了几个月,很有可能被怀疑不适任或另有隐情,因此建议过短的资历不要写进去。</p>
<p>整体来说,中断型的工作经历带来的未必是加分,有时反而使你被贴上「低忠诚度」的标签,所以若是你的经历不连贯,最好能针对工作间的空档提出让人满意的解释。如果不想变成经验愈多却起薪愈低的情况,最根本的方法,还是确立志向及戒除频繁变换工作的习性,和年少轻狂的自己彻底道别。</p>
<p>下班后,宁可发呆也不要再想工作</p>
<p>有天我到员工餐厅用餐,坐在我附近的,都是大学毕业就开始工作,已经累积两、三年职场经历的「半熟人」,一个说每天事情多到做不完,另一个就问,那你怎么不把工作带回家做?</p>
<p>那个年轻人说:「我不想把工作带回家,很多职场专家都说工作与生活要分开,适度的休息很重要,我觉得专家说得很有道理,所以我回家吃完饭洗完澡之后,什么都不想,就呆在房间用计算机,上网看影片到12点睡觉。」</p>
<p>另一个年轻人也跟着附和:「没错没错,我最喜欢坐在沙发上看电视放空,再不然就是打开计算机上网,不过很奇怪,明明什么也没做,随便混一下就不知不觉快凌晨了,每天都发誓要早睡,结果最后还是搞到三更半夜。」我听着这些年轻小伙子的下班生活,发现每个人的生活经验几乎千篇一律。</p>
<p>明明是30岁不到的年轻人,却过着老年般的退休生活</p>
<p>很多年轻人准时六点下班打卡,回到住处后,拿起电视遥控器按下开关,一边看着电视上正在播放着热门的韩剧,一边上网逛着社群网站、吃着巷口买来的便当,吃完饭之后开始打混,抱着手机不停和朋友传讯息,用各种表情符号聊天,早已把今天上班时发生的所有一切抛到脑后,最后在一堆没有建设性又鬼打墙的废话中强迫自己入睡。</p>
<p>接着,放假日就在家里坐着当沙发马铃薯,拿着电视遥控器毫无目地乱转一通,这些听起来像是70岁的退休老人生活,却不幸的是现在大部分还不到30岁年轻上班族的真实写照。</p>
<p>现在的年轻人,明明处于各方面都很精力旺盛的时期,却总在下班后会自动变成无法思考的机器人,最后,总在隔天早上起床之后才开始懊恼:「我昨天下班回家后到底在干什么?」</p>
<p>我曾经听过不少人抱怨说:「我每天上班在办公室里面已经用脑过度,体力也消耗殆尽了,谁还有心情做其它事情啊?所以我只能放空,做一些不需要动脑的事情。」</p>
<p>对于上班族来说,最大的痛苦莫过于连下班后都还保持在工作状态,因为老板又不会发给你薪水;其实,真正会影响家庭和生活从来都不是工作本身,反而把时间都拿来浪费在杂事上,缺乏时间管理的意识,才是大多数年轻人的悲哀。</p>
<p>有很多人在社群网站上的朋友,动辄高达五、六百人,他们每天拿起手机上网不断地盯着关注别人的动态;但是说实话,看再久你们的感情也不会累积,只不过是花了好几倍的时间在重复做一样的事情。</p>
<p>记得之前看过一个统计数据,台湾的上班族在下班后最常做的事,前两名就是「上网」和「在家看电视」。</p>
<p>看电视和上网当然是正常的娱乐,这不是什么罪不可赦的事情,但是你却没想过,这样用来打发时间的模式,同时把你的人生体验和可以创造的价值都消磨殆尽了,这样下去,你无论在工作上还是生活,都注定成为一只找不到方向的无头苍蝇。</p>
<p>他的全世界,只有3坪大</p>
<p>先前有个新闻报导,年届30岁的几个年轻人分租一间房,每个人只能分到3坪大(1坪= 3.30578平米)的房间,大小只够放一张桌子和一张床,连转身都有困难。</p>
<p>当记者问及难道不嫌房间太小时,这几个20几岁的年轻人,蛮不在乎地说:「反正只是睡觉的地方,有得住就好。」记者再问,现在政府祭出许多青年首购优惠贷款,为什么不趁此机会买房置产时,只听年轻人又回答:「我的脑袋没有坏掉,为什么要为了沉重的房贷压力搞垮自己,只换来未来要帮银行与建商赚钱30年?」</p>
<p>事实上,有这种思维的年轻人实在不少,根据房仲业者的统计,现在30岁以下买房的年轻人只占一成,与十年前相较,足足萎缩超过一成,显示年轻买方的确对购屋愈来愈没有意愿,他们要不就是宁愿窝在小雅房,要不就是等着父母亲买房。</p>
<p>难道买房对你来说除了带来房贷压力,真的没有其它意义吗?</p>
<p>年轻人买房得靠「母力」,否则婚姻市场没有竞争力?</p>
<p>我从来没想过,现在年轻人能否顺利成婚的关键,竟然是「母力」是否雄厚。</p>
<p>某天几个老友聚餐,听到朋友的感叹,她问即将35岁的儿子,为什么宁愿与女朋友同居也不肯结婚时,儿子竟然回答她:「我女朋友说没有房子结婚免谈,她不肯嫁给我,都是因为妳一直不肯买房子给我,我们只好继续同居。」听了这话,做母亲的眼泪差点掉下来,一整个既无奈又心酸。</p>
<p>这种等着母亲买房,否则就没法结婚的年轻人,还有我公司里的年轻人。</p>
<p>有次我与几个员工开车路经新板特区时,听到两个刚退伍,还不到30岁的员工在聊天,两人左一句右一句说,豪宅一直盖,到底都卖给谁?这些一间动辄6千万的豪宅,真的有那么多有钱人来买吗?建商一直盖豪宅,意义究竟在哪里?</p>
<p>听着这两人的酸言酸语,我忍不住开口问:「难道你们从来没有想过,有朝一日也能在这个特区,买下一间属于自己的豪宅吗?」</p>
<p>没想到这两人忙不迭地摇头说:「现在房价这么高,我们根本不敢妄想买房子,现在能付得起房租就已经不错了。」</p>
<p>我想起朋友儿子说的话,又问,如果女朋友因为你名下没有房子而不跟你结婚怎么办?结果这两人又像约好似地回答:「那就只好问我妈要不要帮我出头期款,不然就只好问女友,愿不愿意结婚后跟我爸妈住在一起,如果不愿意,那也没办法。」</p>
<p>我听了咋舌,曾几何时,「母力」竟然成了年轻人在婚姻市场的竞争力?</p>
<p>现在守着三坪大,未来只能睡草席</p>
<p>不可否认,现在的30世代和50年前的30世代相比,的确是一个大环境相对更加严苛的时代,就连主计处也统计,现在青贫族的收入倒退17年,但房价与物价却是17年前的十倍,难怪有个广告说,现在的白领阶级,每个月薪水领了也像「白领」。</p>
<p>我可以体会年轻人对未来不确定感的焦虑,但是我不能理解,为什么年轻人会如此丧志,被大环境吓得连做梦都不敢?我告诉两个员工,如果你们以后买不起豪宅,绝对不是因为没有能力,而是因为你们不愿意对自己的人生负责。高房价,只不过是你们拿来当作逃避现实的借口。</p>
<p>你因为房价高而不买房,更因为生小孩的「成本高」决定当顶客族(丁克一族),你乐得不想背房贷,更乐得没有养育孩子的重担。然而,如果你仍旧把每个月薪水的1/3拿来付房租,到了月底还得勒紧裤带,年复一年过着月光族的日子,当你年老退休没有工作,连养活自己都成困难的时候,届时孤家寡人的你,还能指望谁来替你付房租?</p>
<p>现在20几岁的你,看起来好像还有本钱可以选择继续窝在3坪大(1坪= 3.30578平米)的世界里,继续在网络上票选心目中的宅男女神,继续在电玩游戏中与人交换宝物练等级,但是这种日子,你打算再过多久?</p>
<p>或许现在,你「靠势」还有母力可以依赖,未来还有几百万的国民年金可以盘算,再不济,每个月也有3千元(新台币)的老人年金可以救急。</p>
<p>但是别忘了,母亲会老、政府会倒,租金更只会随着房价愈来愈高,你自以为能够掌握的这些钱,在未来,别说养老院你住不起,甚至区区3坪大(1坪= 3.30578平米)的房租,都足够成为压垮你老年生活的最后一根稻草。到时候,流浪汉的草席,就是未来你只能栖身的最后三分地。</p>
<p>30岁后,你会站在哪里?</p>
<p>我常问20几岁的年轻人,30岁后,你会站在哪里?</p>
<p>人生的策略布局和生涯规画,很像我们去大城市的车站或交通转运站搭车,当你想离开这个转运站,一小时后你会在什么地方,都由你当下买什么路线车次的票,然后坐上哪一班次的车来决定的。</p>
<p>当你做了决策,当你坐上车,你就没有回头路可走了,接下来你的命运,就是由你搭的火车或巴士决定了,它会载你到哪里,会在什么地方把你放下,你是无法有太多个人选择的。</p>
<p>现在的你,不管几岁,过了30岁也好,你眼前的每个当下,都是决定你未来5到10年,你会被整个世界推到什么地方或什么位置的关键时刻。</p>
<p>当你站在车站或交通转运站,茫然地对未来没有目标和规画,就随便买了一张票,车来了就跟着人家上,等过了一小时,你也跟着人家下车,才发现自己竟然是来到十字路口,而你能做的就是举牌度日。</p>
<p>这时,你再怎么后悔都已经来不及了。因为,那台载你来这里的班车,是单向的,没有回头班次的命运专车。</p>
<p>等你无法回头,且发现人生、工作、位置和薪水都已经不可逆转时,你就能看见这种班车的车头上,写着令人惊心动魄的三个字:「时间号」。</p>
30岁后你会站在哪里?(摘取)
关于OpenGL
https://www.zruibin.cn/article/guan_yu_opengl.html
2014-05-31 10:34
2015-10-13 15:10
<p>opengl: <a href="http://www.cnblogs.com/yangxi/category/322690.html" rel="nofollow">http://www.cnblogs.com/yangxi/category/322690.html</a> </p><p>1、 MacOS中:</p>
<p>MacOS默认集成了OpenGL,在codeblocks中只需要create new project,选择glut project即可,codeblocks会自动生成一个main.c文件,点击编译运行,即可成功运行出现界面;</p>
<p>2、 Window环境:</p>
<p>windows环境配置稍微复杂些</p>
<p>(1)下载codeblocks,最好是带mingw的版本,不然则要自己配置mingw;</p>
<p>(2)下载GLUT bin文件 <a href="http://www.xmission.com/~nate/glut.html,解压,将glut32.dll复制到C:\windows\system目录,将glut32.lib复制到mingw\lib目录,将glut.h复制到mingw\include目录,mingw为你的mingw目录,如果是codeblocks自带的,则在codeblocks安装目录下;">http://www.xmission.com/~nate/glut.html,解压,将glut32.dll复制到C:\windows\system目录,将glut32.lib复制到mingw\lib目录,将glut.h复制到mingw\include目录,mingw为你的mingw目录,如果是codeblocks自带的,则在codeblocks安装目录下;</a></p>
<p>(3)新建工程,选择GLUT project,一路确认即可,完成后可看到系统自动生成了一个main.cpp文件,里面有若干行代码;</p>
<p>(4)添加头文件#include <windows.h>,注意这一步非常重要</p>
<p>(5)点击build options--->link libraries,点击add,选择刚才复制到mingw\lib目录下的glut32.lib文件,提示选择no,然后确定,然后直接编译运行程序就可看到效果了</p>
<p><br/><hr><br/></p>
<p>GLUT 3.7 下载地址:<a href="http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip">http://www.opengl.org/resources/libraries/glut/glutdlls37beta.zip</a></p>
<p>下载下来的 GLUT压缩包有 glut.dll, glut.h, glut.lib, glut32.dll, glut32.lib</p>
<p>将glut.h 放在 MinGW\include\GL 下</p>
<p>将glut.lib, glut32.lib 放在 MinGW\lib 下</p>
<p>将glut.dll, glut32.dll 放在 windows\System32 下</p>
<p> (有人说放在 windows\SysWOW64 下,我之前测试的时候两个都放了)</p>
<p>新建Porject -> GLUT projcet</p>
<p>之后会有一句: "Please select GLUT"s location",选择MinGW就好</p>
<p>新建的项目要 #include <windows.h>,之后应该就可以了。</p>
<p>VS2012 error : Required file tracker.exe is missing 解决办法</p>
<p>其实就是找到你的项目文件xxxx.vcxproj,使用编辑器打开,是xml格式的定义文件,查找关键字 PropertyGroup</p>
<p>会发现有几个这样的配置,然后在这样的关键附近插入如下代码:</p>
<div class="highlight"><pre><code><span class="o"><!--</span> <span class="k">for</span> <span class="n">example</span> <span class="n">in</span> <span class="n">the</span> <span class="n">project</span> <span class="n">file</span> <span class="o">--></span>
<span class="o"><</span><span class="n">PropertyGroup</span><span class="o">></span>
<span class="o"><</span><span class="n">TrackFileAccess</span><span class="o">></span><span class="nb">false</span><span class="o"></</span><span class="n">TrackFileAccess</span><span class="o">></span>
<span class="o"></</span><span class="n">PropertyGroup</span><span class="o">></span>
</code></pre></div>
<p>加入后,我的项目文件的代码定义像下面这个样子:</p>
<div class="highlight"><pre><code><span class="o"></</span><span class="n">ItemGroup</span><span class="o">></span>
<span class="o"><</span><span class="n">PropertyGroup</span><span class="o">></span>
<span class="o"><</span><span class="n">TrackFileAccess</span><span class="o">></span><span class="nb">false</span><span class="o"></</span><span class="n">TrackFileAccess</span><span class="o">></span>
<span class="o"></</span><span class="n">PropertyGroup</span><span class="o">></span>
<span class="o"><</span><span class="n">PropertyGroup</span> <span class="n">Label</span><span class="o">=</span><span class="s">"Globals"</span><span class="o">></span>
<span class="o"><</span><span class="n">ProjectGuid</span><span class="o">></span><span class="p">{</span><span class="mi">325</span><span class="n">EC88B</span><span class="o">-</span><span class="mf">5F</span><span class="mi">85</span><span class="o">-</span><span class="mi">493</span><span class="n">D</span><span class="o">-</span><span class="mi">92</span><span class="n">C0</span><span class="o">-</span><span class="n">E5CEBCA0BB39</span><span class="p">}</span><span class="o"></</span><span class="n">ProjectGuid</span><span class="o">></span>
<span class="o"><</span><span class="n">RootNamespace</span><span class="o">></span><span class="n">MFCQQChat</span><span class="o"></</span><span class="n">RootNamespace</span><span class="o">></span>
<span class="o"><</span><span class="n">Keyword</span><span class="o">></span><span class="n">MFCProj</span><span class="o"></</span><span class="n">Keyword</span><span class="o">></span>
<span class="o"></</span><span class="n">PropertyGroup</span><span class="o">></span>
<span class="o"><</span><span class="n">Import</span> <span class="n">Project</span><span class="o">=</span><span class="s">"$(VCTargetsPath)\Microsoft.Cpp.Default.props"</span> <span class="o">/></span>
<span class="o"><</span><span class="n">PropertyGroup</span> <span class="n">Condition</span><span class="o">=</span><span class="s">"'$(Configuration)|$(Platform)'=='Debug|Win32'"</span> <span class="n">Label</span><span class="o">=</span><span class="s">"Configuration"</span><span class="o">></span>
<span class="o"><</span><span class="n">ConfigurationType</span><span class="o">></span><span class="n">Application</span><span class="o"></</span><span class="n">ConfigurationType</span><span class="o">></span>
<span class="o"><</span><span class="n">UseDebugLibraries</span><span class="o">></span><span class="nb">true</span><span class="o"></</span><span class="n">UseDebugLibraries</span><span class="o">></span>
<span class="o"><</span><span class="n">PlatformToolset</span><span class="o">></span><span class="n">v110_xp</span><span class="o"></</span><span class="n">PlatformToolset</span><span class="o">></span>
<span class="o"><</span><span class="n">CharacterSet</span><span class="o">></span><span class="n">Unicode</span><span class="o"></</span><span class="n">CharacterSet</span><span class="o">></span>
<span class="o"><</span><span class="n">UseOfMfc</span><span class="o">></span><span class="n">Static</span><span class="o"></</span><span class="n">UseOfMfc</span><span class="o">></span>
<span class="o"></</span><span class="n">PropertyGroup</span><span class="o">></span>
</code></pre></div>
<p>保存文件后,编译运行,通过!!!!</p>
关于OpenGL 与Visual Stdio tracker.exe is missing 解决办法
C、C++通用Makefile
https://www.zruibin.cn/article/cc++_tong_yong_makefile.html
2014-05-19 13:47
2015-10-13 15:04
<p><br/></p>
<p><hr>
<br/></p>
<p>本文推荐了一个用于对 C/C++ 程序进行编译和连接以产生可执行程序的通用 Makefile。</p>
<p>在使用 Makefile 之前,只需对它进行一些简单的设置即可;而且一经设置,即使以后对源程序文件有所增减一般也不再需要改动 Makefile。因此,即便是一个没有学习过 Makefile 书写规则的人,也可以为自己的 C/C++ 程序快速建立一个可工作的 Makefile。</p>
<p>这个 Makefile 可以在 GNU Make 和 GCC 编译器下正常工作。但是不能保证对于其它版本的 Make 和编译器也能正常工作。</p>
<p>此 Makefile 的使用方法如下:</p>
<h4>程序目录的组织</h4>
<ul>
<li>尽量将自己的源程序集中在一个目录中,并且把 Makefile 和源程序放在一起,这样用起来比较方便。当然,也可以将源程序分类存放在不同的目录中。 </li>
<li>在程序目录中创建一个名为 Makefile 的文本文件,将后面列出的 Makefile 的内容复制到这个文件中。(注意:在复制的过程中,Makfile 中各命令前面的 Tab 字符有可能被转换成若干个空格。这种情况下需要把 Makefile 命令前面的这些空格替换为一个 Tab。) </li>
<li>将当前工作目录切换到 Makefile 所在的目录。目前,这个 Makefile 只支持在当前目录中的调用,不支持当前目录和 Makefile 所在的路径不是同一目录的情况。</li>
</ul>
<h4>指定可执行文件</h4>
<ul>
<li>程序编译和连接成功后产生的可执行文件在 Makefile 中的 PROGRAM 变量中设定。这一项不能为空。为自己程序的可执行文件起一个有意义的名子吧。</li>
</ul>
<h4>指定源程序</h4>
<ul>
<li>要编译的源程序由其所在的路径和文件的扩展名两项来确定。由于头文件是通过包含来使用的,所以在这里说的源程序不应包含头文件。 </li>
<li>程序所在的路径在 SRCDIRS 中设定。如果源程序分布在不同的目录中,那么需要在 SRCDIRS 中一一指定,并且路径名之间用空格分隔。 </li>
<li>在 SRCEXTS 中指定程序中使用的文件类型。C/C++ 程序的扩展名一般有比较固定的几种形式:.c、.C、.cc、.cpp、.CPP、.c++、.cp、或者.cxx(参见 man gcc)。扩展名决定了程序是 C 还是 C++ 程序:.c 是 C 程序,其它扩展名表示 C++ 程序。一般固定使用其中的一种扩展名即可。但是也有可能需要使用多种扩展名,这可以在 SOURCE_EXT 中一一指定,各个扩展名之间用空格分隔。 </li>
</ul>
<p>虽然并不常用,但是 C 程序也可以被作为 C++ 程序编译。这可以通过在 Makefile 中设置 CC = $(CXX) 和 CFLAGS = $(CXXFLAGS) 两项即可实现。</p>
<p>这个 Makefile 支持 C、C++ 以及 C/C++ 混合三种编译方式:</p>
<ul>
<li>如果只指定 .c 扩展名,那么这是一个 C 程序,用 $(CC) 表示的编译命令进行编译和连接。</li>
<li>如果指定的是除 .c 之外的其它扩展名(如 .cc、.cpp、.cxx 等),那么这是一个 C++ 程序,用 $(CXX) 进行编译和连接。</li>
<li>如果既指定了 .c,又指定了其它 C++ 扩展名,那么这是 C/C++ 混合程序,将用 $(CC) 编译其中的 C 程序,用 $(CXX) 编译其中的 C++ 程序,最后再用 $(CXX) 连接程序。
这些工作都是 make 根据在 Makefile 中提供的程序文件类型(扩展名)自动判断进行的,不需要用户干预。</li>
</ul>
<h4>指定编译选项</h4>
<ul>
<li>编译选项由三部分组成:预处理选项、编译选项以及连接选项,分别由 CPPFLAGS、CFLAGS与CXXFLAGS、LDFLAGS 指定。 </li>
<li>CPPFLAGS 选项可参考 C 预处理命令 cpp 的说明,但是注意不能包含 -M 以及和 -M 有关的选项。如果是 C/C++ 混合编程,也可以在这里设置 C/C++ 的一些共同的编译选项。 </li>
<li>CFLAGS 和 CXXFLAGS 两个变量通常用来指定编译选项。前者仅仅用于指定 C 程序的编译选项,后者仅仅用于指定 C++ 程序的编译选项。其实也可以在两个变量中指定一些预处理选项(即一些本来应该放在 CPPFLAGS 中的选项),和 CPPFLAGS 并没有明确的界限。 </li>
<li>连接选项在 LDFLAGS 中指定。如果只使用 C/C++ 标准库,一般没有必要设置。如果使用了非标准库,应该在这里指定连接需要的选项,如库所在的路径、库名以及其它联接选项。 </li>
<li>现在的库一般都提供了一个相应的 .pc 文件来记录使用库所需要的预编译选项、编译选项和连接选项等信息,通过 pkg-config 可以动态提取这些选项。与由用户显式指定各个选项相比,使用 pkg-config 来访问库提供的选项更方便、更具通用性。在后面可以看到一个 GTK+ 程序的例子,其编译和连接选项的指定就是用 pkg-config 实现的。</li>
</ul>
<h4>编译和连接</h4>
<p> 上面的各项设置好之后保存 Makefile 文件。执行 make 命令,程序就开始编译了。 <br/>
命令 make 会根据 Makefile 中设置好的路径和文件类型搜索源程序文件,然后根据文件的类型调用相应的编译命令、使用相应的编译选项对程序进行编译。 <br/>
编译成功之后程序的连接会自动进行。如果没有错误的话最终会产生程序的可执行文件。 <br/>
注意:在对程序编译之后,会产生和源程序文件一一对应的 .d 文件。这是表示依赖关系的文件,通过它们 make 决定在源程序文件变动之后要进行哪些更新。为每一个源程序文件建立相应的 .d 文件这也是 GNU Make 推荐的方式。<br/></p>
<h4>Makefile 目标(Targets)</h4>
<p>下面是关于这个 Makefile 提供的目标以及它所完成的功能:</p>
<p>make
编译和连接程序。相当于 make all。</p>
<p>make objs
仅仅编译程序产生 .o 目标文件,不进行连接(一般很少单独使用)。</p>
<p>make clean
删除编译产生的目标文件和依赖文件。</p>
<p>make cleanall
删除目标文件、依赖文件以及可执行文件。</p>
<p>make rebuild
重新编译和连接程序。相当于 make clean && make all。</p>
<p>关于这个 Makefile 的实现原理不准备详细解释了。如果有兴趣的话,可参考文末列出的“参考资料”。</p>
<p><br/></p>
<h4>Makefile 的内容如下:</h4>
<div class="highlight"><pre><code><span class="cp">############################################################# </span>
<span class="cp"># Generic Makefile for C/C++ Program </span>
<span class="cp"># </span>
<span class="cp"># License: GPL (General Public License) </span>
<span class="cp"># Author: whyglinux <whyglinux AT gmail DOT com> </span>
<span class="cp"># Date: 2006/03/04 (version 0.1) </span>
<span class="cp"># 2007/03/24 (version 0.2) </span>
<span class="cp"># 2007/04/09 (version 0.3) </span>
<span class="cp"># 2007/06/26 (version 0.4) </span>
<span class="cp"># 2008/04/05 (version 0.5) </span>
<span class="cp"># </span>
<span class="cp"># Description: </span>
<span class="cp"># ------------ </span>
<span class="cp"># This is an easily customizable makefile template. The purpose is to </span>
<span class="cp"># provide an instant building environment for C/C++ programs. </span>
<span class="cp"># </span>
<span class="cp"># It searches all the C/C++ source files in the specified directories, </span>
<span class="cp"># makes dependencies, compiles and links to form an executable. </span>
<span class="cp"># </span>
<span class="cp"># Besides its default ability to build C/C++ programs which use only </span>
<span class="cp"># standard C/C++ libraries, you can customize the Makefile to build </span>
<span class="cp"># those using other libraries. Once done, without any changes you can </span>
<span class="cp"># then build programs using the same or less libraries, even if source </span>
<span class="cp"># files are renamed, added or removed. Therefore, it is particularly </span>
<span class="cp"># convenient to use it to build codes for experimental or study use. </span>
<span class="cp"># </span>
<span class="cp"># GNU make is expected to use the Makefile. Other versions of makes </span>
<span class="cp"># may or may not work. </span>
<span class="cp"># </span>
<span class="cp"># Usage: </span>
<span class="cp"># ------ </span>
<span class="cp"># 1. Copy the Makefile to your program directory. </span>
<span class="cp"># 2. Customize in the "Customizable Section" only if necessary: </span>
<span class="cp"># * to use non-standard C/C++ libraries, set pre-processor or compiler </span>
<span class="cp"># options to <MY_CFLAGS> and linker ones to <MY_LIBS> </span>
<span class="cp"># (See Makefile.gtk+-2.0 for an example) </span>
<span class="cp"># * to search sources in more directories, set to <SRCDIRS> </span>
<span class="cp"># * to specify your favorite program name, set to <PROGRAM> </span>
<span class="cp"># 3. Type make to start building your program. </span>
<span class="cp"># </span>
<span class="cp"># Make Target: </span>
<span class="cp"># ------------ </span>
<span class="cp"># The Makefile provides the following targets to make: </span>
<span class="cp"># $ make compile and link </span>
<span class="cp"># $ make NODEP=yes compile and link without generating dependencies </span>
<span class="cp"># $ make objs compile only (no linking) </span>
<span class="cp"># $ make tags create tags for Emacs editor </span>
<span class="cp"># $ make ctags create ctags for VI editor </span>
<span class="cp"># $ make clean clean objects and the executable file </span>
<span class="cp"># $ make distclean clean objects, the executable and dependencies </span>
<span class="cp"># $ make help get the usage of the makefile </span>
<span class="cp"># </span>
<span class="cp">#=========================================================================== </span>
<span class="cp">## Customizable Section: adapt those variables to suit your program. </span>
<span class="cp">##========================================================================== </span>
<span class="cp"># The pre-processor and compiler options. </span>
<span class="n">MY_CFLAGS</span> <span class="o">=</span>
<span class="cp"># The linker options. </span>
<span class="n">MY_LIBS</span> <span class="o">=</span>
<span class="cp"># The pre-processor options used by the cpp (man cpp for more). </span>
<span class="n">CPPFLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">Wall</span>
<span class="cp"># The options used in linking as well as in any direct use of ld. </span>
<span class="n">LDFLAGS</span> <span class="o">=</span>
<span class="cp"># The directories in which source files reside. </span>
<span class="cp"># If not specified, only the current directory will be serached. </span>
<span class="n">SRCDIRS</span> <span class="o">=</span>
<span class="cp"># The executable file name. </span>
<span class="cp"># If not specified, current directory name or `a.out' will be used. </span>
<span class="n">PROGRAM</span> <span class="o">=</span>
<span class="cp">## Implicit Section: change the following only when necessary. </span>
<span class="cp">##========================================================================== </span>
<span class="cp"># The source file types (headers excluded). </span>
<span class="cp"># .c indicates C source files, and others C++ ones. </span>
<span class="n">SRCEXTS</span> <span class="o">=</span> <span class="p">.</span><span class="n">c</span> <span class="p">.</span><span class="n">C</span> <span class="p">.</span><span class="n">cc</span> <span class="p">.</span><span class="n">cpp</span> <span class="p">.</span><span class="n">CPP</span> <span class="p">.</span><span class="n">c</span><span class="o">++</span> <span class="p">.</span><span class="n">cxx</span> <span class="p">.</span><span class="n">cp</span>
<span class="cp"># The header file types. </span>
<span class="n">HDREXTS</span> <span class="o">=</span> <span class="p">.</span><span class="n">h</span> <span class="p">.</span><span class="n">H</span> <span class="p">.</span><span class="n">hh</span> <span class="p">.</span><span class="n">hpp</span> <span class="p">.</span><span class="n">HPP</span> <span class="p">.</span><span class="n">h</span><span class="o">++</span> <span class="p">.</span><span class="n">hxx</span> <span class="p">.</span><span class="n">hp</span>
<span class="cp"># The pre-processor and compiler options. </span>
<span class="cp"># Users can override those variables from the command line. </span>
<span class="n">CFLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">O2</span>
<span class="n">CXXFLAGS</span><span class="o">=</span> <span class="o">-</span><span class="n">g</span> <span class="o">-</span><span class="n">O2</span>
<span class="cp"># The C program compiler. </span>
<span class="cp">#CC = gcc </span>
<span class="cp"># The C++ program compiler. </span>
<span class="cp">#CXX = g++ </span>
<span class="cp"># Un-comment the following line to compile C programs as C++ ones. </span>
<span class="cp">#CC = $(CXX) </span>
<span class="cp"># The command used to delete file. </span>
<span class="cp">#RM = rm -f </span>
<span class="n">ETAGS</span> <span class="o">=</span> <span class="n">etags</span>
<span class="n">ETAGSFLAGS</span> <span class="o">=</span>
<span class="n">CTAGS</span> <span class="o">=</span> <span class="n">ctags</span>
<span class="n">CTAGSFLAGS</span> <span class="o">=</span>
<span class="cp">## Stable Section: usually no need to be changed. But you can add more. </span>
<span class="cp">##========================================================================== </span>
<span class="n">SHELL</span> <span class="o">=</span> <span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">sh</span>
<span class="n">EMPTY</span> <span class="o">=</span>
<span class="n">SPACE</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">EMPTY</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">EMPTY</span><span class="p">)</span>
<span class="n">ifeq</span> <span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">),)</span>
<span class="n">CUR_PATH_NAMES</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">subst</span> <span class="o">/</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">SPACE</span><span class="p">),</span><span class="err">$</span><span class="p">(</span><span class="n">subst</span> <span class="err">$</span><span class="p">(</span><span class="n">SPACE</span><span class="p">),</span><span class="n">_</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">CURDIR</span><span class="p">)))</span>
<span class="n">PROGRAM</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">word</span> <span class="err">$</span><span class="p">(</span><span class="n">words</span> <span class="err">$</span><span class="p">(</span><span class="n">CUR_PATH_NAMES</span><span class="p">)),</span><span class="err">$</span><span class="p">(</span><span class="n">CUR_PATH_NAMES</span><span class="p">))</span>
<span class="n">ifeq</span> <span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">),)</span>
<span class="n">PROGRAM</span> <span class="o">=</span> <span class="n">a</span><span class="p">.</span><span class="n">out</span>
<span class="n">endif</span>
<span class="n">endif</span>
<span class="n">ifeq</span> <span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="n">SRCDIRS</span><span class="p">),)</span>
<span class="n">SRCDIRS</span> <span class="o">=</span> <span class="p">.</span>
<span class="n">endif</span>
<span class="n">SOURCES</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">foreach</span> <span class="n">d</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">SRCDIRS</span><span class="p">),</span><span class="err">$</span><span class="p">(</span><span class="n">wildcard</span> <span class="err">$</span><span class="p">(</span><span class="n">addprefix</span> <span class="err">$</span><span class="p">(</span><span class="n">d</span><span class="p">)</span><span class="o">/*</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">SRCEXTS</span><span class="p">))))</span>
<span class="n">HEADERS</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">foreach</span> <span class="n">d</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">SRCDIRS</span><span class="p">),</span><span class="err">$</span><span class="p">(</span><span class="n">wildcard</span> <span class="err">$</span><span class="p">(</span><span class="n">addprefix</span> <span class="err">$</span><span class="p">(</span><span class="n">d</span><span class="p">)</span><span class="o">/*</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">HDREXTS</span><span class="p">))))</span>
<span class="n">SRC_CXX</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">filter</span><span class="o">-</span><span class="n">out</span> <span class="o">%</span><span class="p">.</span><span class="n">c</span><span class="p">,</span><span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">))</span>
<span class="n">OBJS</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">addsuffix</span> <span class="p">.</span><span class="n">o</span><span class="p">,</span> <span class="err">$</span><span class="p">(</span><span class="n">basename</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)))</span>
<span class="n">DEPS</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="nl">OBJS</span><span class="p">:.</span><span class="n">o</span><span class="o">=</span><span class="p">.</span><span class="n">d</span><span class="p">)</span>
<span class="cp">## Define some useful variables. </span>
<span class="n">DEP_OPT</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="k">if</span> <span class="err">`$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="o">--</span><span class="n">version</span> <span class="o">|</span> <span class="n">grep</span> <span class="s">"GCC"</span> <span class="o">>/</span><span class="n">dev</span><span class="o">/</span><span class="n">null</span><span class="err">`</span><span class="p">;</span> <span class="n">then</span> <span class="err">\</span>
<span class="n">echo</span> <span class="s">"-MM -MP"</span><span class="p">;</span> <span class="k">else</span> <span class="n">echo</span> <span class="s">"-M"</span><span class="p">;</span> <span class="n">fi</span> <span class="p">)</span>
<span class="n">DEPEND</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">DEP_OPT</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CPPFLAGS</span><span class="p">)</span>
<span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">subst</span> <span class="o">-</span><span class="n">g</span> <span class="p">,,</span><span class="err">$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">))</span>
<span class="n">COMPILE</span><span class="p">.</span><span class="n">c</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CPPFLAGS</span><span class="p">)</span> <span class="o">-</span><span class="n">c</span>
<span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">CXX</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CXXFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CPPFLAGS</span><span class="p">)</span> <span class="o">-</span><span class="n">c</span>
<span class="n">LINK</span><span class="p">.</span><span class="n">c</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CPPFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LDFLAGS</span><span class="p">)</span>
<span class="n">LINK</span><span class="p">.</span><span class="n">cxx</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">CXX</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_CFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CXXFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CPPFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LDFLAGS</span><span class="p">)</span>
<span class="p">.</span><span class="nl">PHONY</span><span class="p">:</span> <span class="n">all</span> <span class="n">objs</span> <span class="n">tags</span> <span class="n">ctags</span> <span class="n">clean</span> <span class="n">distclean</span> <span class="n">help</span> <span class="n">show</span>
<span class="cp"># Delete the default suffixes </span>
<span class="p">.</span><span class="nl">SUFFIXES</span><span class="p">:</span>
<span class="nl">all</span><span class="p">:</span> <span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">)</span>
<span class="cp"># Rules for creating dependency files (.d). </span>
<span class="cp">#------------------------------------------ </span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">c</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">C</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cc</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cpp</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">CPP</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">c</span><span class="o">++</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cp</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">d</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cxx</span>
<span class="err">@</span><span class="n">echo</span> <span class="o">-</span><span class="n">n</span> <span class="err">$</span><span class="p">(</span><span class="n">dir</span> <span class="err">$</span><span class="o"><</span><span class="p">)</span> <span class="o">></span> <span class="err">$@</span>
<span class="err">@$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">.</span><span class="n">d</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">>></span> <span class="err">$@</span>
<span class="cp"># Rules for generating object files (.o). </span>
<span class="cp">#---------------------------------------- </span>
<span class="nl">objs</span><span class="p">:</span><span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">c</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">c</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">C</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cc</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cpp</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">CPP</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">c</span><span class="o">++</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cp</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="o">%</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span><span class="o">%</span><span class="p">.</span><span class="n">cxx</span>
<span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="o"><</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="cp"># Rules for generating the tags. </span>
<span class="cp">#------------------------------------- </span>
<span class="nl">tags</span><span class="p">:</span> <span class="err">$</span><span class="p">(</span><span class="n">HEADERS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)</span>
<span class="err">$</span><span class="p">(</span><span class="n">ETAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">ETAGSFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">HEADERS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)</span>
<span class="nl">ctags</span><span class="p">:</span> <span class="err">$</span><span class="p">(</span><span class="n">HEADERS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)</span>
<span class="err">$</span><span class="p">(</span><span class="n">CTAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">CTAGSFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">HEADERS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)</span>
<span class="cp"># Rules for generating the executable. </span>
<span class="cp">#------------------------------------- </span>
<span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">)</span><span class="o">:</span><span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span>
<span class="n">ifeq</span> <span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="n">SRC_CXX</span><span class="p">),)</span> <span class="err">#</span> <span class="n">C</span> <span class="n">program</span>
<span class="err">$</span><span class="p">(</span><span class="n">LINK</span><span class="p">.</span><span class="n">c</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_LIBS</span><span class="p">)</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="err">@</span><span class="n">echo</span> <span class="n">Type</span> <span class="p">.</span><span class="o">/</span><span class="err">$@</span> <span class="n">to</span> <span class="n">execute</span> <span class="n">the</span> <span class="n">program</span><span class="p">.</span>
<span class="k">else</span> <span class="err">#</span> <span class="n">C</span><span class="o">++</span> <span class="n">program</span>
<span class="err">$</span><span class="p">(</span><span class="n">LINK</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">MY_LIBS</span><span class="p">)</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
<span class="err">@</span><span class="n">echo</span> <span class="n">Type</span> <span class="p">.</span><span class="o">/</span><span class="err">$@</span> <span class="n">to</span> <span class="n">execute</span> <span class="n">the</span> <span class="n">program</span><span class="p">.</span>
<span class="n">endif</span>
<span class="n">ifndef</span> <span class="n">NODEP</span>
<span class="n">ifneq</span> <span class="p">(</span><span class="err">$</span><span class="p">(</span><span class="n">DEPS</span><span class="p">),)</span>
<span class="n">sinclude</span> <span class="err">$</span><span class="p">(</span><span class="n">DEPS</span><span class="p">)</span>
<span class="n">endif</span>
<span class="n">endif</span>
<span class="nl">clean</span><span class="p">:</span>
<span class="err">$</span><span class="p">(</span><span class="n">RM</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">).</span><span class="n">exe</span>
<span class="nl">distclean</span><span class="p">:</span> <span class="n">clean</span>
<span class="err">$</span><span class="p">(</span><span class="n">RM</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">DEPS</span><span class="p">)</span> <span class="n">TAGS</span>
<span class="cp"># Show help. </span>
<span class="nl">help</span><span class="p">:</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">Generic</span> <span class="n">Makefile</span> <span class="k">for</span> <span class="n">C</span><span class="o">/</span><span class="n">C</span><span class="o">++</span> <span class="n">Programs</span> <span class="p">(</span><span class="n">gcmakefile</span><span class="p">)</span> <span class="n">version</span> <span class="mf">0.5</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">Copyright</span> <span class="p">(</span><span class="n">C</span><span class="p">)</span> <span class="mi">2007</span><span class="p">,</span> <span class="mi">2008</span> <span class="n">whyglinux</span> <span class="o"><</span><span class="n">whyglinux</span><span class="err">@</span><span class="n">hotmail</span><span class="p">.</span><span class="n">com</span><span class="o">></span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">Usage</span><span class="p">:</span> <span class="n">make</span> <span class="p">[</span><span class="n">TARGET</span><span class="p">]</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">TARGETS</span><span class="p">:</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">all</span> <span class="p">(</span><span class="o">=</span><span class="n">make</span><span class="p">)</span> <span class="n">compile</span> <span class="n">and</span> <span class="n">link</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">NODEP</span><span class="o">=</span><span class="n">yes</span> <span class="n">make</span> <span class="n">without</span> <span class="n">generating</span> <span class="n">dependencies</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">objs</span> <span class="n">compile</span> <span class="n">only</span> <span class="p">(</span><span class="n">no</span> <span class="n">linking</span><span class="p">).</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">tags</span> <span class="n">create</span> <span class="n">tags</span> <span class="k">for</span> <span class="n">Emacs</span> <span class="n">editor</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">ctags</span> <span class="n">create</span> <span class="n">ctags</span> <span class="k">for</span> <span class="n">VI</span> <span class="n">editor</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">clean</span> <span class="n">clean</span> <span class="n">objects</span> <span class="n">and</span> <span class="n">the</span> <span class="n">executable</span> <span class="n">file</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">distclean</span> <span class="n">clean</span> <span class="n">objects</span><span class="p">,</span> <span class="n">the</span> <span class="n">executable</span> <span class="n">and</span> <span class="n">dependencies</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">show</span> <span class="n">show</span> <span class="n">variables</span> <span class="p">(</span><span class="k">for</span> <span class="n">debug</span> <span class="n">use</span> <span class="n">only</span><span class="p">).</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span> <span class="n">help</span> <span class="n">print</span> <span class="n">this</span> <span class="n">message</span><span class="p">.</span><span class="err">'</span>
<span class="err">@</span><span class="n">echo</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">Report</span> <span class="n">bugs</span> <span class="n">to</span> <span class="o"><</span><span class="n">whyglinux</span> <span class="n">AT</span> <span class="n">gmail</span> <span class="n">DOT</span> <span class="n">com</span><span class="o">></span><span class="p">.</span><span class="err">'</span>
<span class="cp"># Show variables (for debug use only.) </span>
<span class="nl">show</span><span class="p">:</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">PROGRAM</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">PROGRAM</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">SRCDIRS</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">SRCDIRS</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">HEADERS</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">HEADERS</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">SOURCES</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">SOURCES</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">SRC_CXX</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">SRC_CXX</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">OBJS</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">OBJS</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">DEPS</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">DEPS</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="nl">DEPEND</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">DEPEND</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">COMPILE</span><span class="p">.</span><span class="nl">c</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">c</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">COMPILE</span><span class="p">.</span><span class="nl">cxx</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">COMPILE</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">link</span><span class="p">.</span><span class="nl">c</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">LINK</span><span class="p">.</span><span class="n">c</span><span class="p">)</span>
<span class="err">@</span><span class="n">echo</span> <span class="err">'</span><span class="n">link</span><span class="p">.</span><span class="nl">cxx</span> <span class="p">:</span><span class="err">'</span> <span class="err">$</span><span class="p">(</span><span class="n">LINK</span><span class="p">.</span><span class="n">cxx</span><span class="p">)</span>
<span class="cp">## End of the Makefile ## Suggestions are welcome ## All rights reserved ## </span>
<span class="cp">##############################################################</span>
</code></pre></div>
<p>下面提供两个例子来具体说明上面 Makefile 的用法。</p>
<h4>例一 Hello World 程序</h4>
<p>这个程序的功能是输出 Hello, world! 这样一行文字。由 hello.h、hello.c、main.cxx 三个文件组成。前两个文件是 C 程序,后一个是 C++ 程序,因此这是一个 C 和 C++ 混编程序。</p>
<div class="highlight"><pre><code><span class="cm">/* File name: hello.h </span>
<span class="cm"> * C header file </span>
<span class="cm"> */</span>
<span class="cp">#ifndef HELLO_H </span>
<span class="cp">#define HELLO_H </span>
<span class="cp">#ifdef __cplusplus </span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="p">{</span>
<span class="cp">#endif </span>
<span class="kt">void</span> <span class="n">print_hello</span><span class="p">();</span>
<span class="cp">#ifdef __cplusplus </span>
<span class="p">}</span>
<span class="cp">#endif </span>
<span class="cp">#endif </span>
<span class="cm">/* File name: hello.c </span>
<span class="cm"> * C source file. </span>
<span class="cm"> */</span>
<span class="cp">#include "hello.h" </span>
<span class="cp">#include <stdio.h> </span>
<span class="kt">void</span> <span class="n">print_hello</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">puts</span><span class="p">(</span> <span class="s">"Hello, world!"</span> <span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* File name: main.cxx </span>
<span class="cm"> * C++ source file. </span>
<span class="cm"> */</span>
<span class="cp">#include "hello.h" </span>
<span class="kt">int</span> <span class="n">main</span><span class="p">()</span>
<span class="p">{</span>
<span class="n">print_hello</span><span class="p">();</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>建立一个新的目录,然后把这三个文件拷贝到目录中,也把 Makefile 文件拷贝到目录中。之后,对 Makefile 的相关项目进行如下设置:</p>
<div class="highlight"><pre><code><span class="nl">PROGRAM</span> <span class="p">:</span><span class="o">=</span> <span class="n">hello</span> <span class="err">#</span> <span class="err">设置运行程序名</span>
<span class="nl">SRCDIRS</span> <span class="p">:</span><span class="o">=</span> <span class="p">.</span> <span class="err">#</span> <span class="err">源程序位于当前目录下</span>
<span class="nl">SRCEXTS</span> <span class="p">:</span><span class="o">=</span> <span class="p">.</span><span class="n">c</span> <span class="p">.</span><span class="n">cxx</span> <span class="err">#</span> <span class="err">源程序文件有</span> <span class="p">.</span><span class="n">c</span> <span class="err">和</span> <span class="p">.</span><span class="n">cxx</span> <span class="err">两种类型</span>
<span class="nl">CFLAGS</span> <span class="p">:</span><span class="o">=</span> <span class="o">-</span><span class="n">g</span> <span class="err">#</span> <span class="err">为</span> <span class="n">C</span> <span class="err">目标程序包含</span> <span class="n">GDB</span> <span class="err">可用的调试信息</span>
<span class="nl">CXXFLAGS</span> <span class="p">:</span><span class="o">=</span> <span class="o">-</span><span class="n">g</span> <span class="err">#</span> <span class="err">为</span> <span class="n">C</span><span class="o">++</span> <span class="err">目标程序包含</span> <span class="n">GDB</span> <span class="err">可用的调试信息</span>
</code></pre></div>
<p> 由于这个简单的程序只使用了 C 标准库的函数(puts),所以对于 CFLAGS 和 CXXFLAGS 没有过多的要求,LDFLAGS 和 CPPFLAGS 选项也无需设置。</p>
<p> 经过上面的设置之后,执行 make 命令就可以编译程序了。如果没有错误出现的话,./hello 就可以运行程序了。</p>
<p> 如果修改了源程序的话,可以看到只有和修改有关的源文件被编译。也可以再为程序添加新的源文件,只要它们的扩展名是已经在 Makefile 中设置过的,那么就没有必要修改 Makefile。</p>
<h4>例二 GTK+ 版 Hello World 程序</h4>
<p> 这个 GTK+ 2.0 版的 Hello World 程序可以从下面的网址上得到:<a href="http://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD。当然,要编译">http://www.gtk.org/tutorial/c58.html#SEC-HELLOWORLD。当然,要编译</a> GTK+ 程序,还需要你的系统上已经安装好了 GTK+。
跟第一个例子一样,单独创建一个新的目录,把上面网页中提供的程序保存为 main.c 文件。对 Makefile 做如下设置:
</p>
<div class="highlight"><pre><code><span class="nl">PROGRAM</span> <span class="p">:</span><span class="o">=</span> <span class="n">hello</span> <span class="err">#</span> <span class="err">设置运行程序名</span>
<span class="nl">SRCDIRS</span> <span class="p">:</span><span class="o">=</span> <span class="p">.</span> <span class="err">#</span> <span class="err">源程序位于当前目录下</span>
<span class="nl">SRCEXTS</span> <span class="p">:</span><span class="o">=</span> <span class="p">.</span><span class="n">c</span> <span class="err">#</span> <span class="err">源程序文件只有</span> <span class="p">.</span><span class="n">c</span> <span class="err">一种类</span>
<span class="nl">CFLAGS</span> <span class="p">:</span><span class="o">=</span> <span class="err">`</span><span class="n">pkg</span><span class="o">-</span><span class="n">config</span> <span class="o">--</span><span class="n">cflags</span> <span class="n">gtk</span><span class="o">+-</span><span class="mf">2.0</span><span class="err">`</span> <span class="err">#</span> <span class="n">CFLAGS</span>
<span class="nl">LDFLAGS</span> <span class="p">:</span><span class="o">=</span> <span class="err">`</span><span class="n">pkg</span><span class="o">-</span><span class="n">config</span> <span class="o">--</span><span class="n">libs</span> <span class="n">gtk</span><span class="o">+-</span><span class="mf">2.0</span><span class="err">`</span> <span class="err">#</span> <span class="n">LDFLAGS</span>
</code></pre></div>
<p>这是一个 C 程序,所以 CXXFLAGS 没有必要设置——即使被设置了也不会被使用。
编译和连接 GTK+ 库所需要的 CFLAGS 和 LDFLAGS 由 pkg-config 程序自动产生。
现在就可以运行 make 命令编译、./hello 执行这个 GTK+ 程序了</p>
C/C++通用Makefile
Mac常用
https://www.zruibin.cn/article/mac_chang_yong.html
2014-05-13 14:44
2015-10-13 14:40
<h3>Mac下PATH环境变量设置</h3>
<p>如果没特殊说明,设置PATH的语法都为:</p>
<div class="highlight"><pre><code><span class="cp">#中间用冒号隔开</span>
<span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="err">$</span><span class="nl">PATH</span><span class="p">:</span><span class="o"><</span><span class="n">PATH</span> <span class="mi">1</span><span class="o">>:<</span><span class="n">PATH</span> <span class="mi">2</span><span class="o">>:<</span><span class="n">PATH</span> <span class="mi">3</span><span class="o">>:------:<</span><span class="n">PATH</span> <span class="n">N</span><span class="o">></span>
</code></pre></div>
<p>1.创建并以 TextEdit 的方式打开 ~/.bash_profile 文件</p>
<p>touch ~/.bash_profile;</p>
<p>open -t ~/.bash_profile</p>
<p>2.新增环境变量</p>
<div class="highlight"><pre><code><span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="s">"$HOME/.rbenv/bin:$PATH"</span>
</code></pre></div>
<p>3.让以上所做的配置生效</p>
<div class="highlight"><pre><code><span class="n">source</span> <span class="o">~/</span><span class="p">.</span><span class="n">bash_profile</span>
</code></pre></div>
<p>4.查看是否生效(有时可能需要关闭当前 Terminal 窗口重新开启一个)</p>
<div class="highlight"><pre><code><span class="n">echo</span> <span class="err">$</span><span class="n">PATH</span>
</code></pre></div>
<p>需要注意的一点(冒号乃环境变量的分隔符):</p>
<div class="highlight"><pre><code><span class="err">$</span><span class="n">HOME</span><span class="o">/</span><span class="p">.</span><span class="n">rbenv</span><span class="o">/</span><span class="nl">bin</span><span class="p">:</span><span class="err">$</span><span class="n">PATH</span> <span class="err">中的</span> <span class="err">$</span><span class="n">PATH</span> <span class="err">特指</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="nl">bin</span><span class="p">:</span><span class="o">/</span><span class="nl">bin</span><span class="p">:</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="nl">sbin</span><span class="p">:</span><span class="o">/</span><span class="nl">sbin</span><span class="p">:</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span>
</code></pre></div>
<p>如果要添加多个环境变量的话,需按照如下的方式来书写:</p>
<div class="highlight"><pre><code><span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="s">"$PATH:/Applications/MacVim-snapshot-68"</span>
<span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="s">"$HOME/.rbenv/bin:$PATH"</span>
<span class="n">eval</span> <span class="s">"$(rbenv init -)"</span>
<span class="n">export</span> <span class="n">PATH</span><span class="o">=</span><span class="s">"$HOME/.rbenv/bin:$PATH"</span>
<span class="n">eval</span> <span class="s">"$(rbenv init -)"</span>
</code></pre></div>
<p>显示/隐藏Mac隐藏文件命令如下(注意其中的空格并且区分大小写):
显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool true</p>
<p>隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles -bool false</p>
<p>或者显示Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles YES</p>
<p>隐藏Mac隐藏文件的命令:defaults write com.apple.finder AppleShowAllFiles NO</p>
<p>输完单击Enter键,退出终端,重新启动Finder就可以了</p>
<p>重启Finder:鼠标单击窗口左上角的苹果标志-->强制退出-->Finder-->重新启动打开终端,进到所在的目录,然后出入一下代码find . -name ".svn" | xargs rm -Rf</p>
<p><br/></p>
<p><hr>
<br/></p>
<h3>mac忘记密码的解决办法</h3>
<p>开机, 启动时按“cmd+S”。这时,你会进入Single User Model,出现像DOS一样的提示符 #root>。请在#root>下 输入 (注意空格, 大小写)</p>
<p> fsck -y</p>
<p> mount -uaw /</p>
<p> rm /var/db/.AppleSetupDone</p>
<p> reboot</p>
<p> 紧接着,苹果电脑会重启 ,并且在开机后出现新装机时的欢迎界面。你需要像第一次打开苹果电脑一样, 重新建立一个新的管理员账号(数据会保留)。当开机完毕之后,在新的管理员下请打开 系统预制 - 账户。打开最下面的锁, 当跳出密码框时, 输入新的管理员帐号密码。这时,你会看到出现至少两个账号,包括了新的管理员的帐号和你原来的帐号。你可以点中原来的账号, 选 密码 - 更改密码。注意,你不需要有原先的密码就直接可以设定新密码了。下一步就是点下面的登陆选项 (小房子)。选中自动以右边的身份登陆, 同时在下拉菜单中选你原先的账号。当重启或注销之后,你可以用新密码登陆原帐户了。当然,你也可以将刚刚新建的帐户删除。</p>
<p><br/></p>
<p><hr>
<br/></p>
<h3>CocoaPods pod install/pod update更新慢的问题</h3>
<p>最近使用CocoaPods来添加第三方类库,无论是执行pod install还是pod update都卡在了Analyzing dependencies不动
原因在于当执行以上两个命令的时候会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少。加参数的命令如下:</p>
<div class="highlight"><pre><code><span class="n">pod</span> <span class="n">install</span> <span class="o">--</span><span class="n">verbose</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">repo</span><span class="o">-</span><span class="n">update</span>
<span class="n">pod</span> <span class="n">update</span> <span class="o">--</span><span class="n">verbose</span> <span class="o">--</span><span class="n">no</span><span class="o">-</span><span class="n">repo</span><span class="o">-</span><span class="n">update</span>
</code></pre></div>
<div class="highlight"><pre><code><span class="err">命令行更新(安装)步骤</span>
<span class="err">$</span> <span class="n">sudo</span> <span class="n">gem</span> <span class="n">update</span> <span class="o">--</span><span class="n">system</span> <span class="c1">// 先更新gem,国内需要切换源</span>
<span class="err">$</span> <span class="n">gem</span> <span class="n">sources</span> <span class="o">--</span><span class="n">remove</span>
<span class="err">$</span> <span class="n">gem</span> <span class="n">sources</span> <span class="o">-</span><span class="n">a</span>
<span class="err">$</span> <span class="n">gem</span> <span class="n">sources</span> <span class="o">-</span><span class="n">l</span>
<span class="err">\</span><span class="o">*</span><span class="err">\</span><span class="o">*</span><span class="err">\</span><span class="o">*</span> <span class="n">CURRENT</span> <span class="n">SOURCES</span> <span class="err">\</span><span class="o">*</span><span class="err">\</span><span class="o">*</span><span class="err">\</span><span class="o">*</span>
<span class="err">$</span> <span class="n">sudo</span> <span class="n">gem</span> <span class="n">install</span> <span class="n">cocoapods</span> <span class="c1">// 安装cocoapods</span>
<span class="err">$</span> <span class="n">pod</span> <span class="n">setup</span>
</code></pre></div>
<p><br/></p>
<p><hr>
<br/></p>
<h3>item2是mac下非常好用的一款终端。但默认的配色实在不好用,经过一翻搜索终于找到了比较满意的。</h3>
<p>配色(Terminal终端要变色也要设置第一步)
1.先要修改~/.bash_profile.代码如下</p>
<div class="highlight"><pre><code><span class="cp">#enables colorin the terminal bash shell export</span>
<span class="n">export</span> <span class="n">CLICOLOR</span><span class="o">=</span><span class="mi">1</span>
<span class="cp">#sets up thecolor scheme for list export</span>
<span class="n">export</span> <span class="n">LSCOLORS</span><span class="o">=</span><span class="n">gxfxcxdxbxegedabagacad</span>
<span class="cp">#sets up theprompt color (currently a green similar to linux terminal)</span>
<span class="n">export</span> <span class="n">PS1</span><span class="o">=</span><span class="err">'\</span><span class="p">[</span><span class="err">\</span><span class="mo">033</span><span class="p">[</span><span class="mo">01</span><span class="p">;</span><span class="mi">32</span><span class="n">m</span><span class="err">\</span><span class="p">]</span><span class="err">\</span><span class="n">u</span><span class="err">@\</span><span class="n">h</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="mo">033</span><span class="p">[</span><span class="mo">00</span><span class="n">m</span><span class="err">\</span><span class="p">]</span><span class="o">:</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="mo">033</span><span class="p">[</span><span class="mo">01</span><span class="p">;</span><span class="mi">36</span><span class="n">m</span><span class="err">\</span><span class="p">]</span><span class="err">\</span><span class="n">w</span><span class="err">\</span><span class="p">[</span><span class="err">\</span><span class="mo">033</span><span class="p">[</span><span class="mo">00</span><span class="n">m</span><span class="err">\</span><span class="p">]</span><span class="err">\$</span> <span class="err">'</span>
<span class="cp">#enables colorfor iTerm</span>
<span class="n">export</span> <span class="n">TERM</span><span class="o">=</span><span class="n">xterm</span><span class="o">-</span><span class="mi">256</span><span class="n">color</span>
</code></pre></div>
<p>每个代码都有注释,第二三行设置终端名,也就是当前用户名、目录,并且变色处理,方便认别。</p>
<p>2.选择喜欢的配色方案。</p>
<p>在Preferences->Profiles->Colors的load presets可以选择某个配色方案。也可以自己下载。在网站<a href="http://iterm2colorschemes.com/,几乎可以找到所有可用的配色方案。大家自己选择吧">http://iterm2colorschemes.com/,几乎可以找到所有可用的配色方案。大家自己选择吧</a></p>
<p>大小写敏感
对于目录中经常有大写字母的情况,使用tab变得很麻烦。google之后找到了解决办法,取消大小写敏感。代码如下:</p>
<div class="highlight"><pre><code><span class="n">echo</span> <span class="s">"set completion-ignore-case On"</span> <span class="o">>></span> <span class="o">~/</span><span class="p">.</span><span class="n">inputrc</span>
</code></pre></div>
<p>快捷揵
这大概是item吸引用户的魅力所在了。</p>
<p>1.⌘ +数字在各 tab标签直接来回切换</p>
<p>2.选择即复制 + 鼠标中键粘贴,这个很实用</p>
<p>3.⌘ + f所查找的内容会被自动复制</p>
<p>4.⌘ + d 横着分屏 / ⌘ + shift + d 竖着分屏</p>
<p>5.⌘ + r = clear,而且只是换到新一屏,不会想 clear一样创建一个空屏</p>
<p>6.ctrl + u 清空当前行,无论光标在什么位置</p>
<p>7.输入开头命令后 按⌘ + ;会自动列出输入过的命令</p>
<p>8.⌘ + shift + h 会列出剪切板历史</p>
<p>9.可以在 Preferences > keys设置全局快捷键调出 iterm,这个也可以用过 Alfred实现</p>
<p>10.⌘← / ⌘→ 到一行命令最左边/最右边 ,这个功能同 C+a / C+e</p>
<p>11.⌥← /⌥→按单词前移/后移,相当与 C+f / C+b,其实这个功能在Iterm中已经预定义好了,⌥f /⌥b,看个人习惯了</p>
<p>再来些linux上也通用的快捷键:</p>
<p>C+a / C+e 这个几乎在哪都可以使用</p>
<p>C+p / !! 上一条命令</p>
<p>C+k 从光标处删至命令行尾 (本来 C+u是删至命令行首,但iterm中是删掉整行)</p>
<p>C+w A+d 从光标处删至字首/尾</p>
<p>C+h C+d 删掉光标前后的自负</p>
<p>C+y 粘贴至光标后</p>
<p>C+r 搜索命令历史,这个较常用</p>
<p>窗口说明</p>
<p>iterm2的窗口分为3个等级:window , tab , pane。请看下图。</p>
<p>此图下侧的是tab, 图中分左右的是pane。用上这两项,iterm2才真得是好用。</p>
<p>默认的话,新建pan是有快捷键的Cmd+d,切换pane有默认设置 Cmd+[ 和 Cmd+] .但是新建tab是没有默认快捷键的,这个用户可以自己设置,在Preferences->Keys。</p>
<p>笔者设新建tab的快捷键是Cmd+t 。</p>
<p><br/></p>
<p><hr>
<br/></p>
<h3>Macports</h3>
<p>一.通过(.pkg)安装: Mac OS X Package (.pkg) Installer</p>
<p>访问官方网站: <a href="http://www.macports.org/install.php">http://www.macports.org/install.php</a></p>
<p><a href="http://distfiles.macports.org/MacPorts/MacPorts-2.1.1-10.7-Lion.pkg">http://distfiles.macports.org/MacPorts/MacPorts-2.1.1-10.7-Lion.pkg</a></p>
<p>二.通过(Source)安装MacPorts:Source Installation</p>
<p>1.cd到Downloads/目录下wget下载 MacPorts-2.1.1.tar.gz</p>
<p>输入: wget <a href="https://distfiles.macports.org/MacPorts/MacPorts-2.1.1.tar.gz">https://distfiles.macports.org/MacPorts/MacPorts-2.1.1.tar.gz</a></p>
<p>2.解压 MacPorts-2.1.1.tar.gz 输入: tar zxvf MacPorts-2.1.1.tar.gz (tar jxvf MacPorts2.1.1.tar.bz2)</p>
<p>3.cd到解压到的目录MacPorts-2.1.1输入: ./configure && make && sudo make install 安装</p>
<p>中间提示输入密码完成安装!</p>
<p>7.然后将/opt/local/bin和/opt/local/sbin添加到$PATH搜索路径中</p>
<p>编辑/etc/profile文件 $ sudo vim /etc/profile (特许编辑,强制保存退出 wq!)文件最后加上下面两句</p>
<div class="highlight"><pre><code><span class="n">export</span> <span class="n">PATH</span><span class="o">=/</span><span class="n">opt</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="nl">bin</span><span class="p">:</span><span class="err">$</span><span class="n">PATH</span>
<span class="n">export</span> <span class="n">PATH</span><span class="o">=/</span><span class="n">opt</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="nl">sbin</span><span class="p">:</span><span class="err">$</span><span class="n">PATH</span>
</code></pre></div>
<p><br/></p>
<p><hr>
<br/></p>
<h3>Homebrew</h3>
<p>Homebrew是啥东东?apt-get和yum知道吧?Homebrew就相当于MacOS中的yum。</p>
<p>安装:终端中输入:</p>
<div class="highlight"><pre><code><span class="n">ruby</span> <span class="o">-</span><span class="n">e</span> <span class="s">"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"</span>
</code></pre></div>
<p>然后按提示输入管理员密码 如果发现安装失败,得到类似这样的错误信息:</p>
<div class="highlight"><pre><code><span class="nl">error</span><span class="p">:</span> <span class="n">unable</span> <span class="n">to</span> <span class="n">unlink</span> <span class="n">old</span> <span class="err">‘</span><span class="n">Library</span><span class="o">/</span><span class="n">ENV</span><span class="o">/</span><span class="mf">3.2.6</span><span class="err">′</span> <span class="p">(</span><span class="n">Permission</span> <span class="n">denied</span><span class="p">)</span>
</code></pre></div>
<p>那说明你遇到了权限问题,这样修复:</p>
<div class="highlight"><pre><code><span class="n">sudo</span> <span class="n">chmod</span> <span class="o">-</span><span class="n">R</span> <span class="mi">775</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">Library</span>
</code></pre></div>
<p>在安装完毕后,还需要执行这条:</p>
<div class="highlight"><pre><code><span class="n">sudo</span> <span class="n">chmod</span> <span class="o">-</span><span class="n">R</span> <span class="mi">775</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">Cellar</span>
</code></pre></div>
<p>如果安装软件后运行软件,发现提示command not found,查看一下/etc/paths文件,确保/usr/local/sbin有添加,我的paths文件内容如下:</p>
<div class="highlight"><pre><code><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">sbin</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">bin</span>
<span class="o">/</span><span class="n">bin</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span>
<span class="o">/</span><span class="n">sbin</span>
</code></pre></div>
<p>你ls -l /usr/local/sbin,会发现里面有你用brew安装过的软件的symlink:</p>
<div class="highlight"><pre><code><span class="n">lrwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span> <span class="mi">1</span> <span class="n">mac</span> <span class="n">admin</span> <span class="mi">34</span> <span class="mi">1</span> <span class="mi">19</span> <span class="mi">15</span><span class="o">:</span><span class="mo">02</span> <span class="n">iftop</span> <span class="o">-></span> <span class="p">..</span><span class="o">/</span><span class="n">Cellar</span><span class="o">/</span><span class="n">iftop</span><span class="o">/</span><span class="mf">1.0</span><span class="n">pre4</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">iftop</span>
<span class="n">lrwxr</span><span class="o">-</span><span class="n">xr</span><span class="o">-</span><span class="n">x</span> <span class="mi">1</span> <span class="n">mac</span> <span class="n">admin</span> <span class="mi">27</span> <span class="mi">1</span> <span class="mi">23</span> <span class="mi">09</span><span class="o">:</span><span class="mi">25</span> <span class="n">mtr</span> <span class="o">-></span> <span class="p">..</span><span class="o">/</span><span class="n">Cellar</span><span class="o">/</span><span class="n">mtr</span><span class="o">/</span><span class="mf">0.86</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">mtr</span>
</code></pre></div>
<p>OS升级后,如果发现brew提示如下错误:</p>
<div class="highlight"><pre><code><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="nl">brew</span><span class="p">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">Library</span><span class="o">/</span><span class="n">brew</span><span class="p">.</span><span class="nl">rb</span><span class="p">:</span> <span class="o">/</span><span class="n">System</span><span class="o">/</span><span class="n">Library</span><span class="o">/</span><span class="n">Frameworks</span><span class="o">/</span><span class="n">Ruby</span><span class="p">.</span><span class="n">framework</span><span class="o">/</span><span class="n">Versions</span><span class="o">/</span><span class="mf">1.8</span><span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="nl">ruby</span><span class="p">:</span> <span class="n">bad</span> <span class="nl">interpreter</span><span class="p">:</span> <span class="n">No</span> <span class="n">such</span> <span class="n">file</span> <span class="n">or</span> <span class="n">directory</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="nl">brew</span><span class="p">:</span> <span class="n">line</span> <span class="mi">26</span><span class="o">:</span> <span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">local</span><span class="o">/</span><span class="n">Library</span><span class="o">/</span><span class="n">brew</span><span class="p">.</span><span class="nl">rb</span><span class="p">:</span> <span class="n">Undefined</span> <span class="nl">error</span><span class="p">:</span> <span class="mi">0</span>
</code></pre></div>
<p>可以通过如下手段修复:</p>
<p>```
cd /usr/local/Library
git pull origin master</p>
<p>删除所有.svn目录</p>
<p>命令如下</p>
<p>find . -type d -name ".svn"|xargs rm -rf</p>
<p>或者</p>
<p>find . -type d -iname ".svn" -exec rm -rf {} ";</p>
Lua Install All Platform
关于Xcode
https://www.zruibin.cn/article/guan_yu_xcode.html
2014-05-09 10:07
2015-10-13 14:31
<p>如何在同一个xcode中装入多个版本的ios sdk</p>
<p>如果你真想在一个Xcode下安装多个ios SDK,只需要把 /Developer/Platforms/iPhoneOS.platform/Developer/SDKs 下的 iPhoneOSxx.sdk 拷贝到某一个Xcode下相同的位置就行了~
<img src="http://static.oschina.net/uploads/img/201405/16132738_Hw9e.png" alt=""></p></p>
<hr>
<p>编译xcode5.1以前版本的Architecture的改动,因为旧版本有些库不支持64位,改成32位的!</p>
<p>$(ARCHS_STANDARD_32_BIT)</p>
<p>Xcode 5.1默认使用ARC对于手动管理内存,编译报错:garbage collection is no longer supported</p>
<p>解决方案:打开程序后 当弹出提示框时,点击“Not Now”,然后去 "build settings" 在最下面 删除 "GCC_ENABLE_OBJC_GC" 即可。</p>
<hr>
<p>XCode兼容ARC和非ARC代码的方法</p>
<p>ARC提高开发效率,毋庸置疑。</p>
<p>在ARC开发模式下引用非ARC文件或库需进行如下操作以告诉编译器此代码需按照非ARC模式对待:</p>
<p>XCode中项目文件-》TARGETS-》Compile Sources</p>
<p>选择需要标记的文件,将该文件的Compiler Flags编辑为:-fno-objc-arc</p>
<p>同理,若想在非ARC工程中标记ARC文件,将对应文件标记为:-fobjc-arc</p>
<hr>
<p>在linux下编译c++ 程序要使用g++ 编译器,如果你要是使用gcc编译器就会有上面的报错信息,只要在在gcc后加上 -lstdc++就ok了</p>
<p>eg: g++ example.c -lstdc++</p>
<p>(二)gcc 和 g++ 是有区别的</p>
<p>(1)gcc和g++都是GNU(组织)的一个编译器。</p>
<p>(2)后缀名为.c的程序和.cpp的程序g++都会当成是c++的源程序来处理。而gcc不然,gcc会把.c的程序处理成c程序。</p>
<p>(3)对于.cpp的程序,编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。(个人觉得这条是最重要的)</p>
<hr>
<p>关于EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0))错误</p>
<p>EXC_ARITHMETIC (code=EXC_I386_DIV, subcode=0x0))</p>
<p>断点的代码为:</p>
<p>startTick(m_iActionFps, m_iTotalTime / m_iTotalFrames);</p>
<p>仔细研究上下文并没有空指针,而且iphone上跑一直没问题。后来google了很久,才找到这个非常容易忽略的问题。。。。</p>
<p>错误在于:</p>
<p>m_iTotalTime / m_iTotalFrames中,m_iTotalFrames这个分母为0了。。。</p>
<p>网上说这个错误是除0错误,而且神奇的是ios7没问题,android和ios虚拟机却运行时崩溃了</p>
<hr>
<p>最近使用CocoaPods来添加第三方类库,无论是执行pod install还是pod update都卡在了Analyzing dependencies不动
原因在于当执行以上两个命令的时候会升级CocoaPods的spec仓库,加一个参数可以省略这一步,然后速度就会提升不少。加参数的命令如下:
pod install --verbose --no-repo-update
pod update --verbose --no-repo-update</p>
<p>在你项目的Podfile里面加入这个第三方库的地址</p>
<div class="highlight"><pre><code><span class="n">pod</span> <span class="err">'</span><span class="n">XCAsyncTestCase</span><span class="err">'</span><span class="p">,</span> <span class="o">:</span><span class="n">git</span> <span class="o">=></span> <span class="err">'</span><span class="nl">https</span><span class="p">:</span><span class="c1">//github.com/iiiyu/XCAsyncTestCase.git'</span>
</code></pre></div>
关于Xcode
向iOS开发者介绍C++(二)
https://www.zruibin.cn/article/xiang_ios_kai_fa_zhe_jie_shao_c++_(_er_).html
2014-05-05 23:23
2015-10-13 14:15
<p>
<span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">原文出处:</span> <a target="_blank" href="http://www.raywenderlich.com/62990/introduction-c-ios-developers-part-2">Matt Galloway</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">译文出处:</span> <a target="_blank" href="http://www.cocoachina.com/applenews/devnews/2014/0417/8182.html">cocoachina</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">。欢迎加入</span> <a target="_blank" href="http://www.jobbole.com/groups/6/">技术翻译小组</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">。</span> <br/> <br/>
</p><p>欢迎回到向iOS开发者介绍C++系列的第二部分(<a href="http://blog.jobbole.com/66887/" target="_blank">向iOS开发者介绍C++(一)</a>) !在第一部分,我们了解了类和内存管理。在第二部分部分我们将深入了解类以及其他有意思的特征。你将会了解到什么是“模板”以及标准模板库。</p>
<p>多态性</p>
<p>简单地说,多态性是一个重载子类中函数的概念。在Objective-C中,你可能已经做过很多次,例如,子类化UIViewController和重载viewDidLoad。
<a href="http://jbcdn2.b0.upaiyun.com/2014/05/7508ee1d44ce5a2784d2051062586f0e.jpg"><img alt="parfwefrot_lion-480x305" src="http://static.oschina.net/uploads/img/201405/05232311_45Ij.jpg"/></a></p>
<p>C++的多态性比Objective-C的多态性更进一层。因此,当我解释这个强大的功能时要紧跟我的思路。</p>
<p>首先,以下为在类中重载成员函数的例子:</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">5</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">class</span> <span class="nl">Bar</span> <span class="p">:</span> <span class="n">public</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">10</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>但是,如果你这样做会发生什么呢:</p>
<div class="highlight"><pre><code><span class="n">Bar</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Bar</span><span class="p">();</span>
<span class="n">Foo</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">Foo</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="o">%</span><span class="n">i</span><span class="err">”</span><span class="p">,</span> <span class="n">f</span><span class="o">-></span><span class="n">value</span><span class="p">());</span>
<span class="c1">// Output = 5</span>
</code></pre></div>
<p>哇,这可不是你所期望的输出!我猜你认为输出值应该是10,对么?这就是C++和Objective-C最大的不同。</p>
<p>在Objective-C中,将子类指针转换成基类指针是无关紧要的。如果你向对象发消息(如调用函数),是运行时找到对象的类并调用最先派生的方法。因此,Objective-C中这种情况下,子类Bar中的方法被调用。这里凸显出了我在第一部分提到的编译时和运行时的不同。</p>
<p>在上面的例子中,编译器调用value()时,编译器的职责是计算出哪个函数需要被调用。由于f的类型是指向Foo类的指针,
它执行跳至Foo:value()的代码。编译器不知道f实际上是Bar类的指针。</p>
<p>在这个简单的例子中,你可以认为编译器能推断出f是Bar类的指针。但是想一想如果f确实是一个函数的输入值的话将会发生什么呢?这种情况下编译器将不会知道它是一个继承了Foo类的指针。</p>
<p>静态绑定和动态绑定
上面的例子很好的证明了C++和Objective-C最主要的区别–静态绑定和动态绑定。上面的例子是静态绑定的例子。编译器负责解决调用哪个函数,并且在编译完成后这个过程将被存储为二进制。在运行时不能改变这个过程。</p>
<p>这与Objective-C中方法调用形成了对比,这就是动态绑定的一个例子。运行时本身负责决定调用哪个函数。</p>
<p>动态绑定会使Objective-C很强大。你可能已经意识到了在运行时可以为类方法或者交换方法实现。这在静态绑定语言中是不能实现的,静态绑定是在编译时调用方法的。</p>
<p>但是,在C++中还不止这样!C++通常是静态绑定,但是也可以使用动态绑定机制,即“虚函数”。</p>
<p>虚函数和虚表</p>
<p>虚函数提供动态绑定机制。通过使用table lookup(每个类定义一个表),虚函数推迟到runtime时选择调用哪个函数。然而,跟静态绑定相比,这确实引起了运行时轻微的间接成本。除了调用函数外,table lookup是必须的。静态绑定时仅需要执行调用的函数。</p>
<p>使用虚函数很简单,只需要将关键词“virtual”添加到谈及的函数。例如上面的例子用虚函数方式写的话,如下:</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span> <span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">5</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">class</span> <span class="nl">Bar</span> <span class="p">:</span> <span class="n">public</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span> <span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">10</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>现在想一想运行同样的代码会发生什么:</p>
<div class="highlight"><pre><code><span class="n">Bar</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Bar</span><span class="p">();</span>
<span class="n">Foo</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">Foo</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="o">%</span><span class="n">i</span><span class="err">”</span><span class="p">,</span> <span class="n">f</span><span class="o">-></span><span class="n">value</span><span class="p">());</span>
<span class="c1">// Output = 10</span>
</code></pre></div>
<p>这正是前面所预期的输出值,对吧?因此在C++中可以用动态绑定,但是你需要根据遇到的情况决定是用静态绑定还是动态绑定。</p>
<p>在C++中这种类型的灵活性是司空见惯的,这使C++成为一种多范型的语言。Objective-C很大程度上迫使你进入严格的模式,尤其是用Cocoa框架时。而C++中,很多都是由开发者决定的。</p>
<p>现在开始了解虚拟函数是如何发挥作用的吧!</p>
<p><a href="http://jbcdn2.b0.upaiyun.com/2014/05/46e5e3e18ec1bc64e837ae6f995d072a.png"><img alt="picrgregregerg3" src="http://static.oschina.net/uploads/img/201405/05232311_e1iF.png" /></a></p>
<p>虚函数的内部功能</p>
<p>在你明白虚函数是怎样工作之前,你需要知道非虚函数是如何工作的。</p>
<p>想一想下面的代码:</p>
<div class="highlight"><pre><code><span class="bp">MyClass</span> <span class="n">a</span><span class="p">;</span>
<span class="n">a</span><span class="p">.</span><span class="n">foo</span><span class="p">();</span>
</code></pre></div>
<p>如果foo()是个非虚函数,那么编译器将会把它转换成代码,直接跳到MyClass类的foo()函数。</p>
<p>但是记住,这就是非虚函数的问题所在。回想之前的例子,如果这个类是多态的,那么编译器由于不知道变量的全部类型,也就不知道应该跳到哪个函数。这就需要一种方法在运行时查找到正确的函数。
要完成这种查找,虚函数要使用“virtual table”(也称“v-table”,虚表)。虚表是一个查找表来将函数映射到其实现上,并且每个类都访问一个表。当一个虚函数被调用时,编译器发出代码来检索对象的虚表从而查找到正确的函数。</p>
<p>回顾上面的例子来看看这是如何工作的:</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span> <span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">5</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
<span class="k">class</span> <span class="nl">Bar</span> <span class="p">:</span> <span class="n">public</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span> <span class="kt">int</span> <span class="n">value</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">10</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
<span class="n">Bar</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Bar</span><span class="p">();</span>
<span class="n">Foo</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">Foo</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="o">%</span><span class="n">i</span><span class="err">”</span><span class="p">,</span> <span class="n">f</span><span class="o">-></span><span class="n">value</span><span class="p">());</span>
<span class="c1">// Output = 10</span>
</code></pre></div>
<p>当你创建一个类指针b和一个Bar类的实例,那么它的虚表将是Bar类的虚表。当b指针转换为Foo类的一个指针时,它并没有改变对象的内容,虚表仍然是Bar类的虚表而不是Foo类的。因此当查找v-table以调用value()时,结果是将调用Bar::value()。</p>
<p>构造函数和析构函数</p>
<p>每个对象在其生命周期中都要经历两个重要阶段:构造函数和析构函数。C++允许你同时控制这两个阶段。在Objective-C中与这两阶段相同的是初始化方法(例如,init或者以init开头的其他方法)和dealloc(释放内存)。</p>
<p>C++中定义构造函数时与类同名。正如在Objective-C中有多个初始化方法,你也可以定义多个构造函数。</p>
<p>例如,下面这个类中有两个不同的构造函数:</p>
<div class="highlight"><pre><code><span class="n">classFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">intx</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Foo</span><span class="p">()</span> <span class="p">{</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">Foo</span><span class="p">(</span><span class="n">intx</span><span class="p">)</span> <span class="p">{</span>
<span class="n">this</span><span class="o">-></span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>这就是两个构造函数,一个是默认构造函数Foo(),另一个构造函数含有一个参数来设置成员变量。</p>
<p>如上例中,如果在构造函数中给成员变量初始化,有用少量代码实现的方法。不需要自己去设置成员变量的值,你可以用下面的语法:</p>
<div class="highlight"><pre><code><span class="n">classFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">intx</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Foo</span><span class="p">()</span> <span class="o">:</span> <span class="n">x</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="n">Foo</span><span class="p">(</span><span class="n">intx</span><span class="p">)</span> <span class="o">:</span> <span class="n">x</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>通常来讲,如果仅仅是给成员变量赋值的话可以用上面这种方式。但是如果你需要用到逻辑或者调用其他函数的话,那么你就要实现函数主体。你也可以结合以上两种方式。</p>
<p>当用继承时,你需要调用父类的构造函数。在Objective-C中,你通常采用先调用父类指定的初始化程序的方法。</p>
<p>在C++中,你可以这样做:</p>
<div class="highlight"><pre><code><span class="n">classFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">intx</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Foo</span><span class="p">()</span> <span class="o">:</span> <span class="n">x</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="n">Foo</span><span class="p">(</span><span class="n">intx</span><span class="p">)</span> <span class="o">:</span> <span class="n">x</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nl">classBar</span> <span class="p">:</span><span class="n">publicFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">inty</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Bar</span><span class="p">()</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">(),</span> <span class="n">y</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="n">Bar</span><span class="p">(</span><span class="n">intx</span><span class="p">)</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="n">y</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="n">Bar</span><span class="p">(</span><span class="n">intx</span><span class="p">,</span><span class="n">inty</span><span class="p">)</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="n">y</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>函数签名后,列表中的第一个元素表示对父类构造函数的调用。你可以调用任何一个你想要的超类构造函数。</p>
<p>C++没有一个指定的初始化程序。目前,没有办法调用同一个类的构造函数。在Objective-C中,有一个指定的初始化程序可以被其他初始化程序调用,并且只有这个指定的初始化程序去调用超类的指定初始化程序,例如:</p>
<div class="highlight"><pre><code><span class="k">@interface</span> <span class="nc">Foo</span> : <span class="bp">NSObject</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">Foo</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">init</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">self</span> <span class="o">=</span> <span class="p">[</span><span class="nb">super</span> <span class="n">init</span><span class="p">])</span> <span class="p">{</span> <span class="c1">///< Call to super’s designated initialiser</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithFoo:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">foo</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">self</span> <span class="o">=</span> <span class="p">[</span><span class="nb">self</span> <span class="n">init</span><span class="p">])</span> <span class="p">{</span> <span class="c1">///< Call to self’s designated initialiser</span>
<span class="c1">// …</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nf">initWithBar:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">bar</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nb">self</span> <span class="o">=</span> <span class="p">[</span><span class="nb">self</span> <span class="n">init</span><span class="p">])</span> <span class="p">{</span> <span class="c1">///< Call to self’s designated initialiser</span>
<span class="c1">// …</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nb">self</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@end</span>
</code></pre></div>
<p>在C++中,虽然你可以调用父类的构造函数,但是目前调用自己的构造函数仍是不合法的。因此,下面的解决方案很常见:</p>
<div class="highlight"><pre><code><span class="nl">classBar</span> <span class="p">:</span><span class="n">publicFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">inty</span><span class="p">;</span>
<span class="n">voidcommonInit</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Perform common initialisation</span>
<span class="p">}</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Bar</span><span class="p">()</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">()</span> <span class="p">{</span>
<span class="n">this</span><span class="o">-></span><span class="n">commonInit</span><span class="p">();</span>
<span class="p">}</span>
<span class="n">Bar</span><span class="p">(</span><span class="n">inty</span><span class="p">)</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">(),</span> <span class="n">y</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="p">{</span>
<span class="n">this</span><span class="o">-></span><span class="n">commonInit</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>
然而,这十分麻烦。为什么你不能用Bar(int y)调用Bar(),然后在Bar()中这样写“Bar::commonInit()”呢?毕竟,Objective-C中恰恰是这样写的。</p>
<p>2011年发布了最新版的C++标准:C++11。在这个更新的标准中确实可以这样做。目前仍有许多C++代码还没有按C++11标准来更新,所以知道这两种方法很重要。任何2011年前标准的C++代码都按以下这种方式:</p>
<div class="highlight"><pre><code><span class="nl">classBar</span> <span class="p">:</span><span class="n">publicFoo</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">inty</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Bar</span><span class="p">()</span> <span class="o">:</span> <span class="n">Foo</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Perform common initialisation</span>
<span class="p">}</span>
<span class="n">Bar</span><span class="p">(</span><span class="n">inty</span><span class="p">)</span> <span class="o">:</span> <span class="n">Bar</span><span class="p">()</span> <span class="p">{</span>
<span class="n">this</span><span class="o">-></span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>这种方法唯一一个不足的地方是,你不能在同一个类中调用构造函数的同时设置一个成员变量。上面的例子中,成员变量y在构造函数主体中设置。</p>
<p>注意:在2011年C++11标准成为一个完整的标准,起初称为C++ 0x。意思是在2000年至2009年之间这项标准成熟的话,x可以替换为这一年的最后一个数字。然而比预期的时间要晚,因此以11为结尾!所有的现代编译器,包括clang,现在都支持C++11标准。</p>
<p>以上为构造函数,那么析构函数呢?当一个堆对象被删除或者一个栈函数溢出时会调用析构函数。在析构函数中你需要做的事情就是清理对象。</p>
<p>析构函数中不能有任何参数。同样,在Objective-C中dealloc也不需要任何参数。因此每个类中只有一个析构函数。</p>
<p>在类中定义析构函数时在函数名字前要加前缀–波浪号(~)。如下:</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="o">~</span><span class="n">Foo</span><span class="p">()</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="n">Foo</span> <span class="n">destructor</span><span class="err">\</span><span class="n">n</span><span class="err">”</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>看一下当你的类被继承时,会发生什么:</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="nl">Bar</span> <span class="p">:</span> <span class="n">public</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="o">~</span><span class="n">Bar</span><span class="p">()</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="n">Bar</span> <span class="n">destructor</span><span class="err">\</span><span class="n">n</span><span class="err">”</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>如果你不这样写的话,当通过Foo指针删除Bar类的一个实例的时候将会发生异常,如下:</p>
<div class="highlight"><pre><code><span class="n">Bar</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span> <span class="n">new</span> <span class="n">Bar</span><span class="p">();</span>
<span class="n">Foo</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">Foo</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">;</span>
<span class="n">delete</span> <span class="n">f</span><span class="p">;</span>
<span class="c1">// Output:</span>
<span class="c1">// Foo destructor</span>
</code></pre></div>
<p>这样是错误的。删除的应该是Bar类的实例,但是为什么是去调用Foo类的析构函数呢?</p>
<p>回想一下,之前发生的同样的问题,你是使用虚函数解决的。这个正是同样的问题。编译器看到是一个Foo需要被删除,因为Foo的析构函数并不是虚函数,所以编译器认为要调用的是Foo的析构函数。</p>
<p>解决这个问题的办法就是将析构函数定义为虚函数,如下:</p>
<div class="highlight"><pre><code><span class="n">classFoo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span><span class="o">~</span><span class="n">Foo</span><span class="p">()</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="n">Foo</span> <span class="n">destructor</span><span class="err">\</span><span class="n">n</span><span class="err">”</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nl">classBar</span> <span class="p">:</span><span class="n">publicFoo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">virtual</span><span class="o">~</span><span class="n">Bar</span><span class="p">()</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="err">“</span><span class="n">Bar</span> <span class="n">destructor</span><span class="err">\</span><span class="n">n</span><span class="err">”</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="n">Bar</span> <span class="o">*</span><span class="n">b</span> <span class="o">=</span><span class="n">newBar</span><span class="p">();</span>
<span class="n">Foo</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="p">(</span><span class="n">Foo</span><span class="o">*</span><span class="p">)</span><span class="n">b</span><span class="p">;</span>
<span class="n">deletef</span><span class="p">;</span>
<span class="c1">// Output:</span>
<span class="c1">// Bar destructor</span>
<span class="c1">// Foo destructor</span>
</code></pre></div>
<p>这就接近了期望的结果,但最终结果不同于之前使用虚函数得到的结果。在这里,两个函数都被调用了。首先Bar的析构函数被调用,然后Foo的析构函数被调用。为什么呢?</p>
<p>这是因为析构函数比较特殊。由于Foo的析构函数是父类的析构函数,所以Bar的析构函数自动调用Foo的析构函数。</p>
<p>这正是所需要的,正如Objective-c中的ARC方法中,你调用的是父类的dealloc。</p>
<p><a href="http://jbcdn2.b0.upaiyun.com/2014/05/ddd35eaa843900a1e83751c0e608da15.png"><img alt="pregregergrgwrgic4" src="http://static.oschina.net/uploads/img/201405/05232311_6AQ4.png" /></a></p>
<p>你可能在想这个:你认为编译器会为你做这个事情,但是并不是在所有类中都是最佳方法。</p>
<p>例如,如果你没有从某个类继承呢?如果析构函数是虚函数,那么每次都要通过虚表来删除一个实例,或许这种间接方法并不是你需要的。C++中你可以自己做决定,另一个方法很强大,但是开发者必须清楚发生了什么。</p>
<p>注意:除非你确定你不需要继承一个类,否则一定要定义析构函数为虚函数。
运算符重载</p>
<p>在Objective-C中没有运算符重载的概念,但是这并不复杂。</p>
<p>操作符是实体,如我们熟悉的+,-,*,/。例如,你可以用“+”运算符与标准常量(操作数)做如下运算:</p>
<div class="highlight"><pre><code><span class="n">intx</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">inty</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">5</span><span class="p">;</span><span class="c1">///< y = 10</span>
</code></pre></div>
<p>运算符“+”在这里的作用显而易见,将x加上5然后返回一个值。或许这个还不够明显,如果以函数的形式就很清楚了:</p>
<div class="highlight"><pre><code><span class="n">intx</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">inty</span> <span class="o">=</span> <span class="n">add</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="mi">5</span><span class="p">);</span>
</code></pre></div>
<p>在函数add()中,两个参数相加并返回一个值。</p>
<p>在C++中,在类中使用操作符时是可以定义功能函数的。这一功能很强大。当然,这也不是总能行得通的。例如,将两个Person类相加就无任何实际意义。</p>
<p>然而,这一特性很有用处。考虑下面的类:</p>
<div class="highlight"><pre><code><span class="n">classDoubleInt</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">intx</span><span class="p">;</span>
<span class="n">inty</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">DoubleInt</span><span class="p">(</span><span class="n">intx</span><span class="p">,</span><span class="n">inty</span><span class="p">)</span> <span class="o">:</span> <span class="n">x</span><span class="p">(</span><span class="n">x</span><span class="p">),</span> <span class="n">y</span><span class="p">(</span><span class="n">y</span><span class="p">)</span> <span class="p">{}</span>
<span class="p">};</span>
</code></pre></div>
<p>这样做可能更好一些:</p>
<div class="highlight"><pre><code><span class="n">DoubleInt</span> <span class="nf">a</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">DoubleInt</span> <span class="nf">b</span><span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
<span class="n">DoubleInt</span> <span class="n">c</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
</code></pre></div>
<p>我们想要将DoubleInt(4, 6)的值赋值给变量c,即将两个DoubleInt的实例x和y相加,然后赋值给c。事实证明这很简单。你需要做的就是给DoubleInt类添加一个方法,即:</p>
<div class="highlight"><pre><code><span class="n">DoubleInt</span> <span class="n">operator</span><span class="o">+</span><span class="p">(</span><span class="n">constDoubleInt</span> <span class="o">&</span><span class="n">rhs</span><span class="p">)</span> <span class="p">{</span>
<span class="n">returnDoubleInt</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">rhs</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">rhs</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>函数operator+很特别。编译器将使用这个函数,当它看到“+”运算符任一侧的DoubleInt时。“+”运算符左边的对象将调用这个函数,将“+”运算符右边的对象作为参数进行传递。因此,经常命名参数为“rhs”,意思是“右边”。</p>
<p>由于使用实参的副本不仅没必要还可能会改变值,函数的参数将作为引用,可能会创建一个新的对象。此外,这个对象将是常量,这是因为在相加的过程中,对于“+”运算符的右边来讲这是非法的。</p>
<p>C++能做的不仅是这些。你可能不仅仅想把DoubleInt添加至DoubleInt。你可能想要给DoubleInt添加一个整数。这些都是可能实现的!</p>
<p>为实现此操作,你需要实现如下成员函数:</p>
<div class="highlight"><pre><code><span class="n">DoubleInt</span> <span class="n">operator</span><span class="o">+</span><span class="p">(</span><span class="n">constint</span><span class="o">&</span><span class="n">rhs</span><span class="p">)</span> <span class="p">{</span>
<span class="n">returnDoubleInt</span><span class="p">(</span><span class="n">x</span> <span class="o">+</span> <span class="n">rhs</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">rhs</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>然后你可以这样做:</p>
<div class="highlight"><pre><code><span class="n">DoubleInt</span> <span class="nf">a</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="n">DoubleInt</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="mi">10</span><span class="p">;</span>
<span class="c1">// b = DoubleInt(11, 12);</span>
</code></pre></div>
<p>很有用吧!</p>
<p>除了加法运算,其他运算也可以这样做。你可以重载++, –, +=, -=, *, ->等等。这里就不一一列举了。如果想要对运算符重载做更多了解,你可以访问learncpp.com,这里有整个章节在介绍运算符重载。</p>
<p>模板</p>
<p>在C++中,模板很有意思。</p>
<p>你是否发现你经常会重复写相同的函数或者类,但只是函数或者类的类型不同呢?例如,交换两个值的函数:</p>
<div class="highlight"><pre><code><span class="n">voidswap</span><span class="p">(</span><span class="kt">int</span><span class="o">&</span><span class="n">a</span><span class="p">,</span><span class="kt">int</span><span class="o">&</span><span class="n">b</span><span class="p">)</span> <span class="p">{</span>
<span class="n">inttemp</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">b</span><span class="p">;</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>注:这里是对参数做引用传递,以确保是对函数的实参作交换。如果两个参数是用值传递,那么所交换的值只是实参的副本。这个例子很好的说明了C++中引用好处。
上面的例子只适用于整数类型。如果是浮点数类型,那么你需要写另一个函数:</p>
<div class="highlight"><pre><code><span class="n">voidswap</span><span class="p">(</span><span class="kt">float</span><span class="o">&</span><span class="n">a</span><span class="p">,</span><span class="kt">float</span><span class="o">&</span><span class="n">b</span><span class="p">)</span> <span class="p">{</span>
<span class="n">floattemp</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">b</span><span class="p">;</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>如果你重复写函数的主体,这样很不明智。C++介绍一种语法可以有效的忽略变量的类型。你可以通过模板这个特性来实现这一功能。取代上面的两种方法,在C++中,你可以这样写:</p>
<div class="highlight"><pre><code><span class="n">template</span><span class="o"><</span><span class="n">typenameT</span><span class="o">></span>
<span class="n">voidswap</span><span class="p">(</span><span class="n">T</span> <span class="n">a</span><span class="p">,</span> <span class="n">T</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
<span class="n">T</span> <span class="n">temp</span> <span class="o">=</span> <span class="n">a</span><span class="p">;</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">b</span><span class="p">;</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">temp</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>因此,你的函数可以交换任何类型的参数。你可以用以下任一种方式来调用函数:</p>
<div class="highlight"><pre><code><span class="n">intix</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="n">iy</span> <span class="o">=</span> <span class="mi">2</span><span class="p">;</span>
<span class="n">swap</span><span class="p">(</span><span class="n">ix</span><span class="p">,</span> <span class="n">iy</span><span class="p">);</span>
<span class="n">floatfx</span> <span class="o">=</span> <span class="mf">3.141</span><span class="p">,</span> <span class="n">iy</span> <span class="o">=</span> <span class="mf">2.901</span><span class="p">;</span>
<span class="n">swap</span><span class="p">(</span><span class="n">fx</span><span class="p">,</span> <span class="n">fy</span><span class="p">);</span>
<span class="n">Person</span> <span class="nf">px</span><span class="p">(</span><span class="err">“</span><span class="n">Matt</span> <span class="n">Galloway</span><span class="err">”</span><span class="p">),</span> <span class="n">py</span><span class="p">(</span><span class="err">“</span><span class="n">Ray</span> <span class="n">Wenderlich</span><span class="err">”</span><span class="p">);</span>
<span class="n">swap</span><span class="p">(</span><span class="n">px</span><span class="p">,</span> <span class="n">py</span><span class="p">);</span>
</code></pre></div>
<p>但是,你在用模板的时候仍需仔细。只有在头文件中实现模板函数,这种方法才能起作用。这是由模板的编译方式决定的。使用模板函数时,如果函数类型不存在,编译器会根据类型实例化一个函数模板。</p>
<p>考虑到编译器需要知道模板函数的实现,你需要在头文件中定义一个实现,并且在使用的时候必须要包含这个头文件。</p>
<p>同理,如果要修改模板函数中的实现,所有用到这个函数的文件都需要重编译。相比之下,如果在实现文件中修改函数或者实现类成员函数,那么只有这个实现文件需要重编译。</p>
<p>因此,过度地使用模板会使应用程序很繁琐。但是正如C++中很多方法,模板的作用很大。</p>
<p>模板类</p>
<p>不仅仅有模板函数,还可以在整个类中使用模板。</p>
<p>假设你的类中有三个值,这三个值用来存储一些数据。首先,你想用整数类型,因此你要这样写:</p>
<div class="highlight"><pre><code><span class="n">classIntTriplet</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">inta</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">IntTriplet</span><span class="p">(</span><span class="n">inta</span><span class="p">,</span><span class="n">intb</span><span class="p">,</span><span class="n">intc</span><span class="p">)</span> <span class="o">:</span> <span class="n">a</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="n">b</span><span class="p">(</span><span class="n">b</span><span class="p">),</span> <span class="n">c</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{}</span>
<span class="n">intgetA</span><span class="p">()</span> <span class="p">{</span><span class="n">returna</span><span class="p">;</span> <span class="p">}</span>
<span class="n">intgetB</span><span class="p">()</span> <span class="p">{</span><span class="n">returnb</span><span class="p">;</span> <span class="p">}</span>
<span class="n">intgetC</span><span class="p">()</span> <span class="p">{</span><span class="n">returnc</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>但是,你继续写程序时发现你需要三个浮点型数据。这是你又要写一个类,如下:</p>
<div class="highlight"><pre><code><span class="n">classFloatTriplet</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">floata</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">FloatTriplet</span><span class="p">(</span><span class="n">floata</span><span class="p">,</span><span class="n">floatb</span><span class="p">,</span><span class="n">floatc</span><span class="p">)</span> <span class="o">:</span> <span class="n">a</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="n">b</span><span class="p">(</span><span class="n">b</span><span class="p">),</span> <span class="n">c</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{}</span>
<span class="n">floatgetA</span><span class="p">()</span> <span class="p">{</span><span class="n">returna</span><span class="p">;</span> <span class="p">}</span>
<span class="n">floatgetB</span><span class="p">()</span> <span class="p">{</span><span class="n">returnb</span><span class="p">;</span> <span class="p">}</span>
<span class="n">floatgetC</span><span class="p">()</span> <span class="p">{</span><span class="n">returnc</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>这里,模板就会很有用。与模板函数相同,可以在类中使用模板。语法是一样的。上面的两个类可以写成这样:</p>
<div class="highlight"><pre><code><span class="n">template</span><span class="o"><</span><span class="n">typenameT</span><span class="o">></span>
<span class="n">classTriplet</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">T</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Triplet</span><span class="p">(</span><span class="n">T</span> <span class="n">a</span><span class="p">,</span> <span class="n">T</span> <span class="n">b</span><span class="p">,</span> <span class="n">T</span> <span class="n">c</span><span class="p">)</span> <span class="o">:</span> <span class="n">a</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="n">b</span><span class="p">(</span><span class="n">b</span><span class="p">),</span> <span class="n">c</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{}</span>
<span class="n">T</span> <span class="n">getA</span><span class="p">()</span> <span class="p">{</span><span class="n">returna</span><span class="p">;</span> <span class="p">}</span>
<span class="n">T</span> <span class="n">getB</span><span class="p">()</span> <span class="p">{</span><span class="n">returnb</span><span class="p">;</span> <span class="p">}</span>
<span class="n">T</span> <span class="n">getC</span><span class="p">()</span> <span class="p">{</span><span class="n">returnc</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>但是,用模板类需要做一些细微的改动。使用模板函数不会改变代码,这是因为参数类型允许模板推断需要做什么。然而,使用模板类时,你要告诉编译器你需要模板类使用什么类型。</p>
<p>幸运的是,这个很简单。用上面的模板类也很简单:</p>
<div class="highlight"><pre><code><span class="n">Triplet</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">intTriplet</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="n">Triplet</span><span class="o"><</span><span class="kt">float</span><span class="o">></span> <span class="n">floatTriplet</span><span class="p">(</span><span class="mf">3.141</span><span class="p">,</span> <span class="mf">2.901</span><span class="p">,</span> <span class="mf">10.5</span><span class="p">);</span>
<span class="n">Triplet</span><span class="o"><</span><span class="n">Person</span><span class="o">></span> <span class="n">personTriplet</span><span class="p">(</span><span class="n">Person</span><span class="p">(</span><span class="err">“</span><span class="n">Matt</span><span class="err">”</span><span class="p">),</span> <span class="n">Person</span><span class="p">(</span><span class="err">“</span><span class="n">Ray</span><span class="err">”</span><span class="p">),</span> <span class="n">Person</span><span class="p">(</span><span class="err">“</span><span class="n">Bob</span><span class="err">”</span><span class="p">));</span>
</code></pre></div>
<p>很强大,对吧?</p>
<p><a href="http://jbcdn2.b0.upaiyun.com/2014/05/e60be5ef685b10d6b1fb93793e6223af.png"><img alt="prgergrwegergic5" src="http://static.oschina.net/uploads/img/201405/05232311_1Lg1.png" /></a></p>
<p>此外,模板函数和模板类并不局限于单个未知类型。三重态的类可以被扩展以支持任何三种类型,而不是每个值必须是同样的类型。</p>
<p>要做到这一点,只需要扩展提供更多类型的模板定义,如下:</p>
<div class="highlight"><pre><code><span class="n">template</span><span class="o"><</span><span class="n">typenameTA</span><span class="p">,</span><span class="n">typenameTB</span><span class="p">,</span><span class="n">typenameTC</span><span class="o">></span>
<span class="n">classTriplet</span> <span class="p">{</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">TA</span> <span class="n">a</span><span class="p">;</span>
<span class="n">TB</span> <span class="n">b</span><span class="p">;</span>
<span class="n">TC</span> <span class="n">c</span><span class="p">;</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">Triplet</span><span class="p">(</span><span class="n">TA</span> <span class="n">a</span><span class="p">,</span> <span class="n">TB</span> <span class="n">b</span><span class="p">,</span> <span class="n">TC</span> <span class="n">c</span><span class="p">)</span> <span class="o">:</span> <span class="n">a</span><span class="p">(</span><span class="n">a</span><span class="p">),</span> <span class="n">b</span><span class="p">(</span><span class="n">b</span><span class="p">),</span> <span class="n">c</span><span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="p">{}</span>
<span class="n">TA</span> <span class="n">getA</span><span class="p">()</span> <span class="p">{</span><span class="n">returna</span><span class="p">;</span> <span class="p">}</span>
<span class="n">TB</span> <span class="n">getB</span><span class="p">()</span> <span class="p">{</span><span class="n">returnb</span><span class="p">;</span> <span class="p">}</span>
<span class="n">TC</span> <span class="n">getC</span><span class="p">()</span> <span class="p">{</span><span class="n">returnc</span><span class="p">;</span> <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>以上模板中有三个不同类型,每个类型都在代码中的适当位置被使用。</p>
<p>使用这样的模板也很简单,如下所示:</p>
<p>1
Triplet<int,float, Person> mixedTriplet(1, 3.141, Person(“Matt”));
以上为模板的间接。接下来看看大量使用其特性的一个库–标准模板库</p>
<p>标准模板库(STL)</p>
<p>每个规范的编程语言都有一个标准库,这个标准库包含通用的数据结构、算法以及函数。在Objective-C中你有Foundation。其中,包含NSArray、NSDictionary等熟悉或者不熟悉的成员函数。在C++中,标准模板库(简称STL)包含这些标准代码。</p>
<p>之所以成为标准模板库,是因为在这个库中使用了大量的模板。</p>
<p>STL中有很多内容,要介绍所有需要很长时间,所以在这里我只介绍一些重要的。</p>
<p>容器</p>
<p>数组、字典和集合都是对象的容器。在Objective-C中,Foundation框架包含了大部分常用容器的实现。在C++中,STL包含了这些实现。实际上,STL所包含的的容器要比Foundation多一些。</p>
<p>在STL中有两点与NSArray不同。分别是vector(列表)和list(列表)。两个都可以存储对象的序列,但是每个容器都有自己的优点和缺点。在C++中,从所给的容器中选择你需要的很重要。</p>
<p>首先,看一看vector的用法:</p>
<div class="highlight"><pre><code><span class="cp">#include <vector></span>
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">v</span><span class="p">;</span>
<span class="n">v</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="n">v</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">v</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="n">v</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="n">v</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
</code></pre></div>
<p>注意std::的用法,这是因为大部分STL位于命名空间内。STL将其所有的类放在自己的名为”std”的命名空间中以避免潜在的命名冲突。
上面的代码中,首先你创建一个vector来存放整型数据(int),然后五个整数被依次压入vector的栈顶。操作完成后,vector中将是从1到5的有序序列。</p>
<p>这里需要注意的是,正如Objective-C中,所有的容器都是可变的,没有可变或者不可变的变量。</p>
<p>访问一个vector的元素是这样完成的:</p>
<div class="highlight"><pre><code><span class="n">intfirst</span> <span class="o">=</span> <span class="n">v</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="n">intoutOfBounds</span> <span class="o">=</span> <span class="n">v</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="mi">100</span><span class="p">);</span>
</code></pre></div>
<p>这两种方法都能有效地访问vector中的元素。第一种使用方括号的方法,这便是索引C语言数组的方法。Objective-C中的下标取值方法也是用这种方法索引NSArray。</p>
<p>上面例子中的第二行使用at()成员函数,和方括号功能相同,只是at()函数需要检查是否在vector范围内索引,超出范围的话会抛出异常。</p>
<p>vector被实现为一个单一的或连续的内存块。vector的空间大小等于所存储的对象的大小乘以vector中对象数(存储4字节或者8字节的整数取决于你使用的体系结构是32位还是64位的)。</p>
<p>向vector中添加元素是很昂贵的,因为一个新的内存块需要被分配给这个新的vector。然而,访问一个确定的索引很快,因为这仅仅是访问内存中的一个字</p>
<p>std::list与std::vector很相似。但是,list的实现方式稍稍有些不同。不是作为一个连续的内存块被实现而是作为一个双向链表被实现。这意味着,list中每个的元素都包含一个数据,一个指向前一个元素的指针和一个指向后一个元素的指针。</p>
<p>由于是双向链表,插入和删除操作很简单。然而,如果要访问list中的第n个元素,就需要从0到n去遍历。</p>
<p>综上,list和vector的用法很相似:</p>
<div class="highlight"><pre><code><span class="cp">#include <list></span>
<span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">l</span><span class="p">;</span>
<span class="n">l</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="n">l</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">2</span><span class="p">);</span>
<span class="n">l</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="n">l</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">4</span><span class="p">);</span>
<span class="n">l</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="mi">5</span><span class="p">);</span>
</code></pre></div>
<p>正如上面的vector例子,这将创建一个从1到5的有序序列。但是,在list中你不能使用方括号或者at()成员函数去访问一个指定元素。你需要用一个迭代器(iterators)去遍历list。</p>
<p>你可以这样遍历list中的每个元素:</p>
<div class="highlight"><pre><code><span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o"><</span><span class="kt">int</span><span class="o">>::</span><span class="n">iterator</span> <span class="n">i</span><span class="p">;</span>
<span class="k">for</span><span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="n">l</span><span class="p">.</span><span class="n">begin</span><span class="p">();</span> <span class="n">i</span> <span class="o">!=</span> <span class="n">l</span><span class="p">.</span><span class="n">end</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">intthisInt</span> <span class="o">=</span> <span class="o">*</span><span class="n">i</span><span class="p">;</span>
<span class="c1">// Do something with thisInt</span>
<span class="p">}</span>
</code></pre></div>
<p>大多数容器类有迭代器(iterator)的概念。迭代器是一个对象,可以遍历并指向一个特定的元素。你可以通过增量或减量来控制迭代前移或者后移。</p>
<p>用迭代器在当前位置获得元素的值与使用解引用运算符(*)一样简单。</p>
<p>注:在上面的代码中,有两个运算符重载的实例。i++是迭代器重载增量运算符(++),<em>i是重载解引用操作符(</em>)。STL中大量使用了这样的运算符重载。
除了vector(向量)和list(列表),C++中还有很多容器。都有不同的特征。例如Objective-C中的集合,C++中为std::set;Objective-C中的字典,C++中为std::map。C++中,另一个常用的容器是std::pair,其中只存储两个值。</p>
<p>Shared Pointer</p>
<p>回想一下内存管理,当在C++中使用堆对象是,你需要自己处理内存。没有引用计数。在C++中确实是这样。但是在C++ 11标准中,STL中添加了一个新类,这个类中添加了引用计数,称之为shared_ptr,意思是“shared pointer”。</p>
<p>Shared Pointer是一个对象,这个对象定义一个指针以便在underlying pointer中实现引用计数。这与在Objective-C中在ARC下使用指针是相同的。例如,以下例子展示了如何用智能指针来定义一个指针去指向一个整数:</p>
<div class="highlight"><pre><code><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">p1</span><span class="p">(</span><span class="n">newint</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span>
<span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">p2</span> <span class="o">=</span> <span class="n">p1</span><span class="p">;</span>
<span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">p3</span> <span class="o">=</span> <span class="n">p1</span><span class="p">;</span>
</code></pre></div>
<p>运行这三行代码后,每个shared pointer的引用计数为3。当每个shared pointer被删除或者被释放后,引用指数减少。直到最后一个包含underlying pointer的shared pointer被删除后,底层指针被删除。</p>
<p>由于shared pointer本身就是栈对象,溢出时就会被删除。因此,shared pointer与Objective-C中的自动引用计数(ARC)下的对象指针的约束规则相同。</p>
<p>下面的例子为shared pointer创建和删除的全过程:</p>
<div class="highlight"><pre><code><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">p1</span><span class="p">(</span><span class="n">newint</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span><span class="c1">///< Use count = 1</span>
<span class="k">if</span><span class="p">(</span><span class="n">doSomething</span><span class="p">)</span> <span class="p">{</span>
<span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">p2</span> <span class="o">=</span> <span class="n">p1</span><span class="p">;</span><span class="c1">///< Use count = 2;</span>
<span class="c1">// Do something with p2</span>
<span class="p">}</span>
<span class="c1">// p2 has gone out of scope and destroyed, so use count = 1</span>
<span class="n">p1</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
<span class="c1">// p1 reset, so use count = 0</span>
<span class="c1">// The underlying int* is deleted</span>
</code></pre></div>
<p>把p1分配给p2是将p1的副本分配给p2。记住当一个函数参数是按值传递的话,是将参数的副本传给了函数。这一点是很有用处的,因为如果你将一个shared pointer传给一个函数,传递给这个函数的是一个新的shared pointer。当然,在函数结束时就会发生越界,从而被删除。所以在函数周期中,underlying pointer的使用数量将会增加。这与在Objective-C中的自动引用计数(ARC)下的引用计数功能相同。</p>
<p>当然,你需要能够获得或者使用underlying pointer,有两种方式可以实现这一操作。重载解引用操作符(*)和箭头操作符(->)以使shared pointer的工作方式本质上与一个正常的指针相同。如下:</p>
<div class="highlight"><pre><code><span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">Person</span><span class="o">></span> <span class="n">p1</span><span class="p">(</span><span class="n">newPerson</span><span class="p">(</span><span class="err">“</span><span class="n">Matt</span> <span class="n">Galloway</span><span class="err">”</span><span class="p">));</span>
<span class="n">Person</span> <span class="o">*</span><span class="n">underlyingPointer</span> <span class="o">=</span> <span class="o">*</span><span class="n">p1</span><span class="p">;</span><span class="c1">///< Grab the underlying pointer</span>
<span class="n">p1</span><span class="o">-></span><span class="n">doADance</span><span class="p">();</span><span class="c1">///< Make Matt dance</span>
</code></pre></div>
<p>Shared Pointer很好地给C++引入了引用计数的技术。当然,shared pointer也添加了一些少量的开销,但是这个开销带来了很明显的好处,所以也是值得的。</p>
<p>Objective-C++</p>
<p>C++很好,但是跟Objective-C有什么关系呢?</p>
<p>通过用Objective-C++可以将Objective-C和C++结合起来。它并不是一个全新的语言,而是Objective-C和C++两者的结合。</p>
<p>通过两者的结合,你可以使用两者的语言特征。可以将C++的对象作为Objective-C的实例,反之亦然。如果在应用程序中使用C++库的话这将会很有用处。</p>
<p>要使编译器理解一个Objective-C++文件是很容易的。你需要做的只是将文件名从.m改为.mm。当你这样做的时候,编译器会考虑到这个文件的不同,并将允许你使用Objective-C++。</p>
<p>以下为如何在两者间使用对象的例子:</p>
<div class="highlight"><pre><code><span class="c1">// Forward declare so that everything works below</span>
<span class="k">@class</span> <span class="nc">ObjcClass</span>;
<span class="k">class</span> <span class="n">CppClass</span><span class="p">;</span>
<span class="c1">// C++ class with an Objective-C member variable</span>
<span class="k">class</span> <span class="n">CppClass</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">ObjcClass</span> <span class="o">*</span><span class="n">objcClass</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// Objective-C class with a C++ object as a property</span>
<span class="k">@interface</span> <span class="nc">ObjcClass</span> : <span class="bp">NSObject</span>
<span class="k">@property</span> <span class="p">(</span><span class="k">nonatomic</span><span class="p">,</span> <span class="k">assign</span><span class="p">)</span> <span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">CppClass</span><span class="o">></span> <span class="n">cppClass</span><span class="p">;</span>
<span class="k">@end</span>
<span class="k">@implementation</span> <span class="nc">ObjcClass</span>
<span class="k">@end</span>
<span class="c1">// Using the two classes above</span>
<span class="n">std</span><span class="o">::</span><span class="n">shared_ptr</span><span class="o"><</span><span class="n">CppClass</span><span class="o">></span> <span class="n">cppClass</span><span class="p">(</span><span class="n">new</span> <span class="n">CppClass</span><span class="p">());</span>
<span class="n">ObjcClass</span> <span class="o">*</span><span class="n">objcClass</span> <span class="o">=</span> <span class="p">[[</span><span class="n">ObjcClass</span> <span class="n">alloc</span><span class="p">]</span> <span class="n">init</span><span class="p">];</span>
<span class="n">cppClass</span><span class="o">-></span><span class="n">objcClass</span> <span class="o">=</span> <span class="n">objcClass</span><span class="p">;</span>
<span class="n">objcClass</span><span class="p">.</span><span class="n">cppClass</span> <span class="o">=</span> <span class="n">cppClass</span><span class="p">;</span>
</code></pre></div>
<p>简单吧!注意这个属性被定义为assign,然而你不能用strong或者weak,因为这些对非OBjective-C对象类型没有意义。编译器不能“保留”或者“释放”一个C++对象类型,因为它并不是一个Objective-C对象。</p>
<p>assign的内存管理仍然是正确的,因为你使用了shared pointer。你可以使用raw pointer,但是你需要自己写一个setter来删除原来的实例并根据情况设置一个新的值。</p>
<p>注:Objective-C++是有局限性的。C++的类不能继承Objective-C的类,反之亦然。异常处理也是需要注意的地方。现代编译器和运行时确实允许C++异常和Objective-C异常共存,但是仍需要注意。使用异常处理之前一定要阅读相关文档。
Objective-C++很有用处,因为很多好的库都是用C++写的。能够在iOS和Mac的应用程序上使用它是很有价值的。</p>
<p>需要注意的是,Objective-C++确实有它需要注意的地方。第一个需要注意的地方是内存管理。记住Objective-C的对象都是建立在堆上的,而C++的对象可以建立在栈上也可以是在堆上。如果Objective-C类的对象是建立在栈上的话会很奇怪。必须是在堆上,因为整个Objective-C对象都是建立在堆上的。</p>
<p>编译器通过自动在代码中添加alloc和dealloc来构造和析构C++栈对象以确保这种情况。在此过程中,编译器需要创建两个函数“.cxx_construct”和“.cxx_destruct”,这两个函数分别被alloc和delloc调用。在这写方法中,执行所有相关的C++处理是必要的。</p>
<p>注:ARC实际上依托于“.cxx_destruct”,现在它为所有的Objective-C类创建了一个函数来写所有的自动消除代码。
这个处理所有基于栈的C++对象,但是你要记住任何基于堆的对象都需要在适当的情况下创建和销毁。你可以在指定的初始化程序中创建对象然后再dealloc中删除。</p>
<p>另一个在Objective-C++中需要注意的地方是减少对C++的依赖。这一点要尽量避免。要想明白这是为什么,看看下面这个使用Objective-C++的类。</p>
<div class="highlight"><pre><code><span class="c1">// MyClass.h</span>
<span class="cp">#import <Foundation/Foundation.h></span>
<span class="cp">#include <list></span>
<span class="err">@</span><span class="n">interface</span> <span class="nl">MyClass</span> <span class="p">:</span> <span class="n">NSObject</span>
<span class="err">@</span><span class="n">property</span> <span class="p">(</span><span class="n">nonatomic</span><span class="p">,</span> <span class="n">assign</span><span class="p">)</span> <span class="n">std</span><span class="o">::</span><span class="n">list</span><span class="o"><</span><span class="kt">int</span><span class="o">></span> <span class="n">listOfIntegers</span><span class="p">;</span>
<span class="err">@</span><span class="n">end</span>
<span class="c1">// MyClass.mm</span>
<span class="cp">#import “MyClass.h”</span>
<span class="err">@</span><span class="n">implementation</span> <span class="n">MyClass</span>
<span class="c1">// …</span>
<span class="err">@</span><span class="n">end</span>
</code></pre></div>
<p>MyClass类的实现文件必须是.mm文件,因为它是使用C++编写的。这没有错,但是想一想如果你想要使用MyCLass类的话会发生什么呢。你需要import MyClass.h,但是这样做你引入了一个使用C++编写的文件。所以即使其他的文件不需要用C++编写,也需要使用Objective-C++来进行编译。</p>
<p>因此,最好是在公共头文件中减少使用C++。你可以使用在实现文件中声明的私有属性或者实体变量实现这一目的。</p>
<p>下一步</p>
<p>C++是一个伟大的语言。它与Objective-C有相似的根源,但是它选择一种很不同的方式去编写程序。总之,学习C++可以很好的理解面向对象程序。而且C++能帮助你在objective – c代码做出更好的设计决策。我鼓励你去学习更多的C++知识并自己写程序。你可以在learncpp.com中找到很多好的资源。如果你有任何评论或者疑问或者C++问题,请留言。</p>
向iOS开发者介绍C++(二)
向iOS开发者介绍C++(一)
https://www.zruibin.cn/article/xiang_ios_kai_fa_zhe_jie_shao_c++_(_yi_).html
2014-05-05 23:18
2015-10-13 13:48
<p>
<span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">原文出处:</span> <a target="_blank" href="http://www.raywenderlich.com/62989/introduction-c-ios-developers-part-1">Matt Galloway</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">译文出处:</span> <a target="_blank" href="http://www.cocoachina.com/applenews/devnews/2014/0415/8163.html">cocoachina</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">。欢迎加入</span> <a target="_blank" href="http://www.jobbole.com/groups/6/">技术翻译小组</a> <span style="font-family:'Microsoft YaHei', 宋体, 'Myriad Pro', Lato, 'Helvetica Neue', Helvetica, Arial, sans-serif;font-size:14px;line-height:21px;background-color:#FFFFFF;">。</span> <br/> <br/>
</p><p>你已经精通了Objective-C,并且一直想学更酷的东西?看看这篇文章吧!本文将向iOS开发者介绍C++。稍后我会介绍,Objective-C能够无缝地使用C和C++代码。因此,基于以下几点原因,iOS开发者理解C++将会很有帮助:</p>
<p>1.有时候你想在应用中使用一个用C++编写的库。
2.你可能用C++写一部分应用程序的代码,以便更容易跨平台移植。</p>
<ol>
<li>了解其他语言通常能帮助你更好地理解编程。
这篇文章针对那些已经理解Objective-C的iOS开发者。前提是假定你已明白怎么写Objective-C代码,并熟悉基本的C概念,比如类型、指针、函数等。</li>
</ol>
<p>准备好学C++了么?那么就马上开始吧!</p>
<p>开始:语言简史</p>
<p>C++和Objective-C有一些共源:它们都根植于老式的好用的C语言,都是C语言的“超集”。因此,你可以在这两种语言中使用C语言的一些功能,和每种语言的附加特性。</p>
<p>如果你熟悉Objective-C,那么你将能粗略地理解你所遇到的C++代码。例如,两种语言中的数值类型(int型、float型和char型)的表现方式和使用规则都是完全一样的。</p>
<p>Objective-C和C++都在C语言基础上添加了面向对象的特征。如果你不熟悉“面向对象”,那么你真正需要明白的是面向对象指数据是由对象表示的,而对象是类的实例。事实上,C++最初称为“C with Classes”,内在的涵义是使C++面向对象。</p>
<p>“那么有什么区别么?”我听到了你的疑问。最大的区别是面向对象特性的方法。在C++中,很多行为是发生在编译时,而在Objective-C中,大多数是发生在运行时。你可能已经修改了Objective-C的运行时间来实现了一个类似method swizzling的诡计,而在C++中这是不可能的。</p>
<p>C++也不像Objective-C一样有大量内省以及映射方法。在C++中,没有办法获得C++对象的类,而在Objective-C中你可以在一个实例中调用“类”方法。同样的,在C++中也没有相当于isMemberOfClass或者isKindOfClass的类。</p>
<p>以上对C++的粗略介绍显示了C++和Objective-C的历史和主要不同点。历史部分已经完成了,到我们继续学习一些C++特征的时间了。</p>
<p>C++ 类</p>
<p>在任何面向对象语言中,首先你要知道的是如何定义一个类。在Objective-C中,你通过创建一个头文件和一个执行文件来定义一个类,在C++中同样如此,语法也十分相似。</p>
<p>如下,是一个Objective-C类的例子:</p>
<div class="highlight"><pre><code><span class="c1">// MyClass.h</span>
<span class="cp">#import <Foundation/Foundation.h></span>
<span class="k">@interface</span> <span class="bp">MyClass</span> : <span class="bp">NSObject</span>
<span class="k">@end</span>
<span class="c1">// MyClass.m</span>
<span class="cp">#import “MyClass.h”</span>
<span class="k">@implementation</span> <span class="bp">MyClass</span>
<span class="k">@end</span>
</code></pre></div>
<p>作为一个经验丰富的iOS开发者你应该很明白,但是看看同样用C++写的例子:</p>
<div class="highlight"><pre><code><span class="c1">// MyClass.h</span>
<span class="n">classMyClass</span> <span class="p">{</span>
<span class="p">};</span>
<span class="c1">// MyClass.cpp</span>
<span class="cp">#include “MyClass.h”</span>
<span class="cm">/* Nothing else in here */</span>
</code></pre></div>
<p>这里有一些本质的区别。首先,C++中的实现文件中什么都没有,这是因为你并没有在类中声明任何的方法。同理,就像Objective-C,一个空类不需要@implemenation/@end模块。</p>
<p>在Objective-C中,几乎每个类都继承自NSObject。你可以创建自己的根类,这意味着你的类将没有任何superclass。但是,你可能从来没有这么做过,除非你只是为了运行时好玩儿。对比C++,正如上面的例子一样,创建一个没有超类的类是很普遍的。</p>
<p>另外一个微小的区别是#include和#import。Objective-C将#import预处理器指令添加到C。在C++中没有相同的,标准的C-style是使用#include。Objective-C中的#import是确保一个文件只被包含一次,但在C++中你必须自己检查。</p>
<p>类成员变量和成员函数</p>
<p>当然,有比声明一个类多得多的事情。正如,在Objective-C和C++中,你可以在类中添加实例变量和方法。或许,你知道在C++中这两个不是这样命名的,C++中通常称为成员变量和成员函数。</p>
<p>注意:“method(实体方法)”这个术语通常不用于C++中,这个特性只用在Objective-C中。在Objective-C中,通过消息分派带调用“method(实体方法)”。另外,function(函数)通过一个静态的C-style函数被调用。稍后在这篇文章中我将更多的解释静态和动态。
那么接下来你要如何声明成员变量和成员函数呢?如下:</p>
<div class="highlight"><pre><code><span class="n">classMyClass</span> <span class="p">{</span>
<span class="n">intx</span><span class="p">;</span>
<span class="n">inty</span><span class="p">;</span>
<span class="n">floatz</span><span class="p">;</span>
<span class="n">voidfoo</span><span class="p">();</span>
<span class="n">voidbar</span><span class="p">();</span>
<span class="p">};</span>
</code></pre></div>
<p>这里有三个成员变量和两个成员函数。但是在C++中这里要有更多,在C++中,你可以限定成员变量和成员函数的范围,并且可以声明它们是公开访问的还是私有访问的。这个可以用于限制什么代码可以访问每个变量或者函数。</p>
<p>思考下面这个例子:</p>
<div class="highlight"><pre><code><span class="n">classMyClass</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="n">intx</span><span class="p">;</span>
<span class="n">inty</span><span class="p">;</span>
<span class="n">voidfoo</span><span class="p">();</span>
<span class="nl">private</span><span class="p">:</span>
<span class="n">floatz</span><span class="p">;</span>
<span class="n">voidbar</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div>
<p>这里,x,y和foo函数是公开访问。意思是可以在MyClass类的外部被调用。然而,z和bar函数是私有的。意味着只能在MyClass内部调用被调用。成员变量默认是私有的。</p>
<p>虽然这种区别确实存在于Objective-C中的实例变量中,但是很少使用。另外,在Objective-C中不太可能限制方法的调用范围。即使你只是在实现类内部声明一个方法而没有在接口中显示,技术上你还是可以外部调用这个方法。</p>
<p>Objective-C中的方法只约定为公开或私有。这就是为什么很多开发者选择给私有方法加前缀(例如“p_”前缀)来定义这个区别。这是为了和C++作比较,在C++中如果你试图从类的外部调用一个私有方法,编译器会抛出一个错误。</p>
<p>那么你要怎么使用类呢?和Objective-C非常相似,真的!你可以像下面这样创建一个实例:</p>
<div class="highlight"><pre><code><span class="bp">MyClass</span> <span class="n">m</span><span class="p">;</span>
<span class="n">m</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">10</span><span class="p">;</span>
<span class="n">m</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="mi">20</span><span class="p">;</span>
<span class="n">m</span><span class="p">.</span><span class="n">foo</span><span class="p">();</span>
</code></pre></div>
<p>简单吧!这里创建了一个MyClass的实例,分别设x=10,y=20,然后调用foo函数。</p>
<p>实现类的成员函数</p>
<p>你已经看到了如何定义一个类接口,但是函数呢?事实证明,这个十分简单。有如下两种方法你可以定义。</p>
<p>第一个实现函数的方法是在类的实现文件中定义–.cpp文件。例如:</p>
<div class="highlight"><pre><code><span class="c1">// MyClass.h</span>
<span class="n">classMyClass</span> <span class="p">{</span>
<span class="n">intx</span><span class="p">;</span>
<span class="n">inty</span><span class="p">;</span>
<span class="n">voidfoo</span><span class="p">();</span>
<span class="p">};</span>
<span class="c1">// MyClass.cpp</span>
<span class="cp">#include “MyClass.h”</span>
<span class="bp">MyClass</span><span class="o">::</span><span class="n">foo</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Do something</span>
<span class="p">}</span>
</code></pre></div>
<p>以上是第一个方法。在Objective-C中定义十分简单。注意MyClass::的用法,这就是你如何表明foo()函数已经作为MyClass类的一部分被实现了。</p>
<p>第二个实现函数的方法是你在Objective-C中不能做到的。在C++中,你可以直接在头文件中定义一个函数,如下:</p>
<div class="highlight"><pre><code><span class="c1">// MyClass.h</span>
<span class="n">classMyClass</span> <span class="p">{</span>
<span class="n">intx</span><span class="p">;</span>
<span class="n">inty</span><span class="p">;</span>
<span class="n">voidfoo</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Do something</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div>
<p>如果你只用过Objective-C,这看上去会很奇怪。确实奇怪,但是这种方法会十分有用。当一个函数以这种方式被声明时,编译器可以执行“内联”优化。这意味着当函数被调用时,整个函数代码在调用站点被内联编译而不是跳到一个新的代码块。</p>
<p>虽然内联可以使代码更快,但会增加编辑器代码的大小,因为如果函数被多次调用,代码将通过二进制复制。如果函数很大,或者被调用很多次,那么这可能会对二进制文件的大小产生重大的影响。由于很少的代码会在缓存中,这将会导致性能下降,这就意味着可能会有潜在的更多的缓存丢失。</p>
<p>我的目标是举例证明C++允许更多的灵活性。作为一个开发者,你需要去理解权衡并做决定。当然,唯一能真正明白哪种选择对你是正确的方法就是测试你的代码!</p>
<p>rgergrgerghh092435_1</p>
<p>命名空间</p>
<p>上面的例子介绍了一些你之前没有遇到过的新的语法–双冒号::,即指在C++中如何指代范围。双冒号用来告诉编译器应该在哪里可以找到foo函数。</p>
<p>下一次你会在使用命名空间的时候遇到双冒号。命名空间是分离代码的一种方式,以便减少命名冲突。</p>
<p>例如,你可能会在代码中定义一个叫Person的类,但是一个第三方库也可能命名一个叫Person的类。因此,在写C++代码时,你通常会将你的代码放到一个命名空间中来避免这些类型的命名冲突。</p>
<p>很容易做这个,套用以下命名空间声明即可:</p>
<div class="highlight"><pre><code><span class="n">namespaceMyNamespace</span> <span class="p">{</span>
<span class="n">classPerson</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
<span class="n">namespaceLibraryNamespace</span> <span class="p">{</span>
<span class="n">classPerson</span> <span class="p">{</span> <span class="err">…</span> <span class="p">};</span>
<span class="p">}</span>
</code></pre></div>
<p>现在,当使用任何一个Person类的实现时,你可以使用两个冒号消除歧义,如下:</p>
<div class="highlight"><pre><code><span class="n">MyNamespace</span><span class="o">::</span><span class="n">Person</span> <span class="n">pOne</span><span class="p">;</span>
<span class="n">LibraryNamespace</span><span class="o">::</span><span class="n">Person</span> <span class="n">pTwo</span><span class="p">;</span>
</code></pre></div>
<p>简单吧?</p>
<p>除了在类前加一个前缀来约定,在Objective-C中没有类似的命名空间。你确实这样命名类,对吧?如果不是这样命名的话,那就马上这样做吧!</p>
<p>注意:在Objective-C中已经有很多命名空间的建议了。这样的方案可以在这里(链接)找到。我不知道在Objective-C中是否还能用到它们,但是我希望如此。
内存管理</p>
<p>哦,不……不是那个可怕的词吧!在任何语言中,内存管理都是需要理解的最重要的概念之一。Java基本上是用内存回收器来管理内存。Objective-C需要你明白引用计数以及ARC所扮演的角色。在C++中,嗯。。。C++又不同了。</p>
<p>首先,在C++中,要理解内存管理,你需要先了解堆和栈。即使你认为你知道这一点,我建议你继续往下阅读,或许你能略有收获。</p>
<p>栈是指用于运行应用程序的一个内存块。栈大小固定,并用于存储应用程序的代码的数据。栈基于puch/pop工作,当一个给定函数将数据压入栈中,当函数运行结束时,出栈的必须是等量的数据。因此,随着时间的推移,栈使用率不会增长。</p>
<p>堆同样也是运行应用程序的一个内存块。堆大小不固定,并且随着程序的运行而增长。应用程序倾向于使用堆来储存在函数范围外使用的数据。此外,大的数据单元通常会存储到堆中,因为存到栈中有可能会溢出。–记住,栈的大小是固定的。</p>
<p>以上是一个堆和栈原理的简述,以下为两者的C语言示例:</p>
<div class="highlight"><pre><code><span class="n">intstackInt</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="kt">int</span><span class="o">*</span><span class="n">heapInt</span> <span class="o">=</span><span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">int</span><span class="p">));</span>
<span class="o">*</span><span class="n">heapInt</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">free</span><span class="p">(</span><span class="n">heapInt</span><span class="p">);</span>
</code></pre></div>
<p>这里,stackInt使用栈空间。程序返回后,用来存储“5”的这块内存就会自动释放。</p>
<p>然而,heapInt使用堆空间,在堆上调用malloc分配足够的空间来存储一个整数(int)。但是由于堆必须是由你分配,在用完数据后,开发者需要调用一个free函数来确保你没有内存泄露。</p>
<p>在Objective-C中,你只能在堆上创建对象。如果你试着在栈上创建对象,那么编译器就会报错。根本行不通。</p>
<p>思考下面的例子:</p>
<div class="highlight"><pre><code><span class="bp">NSString</span> <span class="n">stackString</span><span class="p">;</span>
<span class="c1">// Untitled 32.m:5:18: error: interface type cannot be statically allocated</span>
<span class="c1">// NSString stackString;</span>
<span class="c1">// ^</span>
<span class="c1">// *</span>
<span class="c1">// 1 error generated.</span>
</code></pre></div>
<p>这就是为什么在Objective-C代码上会看到星号,所有的对象都在堆上创建,并且所有对象都有指针。这在很大程度上归结为Objective-C处理内存管理。引用计数广泛应用于Objective-C中,对象需要在堆中以便它们的生命周期能被严格控制。</p>
<p>在C++中你既可以把数据存到栈中也可存到堆中。由开发者自己决定。然而,在C++中你也必须自己管理内存。数据放入栈中时内存将自动被处理;但用堆时,你必须自己管理内存,否则要面临内存泄露的风险。</p>
<p>C++中new和delete运算符</p>
<p>C++中引入一组关键词以帮助堆对象进行内存管理;他们分别用来创建和撤销堆中的对象。</p>
<p>创建对象:</p>
<div class="highlight"><pre><code><span class="n">Person</span> <span class="o">*</span><span class="n">person</span> <span class="o">=</span><span class="n">newPerson</span><span class="p">();</span>
</code></pre></div>
<p>当你不用这个对象时,你就要撤销它:</p>
<div class="highlight"><pre><code><span class="n">deleteperson</span><span class="p">;</span>
</code></pre></div>
<p>事实上,这同样适用于C++中标量类型:</p>
<div class="highlight"><pre><code><span class="kt">int</span><span class="o">*</span><span class="n">x</span> <span class="o">=</span><span class="n">newint</span><span class="p">();</span>
<span class="o">*</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="n">deletex</span><span class="p">;</span>
</code></pre></div>
<p>你可以认为这些运算相当于Objective-C中的初始化和删除对象。在C++中初始化用的new Person()等同于Objective-C中的[[Person alloc] init]。</p>
<p>但是,在Objective-C中没有等同于delete的运算符。但是我想你已经意识到了,当引用计数归零时,运行时Objective-C对象的存储单元就会被释放。记住,C++不会自动处理引用计数,开发者调用对象完成后负责释放对象。</p>
<p>现在你对C++的内存管理有了大致了解,简言之,在C++中的内存管理要比Objective-C中的要复杂得多。你真的需要考虑下一步是怎样,并且要跟踪对象。
fherherheh415092751_1</p>
<p>访问栈和堆对象成员</p>
<p>你已经了解到,C++中既可以在栈上也可以在堆上创建对象。然而,这两种方法还有一点微妙但是很重要的区别,即访问成员变量和成员函数的方式稍有不同。</p>
<p>使用栈对象时,你需要点运算符(.);使用堆对象时,你需要使用箭头操作符(–>)。如下:</p>
<div class="highlight"><pre><code><span class="n">Person</span> <span class="n">stackPerson</span><span class="p">;</span>
<span class="n">stackPerson</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="err">“</span><span class="n">Bob</span> <span class="n">Smith</span><span class="err">”</span><span class="p">;</span><span class="c1">///< Setting a member variable</span>
<span class="n">stackPerson</span><span class="p">.</span><span class="n">doSomething</span><span class="p">();</span><span class="c1">///< Calling a member function</span>
<span class="n">Person</span> <span class="o">*</span><span class="n">heapPerson</span> <span class="o">=</span><span class="n">newPerson</span><span class="p">();</span>
<span class="n">heapPerson</span><span class="o">-></span><span class="n">name</span> <span class="o">=</span> <span class="err">“</span><span class="n">Bob</span> <span class="n">Smith</span><span class="err">”</span><span class="p">;</span><span class="c1">///< Setting a member variable</span>
<span class="n">heapPerson</span><span class="o">-></span><span class="n">doSomething</span><span class="p">();</span><span class="c1">///< Calling a member function</span>
</code></pre></div>
<p>区别很微妙,但是值得注意。</p>
<p>你还看到箭头操作符与this指针一起用,就像在Objective-C中的self指针一样,它用于类内部函数去访问当前的对象。</p>
<p>下面的C++例子展示了箭头操作符的用法:</p>
<div class="highlight"><pre><code><span class="n">Person</span><span class="o">::</span><span class="n">doSomething</span><span class="p">()</span> <span class="p">{</span>
<span class="n">this</span><span class="o">-></span><span class="n">doSomethingElse</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div>
<p>这会引起一个常见的C++陷阱。在Objective-C中,你可以用空指针调用一个方法,你的应用程序仍会运行的很好:</p>
<div class="highlight"><pre><code><span class="n">myPerson</span> <span class="o">=</span> <span class="nb">nil</span><span class="p">;</span>
<span class="p">[</span><span class="n">myPerson</span> <span class="n">doSomething</span><span class="p">];</span> <span class="c1">// does nothing</span>
</code></pre></div>
<p>然而,在C++中,如果你要用一个NULL指针调用一个方法或者访问一个实例,你的应用程序会崩溃:</p>
<div class="highlight"><pre><code><span class="n">myPerson</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">myPerson</span><span class="o">-></span><span class="n">doSomething</span><span class="p">();</span><span class="c1">// crash!</span>
</code></pre></div>
<p>因此,你必须确保在C++中不要试图使用空指针。</p>
<p>引用</p>
<p>向函数传递对象时,你传递的是一个对象副本,而不是对象本身。例如,思考下面的C++代码:</p>
<div class="highlight"><pre><code><span class="n">voidchangeValue</span><span class="p">(</span><span class="n">intx</span><span class="p">)</span> <span class="p">{</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// …</span>
<span class="n">intx</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">changeValue</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="c1">// x still equals 1</span>
</code></pre></div>
<p>很简单,没什么特别的。但是想一想当用一个函数做同样的事情,并且这个函数可以把一个对象作为一个参数。</p>
<div class="highlight"><pre><code><span class="k">class</span> <span class="n">Foo</span> <span class="p">{</span>
<span class="nl">public</span><span class="p">:</span>
<span class="kt">int</span> <span class="n">x</span><span class="p">;</span>
<span class="p">};</span>
<span class="kt">void</span> <span class="nf">changeValue</span><span class="p">(</span><span class="n">Foo</span> <span class="n">foo</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foo</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// …</span>
<span class="n">Foo</span> <span class="n">foo</span><span class="p">;</span>
<span class="n">foo</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">changeValue</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
<span class="c1">// foo.x still equals 1</span>
</code></pre></div>
<p>这或许令你有些惊讶。仔细想想的话,和简单的int型例子没有不同。在将对象传递给函数之前,创建一个Foo object副本会发生什么情况?不过有时候确实需要传递一个实际对象。一种方法是改变函数指向对象的指针,而不是对象本身。但是无论什么时候调用函数都会产生附加代码。</p>
<p>对比上面列举的值传递的例子, C++定义了一个新的概念来允许通过引用来传递变量。这就意味着不需要创建对象副本。</p>
<p>利用引用传递可以很简单的改变你的调用,你可以在函数签名前简单地在变量前使用ampersand (&)即可,如下:</p>
<div class="highlight"><pre><code><span class="n">voidchangeValue</span><span class="p">(</span><span class="n">Foo</span> <span class="o">&</span><span class="n">foo</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foo</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// …</span>
<span class="n">Foo</span> <span class="n">foo</span><span class="p">;</span>
<span class="n">foo</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">changeValue</span><span class="p">(</span><span class="n">foo</span><span class="p">);</span>
<span class="c1">// foo.x equals 5</span>
</code></pre></div>
<p>它也适用于non-class变量:</p>
<div class="highlight"><pre><code><span class="n">voidchangeValue</span><span class="p">(</span><span class="kt">int</span><span class="o">&</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
<span class="p">}</span>
<span class="c1">// …</span>
<span class="n">intx</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="n">changeValue</span><span class="p">(</span><span class="n">x</span><span class="p">);</span>
<span class="c1">// x equals 5</span>
</code></pre></div>
<p>引用传递很有用,并能显著提高性能。当创建一个对象副本成本相当高的时候引用传递更加有用,例如使用一个大型链表,创建副本意味着要对对象执行深度复制。
继承</p>
<p>一个面向对象的语言没有继承就不完整。C++当然不会违反这一趋势。思考下面的两个Objective-C类,其中一个类从另一个类继承:</p>
<div class="highlight"><pre><code><span class="k">@interface</span> <span class="nc">Person</span> : <span class="bp">NSObject</span>
<span class="k">@end</span>
<span class="k">@interface</span> <span class="nc">Employee</span> : <span class="nc">Person</span>
<span class="k">@end</span>
</code></pre></div>
<p>同样的事情可以用C++以很相似的方式表达:</p>
<div class="highlight"><pre><code><span class="n">classPerson</span> <span class="p">{</span>
<span class="p">};</span>
<span class="nl">classEmployee</span> <span class="p">:</span><span class="n">publicPerson</span> <span class="p">{</span>
<span class="p">};</span>
</code></pre></div>
<p>唯一的区别是在C++中要加一个public关键词。这里Employee类公共的继承Person类。这就意味着person类中的公共成员在Employee类中也是公共类型的。如果用private代替public,那么Person类中的公共成员在Employee类中就将变为私有的。关于这个话题的更多信息,我建议读一篇很棒的关于继承和存储说明符的文章。</p>
<p>以上是关于“继承“的简单部分,下面我们开始复杂的部分。与Objective-C不同的是,C++中允许多重继承,即一个类可以继承两个或以上基类。如果你除了Objective-C没有用过其他语言,那么这对你来说一定很陌生。</p>
<p>下面是C++中多重继承的例子:</p>
<div class="highlight"><pre><code><span class="n">classPlayer</span> <span class="p">{</span>
<span class="n">voidplay</span><span class="p">();</span>
<span class="p">};</span>
<span class="n">classManager</span> <span class="p">{</span>
<span class="n">voidmanage</span><span class="p">();</span>
<span class="p">};</span>
<span class="nl">classPlayerManager</span> <span class="p">:</span><span class="n">publicPlayer</span><span class="p">,</span><span class="n">publicManager</span> <span class="p">{</span>
<span class="p">};</span>
</code></pre></div>
<p>在这个例子中,有两个基类,一个类继承这两个基类。意思是PlayerManager类可以访问每个基类的所有成员变量和函数。简单吧?我确定你已经意识到了,在Objective-C中没有这种方法。</p>
<p>然而,这并不完全正确,对吧?</p>
<p>精明的读者一定注意到在Objective-C中有类似的方法,即protocols(协议)。虽然跟多重继承不太相似,但是两种技术都为了解决同样的问题:提供一个机制来连接两个有相似用途的类。</p>
<p>Protocols(协议)有一个微小的区别,那就是协议没有实现,只是描述类必须遵循哪个接口。</p>
<p>在Objective-C中,上面的例子可被写成:</p>
<div class="highlight"><pre><code><span class="k">@protocol</span> <span class="nc">Player</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">play</span><span class="p">;</span>
<span class="k">@end</span>
<span class="k">@protocol</span> <span class="nc">Manager</span>
<span class="p">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">manage</span><span class="p">;</span>
<span class="k">@end</span>
<span class="k">@interface</span> <span class="nc">Player</span> : <span class="bp">NSObject</span> <span class="o"><</span><span class="n">Player</span><span class="o">></span>
<span class="k">@end</span>
<span class="k">@interface</span> <span class="nc">Manager</span> : <span class="bp">NSObject</span> <span class="o"><</span><span class="n">Manager</span><span class="o">></span>
<span class="k">@end</span>
<span class="k">@interface</span> <span class="nc">PlayerManager</span> : <span class="bp">NSObject</span> <span class="o"><</span><span class="n">Player</span><span class="p">,</span> <span class="n">Manager</span><span class="o">></span>
<span class="k">@end</span>
</code></pre></div>
<p>当然,这个细小的差别你是能想象的到的。在Objective-C中你要在PlayerManager类中执行play和manager。在C++中你只要在每个基类中实现该方法,然后PlayerManager类会自动的继承每个实现。</p>
<p>虽然,在实践中,多重继承有时会另人混淆和复杂化。对C++开发者来说,多重继承是一个危险的方法,除非绝对必要,开发者会尽量避免使用。</p>
<p>为什么呢?想一想如果两个基类用同样的名字去实现一个函数,并接受同样的参数的话,那么这两个基类就会有同样的原型。在这种情况下,你就需要消除歧义。例如,假设Player和Manager两个类都有一个命名为foo的函数。</p>
<p>你需要这样消除歧义:</p>
<div class="highlight"><pre><code><span class="n">PlayerManager</span> <span class="n">p</span><span class="p">;</span>
<span class="n">p</span><span class="p">.</span><span class="n">foo</span><span class="p">();</span> <span class="c1">///< Error! Which foo?</span>
<span class="n">p</span><span class="p">.</span><span class="n">Player</span><span class="o">::</span><span class="n">foo</span><span class="p">();</span> <span class="c1">///< Call foo from Player</span>
<span class="n">p</span><span class="p">.</span><span class="n">Manager</span><span class="o">::</span><span class="n">foo</span><span class="p">();</span><span class="c1">///< Call foo from Manager</span>
</code></pre></div>
<p>这绝对是可行的,但是这增加了混淆,而且最好避免复杂性。这由PlayerManager类的使用者决定。使用协议直接使PlayerManager类实现函数foo,因此这里只有一次实现,没有混淆。</p>
<p>下一步</p>
<p>第一部分中我们了解了C++的简史,如何声明类以及C++中内存管理是如何工作的。当然,关于C++还有很多需要学习的!</p>
<p>第二部分中,在查阅Objective-C和C++标准库之前,学到了更多的高级类的特征和模板。</p>
<p>与此同时,如果你在学C++的过程中有任何问题或者观点,请加入下面的讨论!</p>
向iOS开发者介绍C++(一)
python注意
https://www.zruibin.cn/article/python_zhu_yi.html
2014-05-05 23:05
2015-10-13 13:39
<h3>什么是pyc文件</h3>
<p>pyc是一种二进制文件,是由py文件经过编译后,生成的文件,是一种byte code,py文件变成pyc文件后,加载的速度有所提高,而且pyc是一种跨平台的字节码,是由python的虚拟机来执行的,这个是类似于JAVA或者.NET的虚拟机的概念。pyc的内容,是跟python的版本相关的,不同版本编译后的pyc文件是不同的,2.5编译的pyc文件,2.4版本的python是无法执行的。</p>
<h3>为什么需要pyc文件</h3>
<p>这个需求太明显了,因为py文件是可以直接看到源码的,如果你是开发商业软件的话,不可能把源码也泄漏出去吧?所以就需要编译为pyc后,再发布出去。当然,pyc文件也是可以反编译的,不同版本编译后的pyc文件是不同的,根据python源码中提供的opcode,可以根据pyc文件反编译出py文件源码,网上可以找到一个反编译python2.3版本的pyc文件的工具,不过该工具从python2.4开始就要收费了,如果需要反编译出新版本的pyc文件的话,就需要自己动手了(俺暂时还没这能力^--^),不过你可以自己修改python的源代码中的opcode文件,重新编译python,从而防止不法分子的破解。</p>
<h3>生成单个pyc文件</h3>
<p>python就是个好东西,它提供了内置的类库来实现把py文件编译为pyc文件,这个模块就是 py_compile 模块。</p>
<p>使用方法非常简单,如下所示,直接在idle中,就可以把一个py文件编译为pyc文件了。(假设在windows环境下)</p>
<div class="highlight"><pre><code><span class="kn">import</span> <span class="nn">py_compile</span>
<span class="n">py_compile</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="s">r'H:/game/test.py'</span><span class="p">)</span>
</code></pre></div>
<p>compile函数原型:</p>
<div class="highlight"><pre><code><span class="nb">compile</span><span class="p">(</span><span class="nb">file</span><span class="p">[,</span> <span class="n">cfile</span><span class="p">[,</span> <span class="n">dfile</span><span class="p">[,</span> <span class="n">doraise</span><span class="p">]]])</span>
</code></pre></div>
<p>file 表示需要编译的py文件的路径</p>
<p>cfile 表示编译后的pyc文件名称和路径,默认为直接在file文件名后加c 或者 o,o表示优化的字节码</p>
<p>dfile 这个参数英文看不明白,请各位大大赐教。(鄙视下自己)原文:it is used as the name of the source
file in error messages instead of file</p>
<p>doraise 可以是两个值,True或者False,如果为True,则会引发一个PyCompileError,否则如果编译文件出错,则会有一个错误,默认显示在sys.stderr中,而不会引发异常</p>
<h3>批量生成pyc文件</h3>
<p>一般来说,我们的工程都是在一个目录下的,一般不会说仅仅编译一个py文件而已,而是需要把整个文件夹下的py文件都编译为pyc文件,python又为了我们提供了另一个模块:compileall 。使用方法如下:</p>
<div class="highlight"><pre><code><span class="kn">import</span> <span class="nn">compileall</span>
<span class="n">compileall</span><span class="o">.</span><span class="n">compile_dir</span><span class="p">(</span><span class="s">r'H:/game'</span><span class="p">)</span>
</code></pre></div>
<p>这样就把game目录,以及其子目录下的py文件编译为pyc文件了。嘿嘿,够方便吧。来看下compile_dir函数的说明:</p>
<div class="highlight"><pre><code><span class="n">compile_dir</span><span class="p">(</span><span class="nb">dir</span><span class="p">[,</span> <span class="n">maxlevels</span><span class="p">[,</span> <span class="n">ddir</span><span class="p">[,</span> <span class="n">force</span><span class="p">[,</span> <span class="n">rx</span><span class="p">[,</span> <span class="n">quiet</span><span class="p">]]]]])</span>
</code></pre></div>
<p>dir 表示需要编译的文件夹位置</p>
<p>maxlevels 表示需要递归编译的子目录的层数,默认是10层,即默认会把10层子目录中的py文件编译为pyc</p>
<p>ddir 英文没明白,原文:it is used as the base path from which the filenames used in error messages will be generated。</p>
<p>force 如果为True,则会强制编译为pyc,即使现在的pyc文件是最新的,还会强制编译一次,pyc文件中包含有时间戳,python编译器会根据时间来决定,是否需要重新生成一次pyc文件</p>
<p>rx 表示一个正则表达式,比如可以排除掉不想要的目录,或者只有符合条件的目录才进行编译</p>
<p>quiet 如果为True,则编译后,不会在标准输出中,打印出信息</p>
<h3>总结</h3>
<p>通过上面的方法,可以方便的把py文件编译为pyc文件了,从而可以实现部分的源码隐藏,保证了python做商业化软件时,保证了部分的安全性吧,继续学习下,看怎么修改opcode。</p>
<hr>
<p>一般情况下,一些程序的调试过程中我们会让它输出一些信息,特别是一些大型的程序,我们通过这些信息可以了解程序的运行情况,python提供了一个日志模块logging,它可以把我们想要的信息全部保存到一个日志文件中,方面我们查看。我们先看一个简单的例子。</p>
<div class="highlight"><pre><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">logging</span>
<span class="o">>>></span> <span class="n">LOG_FILENAME</span><span class="o">=</span><span class="s">"C:\Python25\log_test.txt"</span>
<span class="o">>>></span> <span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">filename</span><span class="o">=</span><span class="n">LOG_FILENAME</span><span class="p">,</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"This message should go to the log file"</span><span class="p">)</span>
</code></pre></div>
<p>然后我们就可以在C盘python25目录下发现一个名为log_test.txt的文件,打开里面的内容为: DEBUG:root:This message should go to the log file</p>
<p>然后我们重复运行最后一句,会发现这个文本文件每次都会多出一行:DEBUG:root:This message should go to the log file</p>
<p>下面我们看一个更标准的程序:</p>
<div class="highlight"><pre><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">logging</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">getLogger</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">handler</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">FileHandler</span><span class="p">(</span><span class="s">"Log_test.txt"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">.</span><span class="n">addHandler</span><span class="p">(</span><span class="n">handler</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">.</span><span class="n">setLevel</span><span class="p">(</span><span class="n">logging</span><span class="o">.</span><span class="n">NOTSET</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s">"This is an error message"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s">"This is an info message"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">logger</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s">"This is a critical message"</span><span class="p">)</span>
</code></pre></div>
<p>日志文件中会出现三行内容:</p>
<div class="highlight"><pre><code><span class="n">This</span> <span class="ow">is</span> <span class="n">an</span> <span class="n">error</span> <span class="n">message</span>
<span class="n">This</span> <span class="ow">is</span> <span class="n">an</span> <span class="n">info</span> <span class="n">message</span>
<span class="n">This</span> <span class="ow">is</span> <span class="n">a</span> <span class="n">critical</span> <span class="n">message</span>
</code></pre></div>
<p>上面程序的第2行是生成一个日志对象,里面的参数时日志的名字,可以带,也可以不带。第3行是生成了一个handler,logging支持很多种Handler,像FileHandler,SocketHandler等待,这里由于我们要写文件,所以用了FileHandler,它的参数就是filename,默认当前路径,当然我们可以自己指定路径。</p>
<p>第5行设置日志信息输出的级别。Logging提供了多种日志级别,如NOTSET,DEBUG,INFO,WARNING,ERROR,CRITICAL等,每个级别都对应一个数值,如果我们不自己设置输出级别,那么系统会执行缺省级别,值为30,就warning。Logging也提供了一个方法来查看缺省日志级别,getLevelName(logger,getEffectiveLevel())。</p>
<hr>
<h3>python模块——logging(日志管理)</h3>
<p>日志对象对于不同的级别信息提供不同的函数进行输出,如:info(), error(), debug()等。当写入日志时,小于指定级别的信息将被忽略。因此为了输出想要的日志级别一定要设置好此参数。这里我设为NOTSET(值为0),也就是想输出所有信息。系统默认的日志级别排序为,CRITICAL,ERROR,WARNING,INFO,DEBUG,NOTSET。比如说我们要输出的信息为CRITICAL,但是我们的日志级别为DEBUG,那么这个信息将被忽略掉。我们看下面的例子:</p>
<div class="highlight"><pre><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="n">LEVELS</span><span class="o">=</span><span class="p">{</span><span class="s">'debug'</span><span class="p">:</span><span class="n">logging</span><span class="o">.</span><span class="n">DEBUG</span><span class="p">,</span>
<span class="s">'info'</span><span class="p">:</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">,</span>
<span class="s">'warning'</span><span class="p">:</span><span class="n">logging</span><span class="o">.</span><span class="n">WARNING</span><span class="p">,</span>
<span class="s">'error'</span><span class="p">:</span><span class="n">logging</span><span class="o">.</span><span class="n">ERROR</span><span class="p">,</span>
<span class="s">'critical'</span><span class="p">:</span><span class="n">logging</span><span class="o">.</span><span class="n">CRITICAL</span><span class="p">}</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span><span class="o">></span><span class="mi">1</span><span class="p">:</span>
<span class="n">level_name</span><span class="o">=</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">level</span><span class="o">=</span><span class="n">LEVELS</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">level_name</span><span class="p">,</span><span class="n">logging</span><span class="o">.</span><span class="n">NOTSET</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">level</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"This is a debug message"</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s">"This is an info message"</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">warning</span><span class="p">(</span><span class="s">"This is a warning message"</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s">"This is an error message"</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">critical</span><span class="p">(</span><span class="s">"This is a critical error message"</span><span class="p">)</span>
</code></pre></div>
<p>运行时候,我们根据给的参数时debug,info等等,来看看输出情况,就可以知道各个日志级别的输出情况了,下面是结果:</p>
<h4>python模块——logging(日志管理)</h4>
<p>可以看到过滤进行的很明显。当我们设置级别最低位debug时,所有的信息都输出了,当我们设为最高位critical时候,只有critical输出了,低于critical的被过滤了。</p>
<p>Logging是非常有用的,一个程序的健壮性也这个有关,当一个程序包含很多的调试信息时,可以方便我们发现问题,发现错误。</p>
艺术与技术
https://www.zruibin.cn/article/yi_zhu_yu_ji_zhu.html
2014-02-19 17:23
2014-02-19 17:23
<p>艺术是客观世界的反映,源于现实的社会和自然景观,但它又不是对事物的、简单的认识和反映,而是经过人们主观的探索、提炼、剪裁和加工,采用相应的技法和工具,根据不同的需要,使用一定形式语境构成的,形神兼备的作品。形象地传达作者的感情,服务于他人,服务于社会。更有人定义艺术是“人类以创造美为主要目的的技术及其产品”。</p>
<p>技术是指在劳动生产方面的经验、知识和技巧,也泛指其他操作方面的技巧。 艺术与技术之间的关系,我觉得可以用的几个比喻来体现,根据艺术的表现形式的不同又分为:</p>
<p>①例如一个漂亮的花瓶,花瓶的设计需要艺术,然而制作出来就需要技术;</p>
<p>②艺术如同高楼,而技术就是楼房的地基,没有又深有好的地基就无法铸成摩天大楼。</p>
<p>③艺术与技术同金庸先生写的《笑傲江湖》里面关于独孤求败剑法与宝剑的关系一般,青年时利剑“凌厉刚猛,无坚不摧”,中年时重剑“重剑无锋,大巧不工”,老年时木剑“不滞于物,草木竹石均可为剑”。随着独孤求败对剑法的参悟,到后来他对宝剑的依赖就不那么重要了。其中宝剑如同技术,对艺术的理解与想象达到一定高度,有些艺术讲究自然之美,随心之美,对技术的要求就没那么重了。但是没有青年使利剑,中年使重剑的历练和感悟,是不可能直接达到草木为剑的境界,所以为了追求更高的艺术,扎实的技术必不可少。</p>
<p>艺术为了创造美,技术为了更好的解决问题;艺术需要思考、需要想象力和情感投入,技术需要经验、知识与技巧。技术是创造的基础,如果没有技术,一切都是空谈;如果没有艺术,那造出来的就是只是一件物品。所以只有技术与艺术的完美结合才能创造出更好的艺术品。</p>
<p>技术、艺术, 是一条直线的两个端点,一个向东,一个向西, 可是如同地球是圆的一样,只要你走的足够远, 你终将会发现, 在地球的另一端, 它们又会相互碰面!用技术的手法来体现艺术,用艺术的感性来哄托技术,如中国汉字和中国文化没有明确的区别,就像伏羲一画开天地一样,一即是二,二即是一,你中有我,我中有你。</p>
艺术与技术
《仙逆》--- 观之感
https://www.zruibin.cn/article/《_xian_ni_》_---_guan_zhi_gan.html
2013-02-22 20:36
2013-02-22 20:36
<p>一年的时间,仙逆终于让我看完了,六百多万的字,期间断断续续,但却是唯一感觉最深刻的一本的小说,每一个情节都是那么地环环相扣,每一个结果都是那么地令人深思,从恒岳派到杀绝腾家,从逆魔海到古神之地,从雪域之国到雨之仙界再到朱雀国,从天运星到妖古之地,从罗天到联盟再到四圣宗,从云海到风之仙界再到七彩结界,从界内到界外再到巅落之地,从界内外之争到仙域再到洞府而之后的仙罡,从天牛州到中州再到古国,最后的太古神境,每一个环节都是那么地跌宕起伏,每一个环节都有伏笔,又有与之前呼应,又为后绪铺垫,每一个人物都是那么地鲜明,那么地突出,都不可缺少,从练气结丹到元婴,从化神到婴变问鼎,从阴虚阳实到窥涅,从净涅碎涅到第三步空涅,从空灵空玄到空劫,从金尊天尊到跃天尊,从大天尊踏天桥第八桥到最后的第四步踏天境,也许书中会有一些坑有一些谜未解开,也许每一个答案都正确,但仔细想想或许并非只有一个,也许没有,猜不透,但却无关紧要,只有一个字,逆,代表了人的追求与执着跟信念,代表了理想的攀升与遥望,人生、生活就是一张网,充斥着因果、生死、真假、轮回,但逆,却可以冲破这张网,也许会有另一张网嵌套着,逆,却可以改变。很好的一本,或许没有处处充满着激情,但每一个平凡的环节一步一步相扣起来就变成了一本出众不平凡的书!</p>
《仙逆》--- 观之感
大学时光
https://www.zruibin.cn/article/da_xue_shi_guang.html
2013-01-16 22:59
2013-01-16 22:59
<p>转眼间,大学就这样快过了,时间怎么过得那么快呀!!!</p>
<p>犹记得当年第一次来学校,整个宿舍4个人就汤杰第一个先来,当时还真遇上了,厕所水箱坏了,水一直流,晚上为了去上班主任见面会,问了好久,找了好久,才到教学楼,当时的东哥,一满头的金发,还以为他是个小混混呢。那时,有早读,6点多就起来去操场读英语,哈哈,整个宿舍4个人,没一个人去的。那时候,学校还不给用电脑呢,晚上没事就是看手机,不然就学习,记得当时整个宿舍都是用诺基亚的,还是潘晓第一个用上android的,哈哈!</p>
<p>大二时,有电脑了,人老油条了,有时候整个宿舍晚上一晚睡,第二天的结果就是整个都没去上课,在翘课方面,就数汤杰业绩耀眼,一个星期5天课,只去一天,牛逼呀,哈哈,也是宿舍的挂课小王子,记得当时跟一个信息学院聊过,问我认不认识我们学院那个挂科小王子,叫汤杰来的,我勒了个去,马上就说是我们宿舍的,多光荣呀。还记得当时,我晚上睡觉经常打呼噜,经常吵得他们三个睡不着,有次还把汤杰给吵醒被他用手机把声音给录下来呢。还记得当时,汤杰玩上了暗黑破坏神,然后呢,就我玩上了,再然后呢,就是整个宿舍都玩上,有时晚上还一起上战网打通关呢,这日子,还一清二楚!</p>
<p>大三时,我跟东哥一起迷上了看小说,从斗破苍穹,到神墓,到星辰变,到武神,到校花的贴身高手,从天蚕土豆,到唐家三少,到辰东,到我爱吃西红柿,看过了无数的小说,每次都是一个星期就搞定一本,无论是连载,还是完本,默默守护。课更不用说了,大部分都逃了,每个学期都期末那么一个星期或者是几天才看一会儿书,那些书翻得都是新的呢。还记得当时考英语四级时,我跟汤杰两人还是前一天晚上通宵呢,哈哈,祼着考四级,结果不用想都知道,哈哈!记得当时对面宿舍楼的师兄毕业了走了,搬进来的是大一的一群师妹,然后呢,你懂得,不用说!!!</p>
<p>大四了,大家都忙着找工作,找实习,大家最最希望的就是找到工资高点而且事情又不多的工作,那样多轻松呀,但这是不可能的。现在,学校开完年级会议,补完考,都走了,宿舍就只有我一个人,还要实习干到年底才可以回家呢,挺郁闷的,有时一个人在宿舍,就一个人,连找个人说个话都没有,很痛苦!</p>
<p>其实,大学能学得些什么都不重要,那些只要你去工作了就会让你去学的,只要你找得到工作,就可以慢慢练!大学最重要的是有一帮损友陪你渡过那几年最没上进,最没成就,最没目标,最嬉笑,最欢乐,最无忧,每天都不用为钱烦,没钱了可以跟家里要,又没有父母管着,整天想得第一件事就怎么逃课,想逃就逃,多么幸福的一件呀!这该死的毕业!</p>
大学时光