diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 00000000..1691f22a
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+*.png filter=lfs diff=lfs merge=lfs -text
+*.jpg filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 00000000..5c1ce79a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+_site
+.sass-cache
+.jekyll-metadata
+build.sh
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 00000000..73ba84d9
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,50 @@
+# Basic
+title: OneV's Den
+subtitle: 上善若水,人淡如菊
+welcome_message: Swifter.tips - 我维护的 Swift 使用技巧分享网站,每周三更新,欢迎访问
+description: 嗨,我是王巍 (@onevcat),一名来自中国的 iOS / Unity 开发者。现居日本,就职于 LINE。正在修行,探求创意之源。
+creator: @onevcat
+
+url: "http://onevcat.com"
+
+permalink: /:year/:month/:title/
+
+# Format
+highlighter: plain
+markdown: redcarpet
+redcarpet:
+ extensions: ["no_intra_emphasis", "fenced_code_blocks", "autolink", "strikethrough", "superscript", tables]
+
+# supported colors: blue, green, purple, red, orange or slate. If you need clear, leave it empty.
+cover_color: slate
+
+blog_button:
+ title: 博客
+ description: 访问博客
+
+nav:
+ - {title: 项目, description: 我的项目, url: 'http://project.onevcat.com'}
+ - {title: 关于, description: 了解更多关于我, url: 'http://about.onevcat.com/'}
+ - {title: 订阅, description: 邮件订阅本站, url: '//api.onevcat.com/subscribe'}
+
+# Pagination
+gems: [jekyll-paginate]
+paginate: 10
+paginate_path: "page/:num/"
+
+# Comment
+comment:
+ disqus: onevcat
+ # duoshuo:
+# Social
+social:
+ weibo: onevcat
+ github: onevcat
+ twitter: onevcat
+ gplus: 107108267983477358170
+ mail: onev@onevcat.com
+
+# Google Analytics
+ga:
+ id: UA-25719337-1
+ host: onevcat.com
diff --git a/_includes/comments.html b/_includes/comments.html
new file mode 100644
index 00000000..4f53e711
--- /dev/null
+++ b/_includes/comments.html
@@ -0,0 +1,38 @@
+
diff --git a/_includes/external.html b/_includes/external.html
new file mode 100644
index 00000000..30669b52
--- /dev/null
+++ b/_includes/external.html
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+{% if site.ga.id %}
+
+{% endif %}
diff --git a/_includes/footer.html b/_includes/footer.html
new file mode 100644
index 00000000..6ebd8605
--- /dev/null
+++ b/_includes/footer.html
@@ -0,0 +1,7 @@
+
diff --git a/_includes/head.html b/_includes/head.html
new file mode 100644
index 00000000..3466acb3
--- /dev/null
+++ b/_includes/head.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+ {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_includes/pagination.html b/_includes/pagination.html
new file mode 100644
index 00000000..7b84e5b8
--- /dev/null
+++ b/_includes/pagination.html
@@ -0,0 +1,11 @@
+
+
+
diff --git a/_includes/read-more.html b/_includes/read-more.html
new file mode 100644
index 00000000..95f61eb1
--- /dev/null
+++ b/_includes/read-more.html
@@ -0,0 +1,21 @@
+
+ {% if page.next %}
+ {% assign current = page.next %}
+
+
最近的文章
+
+
{{ current.content | markdownify | strip_html | strip_newlines | truncate: 250 }}…
+
•
{{current.tags}}继续阅读
+
+ {% endif %}
+
+ {% if page.previous %}
+ {% assign current = page.previous %}
+
+
更早的文章
+
+
{{ current.content | markdownify | strip_html | strip_newlines | truncate: 250 }}…
+
•
{{current.tags}}继续阅读
+
+ {% endif %}
+
diff --git a/_includes/side-panel.html b/_includes/side-panel.html
new file mode 100644
index 00000000..9a54e0bd
--- /dev/null
+++ b/_includes/side-panel.html
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+

+
+
+ {% if site.subtitle %}
+
{{site.subtitle}}
+ {% endif %}
+
+
{{ site.description }}
+
+
+ {% if site.welcome_message %}
+
{{ site.welcome_message }}
+ {% endif %}
+
+
+
+
+
{% include social.html %}
+
+
+
+
+ {% if site.cover_color %}
+
+ {% else %}
+
+ {% endif %}
+
+
diff --git a/_includes/social.html b/_includes/social.html
new file mode 100644
index 00000000..5159bc13
--- /dev/null
+++ b/_includes/social.html
@@ -0,0 +1,63 @@
+
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 00000000..5b600d2e
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,26 @@
+
+
+
+ {% include head.html %}
+
+
+
+
+
+ {% include side-panel.html %}
+
+
+
+ {{ content }}
+ {% include footer.html %}
+
+
+
+ {% include external.html %}
+
+
+
+
diff --git a/_layouts/page.html b/_layouts/page.html
new file mode 100644
index 00000000..723c1dd6
--- /dev/null
+++ b/_layouts/page.html
@@ -0,0 +1,16 @@
+---
+layout: default
+---
+
+
+
+
+
+
diff --git a/_layouts/post.html b/_layouts/post.html
new file mode 100644
index 00000000..65644bc5
--- /dev/null
+++ b/_layouts/post.html
@@ -0,0 +1,30 @@
+---
+layout: default
+---
+
+
+
+
+
+
+
+
+
+
+
+
+{% include read-more.html %}
+{% include comments.html %}
diff --git a/_posts/2011-09-06-ban-jia.markdown b/_posts/2011-09-06-ban-jia.markdown
new file mode 100644
index 00000000..0dac877d
--- /dev/null
+++ b/_posts/2011-09-06-ban-jia.markdown
@@ -0,0 +1,17 @@
+---
+layout: post
+title: 搬家
+date: 2011-09-06 23:32:54.000000000 +09:00
+tags: 胡言乱语集
+---
+最早开博于新浪,大概已经是六七年前的事情了吧。
+
+之后本科入学,拜悲剧的cernet网络所赐,新浪博客直接无法登陆。偶然看到搜狐就在学校边上,小尝试一下发现速度奇快,于是毫不犹豫地搬家。
+
+然后是稳定的写博期,然后人开始变懒就不怎么写东西了,再然后...再然后就没有了...
+
+最近总被问“你玩微博么”,其实在twitter诞生之初的07年我就有帐号了..不幸的是周围没人和我有相同的爱好,加上智能手机不流行以及跨国短信贵死人等诸多因素,导致现在的twiiter上只有一个follower。而社交网络这种东西要是没人和你玩的话,估计自己连自己玩蛋的兴趣都不会有,于是后来加上伟大的党的伟大封杀,就彻底对微博没了胃口。不过就算这样我还是会很礼貌的把自己的新浪微博腾讯微博搜狐微博网易微博凤凰微博一股脑的告诉他,然后加一句:上面什么都没有哦,亲!
+
+其实心底里还是觉得标准博客才是真正能写出东西的地方,而微博更多地还是掺杂有一些被追捧性质的信息时事交流的类快餐文化平台。真的很难想象在极度口语化的时代,微博那短短几十个字能够承载起文化的传承和积淀,而信息传播这一主要功能却又无时无刻不在“有关部门”的监管之下,似乎也难有作为。于是乎造成了中国微博看似繁荣,实则尴尬的局面。
+
+重新开始写博,一方面是希望今后的生活能有些许积淀,能为以后的回忆留下可以追溯的足迹;另一方面也算机缘所致,恰好有这么个拾回以前习惯的机会。总之,何乐不为呢~?
diff --git a/_posts/2011-09-11-shi-nian.markdown b/_posts/2011-09-11-shi-nian.markdown
new file mode 100644
index 00000000..20989a01
--- /dev/null
+++ b/_posts/2011-09-11-shi-nian.markdown
@@ -0,0 +1,16 @@
+---
+layout: post
+title: 十年
+date: 2011-09-11 23:34:15.000000000 +09:00
+tags: 胡言乱语集
+---
+倏忽之间,十年已逝。于一个历史事件而言,十年时间也许还不足以给出公允的评判;但于生者或逝者而言,却已然是应当忘怀的时候了。纵然一个国家的政府有再大的过错,但是国家的人民永远是无辜的。在这样的时刻,惟有为逝者哀悼,为生者祈福。
+
+
+过去的十年,两次经济危机,两次局部战争,美国的霸主地位依然不可动摇。这十年间,美国更多地把精力集中在了恐怖主义身上,中国这一曾经的假想敌也更多地被作为“反恐合作伙伴”而受到欢迎。不得不说这是中国的极大的发展机会,可惜的是,这十年于中国政府来说可能是大发展,但是于中国人民来说,可能却是大倒退。每个月可怖的CPI数据,一团混乱的税收体制,各种不安全的社会因素,拜金主义下的道德沦丧,触目惊心的贪腐及权钱交易,超高物价和超低收入之间的矛盾。一切的一切似乎都在暗示,中国的运转存在问题。但是庞大的行政机构导致中央与地方的行政令完全不统一,地方权力逐渐膨胀,中央约束日渐微势,纵有胡温这样的开明领袖依然难以扭转中国当今局势。人大被架空,各种所谓的“行政调节”层出不穷,层削盘剥,民众哀怨,已经是当今的社会现实。
+
+
+明年是世界政坛领导人大换血的一年,中共换届,美俄大选,台府港府皆要换人,可有得热闹。明年之后中国要如何走,新的政府能否抓住国内主要矛盾,能否有有效的运转国家机器的手段,来保证她的国民的幸福,只能拭目以待了...
+
+
+希望明天会更好。
diff --git a/_posts/2011-09-14-windows-slatezheng-zhuang-dai-fa.markdown b/_posts/2011-09-14-windows-slatezheng-zhuang-dai-fa.markdown
new file mode 100644
index 00000000..35f8cf2e
--- /dev/null
+++ b/_posts/2011-09-14-windows-slatezheng-zhuang-dai-fa.markdown
@@ -0,0 +1,19 @@
+---
+layout: post
+title: Windows Slate,整装待发
+date: 2011-09-14 23:36:37.000000000 +09:00
+tags: 南箕北斗集
+---
+决定稍微晚睡一两小时,看看传说中的Win8到底是什么德行。接近发布的最后时刻,一直神秘的Win8终于决定了真正的名字——Slate...唔,确实总是不能叫做WinPad的吧...
+
+Metro,Start页面,支持arm,内置应用商店...其实这一切都透出了一股浓浓的山寨味。
+
+Macintosh被华丽剽窃的悲剧已经上演过一次,而如今微软又想故伎重演,估计这次苹果再笨怎么也不会重蹈当年的覆辙了吧~
+
+其实个人并不是很看好这款Windows,Ribbon的滥用也许能在平板设备上能有所建树,但是在传统PC上却略显臃肿和无趣了。微软如果不能巩固住其在PC系统领域的霸主地位,而又想急于进入已经被iOS和Android充分瓜分的移动市场,最终的结果会不会是两边不讨好呢...怀疑中?
+
+坐看微软是否能把两强争霸的局面硬生生拖入三足鼎立吧..
+
+毕竟多元化才能真正激发这个行业的创新,才能继续推动IT的发展,才能最终使人类受益吧。
+
+不写了,直播开始了..最后...希望WinSlate起航顺风,一路走好...还有..希望WinXP在国内的占有率能掉下来..XD...
diff --git a/_posts/2011-09-22-luan-shi-zhi-qiu.markdown b/_posts/2011-09-22-luan-shi-zhi-qiu.markdown
new file mode 100644
index 00000000..92f7bc19
--- /dev/null
+++ b/_posts/2011-09-22-luan-shi-zhi-qiu.markdown
@@ -0,0 +1,11 @@
+---
+layout: post
+title: 乱世之秋
+date: 2011-09-22 23:37:52.000000000 +09:00
+tags: 胡言乱语集
+---
+这是和平的年代,没有世界大战,也没有美苏争霸。这是混乱的时代,到处政治动乱,左右都是纷争。利比亚战乱尚未平息,也门又掀乱局。利益分配的戏码今年唱罢明年再演,丝毫没有新意..只是中国在这场利益分配的游戏中貌似永远拿不到自己的那份蛋糕,真不知是可歌可泣还是可悲可叹。
+
+这个秋天来得似乎比以往都早..北京三场秋雨下过,暑气便散得无影无踪,出门一件单衣俨然已经过于薄弱了。身边的一切 显得井井有条,学习、实验、测试、代码,早出晚归,披星戴月,虽然过得不算轻松,但却也还是愉快幸福。诚然我在这个表面的治世下活得还算潇洒,比起各种不幸来,我确实是幸运的。但是有时候总是在想,自己期望的到底是什么,我真正想要的生活到底是什么。呵呵,可能所有我这年纪或者比我还年轻一些的的同龄人都在考虑这样的问题吧。我也许会说我想要的不是那种平淡一生、安安静静的生活,我不满,我需要闯荡,也需要激情。但是心里却会有莫名的惧怕,惧怕失败的可能,也惧怕激情以后那无尽的繁杂。退却的心理便占据了上风,做事的时候只能蹑手蹑脚..但是转念想想,即使失败,又能失去什么呢?确实,我没有什么可以失去的东西,而这正是我最轻松最可能放手一搏的时机呐。人就是矛盾的生物吧,也许什么时候我不再思前想后了,什么时候心中有一份执着的追求了,也许我就能找到我的答案了吧。
+
+可能我需要的是一个乱世?唔,一个技术的乱世,也许就够了吧。笑。
diff --git a/_posts/2011-10-03-mai-ke-ji-man-sai.markdown b/_posts/2011-10-03-mai-ke-ji-man-sai.markdown
new file mode 100644
index 00000000..7e019a15
--- /dev/null
+++ b/_posts/2011-10-03-mai-ke-ji-man-sai.markdown
@@ -0,0 +1,7 @@
+---
+layout: post
+title: 麦克基满塞
+date: 2011-10-03 23:38:59.000000000 +09:00
+tags: 胡言乱语集
+---
+出游总是会看到新的东西,总是会给人带来惊喜,总是能令你开阔眼界。我素来知道中国的山寨事业一直兴旺发达,但是今天在长沙汽车站看到的这家店确实震惊了...名字霸气就不说了,以前类似的店 面也就记得有个“麦肯基”,如今连德克士也不能幸免了~关键在于,这家店的位置好的出奇.就紧靠汽车站大门..走过路过完全没有可能错过啊...嗯,当然 边上也还是有传统的车站餐馆K记,不过悲剧的是正牌K记被挤到了一条街之外。 嗯...这家店边上嘛,貌似附送了一家传说中的阿迪王...长沙是个蛮囧的地方..至少汽车站感觉是这样的 =_=~祝我旅途愉快吧..吼吼
diff --git a/_posts/2011-10-07-iphone.markdown b/_posts/2011-10-07-iphone.markdown
new file mode 100644
index 00000000..d3acd9fb
--- /dev/null
+++ b/_posts/2011-10-07-iphone.markdown
@@ -0,0 +1,19 @@
+---
+layout: post
+title: 乔帮主,一路走好
+date: 2011-10-07 23:41:23.000000000 +09:00
+tags: 南箕北斗集
+---
+一个时代的终结,意味着另一个时代的开始。一个巨匠的陨落,代表着一颗新星的诞生。1642年伽利略带着“追求科学需要特殊的勇气”,以自己的溘然辞世宣告了旧时代的终焉。一年之后,牛顿呱呱坠地,预告了新的物理学时代的开幕。当世之时,乔布斯所代表的便是对旧秩序的挑战以及对完美创新的不懈追求。
+
+当我们一遍一遍地被IBM和Wintel联盟强奸的时候,乔布斯犹如救世主一般一次又一次降临。1984对蓝色巨人的挑战,1998的强势回归,唯美的工业设计追求一直给予消费者无尽的享受。我相信没有其他人能设计出iMac的半透明一体机箱,也没有人能设计出双面玻璃金属圈天线,或者是哪怕玻璃旋转楼梯这样的产品。正如Apple官网所述,一位杰出的,了不起的人物告别了世界。
+
+而几乎是同时,iPhone4终于迎来了自己的接任者。与其把iPhone4s解读为iPhone Second,不如把它认为是iPhone4 Steve——这是乔布斯留给世界的最后的礼物...iOS5的新鲜特性,iMessage向传统电信运营商强有力的挑战,Siri俨然让人看到了iRobot的希望。创新依旧,美丽依旧,对传统权威的挑战也依旧。
+
+但是,它确实缺少了一些东西..缺少了背后的一位伟人的关注。
+
+乔帮主,一路走好...你没有离去,我们将铭记你的教诲,
+
+>Stay hungry. Stay Foolish
+
+你的设计和思想将在另一个世界继续发光。因为你的离去,一定是因为上帝也想用iPhone了...
diff --git a/_posts/2011-10-15-zhaopin.markdown b/_posts/2011-10-15-zhaopin.markdown
new file mode 100644
index 00000000..d3a80d74
--- /dev/null
+++ b/_posts/2011-10-15-zhaopin.markdown
@@ -0,0 +1,15 @@
+---
+layout: post
+title: 求职季
+date: 2011-10-15 23:43:39.000000000 +09:00
+tags: 南箕北斗集
+---
+又到了一年一度的求职季~招人者基本年年不变,求职者换了一批又一批。年复一年,却也正见证了这个社会的新陈代谢。简历已经扔出去一些了,但是事实上特别满意的或者特别想要拿到的职位并不多..也许是因为这么多年书读出来,已然迷失了自己,我现在很难安静下来询问自己的内心:你想做什么,你能做什么?往往一思考到这个问题,我就选择逃避..我会告诉自己,我想要的很简单,安静恬逸的小生活而已——虽然我知道这不是真的,这只是我多年前的梦想罢了...
+
+也许人都会经历这样的过程吧,从满是理想主义的充满幻想到被现实主义约束的看清事实,最后又看透这个世界,返璞归真。前两个阶段是都能达到的,但是第三阶段看似是需要一些“修为”了的。也不知这样的境界我是否能够看到..
+
+不过现在最要紧的好像是找到份自己还算喜欢的工作吧~
+
+今天有两场笔试,上午10点一场,我本来以为剩下的时间不够写完的。但是貌似由于来参加的人太多,导致考场爆满,没参加网申的人现在被陆续赶出去了~于是笔试华丽的推迟了几分钟...
+
+祝自己求职顺利,一帆风顺吧,加油!
diff --git a/_posts/2011-10-22-pandaria.markdown b/_posts/2011-10-22-pandaria.markdown
new file mode 100644
index 00000000..3ae23e35
--- /dev/null
+++ b/_posts/2011-10-22-pandaria.markdown
@@ -0,0 +1,13 @@
+---
+layout: post
+title: 潘达利亚,你好
+date: 2011-10-22 23:46:18.000000000 +09:00
+tags: 胡言乱语集
+---
+因为一直忙着找工作,最近对各种消息新闻的关注很少。早上起来难得有时间看看新闻,发现BlizzCon2011最终还是没有让人失望,一年一部的WOW资料片又被推上了前台。潘达利亚之雾(Mists of Pandaria)的背后,熊猫人最终还是被BLZ作为填充新作空白期的大菜呈递了出来。虽说整个WOW主线和熊猫人其实没有什么关系,但是作为War3的重要英雄之一,熊猫人的出现倒也还算是合情合理吧。
+
+一如既往的,新的种族,新的职业,新的大陆,新的副本,新的系统,一切看起来都很美好。但是...仔细发觉一下就会发现有不少问题。小宠物对战系统很是让人无语,我能吐槽这整就一个宠物小精灵么?螳螂人蘑菇人XX人也不禁让人看到了不少"中国玄幻类"免费网游的影子。而对于天赋的修改也让人依稀看到了Diablo的方式——莫非这就是BLZ的终极目标星际魔兽大菠萝3in1的重要一步?
+
+CTM毫无疑问是失败的,因为副本模式的变化导致了玩家黏合力的下降,随之而来的是批量AFK的浪潮。而MOP中却没有对这个关键因素做出修改。4.3也还没来,现在谁也不知道休闲模式的效果如何,但是由于所谓的休闲模式出产实在鸡肋,估计也难以成功。倘若MOP完全沿用现在的模式的话,其失败已然是可以预见的了。
+
+或许这正是BLZ想要的吧,因为没有谁能有精力同时投身两个大型RPG,买一年WOW送终身Diablo也许能说明一些问题——想要把死忠都赶到新的世界中去。七年风雨,七年辉煌之后,WOW的时代终要过去了,而网络游戏新的篇章却正要开启...
diff --git a/_posts/2011-10-27-xcode4.markdown b/_posts/2011-10-27-xcode4.markdown
new file mode 100644
index 00000000..080b203d
--- /dev/null
+++ b/_posts/2011-10-27-xcode4.markdown
@@ -0,0 +1,15 @@
+---
+layout: post
+title: Xcode4.2,想说爱你不容易
+date: 2011-10-27 23:48:33.000000000 +09:00
+tags: 南箕北斗集
+---
+随着iOS5,最终还是在一个项目结束之前就被迫换到XCode4.2了。XCode4初出的时候就有无数先辈惨死在无尽的bug和极度不适中,而我选择了在一段时间的4.1和3.2.6共存的过渡期后再完全转到新版本下继续工作,现在看来是非常明智的。
+
+GCC在4.2彻底再见了,同样标着4.2,但是想在XCode4.2上弄个GCC4.2的编译器还真是费力。还好LLVM还不错,就是可怜了那些写了N多GCC only的stand-alone的苦逼程序员了。LLVM最终还是暴露了了Apple想要脱离GNU的目标,不过新的编译环境给人的感受确实很棒。得益于LLVM,coding的时候实时能看到代码错误,ARC的引入彻底让人可以忘掉繁琐又无聊的GC策略,从而可以专注于内容实现。这在以前的IDE中是难以想象的。
+
+功能的整合与集成也很方便,Interface Builder终于内嵌了,IBOutlet和IBAction前面也加上了小圆圈,这样一来很不容易再出现忘了拉线这样尴尬的失误了。不知道什么时候能把Instrument也整合到XCode里来,那样生活会更美好=。=
+
+当然也有让人不满的地方,或者说是我还不懂的地方。为什么新工程的默认设置是禁用C和Objective-C的异常捕捉呢...拖别人的代码的时候基本都会有try..catch,于是每次都要去手动改..满讨厌,不知道是不是有什么新的异常处理的机制?另外iOS5的SDK还是有些问题,segment的property设置不再调用change方法把我坑了一个下午,ASI的一个消息也会莫名中断...完美的iOS5 SDK和相应的开源库毕竟还需要时间。嗯...还有一点就是我的机子跑XCode4真的好慢!
+
+最后抱怨下...习惯了写release以后现在在ARC的光芒下写release就被打红叉,每次都苦笑..看来习惯它还真的是需要一定时间呢~
diff --git a/_posts/2011-10-31-seeking-job.markdown b/_posts/2011-10-31-seeking-job.markdown
new file mode 100644
index 00000000..2a450587
--- /dev/null
+++ b/_posts/2011-10-31-seeking-job.markdown
@@ -0,0 +1,31 @@
+---
+layout: post
+title: 近期求职总结-AHRP和DeNA面试
+date: 2011-10-31 23:52:17.000000000 +09:00
+tags: 胡言乱语集
+---
+AHRP2012秋季项目已经结束,春季项目博主可以以内定者的身份进行内推,具体可以参看这篇帖子。
+
+在求职路上已经走了一段时间了,一直不太顺利,趁现在有时间有心情稍微整理一下近期在求职中的感受,也算做一个阶段总结吧。
+
+其实一开始目标就比较明确了,不太想继续在做硕士期间的方向走下去,而是想做一些移动互联开发相关的事情,同时也看好智能电视和近距传输这样有可能在近期再一次改变人类生活的东西。所以简历大都扔到了IT公司。投出去的简历无非三种结果:第一是毫无回音的,比如支付宝;第二是通知笔试但是被我拒了,比如盛大;最后确实比较诱人的企业,比如网易腾讯之流,但是参加完笔试就毫无音讯——作为一个学IC的,要去和软院的孩子们比默写最短路径之类的无聊算法实在有些强人所难,以及无趣。其实真正全心投入了的恐怕只有AHRP项目过来的DeNA的面试了吧。
+
+AHRP项目很赞,最初其实只是在C楼紫超下楼拐角看到个易拉宝而已,结果很巧的发现了DeNA。可能在移动互联这个行业也干了大半年了,每天算是耳濡目染,对比如Gameloft,DeNA或者Mixi这样的公司的名字已经有天然的感觉了吧。而且能到国外工作生活一段时间对自己人生来说也是一件很有意义的事情,就决定参加了。AHRP在清华的宣讲会场面很火爆,我也很幸运地抢到了最后一把椅子,而宣讲会上武田先生和小畑先生的表现也都很让人印象深刻。和中国企业的招聘会的不同之处在会后收简历的时候就已经体现出来了:一共有大概三到四个人在收简历,而每个学生交简历的时候AHRP的工作人员都会和学生简单确认求职意向、专业情况,并且尝试了解一些学生最初的想法并且在简历上圈点。DeNA这次在中国的招聘来得很早,因此我被提醒要保持手机通讯通畅,以便及时联系。
+
+其实当天晚上就收到了AHRP的一面通知,第二天就开始了AHRP和DeNA的面试旅程。在网上也翻找过一些AHRP的面经,基本上这个项目的面试或者笔试会是一个漫长的过程,但是由于面试性质和国内的传统意义上的面试不太一样,所以也还算轻松。比较标准的流程大概是6面,有部分企业的话还会要求一次笔试。前三次面试是AHRP组织的,确认你的专业情况和去日本的动机,基本上就是作为初筛,把觉得值得向企业推荐的人推荐给企业进行面试,鉴于中国毕业生现在的海投简历的习惯,这相当于减小了企业组织面试的压力。后三面属于填报的企业进行的面试,主要考察是否和公司文化匹配以及基本的职业技能。而笔试常常在企业一面前后作为企业筛选的一道关卡吧。
+
+一面很幸运遇到了小畑先生,和大多数面试官不同,小畑先生似乎天生就有一种亲和力,和他的对话令人非常开心,并且能很快的放松下来。这大概就是个人魅力的所在吧,也难怪小畑先生会被誉为日本人力资源界的神话。问题都很随和简单,整个过程就是在聊天,主要是了解你为什么想去日本。其实这个问题对于每个中国学生来说可能答案都会不一样,会来关注AHRP项目的学生基本上都会对海外工作有种朦胧的向往,期待着能在异国找到理想的生活和不错的薪资。但是若要真正的让大家说出自己的想法的时候,理由往往会变得冠冕堂皇起来,比如开阔眼界、创新精神这样的回答。我想大概所有AHRP的面试官都对这样的回答不会陌生,甚至估计耳朵都听出茧子了吧..但是无疑这会是最保守最靠谱的答案。但是除此之外,可能真的需要思考为什么自己会来参加AHRP,为什么自己会想要到日本工作?Listen to your heart,让你的心来作答,也许是最好的选择。如果你只是有种朦胧的向往,却拨不开那层迷雾,看不到自己真实的想法,那日本的忙碌的生活和高强度的工作也许并不是想像中的那样美好。
+
+由于DeNA招人很急,AHRP的二面貌似被省略了,取而代之的是拿到了一面通过的通知,同时被告知需要提交一份技术报告和参加笔试。技术报告很开心的完成了,毕竟大半年的兼职让我确实有不少可以能拿得出手的项目,而这些项目对于一个应届生来说绝对是难能可贵的财富。笔试比较有趣,看起来是DeNA组织的,大概是是一份给日本本土员工的智力测验+性格测验题。有技巧的算术计算,图形规律,按指令变换图形和按图形推断指令,总之都是很传统的智商测验的题目,这点我很有优势——因为小时候自己做过相当多的智商训练题目,而那些题目大多要比这次笔试的难很多。我个人觉得这种笔试题的方向很好,因为它测试的是人的逻辑思维和观察能力,而这两者大概是完成工作所最需要的素质(当然在中国这样人际关系复杂的地方可能还不够),而我很讨厌那种考一个迷宫算法或者排序算法的无聊的默写代码题,在这样一个时代默写算法已经解决不了任何问题了,但是中国很多公司还是乐此不疲,真是悲哀..
+
+很顺利接到三面通知,AHRP的三面貌似不太刷人,大概就是确认下你的意向是否有变,同时稍微考察下你对要应聘的公司的了解吧。三面我遇到了中国的面试官,大概为了效率,一个面试官同时面了四个人。我好像一直不太适应这种一对多的面试,发挥其实不太好,回来还是忐忑了一阵子。不过也许确实三面不刷人,也很开心的接到了通过的通知。
+
+企业一面,在DeNA的话是一个简单的技术面,有一个日本的人事和一个中国的技术做面试官。会对你之前提交的技术报告做一个简单的了解。我本来会很有优势,因为我的技术报告不需要我过多的介绍,拿出一台iOS设备来run一下自己的成果就好了。但是突发状况让我很郁闷,前一天的bug还没有修正直接导致了app打不开...还好之前就说过这还是在测试开发中的版本,然后很不介意的打了圆场说技术二面还有机会的话再给你们展示,然后blablabla答了一些问题。大概是花了多长时间做出来,有多少人的团队完成的项目,在团队中担任什么角色等等。除了项目的话也还问了些关于之前实习的情况。总之如果确实有很好的项目经历的话,如实介绍就好了~
+
+当晚收到企业二面的通知——技术二面。面试官比之前的一面多了一个日本的技术人员,其实就是对你的项目进行深入的了解。没什么说的,同样的错误不可能犯第二次,而且他们也都到App Store下载装上了,于是一边介绍一边玩,时间哗哗的就过去了。我估计他们一整天坐那面试也很烦闷了,现在难得有个机会还能正经地玩会儿游戏,都很开心的样子。于是我知道技术面肯定是OK了..
+
+在网上看到的面经一般公司就两面,然后第三面看看人没问题就签了。所以收到企业三面的通知看到是人事面的时候还是担心了一下的。而事实证明了我在面对人事面试的时候准备和经验也确实还不够充分。人事面基本是以了解你性格特点为目的的,大概问题就是很经典的你觉得成功的事情啦,遇到的挫折啦之类的。其实和面试官聊得不是很开心吧,我想是因为时间选的不好,感觉下午四点半面试官已经蛮累了。加上貌似翻译上面出了一点小问题,导致了他提问的问题被误解了,最后我的回答和他的问题对不上。虽然后来我发现了,补正了几句,但是不好的印象已经留下了,可能也于事无补了吧~
+
+人事面出来就大概知道可能死在了这最后一坎上,一开始还有一点侥幸心理,希望能够靠技术面的表现能有一个argue的机会,不过最后证明我在这点上的想法还是过于天真了。当天晚上没有能够等到通过的通知,基本上还是难过到了后半夜才有睡着——毕竟是一个为之付出了不少心血的项目,却最后在本不该失败的地方跌到了,失去了这样一个机会,自己都觉得惋惜。
+
+不过生活总是还要继续,得之我幸,失之我命~一扇门关闭了,你才能够走到另一扇门中去发现那里的世界。正如我在简历上写的,平淡对待得失,冷眼看尽繁华,现在的我,其实离那样理想的境界更近了一步。
diff --git a/_posts/2011-11-09-objc-block.markdown b/_posts/2011-11-09-objc-block.markdown
new file mode 100644
index 00000000..cee1c698
--- /dev/null
+++ b/_posts/2011-11-09-objc-block.markdown
@@ -0,0 +1,137 @@
+---
+layout: post
+title: Objective-C中的Block
+date: 2011-11-09 23:55:12.000000000 +09:00
+tags: 能工巧匠集
+---
+技术是需要沉淀的。接触iOS开发也有大半年时间了,从一开始的纯白到现在自我感觉略懂一点,其实进步是明显的。无数牛人表示技术博是完成菜鸟到高手蜕变的途径之一,虽然这个博客并非是为技术而生,但是也许作为工科背景下的我来说,每天都写文艺的东西显然并不现实。于是就有了这个集子:能工巧匠集。用这篇开篇,
+
+写一些在开发过程中的积累和感悟,大部分应该是Objectiv-C和XCode的内容,包括基本语法特性和小技巧,或者自己喜欢的一些开源代码的用法分析等等。也许以后会扩展到Unity3D或者UDK的一些3D引擎的心得,当然也有可能会有别的一些自己认为值得分享的东西。这个集子的目的,一来是记录自己一步一步成长的脚印,二来也算是为新来者铺一条直一点的道路。集子里的东西仅仅是自己的心得体会,高手路过请一笑置之..感恩。
+
+iOS SDK 4.0开始,Apple引入了block这一特性,而自从block特性诞生之日起,似乎它就受到了Apple特殊的照顾和青睐。字面上说,block就是一个代码块,但是它的神奇之处在于在内联(inline)执行的时候(这和C++很像)还可以传递参数。同时block本身也可以被作为参数在方法和函数间传递,这就给予了block无限的可能。
+
+在日常的coding里绝大时间里开发者会是各种block的使用者,但是当你需要构建一些比较基础的,提供给别人用的类的时候,使用block会给别人的使用带来很多便利。当然如果你已经厌烦了一直使用delegate模式来编程的话,偶尔转转写一些block,不仅可以锻炼思维,也能让你写的代码看起来高端洋气一些,而且因为代码跳转变少,所以可读性也会增加。
+
+先来看一个简单的block吧:
+
+```objc
+// Defining a block variable
+BOOL (^isInputEven)(int) = ^(int input) {
+ if (input % 2 == 0) {
+ return YES;
+ } else {
+ return NO;
+ }
+};
+```
+
+以上定义了一个block变量,block本身就是一个程序段,因此有返回值有输入参数,这里这个block返回的类型为BOOL。天赋异秉的OC用了同样不走寻常路的"{% raw %}
+^{% endraw %}"符号来表示block定义的开始(就像用减号和加号来定义方法一样),block的名称紧跟在{% raw %}
+^{% endraw %}符号之后,这里是isInputEven(也即以后使用inline方式调用该block时所需要的名称)。这段block接受一个int型的参数,而在等号后面的int input是对这个传入int参数的说明:在该block内,将使用input这个名字来指代传入的int参数。一开始看block的定义和写法时可能会比较痛苦,但是请谨记它只是把我们常见的方法实现换了一种写法而已,请以习惯OC中括号发送消息的速度和决心,尽快习惯block的写法吧!
+
+调用这个block的方法就非常简单和直观了,类似调用c函数的方式即可:
+
+```objc
+// Call similar to a C function call
+int x = -101;
+NSLog(@"%d %@ number", x, isInputEven(x) ? @"is an even" : @"is not an even");
+```
+
+不出意外的话输出为**-101 is not an even number**
+
+以上的用法没有什么特别之处,只不过是类似内联函数罢了。但是block的神奇之处在于block外的变量可以无缝地直接在block内部使用,比如这样:
+
+```objc
+float price = 1.99;
+float (^finalPrice)(int) = ^(int quantity) {
+ // Notice local variable price is
+ // accessible in the block
+ return quantity * price;
+};
+int orderQuantity = 10;
+NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
+```
+
+输出为**Ordering 10 units, final price is: $19.90**
+
+相当开心啊,block外的`price`成功地在block内部也能使用了,这意味着内联函数可以使用处于同一scope里的局部变量。但是需要注意的是,你不能在block内部改变本地变量的值,比如在{% raw %}
+^{% endraw %}{}里写`price = 0.99`这样的语句的话,你亲爱的compiler一定是会叫的。而更需要注意的是`price`这样的局部变量的变化是不会体现在block里的!比如接着上面的代码,继续写:
+
+```objc
+price = .99;
+NSLog(@"Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
+```
+
+输出还是**Ordering 10 units, final price is: $19.90,**这就比较忧伤了,可以理解为在block内的`price`是readonly的,只在定义block时能够被赋值(补充说明,实际上是因为`price`是value type,block内的`price`是在申明block时复制了一份到block内,block外面的`price`无论怎么变化都和block内的`price`无关了。如果是reference type的话,外部的变化实际上是会影响block内的)。
+
+但是如果确实需要传递给block变量值的话,可以考虑下面两种方法:
+
+1、将局部变量声明为`__block`,表示外部变化将会在block内进行同样操作,比如:
+
+```objc
+// Use the __block storage modifier to allow changes to 'price'
+__block float price = 1.99;
+float (^finalPrice)(int) = ^(int quantity) {
+ return quantity * price;
+};
+
+int orderQuantity = 10;
+price = .99;
+
+NSLog(@"With block storage modifier - Ordering %d units, final price is: $%2.2f", orderQuantity, finalPrice(orderQuantity));
+```
+
+此时输出为**With block storage modifier – Ordering 10 units, final price is: $9.90**
+
+2、使用实例变量——这个比较没什么好说的,实例内的变量横行于整个实例内..可谓霸道无敌...=_=
+
+block外的对象和基本数据一样,也可以作为block的参数。而让人开心的是,block将自动retain传递进来的参数,而不需担心在block执行之前局部对象变量已经被释放的问题。这里就不深究这个问题了,只要严格遵循Apple的thread safe来写,block的内存管理并不存在问题。(更新,ARC的引入再次简化了这个问题,完全不用担心内存管理的问题了)
+
+由于block的灵活的机制,导致iOS SDK 4.0开始,Apple大力提倡在各种地方应用block机制。最典型的当属UIView的动画了:在4.0前写一个UIView的Animation大概是这样的:
+
+```objc
+[UIView beginAnimations:@"ToggleSiblings"context:nil];
+[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:self.view cache:YES];
+[UIViewsetAnimationDuration:1.0f];
+// Make your changes
+[UIView commitAnimations];
+```
+
+在一个不知名的小角落里的begin/commit两行代码间写下需要进行的动作,然后静待发生。而4.0后这样的方法直接被discouraged了(虽然还没Deprecated),取而代之的正是block:
+
+```objc
+[UIView animateWithDuration:5.0f animations:^{
+ view.opacity = 0.5f;
+}];
+```
+
+简单明了,一切就这么发生了..
+
+可能有人会觉得block的语法很奇怪,不像是OOP的风格,诚然直接使用的block看起来破坏了OOP的结构,也让实例的内存管理出现了某些“看上去奇怪”的现象。但是通过`typedef`的方法,可以将block进行简单包装,让它的行为更靠近对象一些:
+
+```objc
+typedef double (^unary_operation_t)(double op);
+```
+
+定义了一个接受一个double型作为变量,类型为unary_operation_t的block,之后在使用前用类似C的语法声明一个unary_operation_t类型的"实例",并且定义内容后便可以直接使用这个block了~
+
+```objc
+unary_operation_t square;
+square = ^(double operand) {
+ return operand * operand;
+}
+```
+
+啰嗦一句的还是内存管理的问题,block始终不是对象,而block的内存管理自然也是和普通对象不一样。系统会为block在堆上分配内存,而当把block当做对象进行处理时(比如将其压入一个NSMutableArray),我们需要获取它的一份copy(比如[square copy]),并且在Array retain了这个block后将其释放([square autorelease]是不错的选择)。而对于block本身和调用该block的实例,则可以放心:SDK会将调用block的实例自动retain,直至block执行完毕后再对实例release,因此不会出现block执行到一半,实例就被dealloc这样的尴尬的局面。 在ARC的时代,这些都是废话了。打开ARC,然后瞎用就可以了。ARC解决了block的最让开发者头疼的最大的也是唯一的问题,内存管理。关于block的内存管理方面,有一个很好玩的小quiz,可以做做玩~[传送门](http://blog.parse.com/2013/02/05/objective-c-blocks-quiz/)
+
+iOS SDK 4.0以后,随着block的加入很多特性也随之添加或者发生了升级。Apple所推荐的block使用范围包括以下几个方面:
+
+ * 枚举——通过block获取枚举对象或控制枚举进程
+ * View动画——简单明了的方式规定动画
+ * 排序——在block内写排序算法
+ * 通知——当某事件发生后执行block内的代码
+ * 错误处理——当错误发生时执行block代码
+ * 完成处理——当方法执行完毕后执行block代码
+ * GCD多线程——多线程控制,关于这个以后有机会再写…
+
+仔细研读4.0的SDK的话,会发现很多常用类中都加入了不少带block作为参数的方法,改变固有思维习惯,适应并尽可能利用block给程序上带来的便捷,无疑是提高效率和使代码优雅的很好的途径~
diff --git a/_posts/2011-11-20-rest.markdown b/_posts/2011-11-20-rest.markdown
new file mode 100644
index 00000000..6c6ee01a
--- /dev/null
+++ b/_posts/2011-11-20-rest.markdown
@@ -0,0 +1,9 @@
+---
+layout: post
+title: 难得的休假
+date: 2011-11-20 23:57:19.000000000 +09:00
+tags: 胡言乱语集
+---
+海南是个不错的地方,十多年前来就很不错,现在的话貌似也还行。难得能在这种忙得死人的时候能拿出三天时间跑到海南放松一下…刚做完一个会议报告就被大家围着提问,十分钟的演讲,提问居然持续了十五分钟,这不是明摆着坑我么...看来之前的准备还是过于浅显了。下次有这样的会议报告的话一定努力做得让大家看不懂才行。
+
+讲完以后现在可以开始休息两天了~这半年来被各种杂务缠身,不过貌似现在已经习惯这样的忙碌生活了,突然有机会放松下来心里居然会有一些不安。而幸好,海南正好是一个能够让人轻松下来的地方,只要躲开白天的下饺子,选择半夜三点到海边吹风,在静谧的海滩上倾心聆听。深夜里视觉的丧失,能换来听觉的敏锐,由耳及心,去感受那一份独特的宽广和优雅,这样的体会在能看到喧闹的海浪的时候是永远体会不到的。
diff --git a/_posts/2011-11-26-debug.markdown b/_posts/2011-11-26-debug.markdown
new file mode 100644
index 00000000..c849964e
--- /dev/null
+++ b/_posts/2011-11-26-debug.markdown
@@ -0,0 +1,15 @@
+---
+layout: post
+title: Xcode4.2的debug小技巧
+date: 2011-11-26 23:58:57.000000000 +09:00
+tags: 能工巧匠集
+---
+GNU Debugger(gdb)
+
+一直是UNIX下最为流行的调试器,而在Cocoa框架中也一直被作为默认的调试工具。在gcc都被LLVM取代了的如今,gdb还是作为默认调试器,更可见其优秀特性。
+
+最近在调试过程中发现了一些小窍门或者说是小技巧,不敢独飨。也许调试在大多数人看来不过是切断点,run程序,断住,然后开始分析。很多时候我们需要在gdb中一行行敲命令去控制gdb的运行,而如果我们右击代码段左侧的断点标记,可以发现一个很有趣的菜单,那就是Edit Breakponit。
+
+然后你会发现,原来断点和调试器如此强大。不同的action可以解放你的双手完成无脑傻瓜调试,比如自动在断点处输入调试命令,按照需求输出log,为断点次数计数,执行shell命令,捕捉断点时的屏幕状态等,更甚至于可以运行AppleScript。熟悉AppleScript的朋友便知道这意味着什么,你甚至可以在gdb上外挂上自己的另外的程序,这是无数的可能。这种方式让我联想到用lua和python这样的脚本语言制作的各类插件,而AppleScript一向是被低估的语言(很大程度上是由于平台局限)。但是显然现在AppleScript可以用来作为一个gdb的插件语言,保不准明天它就会成为iOS设备上的标准插件语言,如果Apple有这样的气魄、决心和能力让AppleScript可以运行在iOS设备的话,对于所有的Cocoa开发者来说,都将是一场激动人心的革命(好吧因为系统的封闭性被打破,这也许对用户会是灾难..)~
+
+总之,断点设置不错,配合AppleScript的话很好很强大,花上5分钟研究研究断点设置能实现的功能,一定能提高gdb的使用效率~
diff --git a/_posts/2011-11-26-kayac.markdown b/_posts/2011-11-26-kayac.markdown
new file mode 100644
index 00000000..f7355a3e
--- /dev/null
+++ b/_posts/2011-11-26-kayac.markdown
@@ -0,0 +1,17 @@
+---
+layout: post
+title: 尘埃落定,下一站Kayac
+date: 2011-11-26 00:02:48.000000000 +09:00
+tags: 胡言乱语集
+---
+AHRP2012秋季项目已经结束,春季项目博主可以以内定者的身份进行内推,具体可以参看这篇帖子。
+
+从10月初开始到现在,一个多月的求职尘埃落定,没有意外的话我明年将在日本镰仓(Kamakura)的Kayac公司开始自己的职业生涯。回顾这一个多月来的心情沉浮,颇有收获:从一开始的国内公司连连被拒,到之后几近看到出路,到最后寻找到Kayac,一路过来似乎有些宿命的感觉,而在一次又一次的笔试面试谈话中,确实渐渐看清了今后的道路和心中的诉求。这一个多月里,思考了很多人生的问题,整个人成熟了不少。
+
+Kayac有个日文名字叫做“面白法人”,中文里是“有趣的公司”的意思。一个公司能取出这样的名字,足以证明这是一个富有想象力的有趣的地方。这其实本身就和我略有玩世不恭的个性有几分呼应,在之后和公司CEO柳泽先生和CTO贝畑先生聊过后觉得他们的气场确实很吸引我,这正是我所寻觅的一份工作。而好像恰好我在面试中所表现的技能和性格也很合他们的意,所以双方似乎并没有太多的犹豫和纠结,很开心地就把这件事情定下来了。
+
+在国外生活对我这个土包子来说已经是很大的挑战了,现在还加上是在国外开始自己的职业生涯,那将会是一个莫大的挑战了。一开始当然会是新奇感占据上风,但是随着时间的积淀,各种未知的因素自然会浮现出来。虽然在面试时信心满满地表示适应新的生活完全不是问题,但是到底需要多久才能随心无拘,自己也并不知道。还好我想来不是一个依赖环境的人,也常常喜欢独立,所以也许现在的担心都是多余的吧~
+
+到今天为止,求职算是告一段落,近期的重点将转到学术实验和语言学习上了。
+
+从这里开始,职业生涯的序章已经拉开,希望我能将之后的篇章谱写的精彩有趣。
diff --git a/_posts/2011-11-30-nsurl.markdown b/_posts/2011-11-30-nsurl.markdown
new file mode 100644
index 00000000..6803b957
--- /dev/null
+++ b/_posts/2011-11-30-nsurl.markdown
@@ -0,0 +1,34 @@
+---
+layout: post
+title: 关于 NSURL 的解析和编码
+date: 2011-11-30 00:06:37.000000000 +09:00
+tags: 能工巧匠集
+---
+NSURL毫无疑问是常用类,有时候我们需要对一个url进行分析整理,当然是可以按照RFC 1808的定义去自己分析,但是万能的Apple大大已经在SDK里扔了不少方法来帮助解析一个url了…方便又快捷呐~比如下面的输入:
+
+```
+NSURL *url = [NSURL URLWithString:
+ @"http://www.onevcat.com/2011/11/debug/;param?p=307#more-307"];
+NSLog(@“Scheme: %@”, [url scheme]);
+NSLog(@“Host: %@”, [url host]);
+NSLog(@“Port: %@”, [url port]);
+NSLog(@“Path: %@”, [url path]);
+NSLog(@“Relative path: %@”, [url relativePath]);
+NSLog(@“Path components as array: %@”, [url pathComponents]);
+NSLog(@“Parameter string: %@”, [url parameterString]);
+NSLog(@“Query: %@”, [url query]);
+NSLog(@“Fragment: %@”, [url fragment]);
+```
+
+将得到以下输出:
+
+
+
+没什么值得多说的~相当方便就能得到所要结果的方法~
+另外,在由`NSString`生成`NSURL`对象时,有可能会出现`NSString`中包含百分号各类括号冒号等对于url来说的非法字符如果直接进行转换的话将得到nil。在对于复杂url进行转换前,可以先试试对待转换的NSString发送
+`stringByAddingPercentEscapesUsingEncoding:`
+将其转换为合法的url字符串(其实目的就是保证非法字符用UTF8编码..) 比如这样:
+
+```
+NSString *fixedStr = [reqStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
+```
diff --git a/_posts/2011-12-03-debug-2.markdown b/_posts/2011-12-03-debug-2.markdown
new file mode 100644
index 00000000..a4a04c0f
--- /dev/null
+++ b/_posts/2011-12-03-debug-2.markdown
@@ -0,0 +1,43 @@
+---
+layout: post
+title: Objective-C中的Debug表达式
+date: 2011-12-03 00:11:55.000000000 +09:00
+tags: 能工巧匠集
+---
+有程序的地方就有bug,有bug的地方就需要debug。对于程序员来说,coding的过程便是制造bug和解决bug。Objective定义了不少表达式来协助debug的流程,将这些表达式用在NSLog中,可以简化部分工作,快速定义到debug的部分。 比如以下代码:
+
+```
+-(id) initWithPlayer:(VVPlayer *)aPlayer seatsNum:(int)seatsNum
+{ if (self = [super init])
+{
+NSLog(@”\n Function: %s\n Pretty function: %s\n Line: %d\n File: %s\n Object: %@”,__func__, __PRETTY_FUNCTION__, __LINE__, __FILE__, aPlayer);
+}
+…
+}
+```
+
+输出结果为:
+
+
+
+`__func__`, `__PRETTY_FUNCTION__`, `__LINE__`, `__FILE__`等都是系统预留的定义词,简单易用。
+
+另外有一些Core Foundation的方法可以从CFString的层级拿到一些有用的字符串,包括且不限于selector,class,protocol等,参考下面的代码:
+
+```
+-(id) initWithPlayer:(VVPlayer *)aPlayer seatsNum:(int)seatsNum
+{ if (self = [super init])
+{
+NSLog(@”Current selector: %@”, NSStringFromSelector(_cmd));
+NSLog(@”Object class: %@”, NSStringFromClass([self class]));
+NSLog(@”Filename: %@”, [[NSString stringWithUTF8String:__FILE__] lastPathComponent]);
+}
+…
+}
+```
+
+输出结果为:
+
+
+
+拿到了相关的字符串,其实这并不仅在调试中有用,在进行selector的传递时也很好用~
diff --git a/_posts/2011-12-06-neltharion.markdown b/_posts/2011-12-06-neltharion.markdown
new file mode 100644
index 00000000..e2b89a61
--- /dev/null
+++ b/_posts/2011-12-06-neltharion.markdown
@@ -0,0 +1,16 @@
+---
+layout: post
+title: 别了,耐萨里奥
+date: 2011-12-06 00:33:41.000000000 +09:00
+tags: 胡言乱语集
+---
+
+耐萨里奥,艾泽拉斯最强大的生物之一,受到泰坦祝福的大地守护者,黑龙领袖。他一直是睿智、高贵、沉着和强大力量的象征。在燃烧军团第一次入侵时,他率领了五色巨龙军团协助暗夜精灵抗击恶魔。也正是那时,他提议五大龙族领袖将力量注入巨龙之魂中,以抵抗军团。而不幸的是,在无数恶魔的萦绕下,在无尽的战斗中,在古老邪神的诱惑下,他癫狂了——他的身体一块块裂开,赤红的火焰从身体的裂缝中喷涌出来,他调转龙头,赶走了其他四色巨龙领袖。正是从此刻开始,他有了一个新的名字,死亡之翼。
+
+11月30日凌晨3时,WOW美服开放4.3暮光审判,四小时后,死亡之翼首次倒下;截止12月1日午11时,283家各色公会已经击杀死亡之翼;12月5日,爆出游戏漏洞,可以利用随机团队副本bug从一个BOSS身上反复获取装备,死亡之翼被高端公会刷烂。
+
+从不可一世的强大,到人见人欺的寂寥,这一切在暴雪的导演下,只需要一周不到的时间。艾泽拉斯的凡人们以不可思议的速度改变着他们的世界:存在于这个星球上上万年的生物被轻而易举地消灭,他们也和这个世界的创造者建立了沟通与联系。这个世界已经没有能够阻挡他们的力量了,无数的传奇和知名形象一个接一个地轰塌。在暴雪的世界里,似乎形象的塑造不需要成本,而他们的终途不过是凡人们通向更高的基石...这一切,是不是来的太快,也太突然?
+
+别了,耐萨里奥,是你见证了WOW的由盛转衰,是你见证了我离开艾泽拉斯这个世界的前前后后。你的那一份孤狂与高傲无人能懂,因为还没有人能够站在你那凛冽的高度俯视众生。也许你会有无数的继任者,但是那与你已经毫无关系。希望这是你的终结,希望你能得到安息。
+
+别了,耐萨里奥,你需要记住,你不需要祭文,也没有人会为你哀伤...
diff --git a/_posts/2011-12-08-nslogger.markdown b/_posts/2011-12-08-nslogger.markdown
new file mode 100644
index 00000000..2251df1a
--- /dev/null
+++ b/_posts/2011-12-08-nslogger.markdown
@@ -0,0 +1,13 @@
+---
+layout: post
+title: Log的艺术,顺带赞NSLogger
+date: 2011-12-08 00:35:22.000000000 +09:00
+tags: 能工巧匠集
+---
+写代码易,调程序难。不论是多么资深的程序员,都不可能在毛线球一般的代码中弄清到底发生了什么,特别是当在程序在N多个线程中来回跳转和涉及到难以理解的内存操作的时候,我们不可避免地需要log的帮助来整理思路,确认到底发生了什么。而这时候,输出log的好坏和时机,往往决定了花在调试上的时间。
+
+其实某种程度上来说,log是一门艺术,而从输出log上往往也能判断程序员的水平。新手往往都很可爱,NSLog(@"Hello World")会是不变的模式。不得不承认这样的输出如果在恰当的时候也能一击致命,但是它所能带给我们的信息量实在太少了。水平再高一点的程序员大概会在关键事件的时候在保证代码通用性的前提下使用诸如`__func__`之类的东西监测程序流程。高级程序员在log方面就会显得大巧若拙,也许会把整个代码流的行为都进行log,而不漏掉任何一个细节,包括所有的内部状态、各种事件、异常等,完全受掌控的代码和预期中的程序行为是他们成功的关键。而传说中的大牛级人物可能把更多的注意力放在线程,网络等时序上可能出错的地方,无谓的内存释放或是某个超时的网络请求都不会遗漏。
+
+其实在面对纷繁芜杂的输出需求的时候,Cocoa环境默认的NSLog基本就是个废品:它不能给输出分门别类,不能按照需求输出除了字符的格式,也不能兼顾线程区别对待。而在高水平log的要求中,这些都应当是提供给开发者最基本、最便利的输出方式。
+
+于是NSLogger出现了,在Florent Pillet的打造下,一个开源强力的输出工具给了log这一古老的工作崭新的生命。标签输出,优先级查找,直接输出图像,多线程标记,时序控制,甚至是通过网络log到别人的终端或者是从别人的终端程序中记录log。在这里,只有想不到没有做不到,堪称是史上最为强大的logger,而且最重要的是他是BSD License的。在我看来,任何apple开发者都不应该错过他。
diff --git a/_posts/2011-12-11-skyrim.markdown b/_posts/2011-12-11-skyrim.markdown
new file mode 100644
index 00000000..c72ec0cf
--- /dev/null
+++ b/_posts/2011-12-11-skyrim.markdown
@@ -0,0 +1,17 @@
+---
+layout: post
+title: 直到我膝盖中了一箭...
+date: 2011-12-11 00:36:43.000000000 +09:00
+tags: 胡言乱语集
+---
+这句话就这么火了。其实通宵等了一夜天际,但是之后却一直没有大块时间,所以至今主线还没有玩完,今年之内估计也是没有机会完结了。在2011年各种组织评选年度游戏之前,我想我得为老滚5写点什么。
+
+今年是大作频出的年份,传送门2,巫师2,刺客信条:启示录,英雄无敌6,新的COD...当然,还有让人等了五年的上古卷轴5。一个人的一生并没有多少个五年可以等,而这个世界上也没有多少游戏值得人们等待五年。恰恰上古卷轴就是这样的一款游戏,优点的评说似乎都很相似,无非就是超高的自由度,开放的引擎,官方支持的MOD,以及还不赖的画面表现效果。单独的某一点都不足以使上古卷轴取得成功:自由度再高不可能超越GTA,引擎再开放也不如Minecraft,官方支持的MOD一抓一大把,画面效果远逊于巫师2。但是,可怕的是Bethesda可以把这一切要素集中到一款游戏中,这注定了上古卷轴自从它诞生之时起就会是一款基因优秀的游戏。
+
+五年前湮没上市的时候,没有人会想到五年之后还有无数的Moder沉迷于斯,也没有人会想到当初4G的游戏如今居然滚到了30G的硬盘空间。在时间的流逝、岁月的积淀过后,那些当年光鲜华丽的画面早已是不堪入目,无数的创新玩法也早已被各家产商用烂,但是这个游戏留给我们了一笔宝贵的财富,那就是MOD,以及开发游戏,开放MOD的开发模式。
+
+厂商最宝贵的资源是什么?不是产品,不是用户,更不是资金。苹果和安卓告诉我们,厂商最宝贵的资源其实是良好的平台和对平台生态系统的维护。提供一个基础,聚集一批用户和开发者,让开发者在官方基础上自行拓展功能,拉动消费者群体,最终形成良性的厂商-开发者-消费者的模式,赢取稳定的用户和开发者忠诚度。而在像上古卷轴这样的大型游戏中,MOD的出现和官方提供的MOD开发平台,恰是遵循了这样的理念。各种MOD达人以炫耀技术或者只是单纯地对游戏的喜爱而在Bethesda的平台上对游戏进行二次开发,无数的玩家因此得益玩到更多的内容,制作商获得更大的用户群体,又有更多的个人制作者投身到更多更好的MOD制作...
+
+嗯..一切都很美好,直到我膝盖中了一箭。这个循环里其实有一链并不靠谱,与标准的良性生态系统稍有区别,那就是开发者的利益问题。现在开发者只能够得到精神上的满足,而精神满足永远不能成为长久的动力,这也是为什么绝大部分的MOD都只停留在换贴图的高度,因为不会有强力开发者将绝大部分时间和经历投入到这项没有实际收益的事业中去。
+
+如果Bethesda想在现在日趋激烈的游戏行业做大做强,而不是停留原地等待其他更具优势的厂商复制甚至改良这种模式的话,也许效仿成功者建立一个开发者能够受益的平台会是一个很好的选择。
diff --git a/_posts/2011-12-21-uiimage.markdown b/_posts/2011-12-21-uiimage.markdown
new file mode 100644
index 00000000..9209947a
--- /dev/null
+++ b/_posts/2011-12-21-uiimage.markdown
@@ -0,0 +1,70 @@
+---
+layout: post
+title: 带边框的UIImage缩放
+date: 2011-12-21 00:46:41.000000000 +09:00
+tags: 能工巧匠集
+---
+一个带边框的UIImage如果使用常规的缩放,边框部分将被按照缩放比例拉伸或压缩,有些时候这并不是我们所期望的..比如这个边框是根据图片大小变化的外框。比如下面的类似按钮的不明物体图片:主体为渐变蓝色,边框为外圈白色,灰色底板为背景。
+
+
+
+常见的按钮添加和背景设置如下:
+
+```
+UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(80, 130, 160, 44)];
+[button setTitle:@”Test Button” forState:UIControlStateNormal];
+// Image with without cap insets
+UIImage *buttonImage = [UIImage imageNamed:@”blueButton”];
+[button addTarget:self action:@selector(buttonPressed:) forControlEvents: UIControlEventTouchUpInside];
+[button setBackgroundImage:buttonImage forState:UIControlStateNormal];
+[[self view] addSubview:button];
+```
+
+所得到的按钮会相当悲剧…
+
+
+
+边框,特别是左右边框由于按钮frame过大被惨烈拉伸…
+iOS5中提供了一个新的UIImage方法,[resizableImageWithCapInsets:](http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/UIImage_Class/Reference/Reference.html),可以将图片转换为以某一偏移值为偏移的可伸缩图像(偏移值内的图像将不被拉伸或压缩)。
+
+用法引述如下:
+
+```
+resizableImageWithCapInsets:
+ Creates and returns a new image object with the specified cap insets.
+ - (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets
+ Parameters
+ capInsets
+ The values to use for the cap insets.
+ Return Value
+ A new image object with the specified cap insets.
+ Discussion
+ You use this method to add cap insets to an image or to change the existing cap insets of an image. In both cases, you get back a new image and the original image remains untouched.
+
+ During scaling or resizing of the image, areas covered by a cap are not scaled or resized. Instead, the pixel area not covered by the cap in each direction is tiled, left-to-right and top-to-bottom, to resize the image. This technique is often used to create variable-width buttons, which retain the same rounded corners but whose center region grows or shrinks as needed. For best performance, use a tiled area that is a 1x1 pixel area in size.
+```
+
+输入参数为一个capInsets结构体:
+
+```
+//Defines inset distances for views.
+typedef struct {
+CGFloat top, left, bottom, right;
+} UIEdgeInsets;
+```
+
+分别表示上左下右四个方向的偏移量。于是把上面按钮的UIImage改为如下形式:
+
+```
+// Image with cap insets
+UIImage *buttonImage = [[UIImage imageNamed:@”blueButton”]
+resizableImageWithCapInsets:UIEdgeInsetsMake(0, 16, 0, 16)];
+```
+
+可以得到如下按钮:
+
+
+
+问题得到解决。
+
+但是值得注意的是该方法需要至少iOS5的运行环境,因此对于需要开发支持iOS5之前的App来说是不可行的。替代方案是[stretchableImageWithLeftCapWidth:topCapHeight:](http://developer.apple.com/library/IOs/#documentation/UIKit/Reference/UIImage_Class/DeprecationAppendix/AppendixADeprecatedAPI.html#//apple_ref/occ/instm/UIImage/stretchableImageWithLeftCapWidth:topCapHeight:),但是在iOS5中,这已经是被Deprecated的方法了,而且该方法只能以1px作为重复铺满拉伸区域,无法做到类似渐变等图片效果,是存在一定局限的。
diff --git a/_posts/2011-12-22-password.markdown b/_posts/2011-12-22-password.markdown
new file mode 100644
index 00000000..8a97a74c
--- /dev/null
+++ b/_posts/2011-12-22-password.markdown
@@ -0,0 +1,11 @@
+---
+layout: post
+title: 凑热闹,谈密码,Challenge-Response密码验证
+date: 2011-12-22 00:49:48.000000000 +09:00
+tags: 南箕北斗集
+---
+CSDN的密码事件闹得沸沸扬扬,600万用户数据的泄露应该是中国互联网历史上最严重的帐号信息泄露事件。让人不可思议的是,2009年4月之前的用户密码居然是以明文存储。使用明文存储密码本身就是一件相当扯淡的事情,而当这种事情发生在以程序员为主要客户的大型网站上,真是让人哭笑不得。
+
+之后又陆续爆出人人、多玩以及各种知名网站的账户信息泄露的消息,虽然还未确知真伪,但也很是让人揪心。而“不能明文保存密码”这一个初级中的初级的错误之所以会在中国这篇神奇的土地上一次又一次的出现,我认为是与中国的网络审查制度和相关法制不全造成的(其实就算法令全了我估计在特权之下也只是一纸空文)。负责网络审查的大大们往往一个电话过来指明要XXX的密码,而在天朝这样的地方如果你拿不出这个密码的话,基本就被扣上了政治不合作和给领导难看的大帽子,很多时候程序员被逼无奈只能明文存储。而像这次CSDN这样的由于系统升级而遗留下的历史问题,可以相信溯其根源也或多或少会与这样的特权有关吧...
+
+扯远了扯远了...作为有良知的程序员,绝不能用明文存储密码,而作为简单的密码存放验证解决方案,MD5的Challenge-Response验证方法可以满足绝大部分非机密级别的应用了。使用MD5存储密码在验证中是很常见的做法,虽然王小云教授提出了有效的MD5强无碰撞算法,但是作为民用级别的网站认证,简单的散列MD5的方便快速简单易用的特性还是相当吸引人。为了防止截取MD5伪造身份完成认证,再加入一个Challenge-Response机制,客户端请求验证时,由服务器随机一个串给客户端进行挑战,客户端使用密码的散列值与从服务器取得的串组合得到新的散列值,将此散列值提交给服务器生成的散列值进行应答验证,若两个散列一致则通过,否则失效。如果希望能够得到更加安全的散列算法,可以选择SHA-256,SHA-384或者SHA-512等还未被攻破的散列(仅限民用..我猜政府军事部门不太可能用散列这种悲剧又简单的摘要算法)。对于iPhone SDK来说,常用的散列算法都在头文件中均有说明。而libcommonCrypto.dylib和Security.framework中也都提供了相当多的安全方法,涉及网络应用的app开发的话,不论是作为自身修养的提升还是对自己的代码负责,都应当对安全问题有基本的认识和思索...
diff --git a/_posts/2011-12-24-vvbordertimer.markdown b/_posts/2011-12-24-vvbordertimer.markdown
new file mode 100644
index 00000000..f6bab5fa
--- /dev/null
+++ b/_posts/2011-12-24-vvbordertimer.markdown
@@ -0,0 +1,133 @@
+---
+layout: post
+title: VVBorderTimer
+date: 2011-12-24 22:04:43.000000000 +09:00
+tags: 能工巧匠集
+---
+GitHub 链接: [https://github.com/onevcat/VVBorderTimerView](https://github.com/onevcat/VVBorderTimerView)
+
+### 是什么
+
+* **VVBorderTimer**是UIView的子类
+* 它为UIView提供使用边界进行倒计时的效果
+* 边框角落的半径和线宽在运行时可调
+* 倒计时是有颜色渐变效果
+
+### What’s this
+
+* **VVBorderTimer** is a subclass of UIView.
+* It provides an counting down effect using the view’s border.
+* The radius of round corners and line width are configurable in runtime.
+* There is also a color transition effect during the counting.
+
+### 怎么用
+
+* 将VVBorderTimerView.h和VVBorderTimerView.m导入您的工程。请根据您的情况选择使用ARC版本或非ARC版本
+* 分配并初始化一个VVBorderTimerView. 设置其背景颜色
+```
+VVBorderTimerView *btv = [[VVBorderTimerView alloc] initWithFrame:CGRectMake(20, 20, 280, 280)];
+//为计时器设置背景颜色
+btv.backgroundColor = [UIColor clearColor];
+```
+3. 配置计时器属性,如: 颜色(可选), 总时间和delegate.
+```
+//上边界为绿色
+UIColor *color0 = [UIColor greenColor];
+//右边黄色
+UIColor *color1 = [UIColor yellowColor];
+//下边橙色
+UIColor *color2 = [UIColor colorWithRed:1.0 green:140.0/255.0 blue:16.0/255.0 alpha:1.0];
+//左边红色
+UIColor *color3 = [UIColor redColor];
+//为计时器指定颜色. 不同颜色将在转角处发生渐变.
+//如果您没有指定颜色,或者指定其为nil(btv.colors = nil),所有边将默认使用黑色
+btv.colors = [NSArray arrayWithObjects:color0,color1,color2,color3,nil];
+//为计时器设定总时间
+btv.totalTime = 10;
+//为计时器设定delegate
+btv.delegate = self;
+```
+4. 实现计时器的delegate
+```
+//转角半径(0 代表矩形)
+-(float) cornerRadius:(VVBorderTimerView *)requestor
+{ return 30;
+}
+//计时器线宽
+-(float) lineWidth:(VVBorderTimerView *)requestor
+{ return 10;
+}
+//当计时器停止时,该方法被调用
+-(void) timerViewDidFinishedTiming:(VVBorderTimerView *)aTimerView
+{ //do something
+}
+```
+5. 将计时器加入您的viewController的view,并使用 -(void)start 开始计时
+```
+[self.view addSubview:btv];
+[btv start];
+```
+
+如果您使用的是非ARC,请不要忘记在将计时器加入view结构后释放它。可能您需要保留一个指向该计时器的指针,以便在即使结束后将其移除
+在GitHub网站的这个页面上有一个简单的demo供您参考,如果您感兴趣,可以关注或者分支该项目。祝您好运~
+
+### How to use
+
+1. Import VVBorderTimerView.h and VVBorderTimerView.m to your project. Select either ARC version or non-ARC version for your situation.
+2. Alloc and init a VVBorderTimerView. Set its background color.
+```
+VVBorderTimerView *btv = [[VVBorderTimerView alloc] initWithFrame:CGRectMake(20, 20, 280, 280)];
+//Specify a background color for the timer
+btv.backgroundColor = [UIColor clearColor];
+```
+3. Set the properties for the timer: colors(optional), totalTime and delegate.
+```
+//Top border will be green
+UIColor *color0 = [UIColor greenColor];
+//Right border yellow
+UIColor *color1 = [UIColor yellowColor];
+//Buttom border orange
+UIColor *color2 = [UIColor colorWithRed:1.0 green:140.0/255.0 blue:16.0/255.0 alpha:1.0];
+//Left border red
+UIColor *color3 = [UIColor redColor];
+//Set the colors for the timer. Color transition will be occured in the corner.
+//If your DID NOT specify the colors or specify it to nil(btv.colors = nil), default black color for all edge will be used.
+btv.colors = [NSArray arrayWithObjects:color0,color1,color2,color3,nil];
+//Set the total time the timer should count in second
+btv.totalTime = 10;
+//Set the delegate for the timer
+btv.delegate = self;
+```
+4. Implement the timer’s delegate
+```
+//Corner radius for a timer(0 means rectangle)
+-(float) cornerRadius:(VVBorderTimerView *)requestor
+{ return 30;
+}
+//Line width for a timer
+-(float) lineWidth:(VVBorderTimerView *)requestor
+{ return 10;
+}
+//When the timer stopped, this method will be called
+-(void) timerViewDidFinishedTiming:(VVBorderTimerView *)aTimerView
+{ //do something
+}
+```
+5. Add it to your viewController’s view and then start the timer using -(void)start
+```
+[self.view addSubview:btv];
+[btv start];
+```
+
+DO NOT forget to release the timer after it is added to the view’s hierarchy if you use non-ARC. You may want to keey a pointer to the timer, so you can remove it from superview when it stops.
+You can also find a demo in the github page here. You can watch and fork it if you are intrested in it. Enjoy!
+
+### Lisence
+VVBorderTimer is Copyright © 2011 Wei Wang(onevcat), All Rights Reserved, All Wrongs Revenged. Released under the New BSD Licence.
+* https://github.com/onevcat/VVBorderTimerView/
+BSD license follows (http://www.opensource.org/licenses/bsd-license.php)
+Copyright (c) 2011 Wei Wang(onevcat) All Rights Reserved.
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBU -TORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/_posts/2012-01-11-ahrp2012.markdown b/_posts/2012-01-11-ahrp2012.markdown
new file mode 100644
index 00000000..fcdf7f1e
--- /dev/null
+++ b/_posts/2012-01-11-ahrp2012.markdown
@@ -0,0 +1,54 @@
+---
+layout: post
+title: AHRP 2013 内部推荐机会
+date: 2012-01-11 22:14:04.000000000 +09:00
+tags: 胡言乱语集
+---
+#### 2013秋季项目内推已经结束。如果打算参加的话,可以关注9月份陆续开始的宣讲会和AHRP官方网站的一些信息。谢谢大家对AHRP项目和我的blog的关注~
+
+#### ~~~AHRP新一年的秋季项目即将开始,2012春季项目中博主内推的童鞋中有2人最终拿到了offer,而2013秋季依然我有机会作为内定者为大家进行内推。如果有对该项目感兴趣的童鞋欢迎给我邮件或留言咨询最新情况。此次内推5月26日就将截止,AHRP秋季项目将在6月初和被内推者先行联系,现在申请可以先人一步,将对您求职路上占据主动有很大帮助!~~~
+
+~~~——OneV 2012.5.14~~~
+
+~~~####2012春季项目内推已经结束,项目已经正式启动。如打算参加的话,可以关注近期在北京各高校开展的宣讲会或在AHRP官网申请(推荐现场投递简历,官网会可能比较慢导致错过企业招聘)。感谢大家对OneV's Den和AHRP的关注~!如果还有什么问题,欢迎给我留言或邮件。 ——OneV 2012.2.20~~~
+
+## 我和AHRP
+我今年通过AHRP找到了日本的工作,今年晚些时候将赴日加入Kayac公司开始职场生涯。一路走来,我觉得能和AHRP工作组的人员成为朋友是很幸运的事情。而现在,作为AHRP的内定者,我也有机会向AHRP进行**内部推荐**,让更多的朋友接触到这个项目,了解这个项目,有机会去倾听AHRP工作人员们对职场的独特见解,最终达成自己的梦想。
+
+## 什么是AHRP
+
+AHRP是Asian Human Resource Project的简称,是日本人力资源公司株式会社TRANSCEND旗下的校园招聘项目,负责在中国选拔优秀的大学毕业生赴日工作。选拔合格者将直接与企业签订正式的日本劳动合同并作为正社员加入公司,享有与当地毕业生相同的薪水福利、培训机会及晋升空间(小科普:日本劳动合同分为正社员和合同制,正社员是众生雇佣,而合同制是有年限的)。
+AHRP项目自成立以来,已成功举办了四届校园招聘:AHRP2008、AHRP2009、AHRP2010及AHRP2011。共有超过180位来自北大、清华、复旦、南开等著名高校的同学通过本项目成功进入伊藤忠商社、软银集团、乐天株式会社、RECRUIT、日产汽车等优秀日企,走上国际舞台,实现海外就职。
+参加AHRP项目,您也许可以获得
+
+* 国际化的视野,国际化的职业背景
+* 在有潜力行业中的无限发展可能
+* 掌握多一门外语
+* 崭新的异国生活
+* 实现梦想的机会
+
+## AHRP2012
+AHRP2012面向2012年的应届毕业生,也欢迎工作了一至两年但还是充满激情的年轻人。AHRP2012的秋季部分已经结束,27家日本企业与78位中国应届毕业生完成了签约。这些企业包括DeNA,SoftBank(软银集团),GREE等知名企业。2012秋季项目中共有27个企业参加,即将来到的春季项目也将有20个左右的企业,具体的名单将会在二月中下旬确定,届时也请不要错过。
+
+## 为什么要参加AHRP
+不论是因为想要找一份国际化的工作,让自己在职场开始的时候就能站在相对高点,拥有开阔的眼界,还是因为对现实不满,对已经签约的工作还有忧虑,我认为都可以尝试参加AHRP项目。以我自己的经历来看,AHRP的面试和绝大多数国内企业的面试是截然不同的。在AHRP,你将会被一步一步引导看清你真正所需要的未来,而面试官也将会在了解你的情况之后,凭借他们丰富的人力资源经验为你指出之后可能的道路。在这里,面试官和应聘者的界限似乎不这么明显,大家更像是朋友一样和下午茶聊天。在和AHRP工作人员的谈天中,你往往能对职场和今后的职场道路产生新的理解和认识。不论如何,我认为只要你有心到日本工作,愿意尝试挑战和全新的道路,你都可以参与到这个项目里来。
+
+有一句话我很欣赏:一个人从来不会后悔做过了什么,而只会后悔没做过什么。其实很多时候您需要的仅仅是一次尝试的勇气,就足以让您的人生变得精彩。
+
+## 听起来很有趣,如何参加?
+如果您能看到这里,那也许您不会介意再看看我的前面两篇与AHRP有关的博文了..它们实在我接触AHRP的过程中我的真实感受。
+
+* 近期求职总结-AHRP和DeNA面试
+* 尘埃落定,下一站Kayac
+
+AHRP项目组的官方网站上也有大量的信息,包括AHRP介绍,历年情况和详细的FAQ。
+特别说明几点:
+
+* 参加AHRP全程不收取任何费用,被录取后的日语培训费用也全部由AHRP承担
+* 首次去日本的机票貌似也是AHRP负责,到日本后会提供无息贷款帮助渡过前几个月
+* 专业不限,由于公司数量不少,基本各专业都有机会找到合适自己的企业。相对来说的话,IT方面的招收会多一些,机会也会大一些。
+* 日语无要求,内定(签约)后会有免费的日语培训,以确保大家能在日本生活。
+* 到日本也会有语言学习时期和相应的技能培训,并且会有前辈帮助熟悉环境。
+
+如果您确实对AHRP项目有兴趣,可以考虑给我留言(评论时请注明邮箱,不用担心,您的邮件地址除了我没人能看到)或者是直接给我发邮件(onevcat at gmail.com)。我在对您的情况进行基本确认后会将您的信息作为内部推荐提交给AHRP项目组的工作人员。您将可以在第一时间收到关于AHRP2012春季项目的最新情报,以及优先于春季招聘档期和AHRP面试接触的机会,以提高您被成功录用的可能性。
+我十分期待着能与您成为朋友,能成为在异国他乡的土地上能够彼此照应的同伴!
diff --git a/_posts/2012-01-17-testflight.markdown b/_posts/2012-01-17-testflight.markdown
new file mode 100644
index 00000000..bcae7307
--- /dev/null
+++ b/_posts/2012-01-17-testflight.markdown
@@ -0,0 +1,214 @@
+---
+layout: post
+title: TestFlight——完美的iOS app测试方案
+date: 2012-01-17 22:17:25.000000000 +09:00
+tags: 南箕北斗集
+---
+转载本文请保留以下原作者信息:
+原作:onevcat [http://www.onevcat.com/2012/01/testflight/](http://www.onevcat.com/2012/01/testflight/)
+
+## 2014.5.3补充
+
+TestFlight 现在已经修成正果,被 Apple 高价收购。虽然很遗憾不能再支持 Android 版本,但是有理由相信在 Apple 旗下的 TestFlight 将被深度整合进 Apple 开发的生态体系,并且承担更加重要的作用。不妨期待一下今年的 WWDC 上 Apple 在 CI 方面的进一步动作,预测应该会有 OSX Server 和 TestFlight 的协作方面内容。对于 CI 方面的一些其他介绍,可以参看 objc 中国的[这篇帖子](http://objccn.io/issue-6-5/)。
+
+## 2013.3.31补充
+
+在整理以前写的内容,想不到还有机会再对这篇帖子进行一些更新。当时写这篇帖子的时候,app内部测试以及对应的crash报告类的服务相对很少,而且并不成熟。TestFlight算是在这一领域的先行者,而随着app市场的不断膨胀,相应的类似服务也逐渐增多,比较常用的有:
+
+崩溃报告类:
+
+* [Crittercism](https://www.crittercism.com/) 个人用了一段时间,表现很稳定,但是版本更新时设置比较麻烦
+* [Crashlytics](https://www.crashlytics.com/) 相当优雅方便,最近被Twitter收购。十分推荐
+
+用户行为统计类:
+
+* [Flurry](http://www.flurry.com/) 这个太有名了,不多说了
+* [Countly](http://count.ly/) 好处是轻量开源,数据可以自己掌控
+
+但是在“发布前”测试分发这个环节上,基本还没有出现能与TestFlight相匹敌的服务出现,因此如果有这方面的测试需求的话,TF依然是开发人员的首选。
+
+当然,这一年多来,TF也进步了很多。从整个队伍建立和开发者添加开始,到桌面客户端的出现以及打包上传的简化,可以说TF也逐渐向着一个更成熟易用的方向发展。本文虽然写的时间比较早,但是整个TF的基本流程并没有发生变化,依然可以作为入门的参考。
+
+## 前言
+
+iOS开发的测试一直是令人头疼的问题。app开发的短周期和高效率的要求注定了一款app,特别是小公司的app,不会留给开发人员很多测试的时间。而在测试时往往又遇到crash报告提交困难,测试人员与开发人员沟通不便等等问题,极大延缓了测试进度。TestFlight即是为了解决iOS开发时测试的种种困难而生的服务,使用TestFlight可以十分便利地完成版本部署,测试用户Log提交,收集Carsh Log和收集用户反馈等工作,而这一切居然连一个iDP账号都不需要!
+
+## 基本使用
+
+### 注册
+
+TestFlight界面友好,文档齐全,开发者在使用上不会遇到很多问题。到[TestFlight官网][4]注册账号即可开始使用。
+
+ [4]: https://testflightapp.com/
+
+
+
+注册时记得勾选I am a developer,之后便可以以开发者身份管理开发和测试团队,提交测试版本和查看报告等,若没有勾选则是以测试者身份注册。若在注册时没有选上,之后在帐号设置中也可以进行更改。
+
+
+
+### 确认
+
+注册完成以后会在注册邮箱中收到确认邮件。使用你的iDevice用邮件内的帐号登陆,并且完成设备注册,加入TestFlight的描述文件。关于设备注册和可能遇到的问题,可以参看[这篇帖子][9]。
+
+ [9]: http://liucheng.easymorse.com/?tag=testflight
+
+### 创建团队
+
+登陆TestFlight后在自己的Dashboard可以新建一个团队。团队包括了开发者、测试者和相应的测试版本。创建团队后可以通过选择团队来查看团队的信息等情况。
+
+
+
+### 添加测试者
+
+在团队管理界面可以为团队添加成员。填写受邀者的邮件和简单的说明,一封包含注册链接的邮件将被发送到指定邮箱。受邀者通过类似的注册和确认流程即可加入团队,参与共同开发和测试。
+
+
+
+### 上传测试版本
+
+上传的版本必须是包含签名的ipa,成功上传版本后即可选择给团队内的成员发邮件或推送邀请他们进行新版本的安装和测试。之后在版本管理中即可看到关于该版本的测试信息。该部分具体内容参看本文最后。
+
+### 收集测试信息
+
+在build界面中选择需要查看的版本的对应按钮即可看到收集到的测试信息,包括一般的session信息,设备使用TFLog进行的输出(需要TestFlight SDK),crash报告,是否通过了预先设定的检查点,测试人员的安装情况等信息。
+
+结合SDK来使用,一切测试机仿佛都变成了你自己的终端,所有的Log和设备的状态尽在掌握,而这样的便利仅仅需要点击下鼠标和写几行代码,这便是TestFlight的强大之处。
+
+
+
+## TestFlight SDK使用
+
+### 下载
+
+不使用TestFlight的SDK的话,可以说就连这个强大的平台的一成功力都发挥不出来。点击[这里][16]从官方网站下载SDK,[官方文档][17]提供了关于SDK的很全面的说明,在[支持页面][18]也能找到很多有用的信息。
+
+ [16]: https://d3fqheiq7nlyrx.cloudfront.net/sdk-downloads/TestFlightSDK0.8.2.zip
+ [17]: https://testflightapp.com/sdk/doc/0.8.2/
+ [18]: http://support.testflightapp.com/
+
+之后将以Xcode4为例,简介SDK的使用,更多信息可以参考TestFlight官网。
+
+### 配置
+
+* 将头文件加入工程:File->Add Files to
+
+ * 找到包含SDK的文件夹
+ * 勾选"Copy items into destination folder (if needed)"
+ * 选择"Create groups for any added folders"
+ * 勾上想要使用TestFlight SDK的Target
+
+* 验证libTestFlight.a是否被加到link部件中
+
+ * 在Project Navigation里选中工程文件
+ * 选中想要应用SDK的Target
+ * 选择Build Phase栏
+ * 打开Link Binary With Libraries Phase
+ * 如果libTestFlight.a不在栏内,从Project Navigation里将其拖到栏内
+
+* 开始使用
+
+ * 在需要用到TestFlight SDK的文件中引入头文件:_#import “TestFlight.h”_,方便起见,您也可以在工程的预编译文件中的_#ifdef __OBJC___块中引入
+ * 获取团队token:在[这个页面][19]中对应的团队下选取TeamInfo,获取团队的token。
+ * 在AppDelegate中启动TestFlight
+```objc
+–(BOOL)application:(UIApplication *_)application didFinishLaunchingWithOptions:(NSDictionary _*)launchOptions {
+ // start of your application:didFinishLaunchingWithOptions
+ // …
+ [TestFlight takeOff:@“团队Token”];
+ // The rest of your application:didFinishLaunchingWithOptions method
+ // …
+}
+```
+
+为了能得到有用的crash log(挂载过的),必须在生成ipa的时候不剥离.dSYM文件。在Project Navigation里选中工程文件,选中需要使用TestFlight SDK的Target,在Building Setting的Deployment块下,将以下三项设为NO
+
+ [19]: http://testflightapp.com/dashboard/team/
+
+* Deployment Post Processing
+* Strip Debug Symbols During Copy
+* Strip Linked Product
+
+### 检查点
+
+开发者可以在代码的任意位置设置检查点,当测试者通过检查点时,session里将会对此记录。比如测试者通过了某个关卡,或者提交了某个分数,或者向数据库加入了某条信息等。通过验证检查点,一方面可以检测代码是否正确,另一方面也可以作为游戏的平衡性调整和测试,用来检测用户的普遍水平。
+
+
+
+```objc
+[TestFlight passCheckpoint:@“CHECKPOINT_NAME”];
+```
+
+### 检查点问题
+
+配合检查点可以向测试者提出问题,比如“是否正确地通过了演示界面?”或者“分数榜的提交正常吗?”这样的问题。在build management下可以找到Question选项,为检查点添加问题。问题的回答分为多选,是/否以及任意回答,如果选择多选的话,还需要指出问题的可能的选项。
+当测试者通过问题所对应的检查点时,一个modalViewController形式的问题和选项列表会出现供测试者选择。开发者可以在build的Question选项卡中看到反馈。
+
+
+### 反馈
+
+TestFlight提供了一个默认的反馈界面,测试者可以填写他们想写的任何内容并将这个反馈发送给你。调用一个反馈:
+
+```objc
+–(IBAction)launchFeedback {
+ [TestFlight openFeedbackView];
+}
+```
+
+一般来说可以在主界面或者最常见的界面上设置一个“反馈”按钮,这样测试者可以很方便地将他们的感受和意见发送给你。
+
+### 远程Log
+
+太棒了…配合TestFlight,现在开发者可以拿到远程客户端的Log。使用**TFLog**代替NSLog即可,任何TFLog的输出将被上传到TestFlight的服务器。如果需要详细一些的输出信息,可以用内建的**参数**的方式输出信息,比如:
+
+```objc
+#define NSLog(__FORMAT__, ...) TFLog((@"%s [Line %d] " __FORMAT__), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
+```
+
+将会得到类似这样的输出
+
+```objc
+-[HTFCheckpointsController showYesNoQuestion:] [Line 45] Pressed YES/NO
+```
+
+所有的TFLog都不会阻塞主线程,所有的TFLog都将完成以下三种Logger工作
+
+* TestFlight logger
+* Apple System Log logger
+* STDERR logger
+
+配合以前介绍过的NSLogger(参见[这篇文章][22]),将Log发挥到极致,让你永远掌控自己的代码吧~
+
+ [22]: http://www.onevcat.com/2011/12/nslogger/
+
+Log将会在客户端进入后台或者被退出的时候上传到服务器,如果没有看到应该有的Log的话,很有可能是还在上传中。视Log文件大小而定,一般这个过程需要若干分钟。当然,巨量上几M甚至10+M的Log可能会被TestFlight拒绝哦..毕竟没有那么多存储空间..
+
+当然,客户端必须有可用的网络环境这个功能才会启用。得到的Log会存储在Session下。
+
+
+
+## 生成和上传测试版本
+
+### 打包ipa
+
+..做过部署的童鞋对这个应该很熟了,官方也有一个详细的guide,总之照着做就没错了
+
+* [XCode3如何生成ipa][25]
+* [Xcode4如何生成ipa][26]
+
+ [25]: http://support.testflightapp.com/kb/tutorials/how-to-create-an-ipa-xcode-3
+ [26]: http://support.testflightapp.com/kb/tutorials/how-to-create-an-ipa-xcode-4
+
+### 上传测试版本
+
+打包好ipa后就到版本上传界面,把做好的ipa拖过去就万事大吉了。
+
+
+
+最后一步是邀请团队内的测试者进行测试。把你想邀请的测试者打上勾然后OK,包含链接的邀请邮件将会发到他们的邮箱。然后~等待测试结果和大家的反馈,并且根据反馈完善app吧~
+
+## 写在最后
+
+TestFlight是一个很棒的工具,而且关键,它现在还是免费的~
+
+虽然有趋势以后将会收费,但是这套方案确实是方便易用..希望多支持吧~
diff --git a/_posts/2012-01-18-googlelocation.markdown b/_posts/2012-01-18-googlelocation.markdown
new file mode 100644
index 00000000..7593d7b9
--- /dev/null
+++ b/_posts/2012-01-18-googlelocation.markdown
@@ -0,0 +1,13 @@
+---
+layout: post
+title: Google查询地理信息API
+date: 2012-01-18 22:24:09.000000000 +09:00
+tags: 能工巧匠集
+---
+向Google Map查询给定经纬度的位置信息,返回为JSON
+
+```
++ (NSString *)googleReverseStringWithCoordinate:(CLLocationCoordinate2D)coordinate {
+ return [NSString stringWithFormat:@"http://maps.google.com/maps/geo?q=%lf,%lf&output=json&sensor=false&accuracy=4", coordinate.latitude,coordinate.longitude];
+}
+```
diff --git a/_posts/2012-01-21-caocao.markdown b/_posts/2012-01-21-caocao.markdown
new file mode 100644
index 00000000..91be1a93
--- /dev/null
+++ b/_posts/2012-01-21-caocao.markdown
@@ -0,0 +1,34 @@
+---
+layout: post
+title: 再看曹操
+date: 2012-01-21 22:28:27.000000000 +09:00
+tags: 胡言乱语集
+---
+最近在看《苍天航路》,一部以曹操为主角正剧三国志漫画。我向来是对曹操这个人物有所感慨,并且有所敬畏的,在他六十六载的人生中,有着太多的波澜壮阔和起起伏伏。而在后世,人们对这样一个伟人的人生的争论似乎从未停止。三国演义引导了尊刘贬曹中国基调,而日本更倾向于把曹操解读为苍天的霸者。这很大程度上反应了两国国民的心态差别:一个同情弱小,一个崇尚强大。在那个战火纷飞,英杰辈出的年代,孟德的谋略的高度是毋庸置疑的,而对于天下来说,他也是当时唯一一个敢于背负恶名而生存的人,只此一点,就比其他在那乱世之中只顾保全名节的所谓的“高士”高出许多。
+
+曹操真正发家是从兖州收服青州兵开始的,但是在那之前,他早已成名:五色棒,讨黄巾,伐董卓。麾下更是猛将如云,谋士如雨。在兖州蛰伏的曹操需要的只是一支真正的自己的军队。而黄巾余党的出现,正给了他这个机会。之后接汉帝,屯良田,四方征讨,在短短五年之内横扫徐冀并幽四州,灭吕布袁绍,把刘备打到失魂落魄。在乱世之中,任何的机会稍纵即逝,而主宰人物的小小的失策便会对之后数十甚至数百年的历史走势发生惊天影响。曹操正抓住了这样的机会..试想要是没有脱离伐董联军,没有来到兖州这片发源地,没有听取朝中智士谋,设奇伏,日夜会战,没有最终整编难以驾驭的青州兵为己所用,那也就不会有曹操后来的一切。与其说是历史成就了曹操,不如说是曹操成就了历史。
+
+击溃四世三公不可一世的袁绍后,曹操站在了历史的至高点。这时候北方肃清,中原几已一统,虽然这一切功绩都是以“王师”的名义取得的,但是世人皆知此为曹操所为,也不会有人将中原视为汉室领土。名望倾国,霸权独立的曹操,此时做出了最明智的选择——不称帝,不上位。而今我们只能猜测曹操不愿称帝的缘由,是因为顾虑时机未到,还是不眷虚名?但是可以知道的是,在刚刚击败强敌,问鼎权力巅峰的时候,他却异常冷静——没有几个人能在如此大的成功和如此强的霸权面前把持自若的。
+
+曹操之所以是曹操,是因为你很难在历史上找到一个和他相似的人。一直以来曹操是叛逆的代表,而这个世界的改变,也正是这些“叛逆”的人的功劳。Think Different带给人们的感动正在于它表达了人们所无法表达的对于这个世界的叛逆先驱的敬意,他们不遵循蹈矩,而是以自己的见解来开创新的世界。古代中国从百家争鸣开始其实一直有很好的多元化传统,但是随着历史长河流向下游,河面愈宽容纳愈多的同时,也难免限制了支流的壮大,甚是可惜..回到曹操,没有人会怀疑曹操是强大的支流,看看现世加给曹操的头衔便知:军事家,政治家,谋略家,诗人。包容百家,通晓谋略,乱世奸雄,治世能臣,三曹之首。中国历史,乃至世界历史上也再没有第二个人在一生中取得如此多的成就了。曹操就是曹操,你永远不会认错。
+
+历史永远是由胜利者书写的,而深入人心的三国演义小说又是由三国志这样的历史改编的。那么,今天深入人心的曹操的形象,也是由胜利者灌输到民众心中的。胜利者的老祖宗的顶头上司便是曹操,因此曹操的形象并非很坏,非君子,亦非小人。其实如此更好,要是最终三国以魏统一而收局的话,曹操大抵也就会变成李渊那样的人物,而三国这段故事很可能永远不会被后人所津津乐道了吧...
+
+>寒风摧,隔牖厉
+>
+>廊檐流角血气。
+>
+>狼烟起,连天蔽
+>
+>帆影丛生浪迹。
+>
+>携天子,讨僭逆。
+>
+>海内归一可期。
+>
+>犹不知樯橹灰飞周郎妙计,湮灭在长江滔滔里,命兮,恨兮。
+
+苍天霸者的梦,断在赤壁。在这里,他遭遇了一生的劲敌——孙权。
+曹操一生,其实并没有什么敌人,因为绝大多数的敌人在真正能对曹操构成威胁之前就已经被荡平。而吴地的孙家历经三代,亦是英杰聚集,天灵地宝之所。赤壁之战,只是曹孙争霸的起始,也亦这段历史的转折。曹操在生命旅程中,始终没有能真正打败这个敌人,而自己最终却也打不败时间这个最大的敌人...
+
+呜呼孟德~
diff --git a/_posts/2012-02-02-uiviewcontroller.markdown b/_posts/2012-02-02-uiviewcontroller.markdown
new file mode 100644
index 00000000..cfb38924
--- /dev/null
+++ b/_posts/2012-02-02-uiviewcontroller.markdown
@@ -0,0 +1,72 @@
+---
+layout: post
+title: UIViewController的误用
+date: 2012-02-02 22:50:53.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+转载本文请保留以下原作者信息:
+原作:OneV [http://www.onevcat.com/2012/02/uiviewcontroller/](http://www.onevcat.com/2012/02/uiviewcontroller/)
+
+## 什么是UIViewController的误用
+
+UIViewController是iOS开发中最常见也最重要的部件之一,可以说绝大多数的app都用到了UIViewController来管理页面的view。它是MVC的核心结构和桥梁构成,可以说UIViewController是绝大多数开发者所花时间最多的部分了。
+
+但是正是这样一个重要的类却经常被误用,从而导致app的不稳定,莫名奇妙的bug甚至无法通过appstore的审查。最常见和最可怕的误用在于在一个UIViewController里加入本来不应该由它管理的其他UIViewController,也即违反了Apple在开发者文档中关于UIViewController的描述:
+
+> Each custom view controller object you create is responsible for managing all of the views in a single view hierarchy. In iPhone applications, the views in a view hierarchy traditionally cover the entire screen, but in iPad applications they may cover only a portion of the screen. The one-to-one correspondence between a view controller and the views in its view hierarchy is the key design consideration. You should not use multiple custom view controllers to manage different portions of the same view hierarchy. Similarly, you should not use a single custom view controller object to manage multiple screens worth of content.
+
+一个ViewController应该且只应该管理一个view hierarchy,而通常来说一个完整的view hierarchy指的是整整占满的一个屏幕。而很多app满屏中会有各个区域分管不同的功能,一些开发者喜欢直接新建一个ViewController和一套相应的View来完成所要的功能(比如我自己=_=)。虽然十分方便,但是却面临很多风险..
+
+一般来说,只要你的代码中含有类似这样的语句,那你一定是误用UIViewController了
+
+```
+viewController.view.bounds = CGRectMake(50, 50, 100, 200);
+[viewController.view addSubview:someOtherViewController.view];
+```
+
+这样的代码可能导致莫名的bug,也会令接手的开发者无所适从。
+
+### 小题大做吧,这样用会有什么问题呢
+
+一个很麻烦的问题是,这将会导致你的app在不同的iOS版本上有不同的表现。在iOS5之前,能够对viewController进行管理的类有UINavigationController,UITabBarController和iPad专有的UISplitViewController。而在iOS5中加入了可自定义的ViewControllers的容器。由于新的SDK的处理机制,iOS4前通过addSubview加到当前controller的view上的view的呈现,将不会触发被加入view hierarchy的view的controller的viewWillAppear:方法。而且,新加入的viewController也不会接收到诸如didReceiveMemoryWarning等委托方法,而且也不能响应所有的旋转事件!而iOS5中由于所谓的custom container VC的出现,上述方法又能够运行良好,这导致了同样代码在不同终端产生不同的行为,为之后的维护和进一步开发埋下了隐患。另外,用这样的方法所添加的viewController显然违背了Apple的本意,它的parentViewController,interfaceOrientation显然都是错误的,有时候甚至会出现触摸事件无法响应等严重问题。
+
+### 好吧,那我们要怎么办
+
+如果你已经在一个app里这样误用了大量的viewController,那可能的办法也许是尽力去自行处理各种非正常的状况,比如在addSubview之后手动调用加入的vc的viewWillAppear:,以及在收到didReceiveMemoryWarning后顺次调用子VC的didReceiveMemoryWarning(显然都是很蛋疼的做法啊)。但是需要注意的是iOS5中这些方法的调用似乎是没有问题的(至少我测试是这样),因此需要对不同版本系统进行分别处理。可以用UIDevice的方法确定运行环境的系统版本:
+
+```
+// System Versioning Preprocessor Macros
+#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
+#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
+#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
+```
+
+在合适的时机判定判定系统版本,手动调用对应方法:
+
+```
+if (SYSTEM_VERSION_LESS_THAN(@“5.0”))
+{
+ //viewWillAppear或didReceiveMemoryWarning或其他
+}
+```
+
+显然,这样的代码既非优雅亦难维护,而且随着iOS版本的更新,谁也不知道这段代码之后会不会有什么问题,无形中增加了开发成本。
+
+
+## 真正的解决之道
+
+当然是严格遵守Apple提供的设计规范,每个VC管理一个view hierarchy。在设计的时候,永远记住你的view和view controller都需要重用,而不恰当的使用view controller会导致重用性大打折扣。而通用的view有时也需要一个类似controller的东西来管理它的subview的行为,或者做出某些相应,这个时候我们不妨想一想一些Apple写的经典的view是如何实现的,比如UITableView和UIPickerView,依靠protocol的各种方法进行配置。
+作为自定义的view的controller应当是直接继承自NSObject的类,而不应该是UIViewController。一个UIViewController可以包含若干个这样的controller来控制一个view中的不同部分的功能实现,而对于对应的自定义view是代码写的还是nib出来的就无所谓了。当然,如果是新接触iOS开发的话,我个人不建议使用Interface Builder,除非你确实清楚IB到底在背后为你做了什么。在当你完全清楚之后,IB确实能极大提升开发效率(特别是在Xcode4以后),但是如果你的对IB和view加载连接的概念如同毛线团的话,IB的使用只会让你以及让你的同事茫然失措。
+在iOS5中提供了所谓的container of View Controllers,有兴趣的童鞋可以参看WWDC 2011的[Session 102 – Implementing UIViewController Containment](http://developer.apple.com/videos/wwdc/2011/)(需要一个野生开发者账号)
+
+## 一些资料
+
+作为iOS开发者,Apple的关于UIViewController的文档以及开发者的一些讨论是必读的,简单整理如下:
+
+* [View Controller Programming Guide for iOS](http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/Introduction/Introduction.html#//apple_ref/doc/uid/TP40007457-CH1-SW1)
+* [关于误用UIViewController而造成的私有API调用的讨论](https://devforums.apple.com/message/310806#310806)
+* [stack overflow上关于误用view controller的讨论](http://stackoverflow.com/questions/5691226/am-i-abusing-uiviewcontroller-subclassing/5691708#comment-6507338)
diff --git a/_posts/2012-02-25-iosversion.markdown b/_posts/2012-02-25-iosversion.markdown
new file mode 100644
index 00000000..cdb5e249
--- /dev/null
+++ b/_posts/2012-02-25-iosversion.markdown
@@ -0,0 +1,77 @@
+---
+layout: post
+title: 符合iOS系统兼容性需求的方法
+date: 2012-02-25 22:54:42.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+转载本文请保留以下原作者信息:
+原作:OneV's Den
+http://www.onevcat.com/2012/02/iosversion/
+兼容性,开发者之殇
+兼容性问题是全世界所有开发这面临的最头疼的问题之一,这句话不会有开发者会反驳。往昔的Windows Vista的升级死掉一批应用的惨状历历在目,而如今火热的移动互联网平台上类似的问题也层出不穷。Andriod的开源导致产商繁多,不同的设备不仅硬件配置有差异,系统有差异,就连屏幕比例也有差异。想要开发出一款真正全Android平台都完美运行的app简直是不可能的..
+iOS好很多,因为iPhone和iTouch的分辨率完全一致(就算retina也有Apple帮你打理好了),因此需要关注的往往只有系统版本的差别。在使用到某些新系统才有的特性,或者被废弃的方法时,都需要对运行环境做出判定。
+
+何时需要照顾兼容性
+没有人可以记住所有系统新加入的类或者属性方法,也没有人可以开发出一款能针对全系统的还amazing的应用。
+
+在iOS的话,首先要做的就是为自己所开发的app选择合适的最低版本,在XCode中的对应app的target属性中,设置iOS Deployment Target属性,这对应着你的app将会运行在的最低的系统版本。如果你设成iOS 4.3的话,那你就不需要考虑代码对4.3之前的系统的兼容性了——因为低于iOS 4.3的设备是无法通过正规渠道安装你的应用的。
+
+
+但是你仍然要考虑在此之后的系统的兼容性。随着Apple不断升级系统,有些功能或者特性在新系统版本上会有很容易很高效的实现方法,有些旧的API被废弃或者删除。如果没有对这些可能存在的兼容性问题做出处理的话,小则程序效率低下,之后难以维护,大则直接crash。对于这些可能存在的问题,开发者一定要明确自己所调用的API是否存在于所用的框架中。某个类或者方法所支持的系统版本可以在官方文档中查到,所有的条目都会有类似这样的说明
+Availability Available in iOS 5.0 and later.
+或者
+Availability Available in iOS 2.1 and later.
+
+Deprecated in iOS 5.0.
+从Availability即可得知版本兼容的信息。
+如果iOS Deployment Target要小于系统要求,则需要另写处理代码,否则所使用的特性在老版本的系统上将无法表现。
+
+另外,Apple也不过是一家公司,Apple的开发人员也不过是牛一点的程序员,iOS本身就有可能有某些bug,导致在某些版本上存在瑕疵。开发人员会很care,并尽快修正,但是很多时候用户并不care,或者由于种种原因一直不升级系统。这时候便需要检测系统,去避开这些bug,以增加用户体验了(比如3.0时的UITableView插入行时可能crash)。
+
+
+符合兼容性需求的代码
+判定系统版本,按运行时的版本号运行代码
+这不一定是最有效的方法,但这是最直接的方法。对系统版本的判定可以参考我的另一片文章:UIViewController的误用。
+顺便在这边也把比较的方法贴出来:
+
+```
+// System Versioning Preprocessor Macros
+
+#define SYSTEM_VERSION_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
+#define SYSTEM_VERSION_GREATER_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
+#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
+#define SYSTEM_VERSION_LESS_THAN_OR_EQUAL_TO(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)
+```
+
+使用
+···
+if (SYSTEM_VERSION_LESS_THAN(@“5.0”))
+{
+ //系统版本不小于5.0...Do something
+}
+···
+
+需要注意的是,`[@“5.0” compare:@“5” options:NSNumericSearch]`这样的输入将会返回一个NSOrderedDescending,而不是期望中的NSOrderedSame,因此在输入的时候需要特别小心。另外这个方法的局限还在于对于4.1.3这样的系统版本号比较无力,想要使用的话也许需要自己进行转换(也许去掉第二个小数点再进行比较是个可行的方案)。
+
+对于类,可以检测其是否存在
+如果要使用的新特性是个类,而不是某个特定的方法的话,可以用NSClassFromString来将字符串转为类,如果runtime没有这个类的存在,该方法将返回nil。比如对于iOS 5,在位置解析方面,Apple使用了CoreLocation中的新类CLGeocoder来替代原来的MKReverseGeocoder,要判定使用哪个类,可以用下面的代码:
+
+```
+if(NSClassFromString(@"CLGeocoder")) {
+ //CLGeocoder存在,可以使用
+}
+else{
+ //只能使用MKReverseGeocoder
+}
+```
+
+这个方法应该是绝对靠谱的,但是只有类能够使用。而系统更新往往带来的不止类,也有很多新的方法和属性这样的东西,这个方式就无能为力了。
+
+向特定的类或对象询问是否拥有某特性
+
+对于某些类活期成员,他们本身有类似询问支不支持某种特性这样的方法,比如大家所熟知的UIImagePickerController里检测可用媒体类型的availableMediaTypesForSourceType:方法,以及CMMotionManager里检测三轴陀螺是否可用的方法gyroAvailable。但是有些类询问方法的类和成员所要使用的特性基本基本都是和设备硬件有关的。
+
+另外当然也可以用respondsToSelector:方法来检测是否对某个方法响应。但是这是非常危险的做法,我本人不推荐使用。因为iOS系统里存在很多私有API,而Apple的审查机制对于私有API调用的格杀勿论是业界公知的。而respondsToSelector:如果想要检测的方法在老版本的系统中是作为私有API形式存在的话,你将得到错误的结果,从而去执行私有API,那就完了。这种应用往往是被拒了都不知道自己在哪儿使用了私有API…
diff --git a/_posts/2012-03-03-opencv-build-and-config.markdown b/_posts/2012-03-03-opencv-build-and-config.markdown
new file mode 100644
index 00000000..d13da2f0
--- /dev/null
+++ b/_posts/2012-03-03-opencv-build-and-config.markdown
@@ -0,0 +1,68 @@
+---
+layout: post
+title: OpenCV 在 iOS 开发环境下的编译和配置
+date: 2012-03-03 23:06:23.000000000 +09:00
+tags: 能工巧匠集
+---
+
+转载本文请保留以下原作者信息:
+
+原作:OneV's Den [http://www.onevcat.com/2012/03/opencv-build-and-config/](http://www.onevcat.com/2012/03/opencv-build-and-config/)
+
+## 2014.5.3 更新
+
+现在一般都直接使用方便的 CocoaPods 来进行依赖管理了,特别是对于像 OpenCV 这样关系复杂的类库来说尤为如此。可以访问 [CocoaPods 的页面](http://cocoapods.org)并搜索 OpenCV 找到相关的 pod 信息就可以进行简单的导入了。如果您还不会或者没有开始使用 CocoaPods 的话,现在正是时候学习并实践了!
+
+---
+
+最近在写一个自己的app,用到一些图像识别和处理的东西。研究后发现用OpenCV是最为方便省事的,但是为iOS开发环境编译和配置OpenCV的库还是需要费点功夫,网上资料也并不是很全,而且有不少已经过期。在此进行一些总结,算是留底,也希望能对其他人有所帮助。
+
+OpenCV (Open Source Computer Vision Library) 是跨平台的开源项目,由一系列C函数和少量C++类构成,提供了图像处理和计算机视觉方面很多通用的算法。在开发有关图像识别和处理的app的时候,OpenCV提供了一系列易用轻量的API,而且遵循BSD License。
+
+## OpenCV For iOS一键编译
+
+OpenCV用在iOS上,一般是以静态库的方式提供服务的,因此需要先将源码进行编译。如果你想省事,这里有一个我预先编译好的库,可以直接使用(OpenCV版本为2.3,虽然文件名字有part1,但是只有这一个包,开袋即食),如果需要最新版本的OpenCV,可以选择自行编译。
+
+先从OpenCV的repository下载最新的OpenCV
+
+```
+svn co https://code.ros.org/svn/opencv/trunk
+```
+
+这里包含了源码和所有范例教程等,有1G多,小水管需谨慎。如果只想下载源码的话,可以从这里check out
+
+```
+svn co https://code.ros.org/svn/opencv/trunk/opencv
+```
+
+如果之前有check out过,那么用svn update进行更新即可拿到最新版的源码,或者到[sourceforge进行下载](http://sourceforge.net/projects/opencvlibrary/)。
+
+由于darwin没有内置CMake,因此在编译前需要下载并安装CMake,在CMake的官网可以找到下载。
+Eugene Khvedchenya写了一个超级棒的脚本,可以在这里找到下载,或者这里有一个本地的副本(不再更新)。将下载的脚本放到trunk目录中,运行
+
+```
+sh BuildOpenCV.sh opencv/ opencv_ios_build
+```
+数分钟后即可在opencv_ios_build目录下找到头文件和编译好的静态库。
+
+如果是从官方库签出的OpenCV并且不怕麻烦的话,也可以使用官方的脚本完成编译,具体可以参看下载的`/opencv/ios/readme.txt`文件。
+
+## OpenCV的库配置
+
+和其他静态库的配置基本一致,以Xcode4为例。
+
+* 将编译好的opencv文件夹拖入工程中,记得勾选Copy items into destination group’s folder (if needed)
+
+
+
+* 在Build Settings的Header Search Paths和Library Search Paths中填入相应的头文件位置和库文件位置,并将Always Search User Paths勾为Yes
+
+
+
+
+
+* 在Build Phases中的Link Binary Libraries中添加用到的库文件即可
+
+## 编译脚本
+
+编译OpenCV的脚本如下,请不要直接复制粘贴该脚本,可能某些符号会在字符转换过程中出现问题。可以访问这里下载该脚本的最新版本,或者[点击这里](http://www.onevcat.com/wp-content/uploads/2012/03/BuildOpenCV.sh_.zip)取得脚本的副本。
diff --git a/_posts/2012-03-24-appcode.markdown b/_posts/2012-03-24-appcode.markdown
new file mode 100644
index 00000000..77bb856d
--- /dev/null
+++ b/_posts/2012-03-24-appcode.markdown
@@ -0,0 +1,47 @@
+---
+layout: post
+title: AppCode,Objective-C IDE的另一选择
+date: 2012-03-24 23:10:06.000000000 +09:00
+tags: 南箕北斗集
+---
+
+###Xcode or AppCode
+
+近年来随着iOS设备和Mac发展,Objective-C(以下简写为OC)进步神速,但是这个世界上并没有多少OC的IDE。要说集成了Mac和iOS SDK的OC开发套件,最为常用和普及的一定是Apple自家的Xcode了。真心说来Xcode是一个很棒的IDE,它具备了作为一个优秀IDE所应该拥有的一切要素。其他的OC IDE环境从来不是主流,但是其中却也不乏优秀者,[JetBrains的AppCode](http://www.jetbrains.com/objc/)便是佼佼者之一。
+
+说到JetBrains可能最为人熟知的是它旗下的另一款Java IDE——[IntelliJ IDEA](http://www.jetbrains.com/idea/)。而JetBrains也还同时有PHP,Python,Ruby等语言的专用IDE,可以说JetBrains就是以IDE为主要产品的公司。作为一家专业的IDE解决方案提供商,它的产品自然也能符合绝大多数用户的需求。而AppCode是JetBrains为Mac和iOS下app开发所推出的IDE产品。如果你想要更effective和elegant的coding,那确实应该尝试AppCode;或者只是单单看腻了Xcode,也可以尝尝鲜~
+
+###列举AppCode的几个好用特性
+####代码补全
+
+这是最最基本的特性,我想也是一个合格的IDE及编辑器应该完成的最基本的功能。AppCode的代码补全不仅限于类、方法或者变量名字这样的基本自动完成,它还具备了根据上下文推测的能力,并且推测算法十分可靠。
+
+甚至如果你写了一个从未出现过的变量或者方法,AppCode都会询问你是否想要添加这个方法。开发者将有机会避免一切可能的无意义的来回跳转,而专注于有效代码的编写。
+
+####快速跳转
+
+Xcode的最大确定之一就是难以定位文件和类。想找一个文件的话,基本上不可能完全用键盘实现。而如果遵循效率至上的原则的话,手指离开键盘就意味着效率下降。Sublime Text提供了一种很优秀的寻找和跳转的方法,而AppCode中也有类似的导航方式(我不确定是谁先提出的)。配合类似微博的特定符号,可以完成从文件到类乃至到方法和符号的快速跳转,避免了所有可能的鼠标操作。
+
+####代码分析和修改意见
+
+虽然Xcode也有代码分析的功能(Shift+Cmd+B in Xcode 4),但是大部分情况下是会望了用的,而且Xcode的分析基本只能找到内存上的潜在问题,随着ARC的逐渐普及,相信内存上的issue会在开发过程中越来越少。AppCode的代码分析是实时进行的,在代码完成之前,你就可以看到存在的问题。分析和监测的问题包括且不限于代码内存管理、从未调用的方法、不可到达的代码段等。
+
+关于警告或错误代码的修改可以说是AppCode的强项,自动帮助添加release/autorelease,优化头文件引用(去掉多余头文件以及自动添加需要的头文件),自动帮助完成强制转换等。
+
+代码分析和修正共有超过60种监视的错误,遵循AppCode的建议可以保证代码的整洁。
+
+####代码格式修正
+
+每个人都有自己喜欢和习惯的代码格式,比如{}的位置,缩进和隔行的形式等等。阅读符合自己风格的代码时,往往效率能有大幅提升。AppCode提供了高度可自定制的代码风格模版,并可以很简单地将其套用到任何代码上。这样,不论写代码时多么没有注意格式,最后产生的代码都是完全符合风格的漂亮优雅的代码。这不仅可以为自己之后的维护和修改打下基础,也能在团队合作中快速将自己的代码的风格改为和团队统一。这也是我个人最喜欢的AppCode的一个功能。
+
+####iOS环境
+
+既然是for OC的IDE,那基本上绝大部分时间都是在为iOS或者Mac开发而工作了。AppCode虽然不是Apple的亲儿子,但是不管是设备调试还是模拟器运行也都是没有问题的。而且AppCode也集成了GDB和LLDB,其Debug工具的界面总体上说比Xcode更灵活。另外,单元测试和文档功能也深度集成到了AppCode中,可以随时方便地运行和调用。
+
+####插件
+
+插件这种东西,为一个应用提供了无限的可能(关于插件这种东西的登峰造极的应用,可以参考VIM或者魔兽世界)。可以说使用插件或者自己编写插件来使用,完全可以将AppCode二次开发为一个完全符合自己需求和习惯的IDE。Xcode虽然也提供插件功能,但是Xcode的插件开发相当繁琐,而且成功的Xcode插件也基本不存在与这个世界之上。而AppCode现在已经有50+的插件存在于插件仓库中,已经可以满足大部分开发者的需求了(比如存在把编辑器VIM化的强力插件)。
+
+###AppCode的不足
+
+金无足赤,AppCode也有一些不足之处。比如需要依赖Xcode,没有集成nib编辑器,在打开nib文件时会自动去开Xcode,Instrument工具也要调用Xcode等。但是这并不妨碍AppCode成为一款伟大的IDE,在通过一段时间的对AppCode的使用后,我已经成为了AppCode的忠实拥趸~这款IDE对于开发效率的提高和开发心情的调节可谓是相当成功。
diff --git a/_posts/2012-04-22-objective-c-runtime.markdown b/_posts/2012-04-22-objective-c-runtime.markdown
new file mode 100644
index 00000000..8670f807
--- /dev/null
+++ b/_posts/2012-04-22-objective-c-runtime.markdown
@@ -0,0 +1,103 @@
+---
+layout: post
+title: 深入Objective-C的动态特性
+date: 2012-04-22 23:18:44.000000000 +09:00
+tags: 能工巧匠集
+---
+
+Objective-C具有相当多的动态特性,基本的,也是经常被提到和用到的有动态类型(Dynamic typing),动态绑定(Dynamic binding)和动态加载(Dynamic loading)。
+
+这些动态特性都是在Cocoa程序开发时非常常用的语言特性,而在这之后,OC在底层也提供了相当丰富的运行时的特性,比如枚举类属性方法、获取方法实现等等。虽然在平常的Cocoa开发中这些较底层的运行特性基本用不着,但是在某些情况下如果你知道这些特性并合理加以运用的话,往往能事半功倍~
+
+### 动态特性基础
+
+1、动态类型
+
+即运行时再决定对象的类型。这类动态特性在日常应用中非常常见,简单说就是id类型。id类型即通用的对象类,任何对象都可以被id指针所指,而在实际使用中,往往使用introspection来确定该对象的实际所属类:
+
+```
+id obj = someInstance;
+if ([obj isKindOfClass:someClass])
+{
+ someClass *classSpecifiedInstance = (someClass *)obj;
+ // Do Something to classSpecifiedInstance which now is an instance of someClass
+ //...
+}
+```
+`-isMemberOfClass:` 是 `NSObject` 的方法,用以确定某个 `NSObject` 对象是否是某个类的成员。与之相似的为 `-isKindOfClass:`,可以用以确定某个对象是否是某个类或其子类的成员。这两个方法为典型的introspection方法。在确定对象为某类成员后,可以安全地进行强制转换,继续之后的工作。
+
+2、动态绑定
+
+基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。在继续之前,需要明确Objective-C中消息的概念。由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法。
+
+动态绑定所做的,即是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层,我们一般向一个NSObject对象发送-respondsToSelector:或者-instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:和+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。一个例子:
+
+```
+void dynamicMethodIMP(id self, SEL _cmd)
+{
+ // implementation ....
+}
+
+
+
+//该方法在OC消息转发生效前被调用
++ (BOOL) resolveInstanceMethod:(SEL)aSEL
+{
+ if (aSEL == @selector(resolveThisMethodDynamically)) {
+ //向[self class]中新加入返回为void的实现,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:”);
+ return YES;
+ }
+ return [super resolveInstanceMethod:aSel];
+}
+```
+
+当然也可以在任意需要的地方调用`class_addMethod`或者`method_setImplementation`(前者添加实现,后者替换实现),来完成动态绑定的需求。
+
+3、动态加载
+
+根据需求加载所需要的资源,这点很容易理解,对于iOS开发来说,基本就是根据不同的机型做适配。最经典的例子就是在Retina设备上加载@2x的图片,而在老一些的普通屏设备上加载原图。随着Retina iPad的推出,和之后可能的Retina Mac的出现,这个特性相信会被越来越多地使用。
+
+### 深入运行时特性
+
+基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换(在Java里这个设计模式被叫做Strategy?不是很懂Java,求纠正)。而Objective-C还有一些高级或者说更底层的运行时特性,在一般的Cocoa开发中较为少见,基本被运用与编写OC和其他语言的接口上。但是如果有所了解并使用得当的话,在Cocoa开发中往往可以轻易解决一些棘手问题。
+
+这类运行时特性大多由`/usr/lib/libobjc.A.dylib`这个动态库提供,里面包括了对于类、实例成员、成员方法和消息发送的很多API,包括获取类实例变量列表,替换类中的方法,为类成员添加变量,动态改变方法实现等,十分强大。完整的API列表和手册可以在这里找到。虽然文档开头表明是对于Mac OS X Objective-C 2.0适用,但是由于这些是OC的底层方法,因此对于iOS开发来说也是完全相同的。
+
+一个简单的例子,比如在开发Universal应用或者游戏时,如果使用IB构建了大量的自定义的UI,那么在由iPhone版转向iPad版的过程中所面临的一个重要问题就是如何从不同的nib中加载界面。在iOS5之前,所有的`UIViewController`在使用默认的界面加载时(`init`或者`initWithNibName:bundle:`),都会走`-loadNibNamed:owner:options:`。而因为我们无法拿到`-loadNibNamed:owner:options`的实现,因此对其重载是比较困难而且存在风险的。因此在做iPad版本的nib时,一个简单的办法是将所有的nib的命名方式统一,然后使用自己实现的新的类似`-loadNibNamed:owner:options`的方法将原方法替换掉,同时保证非iPad的设备还走原来的`loadNibNamed:owner:options`方法。使用OC运行时特性可以较简单地完成这一任务。
+
+代码如下,在程序运行时调用`+swizze`,交换自己实现的`loadPadNibNamed:owner:options`和系统的`loadNibNamed:owner:options`,之后所有的`loadNibNamed:owner:options`消息都将会发为`loadPadNibNamed:owner:options`,由自己的代码进行处理。
+
+```
++(BOOL)swizze {
+ Method oldMethod = class_getInstanceMethod(self, @selector(loadNibNamed:owner:options:));
+ if (!oldMethod) {
+ return NO;
+ }
+ Method newMethod = class_getInstanceMethod(self, @selector(loadPadNibNamed:owner:options:));
+ if (!newMethod) {
+ return NO;
+ }
+ method_exchangeImplementations(oldMethod, newMethod);
+ return YES;
+}
+```
+
+`loadPadNibNamed:owner:options`的实现如下,注意在其中的`loadPadNibNamed:owner:options`由于之前已经进行了交换,因此实际会发送为系统的`loadNibNamed:owner:options`。以此完成了对不同资源的加载。
+
+```
+-(NSArray *)loadPadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options {
+ NSString *newName = [name stringByReplacingOccurrencesOfString:@"@pad" withString:@""];
+ newName = [newName stringByAppendingFormat:@"@pad"];
+ //判断是否存在
+ NSFileManager *fm = [NSFileManager defaultManager];
+ NSString* filepath = [[NSBundle mainBundle] pathForResource:newName ofType:@”nib”];
+ //这里调用的loadPadNibNamed:owner:options:实际为为交换后的方法,即loadNibNamed:owner:options:
+ if ([fm fileExistsAtPath:filepath]) {
+ return [self loadPadNibNamed:newName owner:owner options:options];
+ } else {
+ return [self loadPadNibNamed:name owner:owner options:options];
+ }
+}
+```
+
+当然这只是一个简单的例子,而且这个功能也可以通过别的方法来实现。比如添加UIViewController的类别来重载init,但是这样的重载会比较危险,因为你UIViewController的实现你无法完全知道,因此可能会由于重载导致某些本来应有的init代码没有覆盖,从而出现不可预测的错误。当然在上面这个例子中重载VC的init的话没有什么问题(因为对于VC,init的方法会被自动转发为loadNibNamed:owner:options,因此init方法并没有做其他更复杂的事情,因此只要初始化VC时使用的都是init的话就没有问题)。但是并不能保证这样的重载对于其他类也是可行的。因此对于实现未知的系统方法,使用上面的运行时交换方法会是一个不错的选择~
diff --git a/_posts/2012-05-10-tools-for-color-picking.markdown b/_posts/2012-05-10-tools-for-color-picking.markdown
new file mode 100644
index 00000000..3234d7ad
--- /dev/null
+++ b/_posts/2012-05-10-tools-for-color-picking.markdown
@@ -0,0 +1,36 @@
+---
+layout: post
+title: 颜色选取和转换小工具
+date: 2012-05-10 23:26:12.000000000 +09:00
+tags: 能工巧匠集
+---
+
+iOS的app中,交互设计永远是重点中的重点,为用户界面选择合适的配色方法不仅对app整体美观有重要意义,同时也对用户体验的提升至关重要。但是在iOS开发中对于颜色的选取,转换和设定并不十分方便。通过配合使用下面的小工具可以提升颜色选取和转换的效率~
+
+#### 颜色选择器
+
+颜色选取不论在网页开发还是应用开发中都很常见。Mac虽然自带的颜色选择器,但是它并不单独存在,想要选取一个屏幕上的颜色,往往需要打开另外一些臃肿的应用。ColorPicker通过脚本做到只单独打开颜色选择器,从而快速地完成颜色选取工作。有关ColorPicker的详细信息可以参看[这里](http://www.robinwood.com/Catalog/Technical/OtherTuts/MacColorPicker/MacColorPicker.html#colorPickerApp),下载[这个zip包](http://www.robinwood.com/Catalog/Technical/OtherTuts/MacColorPicker/ColorPicker.zip),就可以将颜色选择器当做一个普通的Mac应用来使用了~
+
+
+
+#### 16进制颜色选择器
+
+由于大部分时候需要使用代码控制颜色,因此需要知道选取的颜色的十六进制或者RGB表示,以方便代码使用。[这里](http://wafflesoftware.net/hexpicker/)提供了一个插件,可以在系统的颜色选择面板上显示当前颜色的十六进制编码,恰好满足了要求~
+
+
+
+下载[这个zip包](http://wafflesoftware.net/hexpicker/download/1.6.1/),将包里的HexColorPicker.colorPicker解压到至文件夹 [homefolder]/Library/ColorPickers/ 下(如果不存在的话需要手动创建)即可。再打开系统的颜色选择器时,可以看到标签栏最右边多了一个#符号,点击即可看到当前颜色的十六进制值。
+
+#### 还没结束呢..
+
+据我所知,Cocoa里貌似没有直接通过颜色十六进制字串生成颜色对象的方法..所以可能还需要一点小转换。这个很简单,只是一个十六进制换算而已~
+
+```
+UIColor* UIColorFromHex(NSInteger colorInHex) {
+ // colorInHex should be value like 0xFFFFFF
+ return [UIColor colorWithRed:((float) ((colorInHex & 0xFF0000) >> 16)) / 0xFF
+ green:((float) ((colorInHex & 0xFF00) >> 8)) / 0xFF
+ blue:((float) (colorInHex & 0xFF)) / 0xFF
+ alpha:1.0];
+}
+```
diff --git a/_posts/2012-05-16-tsinghua-photos.markdown b/_posts/2012-05-16-tsinghua-photos.markdown
new file mode 100644
index 00000000..a98dc060
--- /dev/null
+++ b/_posts/2012-05-16-tsinghua-photos.markdown
@@ -0,0 +1,85 @@
+---
+layout: post
+title: 水清木华
+date: 2012-05-16 23:27:51.000000000 +09:00
+tags: 不醒春色集
+---
+七年时光,匆匆飞逝。入学之日还历历在目,离别之时却已悄然而来。我希望自己能挥一挥衣袖,不带走这里的一片云彩,但却留下自己青春的回忆。在这个偌大的园子里,有我的欢笑,有我的泪水,有我的努力。我相信所有清华学子在离别母校时,必定是依依不舍。但是,孩子总有离家之日,外面的舞台也必会更加精彩。
+
+在毕业之际,载着园园骑车逛了一圈校园,再次好好地看了看这个生活了七年的地方,于是有了下面这一组照片。
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/_posts/2012-06-04-arc-hand-by-hand.markdown b/_posts/2012-06-04-arc-hand-by-hand.markdown
new file mode 100644
index 00000000..fe0262dc
--- /dev/null
+++ b/_posts/2012-06-04-arc-hand-by-hand.markdown
@@ -0,0 +1,387 @@
+---
+layout: post
+title: 手把手教你ARC——iOS/Mac开发ARC入门和使用
+date: 2012-06-04 23:30:00.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+本文部分实例取自iOS 5 Toturail一书中关于ARC的[教程和公开内容](http://www.raywenderlich.com/5677/beginning-arc-in-ios-5-part-1),仅用于技术交流和讨论。请不要将本文的部分或全部内容用于商用,谢谢合作。
+
+欢迎转载本文,但是转载请注明本文出处:[http://www.onevcat.com/2012/06/arc-hand-by-hand/][3]
+
+ [3]: http://www.onevcat.com/2012/06/arc-hand-by-hand/
+
+本文适合人群:对iOS开发有一定基础,熟悉iOS开发中内存管理的Reference Counting机制,对ARC机制有听闻很向往但是一直由于种种原因没有使用的童鞋。本文将从ARC机理入手对这个解放广大iOS开发者的伟大机制进行一个剖析,并逐步引导你开始使用ARC。一旦习惯ARC,你一定会被它的简洁高效所征服。
+
+## 写在开头
+
+虽然距离WWDC2011和iOS 5已经快一年时间,但是很多开发者并没有利用新方法来提高自己的水平,这点在ARC的使用上非常明显(特别是国内,基本很少见到同行转向ARC)。我曾经询问过一些同行为什么不转向使用ARC,很多人的回答是担心内存管理不受自己控制..其实我个人认为这是对于ARC机制了解不足从而不自信,所导致的对新事物的恐惧。而作为最需要“追赶时髦”的职业,这样的心态将相当不利。谨以此文希望能清楚表述ARC的机理和用法,也希望能够成为现在中文入门教学缺失的补充。
+
+* * *
+
+## 什么是ARC
+
+Automatic Reference Counting,自动引用计数,即ARC,可以说是WWDC2011和iOS5所引入的最大的变革和最激动人心的变化。ARC是新的LLVM 3.0编译器的一项特性,使用ARC,可以说一举解决了广大iOS开发者所憎恨的手动内存管理的麻烦。
+
+在工程中使用ARC非常简单:只需要像往常那样编写代码,只不过永远不写`retain`,`release`和`autorelease`三个关键字就好~这是ARC的基本原则。当ARC开启时,编译器将自动在代码合适的地方插入`retain`, `release`和`autorelease`,而作为开发者,完全不需要担心编译器会做错(除非开发者自己错用ARC了)。好了,ARC相当简单吧~到此为止,本教程结束。
+
+等等…也许还有其他问题,最严重的问题是“我怎么确定让ARC来管理不会出问题?”或者“用ARC会让程序性能下降吧”。对于ARC不能正处理内存管理的质疑自从ARC出生以来就一直存在,而现在越来越多的代码转向ARC并取得了很好的效果,这证明了ARC是一套有效的简化开发复杂程度的机制,另外通过研究ARC的原理,可以知道使用ARC甚至能提高程序的效率。在接下来将详细解释ARC的运行机理并且提供了一个step-by-step的教程,将非ARC的程序转换为ARC。
+
+* * *
+
+## ARC工作原理
+
+手动内存管理的机理大家应该已经非常清楚了,简单来说,只要遵循以下三点就可以在手动内存管理中避免绝大部分的麻烦:
+
+> 如果需要持有一个对象,那么对其发送retain 如果之后不再使用该对象,那么需要对其发送release(或者autorealse) 每一次对retain,alloc或者new的调用,需要对应一次release或autorealse调用
+
+初学者可能仅仅只是知道这些规则,但是在实际使用时难免犯错。但是当开发者经常使用手动引用计数 Manual Referecen Counting(MRC)的话,这些规则将逐渐变为本能。你会发现少一个`release`的代码怎么看怎么别扭,从而减少或者杜绝内存管理的错误。可以说MRC的规则非常简单,但是同时也非常容易出错。往往很小的错误就将引起crash或者OOM之类的严重问题。
+
+在MRC的年代里,为了避免不小心忘写`release`,Xcode提供了一个很实用的小工具来帮助可能存在的代码问题(Xcode3里默认快捷键Shift+A?不记得了),可以指出潜在的内存泄露或者过多释放。而ARC在此基础上更进一步:ARC是Objective-C编译器的特性,而不是运行时特性或者垃圾回收机制,ARC所做的只不过是在代码编译时为你自动在合适的位置插入`release`或`autorelease`,就如同之前MRC时你所做的那样。因此,至少在效率上ARC机制是不会比MRC弱的,而因为可以在最合适的地方完成引用计数的维护,以及部分优化,使用ARC甚至能比MRC取得更高的运行效率。
+
+### ARC机制
+
+学习ARC很简单,在MRC时代你需要自己`retain`一个想要保持的对象,而现在不需要了。现在唯一要做的是用一个指针指向这个对象,只要指针没有被置空,对象就会一直保持在堆上。当将指针指向新值时,原来的对象会被`release`一次。这对实例变量,synthesize的变量或者局部变量都是适用的。比如
+
+```objc
+NSString *firstName = self.textField.text;
+```
+
+`firstName`现在指向NSString对象,这时这个对象(`textField`的内容字符串)将被hold住。比如用字符串@“OneV"作为例子(虽然实际上不应该用字符串举例子,因为字符串的retainCount规则其实和普通的对象不一样,大家就把它当作一个普通的对象来看吧…),这个时候`firstName`持有了@"OneV"。
+
+
+
+当然,一个对象可以拥有不止一个的持有者(这个类似MRC中的retainCount>1的情况)。在这个例子中显然`self.textField.text`也是@“OneV",那么现在有两个指针指向对象@"OneV”(被持有两次,retainCount=2,其实对NSString对象说retainCount是有问题的,不过anyway~就这个意思而已.)。
+
+
+
+过了一会儿,也许用户在`textField`里输入了其他的东西,那么`self.textField.text`指针显然现在指向了别的字符串,比如@“onevcat",但是这时候原来的对象已然是存在的,因为还有一个指针`firstName`持有它。现在指针的指向关系是这样的:
+
+
+
+只有当`firstName`也被设定了新的值,或者是超出了作用范围的空间(比如它是局部变量但是这个方法执行完了或者它是实例变量但是这个实例被销毁了),那么此时`firstName`也不再持有@“OneV",此时不再有指针指向@"OneV",在ARC下这种状况发生后对象@"OneV"即被销毁,内存释放。
+
+
+
+类似于`firstName`和`self.textField.text`这样的指针使用关键字`strong`进行标志,它意味着只要该指针指向某个对象,那么这个对象就不会被销毁。反过来说,ARC的一个基本规则即是,**只要某个对象被任一`strong`指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁**。在默认情况下,所有的实例变量和局部变量都是`strong`类型的。可以说`strong`类型的指针在行为上和MRC时代`retain`的property是比较相似的。
+
+既然有`strong`,那肯定有`weak`咯~`weak`类型的指针也可以指向对象,但是并不会持有该对象。比如:
+
+```objc
+__weak NSString *weakName = self.textField.text
+```
+
+得到的指向关系是:
+
+
+
+这里声明了一个`weak`的指针`weakName`,它并不持有@“onevcat"。如果`self.textField.text`的内容发生改变的话,根据之前提到的**"只要某个对象被任一strong指针指向,那么它将不会被销毁。如果对象没有被任何strong指针指向,那么就将被销毁”**原则,此时指向@“onevcat"的指针中没有`strong`类型的指针,@"onevcat"将被销毁。同时,在ARC机制作用下,所有指向这个对象的`weak`指针将被置为`nil`。这个特性相当有用,相信无数的开发者都曾经被指针指向已释放对象所造成的EXC_BAD_ACCESS困扰过,使用ARC以后,不论是`strong`还是`weak`类型的指针,都不再会指向一个dealloced的对象,从**根源上解决了意外释放导致的crash**。
+
+
+
+不过在大部分情况下,`weak`类型的指针可能并不会很常用。比较常见的用法是在两个对象间存在包含关系时:对象1有一个`strong`指针指向对象2,并持有它,而对象2中只有一个`weak`指针指回对象1,从而避免了循环持有。一个常见的例子就是oc中常见的delegate设计模式,viewController中有一个`strong`指针指向它所负责管理的UITableView,而UITableView中的`dataSource`和`delegate`指针都是指向viewController的`weak`指针。可以说,`weak`指针的行为和MRC时代的`assign`有一些相似点,但是考虑到`weak`指针更聪明些(会自动指向nil),因此还是有所不同的。细节的东西我们稍后再说。
+
+
+
+注意类似下面的代码似乎是没有什么意义的:
+
+```
+__weak NSString *str = [[NSString alloc] initWithFormat:…];
+NSLog(@"%@",str); //输出是"(null)"
+```
+
+由于`str`是`weak`,它不会持有alloc出来的`NSString`对象,因此这个对象由于没有有效的`strong`指针指向,所以在生成的同时就被销毁了。如果我们在Xcode中写了上面的代码,我们应该会得到一个警告,因为无论何时这种情况似乎都是不太可能出现的。你可以把**weak换成**strong来消除警告,或者直接前面什么都不写,因为ARC中默认的指针类型就是`strong`。
+
+property也可以用`strong`或`weak`来标记,简单地把原来写`retain`和`assign`的地方替换成`strong`或者`weak`就可以了。
+
+```objc
+@property (nonatomic, strong) NSString *firstName;
+@property (nonatomic, weak) id delegate;
+```
+
+ARC可以为开发者节省很多代码,使用ARC以后再也不需要关心什么时候`retain`,什么时候`release`,但是这并不意味你可以不思考内存管理,你可能需要经常性地问自己这个问题:谁持有这个对象?
+
+比如下面的代码,假设`array`是一个`NSMutableArray`并且里面至少有一个对象:
+
+```objc
+id obj = [array objectAtIndex:0];
+[array removeObjectAtIndex:0];
+NSLog(@"%@",obj);
+```
+
+在MRC时代这几行代码应该就挂掉了,因为`array`中0号对象被remove以后就被立即销毁了,因此obj指向了一个dealloced的对象,因此在NSLog的时候将出现EXC_BAD_ACCESS。而在ARC中由于obj是`strong`的,因此它持有了`array`中的首个对象,`array`不再是该对象的唯一持有者。即使我们从`array`中将obj移除了,它也依然被别的指针持有,因此不会被销毁。
+
+### 一点提醒
+
+ARC也有一些缺点,对于初学者来说,可能仅只能将ARC用在objective-c对象上(也即继承自NSObject的对象),但是如果涉及到较为底层的东西,比如Core Foundation中的malloc()或者free()等,ARC就鞭长莫及了,这时候还是需要自己手动进行内存管理。在之后我们会看到一些这方面的例子。另外为了确保ARC能正确的工作,有些语法规则也会因为ARC而变得稍微严格一些。
+
+ARC确实可以在适当的地方为代码添加`retain`或者`release`,但是这并不意味着你可以完全忘记内存管理,因为你必须在合适的地方把`strong`指针手动设置到nil,否则app很可能会oom。简单说还是那句话,你必须时刻清醒谁持有了哪些对象,而这些持有者在什么时候应该变为指向`nil`。
+
+ARC必然是Objective-C以及Apple开发的趋势,今后也会有越来越多的项目采用ARC(甚至不排除MRC在未来某个版本被弃用的可能),Apple也一直鼓励开发者开始使用ARC,因为它确实可以简化代码并增强其稳定性。可以这么说,使用ARC之后,由于内存问题造成的crash基本就是过去式了(OOM除外 :P)
+
+我们正处于由MRC向ARC转变的节点上,因此可能有时候我们需要在ARC和MRC的代码间来回切换和适配。Apple也想到了这一点,因此为开发这提供了一些ARC和非ARC代码混编的机制,这些也将在之后的例子中列出。另外ARC甚至可以用在C++的代码中,而通过遵守一些代码规则,iOS 4里也可以使用ARC(虽然我个人认为在现在iOS 6都呼之欲出的年代已经基本没有需要为iOS 4做适配的必要了)、
+
+总之,聪明的开发者总会尝试尽可能的自动化流程,已减轻自己的工作负担,而ARC恰恰就为我们提供了这样的好处:自动帮我们完成了很多以前需要手动完成的工作,因此对我来说,转向ARC是一件不需要考虑的事情。
+
+* * *
+
+## 具体操作
+
+说了这么多,终于可以实践一下了。在决定使用ARC后,很多开发者面临的首要问题是不知如何下手。因为可能手上的项目已经用MRC写了一部分,不想麻烦做转变;或者因为新项目里用ARC时遇到了奇怪的问题,从而放弃ARC退回MRC。这都是常见的问题,而在下面,将通过一个demo引导大家彻底转向ARC的世界。
+
+### Demo
+
+
+
+例子很简单,这是一个查找歌手的应用,包含一个简单的UITableView和一个搜索框,当用户在搜索框搜索时,调用[MusicBrainz][20]的API完成名字搜索和匹配。MusicBrainz是一个开放的音乐信息平台,它提供了一个免费的XML网页服务,如果对MusicBrainz比较有兴趣的话,可以到它的官网逛一逛。
+
+ [20]: http://musicbrainz.org/
+
+> AppDelegate.h/m 这是整个app的delegate,没什么特殊的,每个iOS/Mac程序在main函数以后的入口,由此进入app的生命周期。在这里加载了最初的viewController并将其放到Window中展示出来。另外appDelegate还负责处理程序开始退出等系统委托的事件
+
+> MainViewController.h/m/xib 这个demo最主要的ViewController,含有一个TableView和一个搜索条。 SoundEffect.h/m 简单的播放声音的类,在MusicBrainz搜索完毕时播放一个音效。 main.m 程序入口,所有c程序都从main函数开始执行
+
+> AFHTTPRequestOperation.h/m 这是有名的网络框架AFNetworking的一部分,用来帮助等简单地处理web服务请求。这里只包含了这一个类而没有将全部的AFNetworking包括进来,因为我们只用了这一个类。完整的框架代码可以在github的相关页面上找到[https://github.com/gowalla/AFNetworking][22]
+
+ [22]: https://github.com/gowalla/AFNetworking
+
+> SVProgresHUD.h/m/bundle 是一个常用的进度条指示,当搜索的时候出现以提示用户正在搜索请稍后。bundle是资源包,里面包含了几张该类用到的图片,打进bundle包的目的一方面是为了资源容易管理,另一方面也是主要方面时为了不和其他资源发生冲突(Xcode中资源名字是资源的唯一标识,同名字的资源只能出现一次,而放到bundle包里可以避免这个潜在的问题)。SVProgresHUD可以在这里找到[https://github.com/samvermette/SVProgressHUD][23]
+
+ [23]: https://github.com/samvermette/SVProgressHUD
+
+快速过一遍这个应用吧:`MainViewController`是`UIViewController`的子类,对应的xib文件定义了对应的`UITableView`和`UISearchBar`。`TableView中`显示`searchResult`数组中的内容。当用户搜索时,用AFHTTPRequestOperation发一个HTTP请求,当从MusicBrainz得到回应后将结果放入`searchResult`数组中并用`tableView`显示,当返回结果是空时在`tableView`中显示没找到。主要的逻辑都在MainViewController.m中的`-searchBarSearchButtonClicked:`方法中,生成了用于查询的URL,根据MusicBrainz的需求替换了请求的header,并且完成了返回逻辑,然后在主线程中刷新UI。整个程序还是比较简单的~
+
+### MRC到ARC的自动转换
+
+回到正题,我们讨论的是ARC,关于REST API和XML解析的技术细节就暂时先忽略吧..整个程序都是用MRC来进行内存管理的,首先来让我们把这个demo转成ARC吧。基本上转换为ARC意味着把所有的`retain`,`release`和`autorelease`关键字去掉,在之前我们明确几件事情:
+
+* Xcode提供了一个ARC自动转换工具,可以帮助你将源码转为ARC
+* 当然你也可以自己动手完成ARC转换
+* 同时你也可以指定对于某些你不想转换的代码禁用ARC,这对于很多庞大复杂的还没有转至ARC的第三方库帮助很大,因为不是你写的代码你想动手修改的话代码超级容易mess…
+
+对于我们的demo,为了说明问题,这三种策略我们都将采用,注意这仅仅只是为了展示如何转换。实际操作中不需要这么麻烦,而且今后的绝大部分情况应该是从工程建立开始就是ARC的。
+
+
+
+首先,ARC是LLVM3.0编译器的特性,而老的工程特别是Xcode3时代的工程的默认编译器很可能是GCC或者LLVM-GCC,因此第一步就是确认编译器是否正确。**在Project设置面板,选择target,在Build Settings中将Compiler for C/C++/Objective-C选为Apple LLVM compiler 3.0或以上。**为了确保之后转换的顺利,在这里我个人建议最好把Treat Warnings as Errors和 Run Static Analyzer都打开,确保在改变编译器后代码依旧没有警告或者内存问题(虽然静态分析可能不太能保证这一点,但是聊胜于无)。好了~clean(`Shift+Cmd+K`)以后Bulid一下试试看,经过修改后的demo工程没有任何警告和错误,这是很好的开始。(对于存在警告的代码,这里是很好的修复的时机..请在转换前确保原来的代码没有内存问题)。
+
+
+
+接下来就是完成从MRC到ARC的伟大转换了。还是在Build Settings页面,把Objective-C Automatic Reference Counting改成YES(如果找不到的话请看一看搜索栏前面的小标签是不是调成All了..这个选项在Basic里是不出现的),这样我们的工程就将在所有源代码中启用ARC了。然后…试着编译一下看看,嗯..无数的错误。
+
+
+
+这是很正常的,因为ARC里不允许出现retain,release之类的,而MRC的代码这些是肯定会有的东西。我们可以手动一个一个对应地去修复这些错误,但是这很麻烦。Xcode为我们提供了一个自动转换工具,可以帮助重写源代码,简单来说就是去掉多余的语句并且重写一些property关键字。
+
+
+
+
+
+这个小工具是Edit->Refactor下的Convert to Objective-C ARC,点击后会让我们选择要转换哪几个文件,在这里为了说明除了自动转换外的方法,我们不全部转换,而只是选取其中几个转换(`MainViewController.m`和`AFHTTPRequestOperation.m`不做转换,之后我们再手动将这两个转为ARC)。注意到这个对话框上有个警告标志告诉我们target已经是ARC了,这是由于之前我们在Build Settings里已经设置了启用ARC,其实直接在这里做转换后Xcode会自动帮我们开启ARC。点击检查后,Xcode告诉我们一个不幸的消息,不能转换,需要修复ARC readiness issues..后面还告诉我们要看到所有的所谓的ARC readiness issues,可以到设置的General里把Continue building after errors勾上…What the f**k…好吧~先乖乖听从Xcode的建议"Cmd+,“然后Continue building after errors打勾然后再build。
+
+
+
+问题依旧,不过在issue面板里应该可以看到所有出问题的代码了。在我们的例子里,问题出在SoundEffect.m里:
+
+```objc
+NSURL *fileURL = [[NSBundle mainBundle] URLForResource:filename withExtension:nil];
+if (fileURL != nil) {
+ SystemSoundID theSoundID;
+ OSStatus error = AudioServicesCreateSystemSoundID((CFURLRef)fileURL, &theSoundID);
+ if (error == kAudioServicesNoError) {
+ soundID = theSoundID;
+ }
+}
+```
+
+这里代码尝试把一个`NSURL`指针强制转换为一个`CFURLRef`指针。这里涉及到一些Core Services特别是Core Foundation(CF)的东西,AudioServicesCreateSystemSoundID()函数接受CFURLRef为参数,这是一个CF的概念,但是我们在较高的抽象层级上所建立的是`NSURL`对象。在Cocoa框架中,有很多顶层对象对底层的抽象,而在使用中我们往往可以不加区别地对这两种对象进行同样的对待,这类对象即为可以"自由桥接"的对象(toll-free bridged)。NSURL和CFURLRef就是一对好基友好例子,在这里其实`CFURLRef`和`NSURL`是可以进行替换的。
+
+通常来说为了代码在底层级上的正确,在iOS开发中对基于C的API的调用所传入的参数一般都是CF对象,而Objective-C的API调用都是传入NSObject对象。因此在采用自由桥接来调用C API的时候就需要进行转换。但是在使用ARC编译的时候,因为内存管理的原因,编译器需要知道对这些桥接对象要实行什么样的操作。如果一个NSURL对象替代了CFURLRef,那么在作用区域外,应该由谁来决定内存释放和对象销毁呢?为了解决这个问题,引入了**bridge,**bridge_transfer和__bridge_retained三个关键字。关于选取哪个关键字做转换,需要由实际的代码行为来决定。如果对于自由桥接机制感兴趣,大家可以自己找找的相关内容,比如[适用类型][36]、[内部机制][37]和[一个简介][38]~之后我也会对这个问题做进一步说明
+
+ [36]: http://developer.apple.com/library/mac/#documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/20002401-767858
+ [37]: http://www.mikeash.com/pyblog/friday-qa-2010-01-22-toll-free-bridging-internals.html
+ [38]: http://ridiculousfish.com/blog/posts/bridge.html
+
+回到demo,我们现在在上面的代码中(CFURLRef)前加上`__bridge`进行转换。然后再运行ARC转换工具,这时候检查应该没有其他问题了,那么让我们进行转换吧~当然在真正转换之前会有一个预览界面,在这里我们最好检查一下转换是不是都按照预想进行了..要是出现大面积错误又没有备份或者出现各种意外的话就可以哭了…
+
+前后变化的话比较简单,基本就是去掉不需要的代码和改变property的类型而已,其实有信心的话不太需要每次都看,但是如果是第一次执行ARC转换的操作的话,我还是建议稍微看一下变化,这样能对ARC有个直观上的了解。检查一遍,应该没什么问题了..需要注意的是main.m里关于autoreleasepool的变化以及所有dealloc调用里的[super dealloc]的删除,它们同样是MRC到ARC的主要变化..
+
+好了~转换完成以后我们再build看看..应该会有一些警告。对于原来`retain`的property,比较保险的做法是转为`strong`,在LLVM3.0中自动转换是这样做的,但是在3.1中property默认并不是`strong`,这样在使用property赋值时存在警告,我们在property声明里加上`strong`就好了~然后就是SVProgressHUD.m里可能存在问题,这是由于原作者把`release`的代码和其他代码写在一行了.导致自动转换时只删掉了部分,而留下了部分不应该存在的代码,删掉对变量的空的调用就好了..
+
+### 自动转换之后的故事
+
+然后再编译,没有任何错误和警告了,好棒~等等…我们刚才没有对MainViewController和AFHTTPRequestOperation进行处理吧,那么这两个文件里应该还存在`release`之类的东西吧..?看一看这两个文件,果然有各种`release`,但是为什么能编译通过呢?!明明刚才在自动转换前他们还有N多错的嘛…答案很简单,在自动转换的时候因为我们没有勾选这两个文件,因此编译器在自动转换过后为这两个文件标记了"不使用ARC编译"。可以看到在target的Building Phases下,MainViewController.m和AFHTTPRequestOperation.m两个文件后面被加上了`-fno-objc-arc`的编译标记,被加上该标记的文件将不使用ARC规则进行编译。(相对地,如果你想强制对某几个文件启用ARC的话,可以为其加上`-fobjc-arc`标记)
+
+
+
+提供这样的编译标记的原因是显而易见的,因为总是有一部分的第三方代码并没有转换为ARC(可能是由于维护者犯懒或者已经终止维护),所以对于这部分代码,为了迅速完成转换,最好是使用-fno-objc-arc标记来禁止在这些源码上使用ARC。
+
+为了方便查找,再此列出一些在转换时可能出现的问题,当然在我们使用ARC时也需要注意避免代码中出现这些问题:
+
+ * “Cast … requires a bridged cast”
+
+ ***这是我们在demo中遇到的问题,不再赘述***
+
+ * Receiver type ‘X’ for instance message is a forward declaration
+
+ ***这往往是引用的问题。ARC要求完整的前向引用,也就是说在MRC时代可能只需要在.h中申明@class就可以,但是在ARC中如果调用某个子类中未覆盖的父类中的方法的话,必须对父类.h引用,否则无法编译。***
+
+ * Switch case is in protected scope
+
+ ***现在switch语句必须加上{}了,ARC需要知道局部变量的作用域,加上{}后switch语法更加严格,否则遇到没有break的分支的话内存管理会出现问题。***
+
+ * A name is referenced outside the NSAutoreleasePool scope that it was declared in...
+
+ ***这是由于写了自己的autoreleasepool,而在转换时在原来的pool中申明的变量在新的@autoreleasepool中作用域将被局限。解决方法是把变量申明拿到pool的申请之前。***
+
+ * ARC forbids Objective-C objects in structs or unions
+
+ ***可以说ARC所引入的最严格的限制是不能在C结构体中放OC对象了..因此类似下面这样的代码是不可用的***
+
+```objc
+typedef struct {
+ UIImage *selectedImage;
+ UIImage *disabledImage;
+} ButtonImages;
+```
+
+这个问题只有乖乖想办法了..改变原来的结构什么的..
+
+### 手动转换
+
+刚才做了对demo的大部分转换,还剩下了MainViewController和AFHTTPRequestOperation是MRC。但是由于使用了`-fno-objc-arc`,因此现在编译和运行都没有问题了。下面我们看看如何手动把MainViewController转为ARC,这也有助于进一步理解ARC的规则。
+
+首先,我们需要转变一下观念…对于MainViewController.h,在.h中申明了两个实例变量:
+
+```objc
+@interface MainViewController : UIViewController
+{
+ NSOperationQueue *queue;
+ NSMutableString *currentStringValue;
+}
+```
+
+我们不妨仔细考虑一下,为什么在interface里出现了实例变量的申明?通常来说,实例变量只是在类的实例中被使用,而你所写的类的使用者并没有太多必要了解你的类中有哪些实例变量。而对于绝大部分的实例变量,应该都是`protected`或者`private`的,对它们的操作只应该用`setter`和`getter`,而这正是property所要做的工作。可以说,**将实例变量写在头文件中是一种遗留的陋习**。更好的写实例变量名字的地方应当与类实现关系更为密切,为了隐藏细节,我们应该考虑将它们写在@implementation里。好消息是,在LLVM3.0中,不论是否开启ARC,编译器是支持将实例变量写到实现文件中的。甚至如果没有特殊需要又用了property,我们都不应该写无意义的实例变量申明,因为在@synthesize中进行绑定时,我们就可以设置变量名字了,这样写的话可以让代码更加简洁。
+
+在这里我们对着两个实例变量不需要property(外部成员不应当能访问到它们),因此我们把申明移到.m里中。修改后的.h是这样的,十分简洁一看就懂~
+
+```objc
+#import
+@interface MainViewController : UIViewController
+@property (nonatomic, retain) IBOutlet UITableView *tableView;
+@property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
+@end
+```
+
+然后.m的开头变成这样:
+
+```objc
+@implementation MainViewController
+{
+ NSOperationQueue *queue;
+ NSMutableString *currentStringValue;
+}
+```
+
+这样的写法让代码相当灵活,而且不得不承认.m确实是这些实例变量的应该在的地方…build一下,没问题..当然对于SoundEffect类也可以做相似的操作,这会让使用你的类的人很开心,因为.h越简单越好..P.S.另外一个好处可以减少.h里的引用,减少编译时间(虽然不明显=。=)
+
+然后就可以在MainViewController里启用ARC了,方法很简单,删掉Build Phases里相关文件的-fno-objc-arc标记就可以了~然后..然后当然是一大堆错误啦。我们来手动一个个改吧,虽然谈不上乐趣,但是成功以后也会很有成就~(如果你不幸在启用ARC后build还是成功了,恭喜你遇到了Xcode的bug,请Cmd+Q然后重新打开Xcode把=_=)
+
+#### dealloc
+
+红色最密集的地方是`dealloc`,因为每一行都是`release`。由于在这里`dealloc`并没有做除了`release`和`super dealloc`之外的任何事情,因此简单地把整个方法删掉就好了。当然,在对象被销毁时,`dealloc`还是会被调用的,因此我们在需要对非ARC管理的内存进行管理和必要的逻辑操作的时候,还是应该保留`dealloc`的,当然这涉及到CF以及以下层的东西:比如对于`retain`的CF对象要`CFRelease()`,对于`malloc()`到堆上的东西要`free()`掉,对于添加的`observer`可以在这里remove,schedule的timer在这里`invalidate`等等~`[super dealloc]`这个消息也不再需要发了,ARC会自动帮你搞定。
+
+另外,在MRC时代一个常做的事情是在`dealloc`里把指向自己的delegate设成nil(否则就等着EXC_BAD_ACCESS吧 :P),而现在一般delegate都是`weak`的,因此在self被销毁后这个指针自动被置成`nil`了,你不用再为之担心,好棒啊..
+
+#### 去掉各种release和autorelease
+
+这个很直接,没有任何问题。去掉就行了~不再多说
+
+#### 讨论一下Property
+
+在MainViewController.m里的类扩展中定义了两个property:
+
+```objc
+@interface MainViewController ()
+@property (nonatomic, retain) NSMutableArray *searchResults;
+@property (nonatomic, retain) SoundEffect *soundEffect;
+@end
+```
+
+申明的类型是`retain`,关于`retain`,`assign`和`copy`的讨论已经烂大街了,在此不再讨论。在MRC的年代使用property可以帮助我们使用dot notation的时候简化对象的`retain`和`copy`,而在ARC时代,这就显得比较多余了。在我看来,使用property和点方法来调用setter和getter是不必要的。property只在将需要的数据在.h中暴露给其他类时才需要,而在本类中,只需要用实例变量就可以。(更新,现在笔者在这点上已经不纠结了,随意就好,自己明白就行。但是也许还是用点方法会好一些,至少可以分清楚到底是操作了实例变量还是调用了setter和getter)。因此我们可以移去searchResults和soundEffect的@property和@synthesize,并将起移到实例变量申明中:
+
+```objc
+#import "plementation MainViewController
+{
+ NSOperationQueue *queue;
+ NSMutableString *currentStringValue;
+ NSMutableArray *searchResults;
+ SoundEffect *soundEffect;
+}
+```
+
+相应地,我们需要将对应的`self.searchResult`和`self.soundEffect`的self.都去去掉。在这里需要注意的是,虽然我们去掉了soundEffect的property和synthesize,但是我们依然有一个lazy loading的方法`-(SoundEffect *)soundEffect`,神奇之处在于(可能你以前也不知道),点方法并不需要@property关键字的支持,虽然大部分时间是这么用的..(property只是对setter或者getter的申明,而点方法是对其的调用,在这个例子的实现中我们事实上实现了-soundEffect这个getter方法,所以点方法在等号右边的getter调用是没有问题的)。为了避免误解,建议把self.soundEffect的getter调用改写成[self soundEffect]。
+
+然后我们看看.h里的property~里面有两个`retain`的IBOutlet。`retain`关键字在ARC中是依旧可用的,它在ARC中所扮演的角色和`strong`完全一样。为了避免迷惑,最好在需要的时候将其写为strong,那样更符合ARC的规则。对于这两个property,我们将其申明为`weak`(事实上,如果没有特别意外,除了最顶层的IBOutlet意外,自己写的outlet都应该是`weak`)。通过加载xib得到的用户界面,在其从xib文件加载时,就已经是view hierarchy的一部分了,而view hierarchy中的指向都是strong的。因此outlet所指向的UI对象不应当再被hold一次了。将这些outlet写为weak的最显而易见的好处是你就不用再viewDidUnload方法中再将这些outlet设为nil了(否则就算view被摧毁了,但是由于这些UI对象还在被outlet指针指向而无法释放,代码简洁了很多啊..)。
+
+在我们的demo中将IBOutlet的property改为`weak`并且删掉viewDidUnload中关于这两个IBOutlet的内容~
+
+总结一下新加入的property的关键字类型:
+
+ * strong 和原来的retain比较相似,strong的property将对应__strong的指针,它将持有所指向的对象
+ * weak 不持有所指向的对象,而且当所指对象销毁时能将自己置为nil,基本所有的outlet都应该用weak
+ * unsafe_unretained 这就是原来的assign。当需要支持iOS4时需要用到这个关键字
+ * copy 和原来基本一样..copy一个对象并且为其创建一个strong指针
+ * assign 对于对象来说应该永远不用assign了,实在需要的话应该用unsafe_unretained代替(基本找不到这种时候,大部分assign应该都被weak替代)。但是对于基本类型比如int,float,BOOL这样的东西,还是要用assign。
+
+特别地,对于`NSString`对象,在MRC时代很多人喜欢用copy,而ARC时代一般喜欢用strong…(我也不懂为什么..求指教)
+
+#### 自由桥接的细节
+
+MainViewController现在剩下的问题都是桥接转换问题了~有关桥接的部分有三处:
+
+ * (NSString *)CFURLCreateStringByAddingPercentEscapes(…):CFStringRef至NSString *
+ * (CFStringRef)text:NSString *至CFStringRef
+ * (CFStringRef)@“!_‘();:@&=+$,/?%#[]":NSString _至CFStringRef
+
+编译器对前两个进行了报错,最后一个是常量转换不涉及内存管理。
+
+关于toll-free bridged,如果不进行细究,`NSString`和`CFStringRef`是一样的东西,新建一个`CFStringRef`可以这么做:
+
+```objc
+CFStringRef s1 = [[NSString alloc] initWithFormat:@"Hello, %@!",name];
+```
+
+然后,这里`alloc`了而s1是一个CF指针,要释放的话,需要这样:
+
+```objc
+CFRelease(s1);
+```
+
+相似地可以用`CFStringRef`来转成一个`NSString`对象(MRC):
+
+```objc
+CFStringRef s2 = CFStringCreateWithCString(kCFAllocatorDefault,bytes, kCFStringEncodingMacRoman);
+NSString *s3 = (NSString *)s2;
+
+// release the object when you're done
+[s3 release];
+```
+
+在ARC中,编译器需要知道这些指针应该由谁来负责释放,如果把一个`NSObject`看做是CF对象的话,那么ARC就不再负责它的释放工作(记住ARC是only for NSObject的)。对于不需要改变持有者的对象,直接用简单的**bridge就可以了,比如之前在SoundEffect.m做的转换。在这里对于(CFStringRef)text这个转换,ARC已经负责了text这个NSObject的内存管理,因此这里我们需要一个简单的**bridge。而对于`CFURLCreateStringByAddingPercentEscapes`方法,方法中的create暗示了这个方法将形成一个新的对象,如果我们不需要`NSString`转换,那么为了避免内存的问题,我们需要使用`CFRelease`来释放它。而这里我们需要一个`NSString`,因此我们需要告诉编译器接手它的内存管理工作。这里我们使用**bridge_transfer关键字,将内存管理权由CF object移交给NSObject(或者说ARC)。如果这里我们只用**bridge的话,内存管理的负责人没有改变,那么这里就会出现一个内存泄露。另外有时候会看到`CFBridgingRelease()`,这其实就是transfer cast的内联写法..是一样的东西。总之,需要记住的原则是,当在涉及CF层的东西时,如果函数名中有含有Create, Copy, 或者Retain之一,就表示返回的对象的retainCount+1了,对于这样的对象,最安全的做法是将其放在`CFBridgingRelease()`里,来平衡`retain`和`release`。
+
+还有一种bridge方式,`__bridge_retained`。顾名思义,这种转换将在转换时将retainCount加1。和`CFBridgingRelease()`相似,也有一个内联方法`CFBridgingRetain()`来负责和`CFRelease()`进行平衡。
+
+需要注意的是,并非所有的CF对象都是自由桥接的,比如Core Graphics中的所有对象都不是自由桥接的(如`CGImage`和`UIImage`,`CGColor`和`UIColor`)。另外也不是只有自由桥接对象才能用bridge来桥接,一个很好的特例是void _(指向任意对象的指针,类似id),对于void _和任意对象的转换,一般使用`_bridge`。(这在将ARC运用在Cocos2D中很有用)
+
+#### 终于搞定了
+
+至此整个工程都ARC了~对于AFHTTPRequestOperation这样的不支持ARC的第三方代码,我们的选择一般都是就不使用ARC了(或者等开源社区的大大们更新ARC适配版本)。可以预见,在近期会有越来越多的代码转向ARC,但是也一定会有大量的代码暂时或者永远保持MRC等个,所以对于这些代码就不用太纠结了~
+
+* * *
+
+## 写在最后
+
+写了那么多,希望你现在能对ARC有个比较全面的了解和认识了。ARC肯定是以后的趋势,也确实能让代码量大大降低,减少了很多无意义的重复工作,还提高了app的稳定性。但是凡事还是纸上得来终觉浅,希望作为开发者的你,在下一个工程中去尝试用用ARC~相信你会和我一样,马上爱上这种make life easier的方式的~
diff --git a/_posts/2012-06-10-euro2012.markdown b/_posts/2012-06-10-euro2012.markdown
new file mode 100644
index 00000000..25b4c271
--- /dev/null
+++ b/_posts/2012-06-10-euro2012.markdown
@@ -0,0 +1,30 @@
+---
+layout: post
+title: EURO 2012
+date: 2012-06-10 23:32:58.000000000 +09:00
+tags: 胡言乱语集
+---
+
+又是一届欧洲杯。
+
+那一年,当各色时装铺满球场,当克林斯曼哭得像个小孩的时候,我4岁。那时的我,懵懂中认识了足球。
+
+那一年,当小劳德鲁普挑起大梁,在绿茵上奔走书写丹麦童话的时候,我6岁。那时的我,刚懂得什么是足球。
+
+那一年,当巴乔在玫瑰碗忧郁叹息,当塔法雷尔仰天长啸的时候,我8岁。那时的我,已经可以和小伙伴踢踢小场。
+
+那一年,当高卢雄鸡昂首怒掏巴西,我和老爸做在电视前的地上一起喝酒看球的时候,我12岁。那时的我,已经是小学队里最出色的门将,奇拉维特和巴特兹是我的偶像。
+
+那一年,当特雷泽盖打进金球绝杀意大利,我独自在电视前感叹人生如球场瞬息多变的时候,我14岁。那时的我,刚刚开始学会思考和冷静。
+
+那一年,当小组赛第一场开始我就预言希腊夺冠,同学家人纷纷不信,而最终却拜服的时候,我18岁。那时的我,籍以生存的,是理性和惯性,更多的是一种纯粹和执着。
+
+那一年,黄健翔高喊意大利万岁格罗索无敌。
+
+那一年,和我几乎同龄的伊涅斯塔、席尔瓦,托雷斯和法布雷加斯的名字不过刚刚进入人们的视野,西班牙的黄金一代终于成型。
+
+那一年,C罗梅西罗本集体哑火,身价越高状态越差的怪圈笼罩了所有的球星。
+
+可以说,足球,陪伴我长大。关于足球的这些记忆,可能永远也无法抹去了。
+
+而现在,在同样这块七千平米的草地上,又会演绎出什么样的故事呢…?
diff --git a/_posts/2012-06-11-developer-should-know-about-ios6.markdown b/_posts/2012-06-11-developer-should-know-about-ios6.markdown
new file mode 100644
index 00000000..c66ab121
--- /dev/null
+++ b/_posts/2012-06-11-developer-should-know-about-ios6.markdown
@@ -0,0 +1,98 @@
+---
+layout: post
+title: 开发者所需要知道的iOS6 SDK新特性
+date: 2014-06-11 23:35:11.000000000 +09:00
+tags: 能工巧匠集
+---
+
+欢迎转载本文,但是转载请注明本文出处: **[http://2.gy/erSp](http://2.gy/erSp)**
+
+iOS6 beta和相应的SDK已经放出了,WWDC2012要进入session环节了。iOS6无疑是这届WWDC的重点,在keynote上面对消费者展示了很多新鲜的特性,而之后的seesion对于开发者来说应该是更为重要。这里先大概把iOS6里新增的开发者可能用到的特性做个简单的整理。之后我也会挑一些自己感兴趣的session做一些整理和翻译工作,也算是对自己的一种锻炼吧~相关的笔记整理如下:
+
+[Session 200 What's New in Cocoa Touch](http://www.onevcat.com/2012/06/what-is-new-in-cocoa-touch/) Cocoa Touch新特性一览
+
+[Session 405 Modern Objective-C](http://www.onevcat.com/2012/06/modern-objective-c/ "WWDC 2012 Session笔记——405 Modern Objective-C") 先进Objective-C
+
+[Session 205 Introducing Collection Views](http://www.onevcat.com/2012/06/introducing-collection-views/ "WWDC 2012 Session笔记——205 Introducing Collection Views") Collection View入门
+
+[Session 219 Advanced Collection Views and Building Custom Layouts](http://www.onevcat.com/2012/08/advanced-collection-view/ "WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts") 高级Collection View和自定义布局
+
+[Session 202,228,232 AutoLayout使用](http://www.onevcat.com/2012/09/autoayout/ "WWDC 2012 Session笔记——202, 228, 232 AutoLayout(自动布局)入门")
+
+* * *
+
+### 地图
+
+iOS6抛弃了一直用的google map,而使用了自家的地图服务。相应地,MapKit框架也自然变成和Apple自家的地图服务绑定了。随之而来的好处是因为都是自家的内容,所以整合和开放会更进一步,第三方app现在有机会和地图应用进行交互了。也就是说,不使用自身搭载地图信息的app现在可以打开地图应用,并且显示一些感兴趣的路线和地点,这对于路线规划和记录类的应用来说这是个好消息~
+
+
+
+* * *
+
+### 深度社交网络集成
+
+iOS5的时候深度集成了Twitter,而Apple似乎从中尝到了不少甜头。现在Apple深度集成了Facebook和Sina Weibo。是的你没看错..新浪微博现在被深度集成了。对于开发这来说,特别是中国开发者来说确实是个好消息,因为如果只是想发条信息的话,不再需要进行繁琐的API申请,不再需要接受新浪恶心的应用审核,也不再需要忍受新浪程序员写出来的错误百出的SDK了。使用新的Social.framework可以很简单的从系统中拿到认证然后向社交网络发送消息,这对app的推广来说是很好的补充。
+
+另外,Apple提供了一类新的ViewController:UIActivityViewController来询问用户的社交行为,可以看做这是Apple为统一界面和用户体验做的努力,但是估计除了Apple自家的应用意外可能很少有人会用默认界面吧..毕竟冒了会和自己的UI风格不符的危险…
+
+* * *
+
+### Passbook和PassKit
+
+Passbook是iOS6自带的新应用,可以用来存储一些优惠券啊电影票啊登机牌啊什么的。也许Passbook这个新应用不是很被大家看好,但是我坚持认为这会是一个很有前景的方向。这是又一次使用数字系统来取代物理实体的尝试,而且从Passbook里我看到了Apple以后在NFC领域发展的空间。因为iPhone的设备很容易统一,因此也许会由Apple首先制定NFC的新游戏标准也为可知,如果成真那电子钱包和电子支付将会变成一大桶金呐…
+
+扯远了,PassKit是新加入的,可以说是配合或者呼应Passbook存在的框架。开发者可以使用PassKit生成和读取包含一些类似优惠券电影票之类信息的特殊格式的文件,然后以加密签名的方式发送给用户。然后在使用时,出示这些凭证即可按照类似物理凭证的方式进行使用。这给了类似电影院和餐馆这样的地方很多机会,可以利用PassKit进行售票系统或者优惠系统的开发,来引入更方便的购票体系,争取更多的客户。当然,现在还只能是当做物理凭证的补充来使用,我始终相信当iPhone里加入NFC模块以后,Passbook将摇身一变,而你的iPhone便理所当然的成了电子钱包。
+
+* * *
+
+### Game Center
+
+这个iOS4引入的东东一直不是很好用,iOS6里Apple终于对这个体系进行了一些升级。简单说就是完善了一些功能,主要是联机对战匹配的东西,不过我依然不看好…想当时写小熊对战的时候曾经想使用GameCenter的匹配系统来写,结果各种匹配和网络的悲剧,导致白白浪费了一个月时间。而像水果忍者这类的游戏,使用了GameCenter的对战系统,但是也面临经常性的掉线之类的问题,可以说游戏体验是大打折扣的。虽然iOS6里新加了一些特性,但是整个机制和基本没有改变,因此我依旧不看好Game Center的表现(或者说是在中国的表现,如果什么时候Apple能在中国架GameCenter的服务器的话也许会有改善)。
+
+不过值得注意的是,Mountain Lion里也加入了GameCenter。也就是说,我们在以后可能可以用iOS设备和Mac通过GameCenter进行联机对战,或者甚至是直接用Mac和Mac进行联机对战。这对于没有自己服务器/自己不会写服务器后端/没有精力维护的个人开发者提供了很好的思路。使用GameCenter做一些简单的网络游戏并不是很难,而因为GameCenter的特性,这个成本也将会非常低。这也许会是以后的一个不错的方向~
+
+* * *
+
+### 提醒
+
+自带的提醒应用得到了加强,Apple终于开放了向Reminder里添加东西和从中读取的API(Event Kit框架),以及一套标准的用户界面。这个没太多好说的,To-Do类应用已经在AppStore泛滥成灾,无非是提供了一个反向向系统添加list的功能,但是专业To-Do类应用的其他功能相信Apple现在不会以后也不想去替代。
+
+* * *
+
+### 新的IAP
+
+IAP(应用内购买)现在能直接从iTunes Store购买音乐了。这配合iTunes Match什么的用很不错,但是和天朝用户无关…首先是iTunes Store在天朝不开,其次是要是我朝用户什么时候具有买正版音乐的意识的话,我们这些开发者可能就要笑惨了。
+
+* * *
+
+### Collection Views
+
+不得不说Apple很无耻(或者说很聪明)。"会抄袭的艺术家是好的艺术家,会剽窃的艺术家是优秀的艺术家"这句话再次得到了诠释。基本新的UICollectionView实现了[PSCollectionView](https://github.com/ptshih/PSCollectionView)的功能,简单说就是类似Pinterest那样的"瀑布流"的展示方式。当然UICollectionView更灵活一些,可以根据要求变化排列的方式。嗯..Apple还很贴心地提供了相应的VC:UICollectionViewController。
+
+可能这一套UI展现方式在iPhone上不太好用,但是在iPad上会很不错。不少照片展示之类的app可以用到.但是其实如果只是瀑布流的话估计短时间内大家还是会用开源代码,毕竟only for iOS6的话或多或少会减少用户的..
+
+* * *
+
+### UI状态保存
+
+Apple希望用户关闭app,然后下一次打开时能保持关闭时的界面状态。对于支持后台且不被kill掉的app来说是天然的。但是如果不支持后台运行或者用户自己kill掉进程的话,就没那么简单了。现在的做法是从rootViewController开始把所有的VC归档后存成NSData,然后下次启动的时候做检查如果需要恢复的话就解压出来。
+
+每次都要在appDelegate写这些代码的话,既繁杂又不优雅,于是Apple在iOS6里帮开发者做了这件脏活累活,还不错~其实机理应该没变,就是把这些代码放到app启动里去做了..
+
+* * *
+
+### 隐私控制
+
+自从之前Apple被爆隐私门以后,就对这个比较重视了。现在除了位置信息以外,联系人、日历、提醒和照片的访问也强制需求用户的允许了。对普通开发者影响不大,因为如果确实需要的话用户一定会理解,但是可能对于360之流的流氓公司会造成冲击吧,对此只要呵呵就好了..= =?
+
+* * *
+
+### 其他一些值得一提的改动
+
+* 整个UIView都支持NSAttributedString的格式化字符串了。特别是UITextView和UITextField~(再次抄袭开源社区,Apple你又赢了)
+* UIImage现在多了一个新方法,可以在生成UIImage对象时指定scale。为retina iPad开发的童鞋们解脱了..
+* NSUUID,用这个类现在可以很方便的创建一个uuid了.注意这个是uuid,不要和udid弄混了…Apple承诺的udid解决方案貌似还没出现..~~~现在要拿udid的话还是用[OpenUDID](https://github.com/ylechelle/OpenUDID)吧~~~~OpenUDID已死,udid暂时无解,请乖乖使用广告vendor id;或者将一个uuid存入keychain可以在大多数情况下替代udid(onevcat与2013.09.01更新)
+
+* * *
+
+按照以往WWDC的惯例,之后几天的开发者Session会对这些变化以及之前就存在在iOS里的一些issues和tips做解释和交流。在session公布之后我会挑选一些自己感兴趣并且可能比较实用的部分再进行整理~尽情期待~
diff --git a/_posts/2012-06-20-what-is-new-in-cocoa-touch.markdown b/_posts/2012-06-20-what-is-new-in-cocoa-touch.markdown
new file mode 100644
index 00000000..3a4f3862
--- /dev/null
+++ b/_posts/2012-06-20-what-is-new-in-cocoa-touch.markdown
@@ -0,0 +1,198 @@
+---
+layout: post
+title: WWDC 2012 Session笔记——200 What is new in Cocoa Touch
+date: 2012-06-20 23:37:08.000000000 +09:00
+tags: 能工巧匠集
+---
+
+这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看[这里](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)。如果您是首次来到本站,也许您会有兴趣通过[RSS](http://onevcat.com/atom.xml),或者通过页面下方的邮件订阅的方式订阅本站。
+
+之前写过[一篇iOS6 SDK新内容的总览](http://www.onevcat.com/2012/06/%e5%bc%80%e5%8f%91%e8%80%85%e6%89%80%e9%9c%80%e8%a6%81%e7%9f%a5%e9%81%93%e7%9a%84ios6-sdk%e6%96%b0%e7%89%b9%e6%80%a7/),从这篇开始,将对WWDC 2012的我个人比较感兴趣的Session进行一些笔记,和之后的笔记一起应该可以形成一个比较完整的WWDC 2012 Session部分的个人记录。
+
+因为WWDC的内容可谓众多,我自觉不太可能看完所有Session(其实也没有这个必要..),所以对于内容覆盖上可能有所欠缺。另外我本身也只是一个iOS开发初学者加业余爱好者,因此很多地方也都不明白,不理解,因此难免有各种不足。这些笔记的最大作用是给自己做一些留底,同时帮助理解Session的内容。欢迎高手善意地指出我的错误和不足..谢谢!
+
+所有的WWDC 2012 Session的视频和讲义可以在[这里](https://developer.apple.com/videos/wwdc/2012/)找到,如果想看或者下载的话可能需要一个野生开发者账号(就是不用交99美金那种)。iOS6 Beta和Xcode4.5预览版现在已经提供开发者下载(需要家养开发者的账号,就在iOS Resource栏里),当然网上随便搜索一下不是开发者肯定也能下载到,不过如果你不太懂的话还是不建议尝试iOS6 Beta,有时间限制麻烦不说,而且可能存在各种bug,Xcode4.5预览版同理..
+
+
+
+作为WWDC 2012 Session部分的真正的开场环节,Session200可以说是iOS开发者必听必看的。这个Session介绍了关于Cocoa Touch的新内容,可以说是对整个iOS6 SDK的概览。
+
+我也将这个Session作为之后可能会写的一系列的Session笔记的第一章,我觉得用Session 200作为一个开始,是再适合不过的了~
+
+* * *
+
+### 更多的外观自定义
+
+从iOS5开始,Apple就逐渐致力于标准控件的可自定义化,基本包括颜色,图片等的替换。对于标准控件的行为,Apple一向控制的还是比较严格的。而开发者在做app时,最好还是遵守Apple的人机交互手册来确定控件的功能,否则可能遇到意想不到的麻烦…
+
+iOS6中Apple继续扩展了一些控件的可定义性。对于不是特别追求UI的开发团队或者实力有限的个人开发者来说这会是一个不错的消息,使用现有的资源和新加的API,可以快速开发出界面还不错的应用。
+
+#### UIPopoverBackgroundView
+
+UIPopoverBackgroundView是iOS5引入的,可以为popover自定义背景。iOS6中新加入了询问是否以默认方式显示的方法:
+
+`+ (BOOL)wantsDefaultContentAppearance;`
+
+返回NO的话,将以新的立体方式显示popover。
+
+具体关于UIPopoverBackgroundView的用法,可以参考[文档](http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIPopoverBackgroundView_class/Reference/Reference.html)
+
+#### UIStepper
+
+UIStepper也是iOS5引入的新控件,在iOS5中Apple为标准控件自定义做出了相当大的努力(可以参看WWDC2011的相关内容),而对于新出生的UIStepper却没有相应的API。在iOS6里终于加上了..可以说是预料之中的。
+
+`@property (nonatomic,retain) UIColor *tintColor;`
+
+这个属性定义颜色。
+
+
+
+```objc
+- (void)setBackgroundImage:(UIImage*)image forState:(UIControlState)state;
+
+- (void)setDividerImage:(UIImage*)image forLeftSegmentState:(UIControlState)left rightSegmentState:(UIControlState)right;
+
+- (void)setIncrementImage:(UIImage *)image forState:(UIControlState)state;
+
+- (void)setDecrementImage:(UIImage *)image forState:(UIControlState)state;
+```
+
+可以定义背景图片、分隔图片和增减按钮的图片,都很简单明了,似乎没什么好说的。
+
+#### UISwitch
+
+同样地,现在有一系列属性可以自定义了。
+
+```objc
+@property (nonatomic, retain) UIColor *tintColor;
+
+@property (nonatomic, retain) UIColor *thumbTintColor;
+
+@property (nonatomic, retain) UIImage *onImage;
+
+@property (nonatomic, retain) UIImage *offImage;
+```
+
+其中thumbTintColor指的是开关的圆形滑钮的颜色。另外对于on和off时候可以自定义图片,那么很大程度上其实开关控件已经可以完全自定义,基本不再需要自己再去实现一次了..
+
+
+
+#### UINavigationBar & UITabBar
+
+加入了阴影图片的自定义:
+
+`@property (nonatomic,retain) UIImage *shadowImage;`
+
+这个不太清楚,没有自己实际试过。以后有机会做个小demo看看可以…
+
+#### UIBarButtonItem
+
+现在提供设置背景图片的API:
+
+```objc
+(void)setBackgroundImage:(UIImage *)bgImage
+ forState:(UIControlState)state
+ style:(UIBarButtonItemStyle)style
+ barMetrics:(UIBarMetrics)barMetrics;
+```
+
+这个非常有用…以前在自定义UINavigationBar的时候,对于BarButtonItem的背景图片的处理非常复杂,通常需要和designer进行很多配合,以保证对于不同宽度的按钮背景图都可以匹配。现在直接提供一个UIImage就OK了..初步目测是用resizableImageWithCapInsets:做的实现..很赞,可以偷不少懒~
+
+* * *
+
+### UIImage的API变化
+
+随着各类Retina设备的出现,对于图片的处理方面之前的API有点力不从心..反应最大的就是图片在不同设备上的适配问题。对于iPhone4之前,是普通图片。对于iPhone4和4S,由于Retina的原因,需要将图片宽高均乘2,并命名为@2x。对于遵循这样原则的图片,cocoa touch将会自动进行适配,将4个pixel映射到1个point上去,以保证图片不被拉伸以及比例的适配。对于iPhone开发,相关的文档是比较全面的,但是对于iPad就没那么好运了。Apple对于iPad开发的支持显然做的不如对iPhone那样好,所以很多iPad开发者在对图片进行处理的时候往往不知所措——特别是在retina的new iPad出现以后,更为严重。而这次UIImage的最大变化在于自己可以对scale进行指定了~这样虽然在coding的时候变麻烦了一点,但是图片的Pixel to Point对应关系可以自己控制了,在做适配的时候可以省心不少。具体相关几个API如下:
+
+```objc
++ (UIImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
+
+- (id)initWithData:(NSData *)data scale:(CGFloat)scale;
+
++ (UIImage *)imageWithCIImage:(CIImage *)ciImage
+ scale:(CGFloat)scale
+ orientation:(UIImageOrientation)orientation;
+
+- (id)initWithCIImage:(CIImage *)ciImage
+ scale:(CGFloat)scale
+ orientation:(UIImageOrientation)orientation;
+```
+
+指定scale=2之后即可对retina屏幕适配,相对来说还是比较简单的。
+
+* * *
+
+### UITableView的改动
+
+UITableView就不多介绍了,基础中的基础…在iOS5引入StoryBoard之后,由StoryBoard生成的UITableViewController中对cell进行操作时所有的cell的alloc语句都可以不写,可以为cell绑定nib等,都简化了UITableView的使用。在iOS6中,对cell的复用有一些新的方法:
+
+`- (void)registerClass:(Class)cellClass forCellReuseIdentifier:(NSString *)identifier;`
+
+将一个类注册为某个重用ID。
+
+`- (id)dequeueReusableCellWithIdentifier:(NSString *)identifier
+ forIndexPath:(NSIndexPath *)indexPath;`
+
+将指定indexPath的cell重用(若不能重用则返回nil,在StoryBoard会自动生成一个新的cell)。
+
+另外,对UITableView的Header、Footer和Section分隔添加了一系列的property以帮助自定义,并且加入了关于Header和Footer的delegate方法。可以说对于TableView的控制更强大了…
+
+* * *
+
+### UIRefreshControl
+
+这个是新加的东西,Apple的抄袭之作,官方版的下拉刷新。下拉刷新自出现的第一分钟起,就成为了人民群众喜闻乐见的手势,对于这种得到大众认可的手势,Apple是一定不会放过的。
+
+相对与现在已有的开源下拉刷新来说,功能上还不那么强大,可自定义的内容不多,而且需要iOS6以后的系统,因此短期内还难以形成主流。但是相比开源代码,减去了拖源码加库之类的麻烦,并且和系统整合很好,再加上Apple的维护,相信未来是有机会成为主流的。现在来说的话,也就只是一种实现的选择而已。
+
+* * *
+
+### UICollectionView
+
+这个是iOS的UIKit的重头戏..一定意义上可以把UICollectionView理解成多列的UITableView。开源社区有很多类似的实现,基本被称作GridView,我个人比较喜欢的实现有[AQGridView](https://github.com/AlanQuatermain/AQGridView)和[GMGridView](https://github.com/gmoledina/GMGridView).开源实现基本上都是采用了和UITableView类似的方法,继承自UIScrollView,来进行多列排列。功能上来说相对都比较简单..
+
+而UICollectionView可以说是非常强大..强大到基本和UITableView一样了..至少使用起来和UITableView一样,用惯了UITableView的童鞋甚至可以不用看文档就能上手。一样的DataSource和Delegate,不同之处在于多了一个Layout对象对其进行排列的设定,这个稍后再讲。我们先来看Datasource和Delegate的API
+
+```objc
+//DataSource
+-numberOfSectionsInCollectionView:
+-collectionView:numberOfItemsInSection:
+-collectionView:cellForItemAtIndexPath:
+
+//Delegate
+-collectionView:shouldHighlightItemAtIndexPath:
+-collectionView:shouldSelectItemAtIndexPath:
+-collectionView:didSelectItemAtIndexPath:
+```
+
+没什么值得说的,除了名字以外,和UITableView的DataSource和Delegate没有任何不同。值得一提的是对应的UICollectionViewCell和UITableViewCell略有不同,UICollectionViewCell没有所谓的默认style,cell的子view自下而上有Background View、Selected Background View和一个Content View。开发者将自定义内容扔到Content View里即可。
+
+需要认真看看的是Layout对象,它控制了整个UICollectionView中每个Section甚至Section中的每个cell的位置和关系。Apple提供了几种不错的Layout,足以取代现在常用的几个开源库,其中包括了像Linkedin和Pinterest的视图。可以说Apple对于利用AppStore这个平台,向第三方开发者进行学习的能力是超强的。
+
+关于UICollectionView,在之后有两个session专门进行了讨论,我应该也会着重看一看相关内容,之后再进行补充了~
+
+* * *
+
+### UIViewController
+
+**这个绝对是重磅消息~**一直以来我们会在viewDidUnload方法中做一些清空outlet或者移除observer的事情。在viewDidUnload中清理observer其实并不是很安全,因此在iOS5中Apple引入了viewWillUnload,建议开发者们在viewWillUnload的时候就移除observer。而对于出现内存警告时,某些不用的view将被清理,这时候将自动意外执行viewWillUnload和viewDidUnload,很可能造成莫名其妙的crash,而这种内存警告造成的问题又因为其随机性难以debug。
+
+于是Apple这次做了一个惊人的决定,直接在**iOS6里把viewWillUnload和viewDidUnload标注为了Deprecated**,并且不再再会调用他们。绝大部分开发者其实是对iOS3.0以来就伴随我们的viewDidUnload是有深深的感情的,但是现在需要和这个方法说再见了。对于使用iOS6 SDK的app来说不应该再去实现这两个方法,而之前在这两个方法中所做的工作需要重新考虑其更合适的位置:比如在viewWillDisappear中去移除observer,在dealloc中将outlet置为nil等。
+
+* * *
+
+### 状态恢复
+
+在之前的一篇iOS6 SDK的简述中已经说过这个特性。简单讲就是对每个view现在都多了一个属性:
+
+`@property (nonatomic, copy) NSString *restorationIdentifier;`
+
+通过在用户点击Home键时的一系列delegate里对现有的view进行编码存储后,在下一次打开文件时进行解码恢复。更多的详细内容之后也会有session进行详细说明,待更新。
+
+* * *
+
+### 总结
+
+其他的很多新特性,包括社交网络,GameCenter和PassKit等也会在之后逐渐深入WWDC 2012 Session的时候进行笔记..
+
+作为开篇,就这样吧。
diff --git a/_posts/2012-06-24-modern-objective-c.markdown b/_posts/2012-06-24-modern-objective-c.markdown
new file mode 100644
index 00000000..0b5282bf
--- /dev/null
+++ b/_posts/2012-06-24-modern-objective-c.markdown
@@ -0,0 +1,198 @@
+---
+layout: post
+title: WWDC 2012 Session笔记——405 Modern Objective-C
+date: 2012-06-24 23:39:08.000000000 +09:00
+tags: 能工巧匠集
+---
+
+这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看[这里](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)。如果您是首次来到本站,也许您会有兴趣通过[RSS](http://onevcat.com/atom.xml),或者通过页面左侧的邮件订阅的方式订阅本站。
+
+2007年的时候,Objective-C在TIOBE编程语言排名里还排在可怜的第45位,而随着移动互联网的迅速发展和iPhone,iPad等iOS设备的广阔市场前景,Objective-C也迅速崛起,走进了开发者的视野。在最近的TIOBE排名中,Objective-C达到了惊人的第4名,可以说已经成为当今世界上一门非常重要的编程语言。
+
+而Objective-C现在主要是由Apple在负责维护了。一直以来Apple为了适应开发的发展需要,不断在完善OC以及相应的cocoa库,2.0中引入的property,随着iOS4引入的block,以及去年引入的ARC,都受到了绝大部分开发者的欢迎。几乎每年都有重大特性的加入,这不是每种语言都能做到的,更况且这些特性都为大家带来了众多的便利。
+
+今年WWDC也不例外,OC和LLVM将得到重大的改进。本文将对这些改进进行一个简单整理和评述。
+
+### 方法顺序
+
+如果有以下代码:
+
+```objc
+@interface SongPlayer : NSObject
+- (void)playSong:(Song *)song;
+@end
+
+@implementation SongPlayer
+
+- (void)playSong:(Song *)song {
+ NSError *error;
+ [self startAudio:&error];
+ //...
+}
+
+- (void)startAudio:(NSError **)error {
+ //...
+}
+
+@end
+```
+
+在早一些的编译环境中,上面的代码会在[self startAudio:&error]处出现一个实例方法未找到的警告。由于编译顺序,编译器无法得知在-playSong:方法之后还有一个-startAudio:,因此给出警告。以前的解决方案有两种:要么将-startAudio:的实现移到-playSong:的上方,要么在类别中声明-startAudio:(顺便说一句..把-startAudio:直接拿到.h文件中是完全错误的做法,因为这个方法不应该是public的)。前者破坏.m文件的结构打乱了方法排列的顺序,导致以后维护麻烦;后者要写额外的不必要代码,使.m文件变长。其实两种方法都不是很好的解决方案。
+
+现在不需要再头疼这个问题了,LLVM中加入了新特性,现在直接使用上面的代码,不需要做额外处理也可以避免警告了。新编译器改变了以往顺序编译的行为,改为先对方法申明进行扫描,然后在对方法具体实现进行编译。这样,在同一实现文件中,无论方法写在哪里,编译器都可以在对方法实现进行编译前知道所有方法的名称,从而避免了警告。
+
+* * *
+
+### 枚举改进
+
+从Xcode4.4开始,有更好的枚举的写法了:
+
+```objc
+typedef enum NSNumberFormatterStyle : NSUInteger {
+ NSNumberFormatterNoStyle,
+ NSNumberFormatterDecimalStyle,
+ NSNumberFormatterCurrencyStyle,
+ NSNumberFormatterPercentStyle,
+ NSNumberFormatterScientificStyle,
+ NSNumberFormatterSpellOutStyle
+} NSNumberFormatterStyle;
+```
+
+在列出枚举列表的同时绑定了枚举类型为NSUInteger,相比起以前的直接枚举和先枚举再绑定类型好处是方便编译器给出更准确的警告。个人觉得对于一般开发者用处并不是特别大,因为往往并不会涉及到很复杂的枚举,用以前的枚举申明方法也不至于就搞混。所以习惯用哪种枚举方式还是接着用就好了..不过如果有条件或者还没有形成自己的习惯或者要开新工程的话,还是尝试一下这种新方法比较好,因为相对来说要严格一些。
+
+* * *
+
+### 属性自动绑定
+
+人人都爱用property,这是毋庸置疑的。但是写property的时候一般都要对应写实例变量和相应的synthesis,这实在是一件让人高兴不起来的事情。Apple之前做了一些努力,至少把必须写实例变量的要求去掉了。在synthesis中等号后面的值即为实力变量名。**现在Apple更进一步,给我们带来了非常好的消息:以后不用写synthesis了!**Xcode 4.4之后,synthesis现在会对应property自动生成。
+
+默认行为下,对于属性foo,编译器会自动在实现文件中为开发者补全synthesis,就好像你写了@synthesis foo = _foo;一样。默认的实例变量以下划线开始,然后接属性名。如果自己有写synthesis的话,将以开发者自己写的synthesis为准,比如只写了@synthesis foo;那么实例变量名就是foo。如果没有synthesis,而自己又实现了-foo以及-setFoo:的话,该property将不会对应实例变量。而如果只实现了getter或者setter中的一个的话,另外的方法会自动帮助生成(即使没有写synthesis,当然readonly的property另说)。
+
+对于写了@dynamic的实现,所有的对应的synthesis都将不生效(即使没有写synthesis,这是runtime的必然..),可以理解为写了dynamic的话setter和getter就一定是运行时确定的。
+
+总结一下,新的属性绑定规则如下:
+
+* 除非开发者在实现文件中提供getter或setter,否则将自动生成
+* 除非开发者同时提供getter和setter,否则将自动生成实例变量
+* 只要写了synthesis,无论有没有跟实例变量名,都将生成实例变量
+* dynamic优先级高于synthesis
+
+* * *
+
+### 简写
+
+OC的语法一直被认为比较麻烦,绝大多数的消息发送都带有很长的函数名。其实这是一把双刃剑,好的方面,它使得代码相当容易阅读,因为几乎所有的方法都是以完整的英语进行描述的,而且如果遵守命名规则的话,参数类型和方法作用也一清二楚,但是不好的方面,它使得coding的时候要多不少不必要的键盘敲击,降低了开发效率。Apple意识到了这一点,在新的LLVM中引入了一系列列规则来简化OC。经过简化后,以降低部分可读性为代价,换来了开发时候稍微快速一些,可以说比较符合现在短开发周期的需要。简化后的OC代码的样子向Perl或者Python这样的快速开发语言靠近了一步,至于实际用起来好不好使,就还是仁智各异了…至少我个人对于某些简写不是特别喜欢..大概是因为看到简写的代码还没有形成直觉,总要反应一会儿才能知道这是啥…
+
+#### NSNumber
+
+所有的[NSNumber numberWith…:]方法都可以简写了:
+
+* `[NSNumber numberWithChar:‘X’]` 简写为 `@‘X’`;
+* `[NSNumber numberWithInt:12345]` 简写为 `@12345`
+* `[NSNumber numberWithUnsignedLong:12345ul]` 简写为 `@12345ul`
+* `[NSNumber numberWithLongLong:12345ll]` 简写为 `@12345ll`
+* `[NSNumber numberWithFloat:123.45f]` 简写为 `@123.45f`
+* `[NSNumber numberWithDouble:123.45]` 简写为 `@123.45`
+* `[NSNumber numberWithBool:YES]` 简写为 `@YES`
+
+嗯…方便很多啊~以前最讨厌的就是数字放Array里还要封装成NSNumber了…现在的话直接用@开头接数字,可以简化不少。
+
+#### NSArray
+
+部分NSArray方法得到了简化:
+
+* `[NSArray array]` 简写为 `@[]`
+* `[NSArray arrayWithObject:a]` 简写为 `@[ a ]`
+* `[NSArray arrayWithObjects:a, b, c, nil]` 简写为 `@[ a, b, c ]`
+
+可以理解为@符号就表示NS对象(和NSString的@号一样),然后接了一个在很多其他语言中常见的方括号[]来表示数组。实际上在我们使用简写时,编译器会将其自动翻译补全为我们常见的代码。比如对于@[ a, b, c ],实际编译时的代码是
+
+```objc
+// compiler generates:
+id objects[] = { a, b, c };
+NSUInteger count = sizeof(objects)/ sizeof(id);
+array = [NSArray arrayWithObjects:objects count:count];
+```
+
+需要特别注意,要是a,b,c中有nil的话,在生成NSArray时会抛出异常,而不是像[NSArray arrayWithObjects:a, b, c, nil]那样形成一个不完整的NSArray。其实这是很好的特性,避免了难以查找的bug的存在。
+
+#### NSDictionary
+
+既然数组都简化了,字典也没跑儿,还是和Perl啊Python啊Ruby啊很相似,意料之中的写法:
+
+* `[NSDictionary dictionary]` 简写为 `@{}`
+* `[NSDictionary dictionaryWithObject:o1 forKey:k1]` 简写为 `@{ k1 : o1 }`
+* `[NSDictionary dictionaryWithObjectsAndKeys:o1, k1, o2, k2, o3, k3, nil]` 简写为 `@{ k1 : o1, k2 : o2, k3 : o3 }`
+
+和数组类似,当写下@{ k1 : o1, k2 : o2, k3 : o3 }时,实际的代码会是
+
+```objc
+// compiler generates:
+id objects[] = { o1, o2, o3 };
+id keys[] = { k1, k2, k3 };
+NSUInteger count = sizeof(objects) / sizeof(id);
+dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys count:count];
+```
+
+#### Mutable版本和静态版本
+
+上面所生成的版本都是不可变的,想得到可变版本的话,可以对其发送-mutableCopy消息以生成一份可变的拷贝。比如
+
+```objc
+NSMutableArray *mutablePlanets = [@[
+ @"Mercury", @"Venus",
+ @"Earth", @"Mars",
+ @"Jupiter", @"Saturn",
+ @"Uranus", @"Neptune" ]
+ mutableCopy];
+```
+
+另外,对于标记为static的数组(没有static的字典..哈希和排序是在编译时完成的而且cocoa框架的key也不是常数),不能使用简写为其赋值(其实原来的传统写法也不行)。解决方法是在类方法+ (void)initialize中对static进行赋值,比如:
+
+```objc
+static NSArray *thePlanets;
++ (void)initialize {
+ if (self == [MyClass class]) {
+ thePlanets = @[ @"Mercury", @"Venus", @"Earth", @"Mars", @"Jupiter", @"Saturn", @"Uranus", @"Neptune" ];
+ }
+}
+```
+
+#### 下标
+
+其实使用这些简写的一大目的是可以使用下标来访问元素:
+
+* `[_array objectAtIndex:idx]` 简写为 `_array[idx]`;
+* `[_array replaceObjectAtIndex:idx withObject:newObj]` 简写为 `_array[idx] = newObj`
+* `[_dic objectForKey:key]` 简写为 `_dic[key]`
+* `[_dic setObject:object forKey:key]` 简写为 `_dic[key] = newObject`
+
+很方便,但是一定需要注意,对于字典用的也是方括号[],而不是想象中的花括号{}。估计是想避免和代码块的花括号发生冲突吧…简写的实际工作原理其实真的就只是简单的对应的方法的简写,没有什么惊喜。
+
+但是还是有惊喜的..那就是使用类似的一套方法,可以做到对于我们自己的类,也可以使用下标来访问。而为了达到这样的目的,我们需要实现以下方法,
+
+对于类似数组的结构:
+
+```objc
+- (elementType)objectAtIndexedSubscript:(indexType)idx;
+- (void)setObject:(elementType)object atIndexedSubscript:(indexType)idx;
+```
+
+
+对于类似字典的结构:
+```objc
+- (elementType)objectForKeyedSubscript:(keyType)key;
+- (void)setObject:(elementType)object forKeyedSubscript:(keyType)key;
+```
+
+* * *
+
+### 固定桥接
+
+对于ARC来说,最让人迷惑和容易出错的地方大概就是桥接的概念。由于历史原因,CF对象和NSObject对象的转换一直存在一些微妙的关系,而在引入ARC之后,这些关系变得复杂起来:主要是要明确到底应该是由CF还是由NSObject来负责内存管理的问题(关于ARC和更详细的说明,可以参看我之前写的一篇[ARC入门教程](http://www.onevcat.com/2012/06/arc-hand-by-hand/ "手把手教你ARC——ARC入门和使用"))。
+
+在Xcode4.4之后,之前区分到底谁拥有对象的工作可以模糊化了。在代码块区间加上CF_IMPLICIT_BRIDGING_ENABLED和CF_IMPLICIT_BRIDGING_DISABLED,在之前的桥接转换就都可以简单地写作CF和NS之间的强制转换,而不再需要加上__bridging的关键字了。谁来管这块内存呢?交给系统去头疼吧~
+
+* * *
+
+Objective-C确实是一门正在高速变化的语言。一方面,它的动态特性和small talk的烙印深深不去,另一方面,它又正积极朝着各种简单语言的语法方向靠近。各类的自动化处理虽然有些让人不放心,但是事实证明了它们工作良好,而且也确实为开发者节省了时间。尽快努力去拥抱新的变化吧~
diff --git a/_posts/2012-06-30-introducing-collection-views.markdown b/_posts/2012-06-30-introducing-collection-views.markdown
new file mode 100644
index 00000000..08047258
--- /dev/null
+++ b/_posts/2012-06-30-introducing-collection-views.markdown
@@ -0,0 +1,177 @@
+---
+layout: post
+title: WWDC 2012 Session笔记——205 Introducing Collection Views
+date: 2012-06-30 23:41:15.000000000 +09:00
+tags: 能工巧匠集
+---
+
+这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看[这里](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)。如果您是首次来到本站,也许您会有兴趣通过[RSS](http://onevcat.com/atom.xml),或者通过页面左侧的邮件订阅的方式订阅本站。
+
+在之前的[iOS6 SDK新特性前瞻](http://www.onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)中我曾经提到过UICollectionView,当时只把CollectionView当作是一个现在已有的开源GridView,仔细研究了下WWDC2012相关的Session后发现并不是那么简单。Apple这次真的给广大开发者带来了一个非常powerful的view,其强大程度可以说远超UITableView。接下来的这篇笔记将对应Session 205,作为使用UICollectionView的入门,之后还将完成一篇关于深入使用UICollectionView以及相应的Layout的笔记。
+
+废话到此,正式开始。
+
+### 什么是UICollectionView
+
+UICollectionView是一种新的数据展示方式,简单来说可以把他理解成多列的UITableView(请一定注意这是UICollectionView的最最简单的形式)。如果你用过iBooks的话,可能你还对书架布局有一定印象:一个虚拟书架上放着你下载和购买的各类图书,整齐排列。其实这就是一个UICollectionView的表现形式,或者iPad的iOS6中的原生时钟应用中的各个时钟,也是UICollectionView的最简单的一个布局,如图:
+
+
+最简单的UICollectionView就是一个GridView,可以以多列的方式将数据进行展示。标准的UICollectionView包含三个部分,它们都是UIView的子类:
+
+
+* Cells 用于展示内容的主体,对于不同的cell可以指定不同尺寸和不同的内容,这个稍后再说
+* Supplementary Views 追加视图 如果你对UITableView比较熟悉的话,可以理解为每个Section的Header或者Footer,用来标记每个section的view
+* Decoration Views 装饰视图 这是每个section的背景,比如iBooks中的书架就是这个
+
+
+
+
+不管一个UICollectionView的布局如何变化,这三个部件都是存在的。再次说明,复杂的UICollectionView绝不止上面的几幅图,关于较复杂的布局和相应的特性,我会在本文稍后和[下一篇笔记](http://www.onevcat.com/2012/08/advanced-collection-view/")中进行一些深入。
+
+* * *
+
+### 实现一个简单的UICollectionView
+
+先从最简单的开始,UITableView是iOS开发中的非常非常非常重要的一个类,相信如果你是开发者的话应该是对这个类非常熟悉了。实现一个UICollectionView和实现一个UITableView基本没有什么大区别,它们都同样是datasource和delegate设计模式的:datasource为view提供数据源,告诉view要显示些什么东西以及如何显示它们,delegate提供一些样式的小细节以及用户交互的相应。因此在本节里会大量对比collection view和table view来进行说明,如果您还不太熟悉table view的话,也是个对照着复习的好机会。
+
+#### UICollectionViewDataSource
+
+* section的数量 -numberOfSectionsInCollection:
+* 某个section里有多少个item -collectionView:numberOfItemsInSection:
+* 对于某个位置应该显示什么样的cell -collectionView:cellForItemAtIndexPath:
+
+实现以上三个委托方法,基本上就可以保证CollectionView工作正常了。当然,还有提供Supplementary View的方法
+
+* collectionView:viewForSupplementaryElementOfKind:atIndexPath:
+
+对于Decoration Views,提供方法并不在UICollectionViewDataSource中,而是直接UICollectionViewLayout类中的(因为它仅仅是视图相关,而与数据无关),放到稍后再说。
+
+#### 关于重用
+
+为了得到高效的View,对于cell的重用是必须的,避免了不断生成和销毁对象的操作,这与在UITableView中的情况是一致的。但值得注意的时,在UICollectionView中,不仅cell可以重用,Supplementary View和Decoration View也是可以并且应当被重用的。在iOS5中,Apple对UITableView的重用做了简化,以往要写类似这样的代码:
+
+```objc
+UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MY_CELL_ID"];
+if (!cell) { //如果没有可重用的cell,那么生成一个
+ cell = [[UITableViewCell alloc] init];
+}
+//配置cell,blablabla
+return cell
+```
+
+而如果我们在TableView向数据源请求数据之前使用`-registerNib:forCellReuseIdentifier:`方法为@“MY_CELL_ID"注册过nib的话,就可以省下每次判断并初始化cell的代码,要是在重用队列里没有可用的cell的话,runtime将自动帮我们生成并初始化一个可用的cell。
+
+这个特性很受欢迎,因此在UICollectionView中Apple继承使用了这个特性,并且把其进行了一些扩展。使用以下方法进行注册:
+
+* -registerClass:forCellWithReuseIdentifier:
+* -registerClass:forSupplementaryViewOfKind:withReuseIdentifier:
+* -registerNib:forCellWithReuseIdentifier:
+* -registerNib:forSupplementaryViewOfKind:withReuseIdentifier:
+
+相比UITableView有两个主要变化:一是加入了对某个Class的注册,这样即使不用提供nib而是用代码生成的view也可以被接受为cell了;二是不仅只是cell,Supplementary View也可以用注册的方法绑定初始化了。在对collection view的重用ID注册后,就可以像UITableView那样简单的写cell配置了:
+
+```objc
+- (UICollectionView*)collectionView:(UICollectionView*)cv cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ MyCell *cell = [cv dequeueReusableCellWithReuseIdentifier:@”MY_CELL_ID”];
+ // Configure the cell's content
+ cell.imageView.image = ...
+ return cell;
+}
+```
+
+需要吐槽的是,对collection view,取重用队列的方法的名字和UITableView里面不一样了,在Identifier前面多加了Reuse五个字母,语义上要比以前清晰,命名规则也比以前严谨了..不知道Apple会不会为了追求完美而把UITableView中的命名不那么好的方法deprecate掉。
+
+#### UICollectionViewDelegate
+
+数据无关的view的外形啊,用户交互啊什么的,由UICollectionViewDelegate来负责:
+
+* cell的高亮
+* cell的选中状态
+* 可以支持长按后的菜单
+
+关于用户交互,UICollectionView也做了改进。每个cell现在有独立的高亮事件和选中事件的delegate,用户点击cell的时候,现在会按照以下流程向delegate进行询问:
+
+1. -collectionView:shouldHighlightItemAtIndexPath: 是否应该高亮?
+2. -collectionView:didHighlightItemAtIndexPath: 如果1回答为是,那么高亮
+3. -collectionView:shouldSelectItemAtIndexPath: 无论1结果如何,都询问是否可以被选中?
+4. -collectionView:didUnhighlightItemAtIndexPath: 如果1回答为是,那么现在取消高亮
+5. -collectionView:didSelectItemAtIndexPath: 如果3回答为是,那么选中cell
+
+状态控制要比以前灵活一些,对应的高亮和选中状态分别由highlighted和selected两个属性表示。
+
+#### 关于Cell
+
+相对于UITableViewCell来说,UICollectionViewCell没有这么多花头。首先UICollectionViewCell不存在各式各样的默认的style,这主要是由于展示对象的性质决定的,因为UICollectionView所用来展示的对象相比UITableView来说要来得灵活,大部分情况下更偏向于图像而非文字,因此需求将会千奇百怪。因此SDK提供给我们的默认的UICollectionViewCell结构上相对比较简单,由下至上:
+
+* 首先是cell本身作为容器view
+* 然后是一个大小自动适应整个cell的backgroundView,用作cell平时的背景
+* 再其上是selectedBackgroundView,是cell被选中时的背景
+* 最后是一个contentView,自定义内容应被加在这个view上
+
+这次Apple给我们带来的好康是被选中cell的自动变化,所有的cell中的子view,也包括contentView中的子view,在当cell被选中时,会自动去查找view是否有被选中状态下的改变。比如在contentView里加了一个normal和selected指定了不同图片的imageView,那么选中这个cell的同时这张图片也会从normal变成selected,而不需要额外的任何代码。
+
+#### UICollectionViewLayout
+
+终于到UICollectionView的精髓了…这也是UICollectionView和UITableView最大的不同。UICollectionViewLayout可以说是UICollectionView的大脑和中枢,它负责了将各个cell、Supplementary View和Decoration Views进行组织,为它们设定各自的属性,包括但不限于:
+
+* 位置
+* 尺寸
+* 透明度
+* 层级关系
+* 形状
+* 等等等等…
+*
+Layout决定了UICollectionView是如何显示在界面上的。在展示之前,一般需要生成合适的UICollectionViewLayout子类对象,并将其赋予CollectionView的collectionViewLayout属性。关于详细的自定义UICollectionViewLayout和一些细节,我将写在之后一篇笔记中。
+
+Apple为我们提供了一个最简单可能也是最常用的默认layout对象,UICollectionViewFlowLayout。Flow Layout简单说是一个直线对齐的layout,最常见的Grid View形式即为一种Flow Layout配置。上面的照片架界面就是一个典型的Flow Layout。
+
+* 首先一个重要的属性是itemSize,它定义了每一个item的大小。通过设定itemSize可以全局地改变所有cell的尺寸,如果想要对某个cell制定尺寸,可以使用-collectionView:layout:sizeForItemAtIndexPath:方法。
+* 间隔 可以指定item之间的间隔和每一行之间的间隔,和size类似,有全局属性,也可以对每一个item和每一个section做出设定:
+
+ * @property (CGSize) minimumInteritemSpacing
+ * @property (CGSize) minimumLineSpacing
+ * -collectionView:layout:minimumInteritemSpacingForSectionAtIndex:
+ * -collectionView:layout:minimumLineSpacingForSectionAtIndex:
+
+* 滚动方向 由属性scrollDirection确定scroll view的方向,将影响Flow Layout的基本方向和由header及footer确定的section之间的宽度
+
+ * UICollectionViewScrollDirectionVertical
+ * UICollectionViewScrollDirectionHorizontal
+
+* Header和Footer尺寸 同样地分为全局和部分。需要注意根据滚动方向不同,header和footer的高和宽中只有一个会起作用。垂直滚动时section间宽度为该尺寸的高,而水平滚动时为宽度起作用,如图。
+
+ * @property (CGSize) headerReferenceSize
+ * @property (CGSize) footerReferenceSize
+ * -collectionView:layout:referenceSizeForHeaderInSection:
+ * -collectionView:layout:referenceSizeForFooterInSection:
+
+* 缩进
+
+ * @property UIEdgeInsets sectionInset;
+ * -collectionView:layout:insetForSectionAtIndex:
+
+#### 总结
+
+一个UICollectionView的实现包括两个必要部分:UICollectionViewDataSource和UICollectionViewLayout,和一个交互部分:UICollectionViewDelegate。而Apple给出的UICollectionViewFlowLayout已经是一个很强力的layout方案了。
+
+* * *
+
+### 几个自定义的Layout
+
+但是光是UICollectionViewFlowLayout的话,显然是不够用的,而且如果单单是这样的话,就和现有的开源各类Grid View没有区别了…UICollectionView的强大之处,就在于各种layout的自定义实现,以及它们之间的切换。先看几个相当exiciting的例子吧~
+
+比如,堆叠布局:
+
+
+
+圆形布局:
+
+
+
+和Cover Flow布局:
+
+
+
+所有这些布局都采用了同样的数据源和委托方法,因此完全实现了model和view的解耦。但是如果仅这样,那开源社区也已经有很多相应的解决方案了。Apple的强大和开源社区不能比拟的地方在于对SDK的全局掌控,CollectionView提供了非常简单的API可以令开发者只需要一次简单调用,就可以使用CoreAnimation在不同的layout之间进行动画切换,这种切换必定将大幅增加用户体验,代价只是几十行代码就能完成的布局实现,以及简单的一句API调用,不得不说现在所有的开源代码与之相比,都是相形见拙了…不得不佩服和感谢UIKit团队的努力。
+
+关于上面几种自定义Layout和实现细节,和其他高级CollectionView应用,将在[下一篇笔记](http://www.onevcat.com/2012/08/advanced-collection-view/ "WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts")中进行详细说明~
diff --git a/_posts/2012-07-25-pomodoro-do.markdown b/_posts/2012-07-25-pomodoro-do.markdown
new file mode 100644
index 00000000..40025690
--- /dev/null
+++ b/_posts/2012-07-25-pomodoro-do.markdown
@@ -0,0 +1,87 @@
+---
+layout: post
+title: Pomodoro Do - 拖延症患者的福音
+date: 2012-07-25 23:50:56.000000000 +09:00
+tags: 南箕北斗集
+---
+
+
+
+由于完全是自己完成的应用啦,所以详细介绍就写的偏向广告一点吧~欢迎大家购买使用,并给我提意见哦~我会不断完善这款app的。
+
+* App Store地址:[https://itunes.apple.com/app/id791903475?ls=1&mt=8](https://itunes.apple.com/app/id791903475?ls=1&mt=8)
+* Pomodoro Do官方主页:[http://pomo.onevcat.com/](http://pomo.onevcat.com/)
+* i果儿评测:[Pomodoro Do——拖延症什么的,我才不怕呢](http://www.iguor.com/4050.html)
+* PunApp:[用一顆番茄來改變你的人生 – Pomodoro Do 評測](http://punapp.com/review/article/7437)
+
+
+## 什么是Pomodoro Do
+
+一款新鲜上架的番茄工作法辅助应用,功能上十分齐全,从自定义时间到历史统计和推送都很完整。这款应用加入了成就系统和箴言系统的创新,让用户自然地养成使用番茄工作法的习惯,从而提高效率。有拖延症和想提高效率的读者可以试试看这款应用。
+
+拖延症是现在颇为流行的一个说法,人们在习惯网络带来的便利同时也容易被网络分散太多的时间,相信大家多少都有点拖延症患者的感觉,每天工作开始总要那么一些时间浏览一下网页、收收邮件才能进入学习或者工作状态,或者工作到一半不知不觉地就开始刷刷微博,时间不知不觉就过去了,但是手上的事儿却远没做完。这款应用对于那些希望告别拖延症的用户来说正是瞌睡送来的枕头,只要您有一点决心,就可以显著改善拖延症的状况。
+
+我们先来简单了解一下番茄工作法:所谓番茄工作法,就是设定一个任务,将番茄时间设为25分钟,专注工作,中途不允许做任何与该任务无关的事,直到番茄时钟响起,短暂休息一下(5分钟)完成一个番茄时段,每4个番茄时段有一个长休息。通过番茄工作法可以有效提升集中力和注意力。不太了解的朋友对于番茄工作法的详细介绍可以[百度一下](http://baike.baidu.com/view/5259318.htm),E文好的朋友可以直接看看[番茄工作法的官方网站](http://www.pomodorotechnique.com/)哦。
+
+
+## 详细介绍<
+### 主界面
+
+应用打开后就直接是主界面。主界面十分简洁美观,深灰色的主题体现了稳重大方。上方为番茄计时框,下方为今天的历史记录,计时框里的小喇叭可以快速开关声音。
+
+
+
+### 新建
+
+一个番茄任务。点击右上角的“+”号,应用切换到了任务设定的界面,可以设定任务时长,任务名称,选择向社交网络共享,文本框里会随机出现励志箴言,也可以自己进行编辑。设置完成以后,一个番茄时间就开始计时了。
+
+
+
+### 推送提醒
+
+将手机放在一边专注于预设的任务,待一个番茄时间结束时,PomodoroDo会将手机从睡眠中摇醒,用推送信息告诉你,你完成了一个番茄。
+
+
+
+### 打断和箴言
+
+在做一个番茄任务的时候,有事情打扰,我们点击右上角的“X”,可以根据具体情况选择暂停或者打断。如果可以很快回到番茄任务来,选择暂停,有15秒的时间处理问题,如果是费时的紧急事件,就只能选择打断,放弃这个任务了。未能完成的任务也会出现在主界面的历史记录里。回顾一天的番茄任务完成情况,会对这一天的工作情况有个直观的了解,随附的励志箴言也让人充满斗志。
+
+
+
+### 统计功能
+
+回到主界面,我们试着按一下左上角的按钮,出现了菜单界面。其中的任务和历史分别根据任务内容和日期对您做过的番茄任务进行统计。这是这款番茄工作法应用的一大亮点,可以方便用户适时对自己一段时间以来的任务情况做一个总结和调整。
+
+
+
+### 成就系统
+我们看到,菜单中还有成就系统,在时间管理应用中添加成就系统无疑提高了应用的趣味性,在努力实现新成就的同时,您又向高效管理自己的时间迈近了一步。
+
+
+
+### 灵活设置
+设定菜单中可以设置默认番茄时间、音效开关和社交网络管理。另外比较值得注意的是可以选择箴言的语言,目前该应用支持中文、英文和日文。对于正在学习英语和日语的用户来说,换换箴言的语言类型会有意外的收获呢。
+
+
+
+
+## 总结
+
+AppStore上也有不少番茄工作法的应用。相比起来,Pomodoro Do的话,在使用习惯上可能更适合大家一些,另外功能上也非常齐全。
另外相比起来,这款番茄计时器的界面简洁细腻一些,操作流畅,界面可滑动切换和操作,是一款精致的应用。利用番茄工作法,减少对时间的焦虑,使用户快速进入注意力高度集中的状态,利用适当的工作-休息周期,提高一天的工作和学习效率。最后抄一下AppStore中自己总结的应用特点:
+
+1. 不拘泥于番茄工作法推荐的25分钟-5分钟,灵活的番茄和休息时间,根据个人特点指定效率计划;
+2. 加入后台提醒,开始番茄后立即开始工作。达到预定时间后将提醒您进行下一阶段;
+3. 社交网络分享,自我激励,让朋友帮助您提高效率,让相关人士了解您的工作进展和专注;
+4. 每个番茄都对应一句箴言,内心平静方可成就大业;
+5. 丰富的成就系统,照目标提高效率,帮助您建立良好习惯;
+6. 完整的番茄记录,帮助您总结和回顾您的效率情况。
+
+对于有提高自己效率和减少时间浪费的读者,可以推荐尝试一下~
简单的任务设计和使用方法,在iPhone上实践番茄工作法,就能快速提高自己的工作和学习效率。
+
+---
+
+* App Store地址:[https://itunes.apple.com/app/id791903475?ls=1&mt=8](https://itunes.apple.com/app/id791903475?ls=1&mt=8)
+* Pomodoro Do官方主页:[http://pomo.onevcat.com/](http://pomo.onevcat.com/)
+* i果儿评测:[Pomodoro Do——拖延症什么的,我才不怕呢](http://www.iguor.com/4050.html)
+* PunApp:[用一顆番茄來改變你的人生 – Pomodoro Do 評測](http://punapp.com/review/article/7437)
diff --git a/_posts/2012-08-10-not-a-studen.markdown b/_posts/2012-08-10-not-a-studen.markdown
new file mode 100644
index 00000000..6e9418c0
--- /dev/null
+++ b/_posts/2012-08-10-not-a-studen.markdown
@@ -0,0 +1,57 @@
+---
+layout: post
+title: 学生时代的终焉
+date: 2012-08-10 23:53:51.000000000 +09:00
+tags: 胡言乱语集
+---
+
+距离研究生毕业,已经过去一个月了。在毕业季的离愁和从学生身份的转变的怅惘渐渐淡去时,大概是时候对我的整个的大学生涯做一个小结了。很多事情的记忆已经在时间的冲蚀中变得模糊了,但是也有一些事情比其他的琐事更深地印刻在了记忆之中,也许简单的梳理和回忆,无法把这七年刻画的细致入微,但是作为轮廓的勾勒和回顾,却已然绰绰有余了。(写完之后终于发现又被写成标准流水账了,这个从小学开始的写作文的毛病在不写博客两个月之后再次复发了哎…)
+
+### 悲剧的开始
+
+大学本科加上研究生,七年时间,说长不长,说短却也不短。在经历了中学时代的辉煌之后,我终于还是在大学里找到了自己真正的位置。如果大学不在清华,如果不在清华里可谓最变态的电子系,如果不在清华里可谓最变态的电子系中最变态的班的话,也许我的大学生活会完全不一样吧。
+
+这里有在央视热门节目露过脸的高考状元,
+
+这里有“百度一下”能检出几万条结果的全省第一,
+
+这里有奥数好几块金牌的超级达人,
+
+这里有中学带了五个社团还能考到第一的变态,
+
+这里有之后叱咤清华被老师们认可为几十年难遇的特奖得主和学生会主席。
+
+当然,这里还有我,一个基本是以倒数一二的成绩分到这个班的可怜的差生。虽然说来之前已经被打过预防针,但是我真的不知道,这七年,会这么开始。
+
+当每个人都在叫嚷着自己不会啊考试要挂了啊的时候,我还暗自得意过自己貌似这些题都还算能做一做。最后结果出来发现自己在为自己的80分沾沾自喜的时候,周围基本都是一片95+,那种"你是一个傻逼"的打击对于那时的我来说着实不轻。对于从一个教育相对落后的地区出来的学生来说,这可能是很正常很普遍的现象,但是从遥遥领先到远远落后,这样的落差,一时间确实难以接受。不过几次下来,麻木之后,也就对自己所处的位置心安理得了。当时为自己找到的借口是这些东西别人高中里都学过或者接触过,而自己高中时一不搞竞赛二也从来没自己私下刻苦学习过,所以一开始起点就落后很多了。学习这个东西,就像F1赛车一样,发车的时候落后,之后想赶上的话,要付出的代价可要大得多,所以得过且过了…
+
+### 一点点改变
+
+还好之后不久便觉得这样的想法实在很可恶…大二的挂科犹如当头棒喝。本科有整整四年的时间,这比初中的三年和高中的三年都要多,而且这四年时间中真正属于自己的时间很多很多。想要努力学习的话,不一定能在这里出类拔萃,但是有所斩获却是毋庸置疑。有句话很好,上帝给每个人都基本公平地发了时间这种万能货币,而一个人,想要怎么样的生活,想要成为怎样的人,与他把时间这种货币用来换了什么有莫大关系。大一和大二的公共课程和电子通讯方向的学习让我感到十分疲惫,我感到的是时间的浪费。而这时正好有机会在专业上进行一个细分,可以选择继续电子或者转为微电子方向。
+其实这个时候的境遇和我高一结束后文理分班和校区迁移那时候很像。高一的时候的状况真是糟糕透顶,每天上学放学路上疲惫不堪,加上进入高中时相似的心理落差(当然没有从高中到大学差距这么大),让我几乎无法专于学习了。当时也正好遇上了分班和校址迁移,让我有机会得到喘息,从而有了一个新的开始。我当然希望这次也能有同样的效果,于是毫不犹豫的选择了转到新的专业去。
+事实证明了这是一个明智的选择。我也许真的是那种喜欢去适应,喜欢去改变的人吧。有时候奶酪被拿走了,总会喜欢去寻找更新鲜的奶酪,也许是自己潜意识中的那只嗅嗅,在不断指引着我吧(笑)。总之,在微电的这段时光还是很快乐的。在这里虽然在绩点上也没什么了不起的突破,但是却在感情上找到了归宿。其实这么说来,到现在为止,我在自己人生的每个阶段,都很好的完成了我的任务:小学初中是快乐地生活成长,高中考到一个很好的大学,大学时找到很好的伴侣。虽然种种不顺,但是看起来却是不折不扣的成功呐…
+
+### 继续努力
+
+在好不容易真正习惯了清华的生活的时候,本科也快毕业了。靠着本科后两年拉回来的绩点,在本科最后踩着线随大流保了研。那时候真的没有想过继续深造和工作哪个好,也不太明白读研意味着什么。只是盲目地从众,而等我真正明白的时候,硕士都已经快毕业了。
+
+其实硕士期间我是很幸运的,因为遇到了一位真的非常非常非常开明的导师。对比起很多其他同学的导师,我的导师几乎具备了一切优点:发钱多,派活少,不push,除了不太请我们吃饭以外,已经和忘年交的朋友差不多了。所以在硕士阶段,属于自己的时间也有很多,也正是以此为条件,我有机会仔细思考我真正想要的和喜欢的东西是什么。
+
+首先,肯定不是研究。一看论文就犯困,一做试验就想逃,这些特质决定了必然不会是一个好的科研人员。我一直认为很多科学研究是毫无意义并且对这个世界是不会有任何改变的(特别是在中国,对然这句话肯定是错的,不过这就是我的想法)。国内的科研环境,就我所看到的号称中国最好的大学之一来说,也满满充斥着拉关系跑经费,报批各种各样的项目,面临无穷无尽的审计,大家真正忙的一切,都和科研本身没什么关系,而最后往往就靠几个真正还不那么讨厌科研的学生的寒碜的所谓“成果”来应付课题最终检查。在这方面,我完全没有入门,也并不是太了解真正的科研的感觉应该是怎么样的。但是在这里,我体会到的是一种低效和浑噩,从真心里,我不喜欢这样的生活。
+
+为了尽量不在科研上花过多的时间,我选了一个非常奇葩的研究方向,做着前人从未做过的试验。因为课题很新,和研究组里所有人的课题都基本没有交集,导师也对新的方法表示闻所未闻。于是我几乎失去了所有的来自研究组的指导和支持,独自一人在黑暗中摸索。但是好处是,我做的试验没有其他人做过,因此我的结果也就没有人能够给出权威的评判,因为在这个领域其实我就是权威。那种感觉,真心不错。
+
+但是这样做的目的,其实是解放自己的时间。不再被无数的试验束缚的同时,我开始尝试走向高效,去做一些自己喜欢做的事情。其实,每个人在青春的时候都应该有那么一段奋斗的历史,这样才不至于在老去后回首时发现一片苍白。拥有狂热的兴趣爱好也罢,全身心地投入某件事情也罢,都会在十几年甚至几十年后成为一段非常美好的回忆。努力过做过,在这个世界上留下一些什么东西,能够时不时被人想起,有时候,存在感和被认同感,还是十分重要的。
+
+### 新的开始
+
+找工作的那段时间还是相当郁闷的。虽说好歹算是名校毕业,但是一样四处碰壁。首先我很个性的做了一份比较非主流的简历,这直接导致了所有的正统企业都把我拒之门外(其实应该是我把他们拒之门外吧,233);接着,投出去的一些简历直接没有了回音,估计是没见过清华的学生去投他们,觉得是在调戏?但是我真的没有乱投简历啊,给了简历的企业都是我真的想去的地方啊;最后,给了笔试的企业的各种笔试基本都没通过,各种请你写出XX算法,写你妹啊我木有学过啊有木有..而且在我做了这些项目以后我就觉得算法什么的就是扯淡啊有木有,你招的是码农啊,又不是计算机科学家,你要的那些算法google一下不就完事儿了。
+
+于是,我好像是被所有的中国企业抛弃了,或者好听一点的话,是中国这些企业都和自己相性不符吧,真的,没有任何一家中国企业愿意给我offer,无奈最后只能去日本了。
+
+然后,顺利的毕业了。不过毕业前的那段日子还是相当难熬的,每天白天在实验室待一整天,做试验整理数据攒论文不亦乐乎,晚上到兼职的地方作项目有时候deadline前忙到夜里两三点的时候也有,周末两天为了之后的工作还要到北语上课。如此高强度的无休生活如果可能一个月两个月的话还好,再长的话可能真的要崩溃掉。幸运的是我没感到什么太大压力就撑下来了,可能以后遇到什么时间上的压力的话,想想这段狗一般的经历就能平静许多了吧…
+
+离开清华的一个月里,都在北语混迹。每天过着标准的学生生活,上课,食堂,宿舍。只不过上课由模电课、数电课、工艺课变成了日语课、日语课、日语课,食堂由麻辣烫、煎鸡饭、铁板烧变成了超市鸡、超市鸡、超市鸡,宿舍从逛论坛、打魔兽、侃大山变成了写代码、写代码、写代码。不过还好这种生活也就还有两周就结束了,再之后是回家,好好待上一个月。这应该是我最后一个这么长的假期了吧,之后的新的生活,应该会很忙碌。
+大学的生活,很值得回忆。不管以后怎样NB的我们,可能永远都忘不了这段SB的日子。如果用一句话总结这七年,那不妨抄一句游戏台词:虽有遗憾,却无后悔。
+
+流水账结束。今后,祝自己天天开心,愿自己继续加油。
diff --git a/_posts/2012-08-28-advanced-collection-view.markdown b/_posts/2012-08-28-advanced-collection-view.markdown
new file mode 100644
index 00000000..15ba01ae
--- /dev/null
+++ b/_posts/2012-08-28-advanced-collection-view.markdown
@@ -0,0 +1,299 @@
+---
+layout: post
+title: WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts
+date: 2012-08-28 23:55:59.000000000 +09:00
+tags: 能工巧匠集
+---
+
+[](http://www.onevcat.com/wp-content/uploads/2012/08/QQ20120828-3.png)
+
+这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看[这里](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)。如果您是首次来到本站,也许您会有兴趣通过[RSS](http://onevcat.com/atom.xml),或者通过页面左侧的邮件订阅的方式订阅本站。
+
+在上一篇[UICollectionView的入门介绍](http://www.onevcat.com/2012/06/introducing-collection-views/)中,大概地对iOS6新加入的强大的UICollectionView进行了一些说明。在这篇博文中,将结合WWDC2012 Session219:Advanced Collection View的内容,对Collection View进行一个深入的使用探讨,并给出一个自定义的Demo。
+
+## UICollectionView的结构回顾
+
+首先回顾一下Collection View的构成,我们能看到的有三个部分:
+
+* Cells
+* Supplementary Views 追加视图 (类似Header或者Footer)
+* Decoration Views 装饰视图 (用作背景展示)
+
+而在表面下,由两个方面对UICollectionView进行支持。其中之一和tableView一样,即提供数据的UICollectionViewDataSource以及处理用户交互的UICollectionViewDelegate。另一方面,对于cell的样式和组织方式,由于collectionView比tableView要复杂得多,因此没有按照类似于tableView的style的方式来定义,而是专门使用了一个类来对collectionView的布局和行为进行描述,这就是UICollectionViewLayout。
+
+这次的笔记将把重点放在UICollectionViewLayout上,因为这不仅是collectionView和tableView的最重要求的区别,也是整个UICollectionView的精髓所在。
+
+如果对UICollectionView的基本构成要素和使用方法还不清楚的话,可以移步到我之前的一篇笔记:[Session笔记——205 Introducing Collection Views](http://www.onevcat.com/2012/06/introducing-collection-views/)中进行一些了解。
+
+* * *
+
+## UICollectionViewLayoutAttributes
+
+UICollectionViewLayoutAttributes是一个非常重要的类,先来看看property列表:
+
+* @property (nonatomic) CGRect frame
+* @property (nonatomic) CGPoint center
+* @property (nonatomic) CGSize size
+* @property (nonatomic) CATransform3D transform3D
+* @property (nonatomic) CGFloat alpha
+* @property (nonatomic) NSInteger zIndex
+* @property (nonatomic, getter=isHidden) BOOL hidden
+
+可以看到,UICollectionViewLayoutAttributes的实例中包含了诸如边框,中心点,大小,形状,透明度,层次关系和是否隐藏等信息。和DataSource的行为十分类似,当UICollectionView在获取布局时将针对每一个indexPath的部件(包括cell,追加视图和装饰视图),向其上的UICollectionViewLayout实例询问该部件的布局信息(在这个层面上说的话,实现一个UICollectionViewLayout的时候,其实很像是zap一个delegate,之后的例子中会很明显地看出),这个布局信息,就以UICollectionViewLayoutAttributes的实例的方式给出。
+
+* * *
+
+## 自定义的UICollectionViewLayout
+
+UICollectionViewLayout的功能为向UICollectionView提供布局信息,不仅包括cell的布局信息,也包括追加视图和装饰视图的布局信息。实现一个自定义layout的常规做法是继承UICollectionViewLayout类,然后重载下列方法:
+
+* -(CGSize)collectionViewContentSize
+
+ * 返回collectionView的内容的尺寸
+
+* -(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
+
+ * 返回rect中的所有的元素的布局属性
+ * 返回的是包含UICollectionViewLayoutAttributes的NSArray
+ * UICollectionViewLayoutAttributes可以是cell,追加视图或装饰视图的信息,通过不同的UICollectionViewLayoutAttributes初始化方法可以得到不同类型的UICollectionViewLayoutAttributes:
+
+ * layoutAttributesForCellWithIndexPath:
+ * layoutAttributesForSupplementaryViewOfKind:withIndexPath:
+ * layoutAttributesForDecorationViewOfKind:withIndexPath:
+
+* -(UICollectionViewLayoutAttributes _)layoutAttributesForItemAtIndexPath:(NSIndexPath _)indexPath
+
+ * 返回对应于indexPath的位置的cell的布局属性
+
+* -(UICollectionViewLayoutAttributes _)layoutAttributesForSupplementaryViewOfKind:(NSString _)kind atIndexPath:(NSIndexPath *)indexPath
+
+ * 返回对应于indexPath的位置的追加视图的布局属性,如果没有追加视图可不重载
+
+* -(UICollectionViewLayoutAttributes * )layoutAttributesForDecorationViewOfKind:(NSString_)decorationViewKind atIndexPath:(NSIndexPath _)indexPath
+
+ * 返回对应于indexPath的位置的装饰视图的布局属性,如果没有装饰视图可不重载
+
+* -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds
+
+ * 当边界发生改变时,是否应该刷新布局。如果YES则在边界变化(一般是scroll到其他地方)时,将重新计算需要的布局信息。
+
+另外需要了解的是,在初始化一个UICollectionViewLayout实例后,会有一系列准备方法被自动调用,以保证layout实例的正确。
+
+首先,-(void)prepareLayout将被调用,默认下该方法什么没做,但是在自己的子类实现中,一般在该方法中设定一些必要的layout的结构和初始需要的参数等。
+
+之后,-(CGSize) collectionViewContentSize将被调用,以确定collection应该占据的尺寸。注意这里的尺寸不是指可视部分的尺寸,而应该是所有内容所占的尺寸。collectionView的本质是一个scrollView,因此需要这个尺寸来配置滚动行为。
+
+接下来-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect被调用,这个没什么值得多说的。初始的layout的外观将由该方法返回的UICollectionViewLayoutAttributes来决定。
+
+另外,在需要更新layout时,需要给当前layout发送 -invalidateLayout,该消息会立即返回,并且预约在下一个loop的时候刷新当前layout,这一点和UIView的setNeedsLayout方法十分类似。在-invalidateLayout后的下一个collectionView的刷新loop中,又会从prepareLayout开始,依次再调用-collectionViewContentSize和-layoutAttributesForElementsInRect来生成更新后的布局。
+
+* * *
+
+## Demo
+
+说了那么多,其实还是Demo最能解决问题。Apple官方给了一个flow layout和一个circle layout的例子,都很经典,需要的同学可以从[这里下载](http://www.onevcat.com/wp-content/uploads/2012/08/advanced-collection-view-demo.zip)。
+
+### LineLayout——对于个别UICollectionViewLayoutAttributes的调整
+
+先看LineLayout,它继承了UICollectionViewFlowLayout这个Apple提供的基本的布局。它主要实现了单行布局,自动对齐到网格以及当前网格cell放大三个特性。如图:
+
+[](http://www.onevcat.com/wp-content/uploads/2012/08/QQ20120828-1-e1346145550225.png)
+
+先看LineLayout的init方法:
+
+```objc
+-(id)init
+{
+ self = [super init];
+ if (self) {
+ self.itemSize = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
+ self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
+ self.sectionInset = UIEdgeInsetsMake(200, 0.0, 200, 0.0);
+ self.minimumLineSpacing = 50.0;
+ }
+ return self;
+}
+```
+
+self.sectionInset = UIEdgeInsetsMake(200, 0.0, 200, 0.0); 确定了缩进,此处为上方和下方各缩进200个point。由于cell的size已经定义了为200x200,因此屏幕上在缩进后就只有一排item的空间了。
+
+self.minimumLineSpacing = 50.0; 这个定义了每个item在水平方向上的最小间距。
+
+UICollectionViewFlowLayout是Apple为我们准备的开袋即食的现成布局,因此之前提到的几个必须重载的方法中需要我们操心的很少,即使完全不重载它们,现在也可以得到一个不错的线状一行的gridview了。而我们的LineLayout通过重载父类方法后,可以实现一些新特性,比如这里的动对齐到网格以及当前网格cell放大。
+
+自动对齐到网格
+
+```objc
+- (CGPoint)targetContentOffsetForProposedContentOffset: (CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
+{
+ //proposedContentOffset是没有对齐到网格时本来应该停下的位置
+ CGFloat offsetAdjustment = MAXFLOAT;
+ CGFloat horizontalCenter = proposedContentOffset.x + (CGRectGetWidth(self.collectionView.bounds) / 2.0);
+ CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0, self.collectionView.bounds.size.width, self.collectionView.bounds.size.height);
+ NSArray* array = [super layoutAttributesForElementsInRect:targetRect];
+
+ //对当前屏幕中的UICollectionViewLayoutAttributes逐个与屏幕中心进行比较,找出最接近中心的一个
+ for (UICollectionViewLayoutAttributes* layoutAttributes in array) {
+ CGFloat itemHorizontalCenter = layoutAttributes.center.x;
+ if (ABS(itemHorizontalCenter - horizontalCenter) < ABS(offsetAdjustment)) {
+ offsetAdjustment = itemHorizontalCenter - horizontalCenter;
+ }
+ }
+ return CGPointMake(proposedContentOffset.x + offsetAdjustment, proposedContentOffset.y);
+}
+```
+
+当前item放大
+
+```objc
+-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect
+{
+ NSArray *array = [super layoutAttributesForElementsInRect:rect];
+ CGRect visibleRect;
+ visibleRect.origin = self.collectionView.contentOffset;
+ visibleRect.size = self.collectionView.bounds.size;
+
+ for (UICollectionViewLayoutAttributes* attributes in array) {
+ if (CGRectIntersectsRect(attributes.frame, rect)) {
+ CGFloat distance = CGRectGetMidX(visibleRect) - attributes.center.x;
+ CGFloat normalizedDistance = distance / ACTIVE_DISTANCE;
+ if (ABS(distance) < ACTIVE_DISTANCE) {
+ CGFloat zoom = 1 + ZOOM_FACTOR*(1 - ABS(normalizedDistance));
+ attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0);
+ attributes.zIndex = 1;
+ }
+ }
+ }
+ return array;
+}
+```
+
+对于个别UICollectionViewLayoutAttributes进行调整,以达到满足设计需求是UICollectionView使用中的一种思路。在根据位置提供不同layout属性的时候,需要记得让-shouldInvalidateLayoutForBoundsChange:返回YES,这样当边界改变的时候,-invalidateLayout会自动被发送,才能让layout得到刷新。
+
+### CircleLayout——完全自定义的Layout,添加删除item,以及手势识别
+
+CircleLayout的例子稍微复杂一些,cell分布在圆周上,点击cell的话会将其从collectionView中移出,点击空白处会加入一个cell,加入和移出都有动画效果。
+
+这放在以前的话估计够写一阵子了,而得益于UICollectionView,基本只需要100来行代码就可以搞定这一切,非常cheap。通过CircleLayout的实现,可以完整地看到自定义的layout的编写流程,非常具有学习和借鉴的意义。
+
+
+
+首先,布局准备中定义了一些之后计算所需要用到的参数。
+
+```objc
+-(void)prepareLayout
+{ //和init相似,必须call super的prepareLayout以保证初始化正确
+ [super prepareLayout];
+
+ CGSize size = self.collectionView.frame.size;
+ _cellCount = [[self collectionView] numberOfItemsInSection:0];
+ _center = CGPointMake(size.width / 2.0, size.height / 2.0);
+ _radius = MIN(size.width, size.height) / 2.5;
+}
+```
+
+其实对于一个size不变的collectionView来说,除了_cellCount之外的中心和半径的定义也可以扔到init里去做,但是显然在prepareLayout里做的话具有更大的灵活性。因为每次重新给出layout时都会调用prepareLayout,这样在以后如果有collectionView大小变化的需求时也可以自动适应变化。
+
+然后,按照UICollectionViewLayout子类的要求,重载了所需要的方法:
+
+```objc
+//整个collectionView的内容大小就是collectionView的大小(没有滚动)
+-(CGSize)collectionViewContentSize
+{
+ return [self collectionView].frame.size;
+}
+
+//通过所在的indexPath确定位置。
+- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
+{
+ UICollectionViewLayoutAttributes* attributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:path]; //生成空白的attributes对象,其中只记录了类型是cell以及对应的位置是indexPath
+ //配置attributes到圆周上
+ attributes.size = CGSizeMake(ITEM_SIZE, ITEM_SIZE);
+ attributes.center = CGPointMake(_center.x + _radius * cosf(2 * path.item * M_PI / _cellCount), _center.y + _radius * sinf(2 * path.item * M_PI / _cellCount));
+ return attributes;
+}
+
+//用来在一开始给出一套UICollectionViewLayoutAttributes
+-(NSArray*)layoutAttributesForElementsInRect:(CGRect)rect
+{
+ NSMutableArray* attributes = [NSMutableArray array];
+ for (NSInteger i=0 ; i < self.cellCount; i++) {
+ //这里利用了-layoutAttributesForItemAtIndexPath:来获取attributes
+ NSIndexPath* indexPath = [NSIndexPath indexPathForItem:i inSection:0];
+ [attributes addObject:[self layoutAttributesForItemAtIndexPath:indexPath]];
+ }
+ return attributes;
+}
+```
+
+现在已经得到了一个circle layout。为了实现cell的添加和删除,需要为collectionView加上手势识别,这个很简单,在ViewController中:
+
+```objc
+UITapGestureRecognizer* tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)];
+[self.collectionView addGestureRecognizer:tapRecognizer];
+```
+
+对应的处理方法handleTapGesture:为
+
+```objc
+- (void)handleTapGesture:(UITapGestureRecognizer *)sender {
+ if (sender.state == UIGestureRecognizerStateEnded) {
+ CGPoint initialPinchPoint = [sender locationInView:self.collectionView];
+ NSIndexPath* tappedCellPath = [self.collectionView indexPathForItemAtPoint:initialPinchPoint]; //获取点击处的cell的indexPath
+ if (tappedCellPath!=nil) { //点击处没有cell
+ self.cellCount = self.cellCount - 1;
+ [self.collectionView performBatchUpdates:^{
+ [self.collectionView deleteItemsAtIndexPaths:[NSArray arrayWithObject:tappedCellPath]];
+ } completion:nil];
+ } else {
+ self.cellCount = self.cellCount + 1;
+ [self.collectionView performBatchUpdates:^{
+ [self.collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForItem:0 inSection:0]]];
+ } completion:nil];
+ }
+ }
+}
+```
+
+performBatchUpdates:completion: 再次展示了block的强大的一面..这个方法可以用来对collectionView中的元素进行批量的插入,删除,移动等操作,同时将触发collectionView所对应的layout的对应的动画。相应的动画由layout中的下列四个方法来定义:
+
+* initialLayoutAttributesForAppearingItemAtIndexPath:
+* initialLayoutAttributesForAppearingDecorationElementOfKind:atIndexPath:
+* finalLayoutAttributesForDisappearingItemAtIndexPath:
+* finalLayoutAttributesForDisappearingDecorationElementOfKind:atIndexPath:
+
+> 更正:正式版中API发生了变化(而且不止一次变化
+> initialLayoutAttributesForInsertedItemAtIndexPath:在正式版中已经被废除。现在在insert或者delete之前,prepareForCollectionViewUpdates:会被调用,可以使用这个方法来完成添加/删除的布局。关于更多这方面的内容以及新的示例demo,可以参看[这篇博文](http://markpospesel.wordpress.com/2012/10/25/fixing-circlelayout/)(需要翻墙)。新的示例demo在Github上也有,[链接](https://github.com/mpospese/CircleLayout)
+
+在CircleLayout中,实现了cell的动画。
+
+```objc
+//插入前,cell在圆心位置,全透明
+- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForInsertedItemAtIndexPath:(NSIndexPath *)itemIndexPath
+{
+ UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
+ attributes.alpha = 0.0;
+ attributes.center = CGPointMake(_center.x, _center.y);
+ return attributes;
+}
+
+//删除时,cell在圆心位置,全透明,且只有原来的1/10大
+- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDeletedItemAtIndexPath:(NSIndexPath *)itemIndexPath
+{
+ UICollectionViewLayoutAttributes* attributes = [self layoutAttributesForItemAtIndexPath:itemIndexPath];
+ attributes.alpha = 0.0;
+ attributes.center = CGPointMake(_center.x, _center.y);
+ attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
+ return attributes;
+}
+```
+
+在插入或删除时,将分别以插入前和删除后的attributes和普通状态下的attributes为基准,进行UIView的动画过渡。而这一切并没有很多代码要写,几乎是free的,感谢苹果…
+
+* * *
+
+## 布局之间的切换
+
+有时候可能需要不同的布局,Apple也提供了方便的布局间切换的方法。直接更改collectionView的collectionViewLayout属性可以立即切换布局。而如果通过setCollectionViewLayout:animated:,则可以在切换布局的同时,使用动画来过渡。对于每一个cell,都将有对应的UIView动画进行对应,又是一个接近free的特性。
+
+对于我自己来说,UICollectionView可能是我转向iOS 6 SDK的最具有吸引力的特性之一,因为UIKit团队的努力和CoreAnimation的成熟,使得创建一个漂亮优雅的UI变的越来越简单了。可以断言说UICollectionView在今后的iOS开发中,一定会成为和UITableView一样的强大和最常用的类之一。在iOS 6还未正式上市前,先对其特性进行一些学习,以期尽快能使用新特性来简化开发流程,可以说是非常值得的。
diff --git a/_posts/2012-09-20-autoayout.markdown b/_posts/2012-09-20-autoayout.markdown
new file mode 100644
index 00000000..291dd748
--- /dev/null
+++ b/_posts/2012-09-20-autoayout.markdown
@@ -0,0 +1,191 @@
+---
+layout: post
+title: WWDC 2012 Session笔记——202, 228, 232 AutoLayout(自动布局)入门
+date: 2012-09-20 00:01:31.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这是博主的WWDC2012笔记系列中的一篇,完整的笔记列表可以参看[这里](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)。如果您是首次来到本站,也许您会有兴趣通过[RSS](http://onevcat.com/atom.xml),或者通过页面左侧的邮件订阅的方式订阅本站。
+
+AutoLayout在去年的WWDC上被引入Cocoa,而在今年的WWDC上,Apple不惜花费了三个Session的前所未见的篇幅来详细地向开发者讲解AutoLayout在iOS上的应用,是由起原因的:iPhone5的屏幕将变为4寸,开发者即将面临为不同尺寸屏幕进行应用适配的工作。Android平台开发中最令人诟病的适配工作的厄运现在似乎也将降临在iOS开发者的头上。基于这样的情况,Apple大力推广使用AutoLayout的方法来进行UI布局,以一举消除适配的烦恼。AutoLayout将是自Interface Builder和StoryBoard之后UI制作上又一次重要的变化,也必然是之后iOS开发的趋势,因此这个专题很值得学习。
+
+## AutoLayout是什么?
+
+使用一句Apple的官方定义的话
+
+> AutoLayout是一种基于约束的,描述性的布局系统。
+> Auto Layout Is a Constraint-Based, Descriptive Layout System.
+
+关键词:
+
+* 基于约束 - 和以往定义frame的位置和尺寸不同,AutoLayout的位置确定是以所谓相对位置的约束来定义的,比如_x坐标为superView的中心,y坐标为屏幕底部上方10像素_等
+* 描述性 - 约束的定义和各个view的关系使用接近自然语言或者可视化语言(稍后会提到)的方法来进行描述
+* 布局系统 - 即字面意思,用来负责界面的各个元素的位置。
+
+总而言之,AutoLayout为开发者提供了一种不同于传统对于UI元素位置指定的布局方法。以前,不论是在IB里拖放,还是在代码中写,每个UIView都会有自己的frame属性,来定义其在当前视图中的位置和尺寸。使用AutoLayout的话,就变为了使用约束条件来定义view的位置和尺寸。这样的**最大好处是一举解决了不同分辨率和屏幕尺寸下view的适配问题,另外也简化了旋转时view的位置的定义**,原来在底部之上10像素居中的view,不论在旋转屏幕或是更换设备(iPad或者iPhone5或者以后可能出现的mini iPad)的时候,始终还在底部之上10像素居中的位置,不会发生变化。
+
+总结
+
+> 使用约束条件来描述布局,view的frame会依据这些约束来进行计算
+> Describe the layout with constraints, and frames are calculated automatically.
+
+* * *
+
+## AutoLayout和Autoresizing Mask的区别
+
+Autoresizing Mask是我们的老朋友了…如果你以前一直是代码写UI的话,你肯定写过UIViewAutoresizingFlexibleWidth之类的枚举;如果你以前用IB比较多的话,一定注意到过每个view的size inspector中都有一个红色线条的Autoresizing的指示器和相应的动画缩放的示意图,这就是Autoresizing Mask。在iOS6之前,关于屏幕旋转的适配和iPhone,iPad屏幕的自动适配,基本都是由Autoresizing Mask来完成的。但是随着大家对iOS app的要求越来越高,以及已经以及今后可能出现的多种屏幕和分辨率的设备来说,Autoresizing Mask显得有些落伍和迟钝了。AutoLayout可以完成所有原来Autoresizing Mask能完成的工作,同时还能够胜任一些原来无法完成的任务,其中包括:
+
+* AutoLayout可以指定任意两个view的相对位置,而不需要像Autoresizing Mask那样需要两个view在直系的view hierarchy中。
+* AutoLayout不必须指定相等关系的约束,它可以指定非相等约束(大于或者小于等);而Autoresizing Mask所能做的布局只能是相等条件的。
+* AutoLayout可以指定约束的优先级,计算frame时将优先按照满足优先级高的条件进行计算。
+
+总结
+
+> Autoresizing Mask是AutoLayout的子集,任何可以用Autoresizing Mask完成的工作都可以用AutoLayout完成。AutoLayout还具备一些Autoresizing Mask不具备的优良特性,以帮助我们更方便地构建界面。
+
+* * *
+
+## AutoLayout基本使用方法
+
+### Interface Builder
+
+最简单的使用方法是在IB中直接拖。在IB中任意一个view的File inspector下面,都有Use Autolayout的选择框(没有的同学可以考虑升级一下Xcode了=。=),钩上,然后按照平常那样拖控件就可以了。拖动控件后在左边的view hierarchy栏中会出现Constraints一向,其中就是所有的约束条件。
+
+
+
+选中某个约束条件后,在右边的Attributes inspector中可以更改约束的条件,距离值和优先度等:
+
+
+对于没有自动添加的约束,可以在IB中手动添加。选择需要添加约束的view,点击菜单的Edit->Pin里的需要的选项,或者是点击IB主视图右下角的按钮,即可添加格外的约束条件。
+
+可视化的添加不仅很方便直观,而且基本不会出错,是优先推荐的添加约束的方式。但是有时候只靠IB是无法完成某些约束的添加的(比如跨view hierarchy的约束),有时候IB添加的约束不能满足要求,这时就需要使用约束的API进行补充。
+
+### 手动使用API添加约束
+
+#### 创建
+
+iOS6中新加入了一个类:NSLayoutConstraint,一个形如这样的约束
+
+* item1.attribute = multiplier ⨉ item2.attribute + constant
+
+对应的代码为
+
+```objc
+[NSLayoutConstraint constraintWithItem:button
+ attribute:NSLayoutAttributeBottom
+ relatedBy:NSLayoutRelationEqual
+ toItem:superview
+ attribute:NSLayoutAttributeBottom
+ multiplier:1.0
+ constant:-padding]
+```
+
+这对应的约束是“button的底部(y) = superview的底部 -10”。
+
+#### 添加
+
+在创建约束之后,需要将其添加到作用的view上。UIView(当然NSView也一样)加入了一个新的实例方法:
+
+* -(void)addConstraint:(NSLayoutConstraint *)constraint;
+用来将约束添加到view。在添加时唯一要注意的是添加的目标view要遵循以下规则:
+
+ * 对于两个同层级view之间的约束关系,添加到他们的父view上
+
+
+
+ * 对于两个不同层级view之间的约束关系,添加到他们最近的共同父view上
+
+
+
+ * 对于有层次关系的两个view之间的约束关系,添加到层次较高的父view上
+
+
+
+#### 刷新
+
+可以通过-setNeedsUpdateConstraints和-layoutIfNeeded两个方法来刷新约束的改变,使UIView重新布局。这和CoreGraphic的-setNeedsDisplay一套东西是一样的~
+
+### Visual Format Language 可视格式语言
+
+UIKit团队这次相当有爱,估计他们自己也觉得新加约束的API名字太长了,因此他们发明了一种新的方式来描述约束条件,十分有趣。这种语言是对视觉描述的一种抽象,大概过程看起来是这样的:
+
+accept按钮在cancel按钮右侧默认间距处
+
+
+
+
+
+
+
+最后使用VFL(Visual Format Language)描述变成这样:
+
+```objc
+[NSLayoutConstraint constraintsWithVisualFormat:@"[cancelButton]-[acceptButton]"
+ options:0
+ metrics:nil
+ views:viewsDictionary];
+```
+
+其中viewsDictionary是绑定了view的名字和对象的字典,对于这个例子可以用以下方法得到对应的字典:
+
+```objc
+UIButton *cancelButton = ...
+UIButton *acceptButton = ...
+viewsDictionary = NSDictionaryOfVariableBindings(cancelButton,acceptButton);
+```
+
+生成的字典为
+
+`{ acceptButton = ""; cancelButton = ""; }`
+
+当然,不嫌累的话自己手写也未尝不可。现在字典啊数组啊写法相对简化了很多了,因此也不复杂。关于Objective-C的新语法,可以参考我之前的一篇WWDC 2012笔记:[WWDC 2012 Session笔记——405 Modern Objective-C](http://www.onevcat.com/2012/06/modern-objective-c/)。
+
+在view名字后面添加括号以及连接处的数字可以赋予表达式更多意义,以下进行一些举例:
+
+* [cancelButton(72)]-12-[acceptButton(50)]
+ * 取消按钮宽72point,accept按钮宽50point,它们之间间距12point
+* [wideView(>=60@700)]
+ * wideView宽度大于等于60point,该约束条件优先级为700(优先级最大值为1000,优先级越高的约束越先被满足)
+* V:[redBox][yellowBox(==redBox)]
+ * 竖直布局,先是一个redBox,其下方紧接一个宽度等于redBox宽度的yellowBox
+* H:|-[Find]-[FindNext]-[FindField(>=20)]-|
+ * 水平布局,Find距离父view左边缘默认间隔宽度,之后是FindNext距离Find间隔默认宽度;再之后是宽度不小于20的FindField,它和FindNext以及父view右边缘的间距都是默认宽度。(竖线'|‘ 表示superview的边缘)
+
+* * *
+
+## 容易出现的错误
+
+因为涉及约束问题,因此约束模型下的所有可能出现的问题这里都会出现,具体来说包括两种:
+
+* Ambiguous Layout 布局不能确定
+* Unsatisfiable Constraints 无法满足约束
+
+布局不能确定指的是给出的约束条件无法唯一确定一种布局,也即约束条件不足,无法得到唯一的布局结果。这种情况一般添加一些必要的约束或者调整优先级可以解决。无法满足约束的问题来源是有约束条件互相冲突,因此无法同时满足,需要删掉一些约束。两种错误在出现时均会导致布局的不稳定和错误,Ambiguous可以被容忍并且选择一种可行布局呈现在UI上,Unsatisfiable的话会无法得到UI布局并报错。
+
+对于不能确定的布局,可以通过调试时暂停程序,在debugger中输入
+
+* po [[UIWindow keyWindow] _autolayoutTrace]
+
+来检查是否存在Ambiguous Layout以及存在的位置,来帮助添加条件。另外还有一些检查方法,来查看view的约束和约束状态:
+
+* [view constraintsAffectingLayoutForOrientation/Axis: NSLayoutConstraintOrientationHorizontal/Vertical]
+* [view hasAmbiguousLayout]
+ * [view exerciseAmbiguityInLayout]
+
+> 2013年9月1日作者更新:在iOS7和Xcode5中,IB在添加和检查Autolayout约束方面有了长足的进步。现在使用IB可以比较容易地完成复杂约束,而得益于新的IB的约束检查机制,我们也很少再会遇到遗漏或者多余约束情况的出现(有问题的约束条件将直接在IB中得到错误或者警告)。但是对于确实很奇葩的约束条件有可能使用IB无法达成,这时候还是有可能需要代码补充的。
+
+* * *
+
+## 布局动画
+
+动画是UI体验的重要部分,更改布局以后的动画也非常关键。说到动画,Core Animation又立功了..自从CA出现以后,所有的动画效果都非常cheap,在auto layout中情况也和collection view里一样,很简单(可以参考[WWDC 2012 Session笔记——219 Advanced Collection Views and Building Custom Layouts](http://www.onevcat.com/2012/08/advanced-collection-view/)),只需要把layoutIfNeeded放到animation block中即可~
+
+```objc
+[UIView animateWithDuration:0.5 animations:^{
+ [view layoutIfNeeded];
+}];
+```
+
+如果对block不熟悉的话,可以看看我很早时候写的一篇[block的文章](http://blog.onevcat.com/2011/11/objc-block/)。
diff --git a/_posts/2012-09-27-see-you-my-country.markdown b/_posts/2012-09-27-see-you-my-country.markdown
new file mode 100644
index 00000000..b90b79e2
--- /dev/null
+++ b/_posts/2012-09-27-see-you-my-country.markdown
@@ -0,0 +1,15 @@
+---
+layout: post
+title: 再见,祖国
+date: 2012-09-27 00:03:18.000000000 +09:00
+tags: 碎碎念
+---
+这篇日志发送的时候,我应该已经在离开中国的飞机上了,也有可能已经到达异国他乡了。这是一次不知道归期的旅途,也是一次充满未知的探索,从现在开始,祝自己好运。
+
+我在中国待了二十六载春秋,对这片土地有着深厚的感情。无论何时何地,无论此去经年,这个经历了太多磨难和挫折并且面临着巨大挑战的国家,始终是我魂牵梦萦的地方。虽然还有各种问题,虽然不足和缺点明显到让人无力吐槽,但是这个国家是我的祖国。对于自己的国家,一个人是没有任何理由和立场去厌恶的。
+
+记得本科入学式的时候,校长有这样一句话:“你们来到这里,是因为祖国选择了你们”。而讽刺的是,在四年或者七年后,我们中有一大批人选择了离开祖国,而且可能永远不会再回来。学校很尴尬的恢复成了最初成立时的“留美预备学校”的性质。确实,在这100年的时间里,似乎很多东西都回到了起点…
+
+到达之后可能会消失一段时间了,因为网络和通讯以及生活的安定都会需要一段时间。之后的blog风格依然会以iOS开发的技术的入门介绍为主,当然因为在国外一切都很新鲜,也许国外生活的介绍和感想的比例会稍微高一些。也许工作会相当忙,但是我仍然希望能有时间做一些工作之外的自己的事情,因为毕竟工作只是工作,它的目的不是代替生活,而是服务生活的~
+
+那么,祖国,再见。
diff --git a/_posts/2012-10-29-perl-json-utf.markdown b/_posts/2012-10-29-perl-json-utf.markdown
new file mode 100644
index 00000000..77b806dc
--- /dev/null
+++ b/_posts/2012-10-29-perl-json-utf.markdown
@@ -0,0 +1,68 @@
+---
+layout: post
+title: Perl中JSON的解析和utf-8乱码的解决
+date: 2012-10-29 00:08:28.000000000 +09:00
+tags: 能工巧匠集
+---
+最近在做一个带有网络通讯和同步功能的app,需要自己写一些后台的东西。因为是半路入门,所以从事开发以来就没有做过后台相关的工作,属于绝对的小白菜鸟。而因为公司在入职前给新员工提过学习Perl的要求,所以还算是稍微看过一些。这次的后台也直接就用Perl来写了。
+
+### 基本使用
+
+和app的通讯,很大程度上依赖了JSON,一来是熟悉,二来是iOS现在解析JSON也十分方便。iOS客户端的话JSON的解析和生成都是没什么问题的:iOS5中加入了[NSJSONSerialization](http://developer.apple.com/library/ios/#documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html)类来提供相关功能,如果希望支持更早的系统版本的话,相关的开源代码也有很多,也简单易用,比如[SBJson](http://stig.github.com/json-framework/)或者[JSONKit](https://github.com/johnezang/JSONKit)。同样,在Perl里也有不少类似的JSON处理的模块,最有名最早的应该是[JSON](http://search.cpan.org/~makamaka/JSON-2.53/lib/JSON.pm)模块了,同时也简单易用,应该可以满足大部分情况下的需求了。
+
+使用也很简单,安装完模块后,use之后使用encode_json命令即可将perl的array或者dic转换为标准的JSON字符串了:
+
+```
+use JSON qw/encode_json decode_json/;
+my $data = [
+ {
+ 'name' => 'Ken',
+ 'age' => 19
+ },
+ {
+ 'name' => 'Ken',
+ 'age' => 25
+ }
+];
+my $json_out = encode_json($data);
+```
+
+得到的字符串为
+
+```
+[{"name":"Ken","age":19},{"name":"Ken","age":25}]
+```
+
+相对应地,解析也很容易
+
+```
+my $array = decode_json($json_out);
+```
+
+得到的$array是含有两个字典的数组的ref。
+
+
+### UTF-8乱码解决
+
+在数据中含有UTF-8字符的时候需要稍微注意,如果直接按照上面的方法将会出现乱码。JSON模块的encode_json和decode_json自身是支持UTF8编码的,但是perl为了简洁高效,默认是认为程序是非UTF8的,因此在程序开头处需要申明需要UTF8支持。另外,如果需要用到JSON编码的功能(即encode_json)的话,还需要加入Encode模块的支持。总之,在程序开始处加入以下:
+
+```perl
+use utf8;
+use Encode;
+```
+
+另外,如果使用非UTF8进行编码的内容的话,最好先使用Encode的from_to命令转换成UTF8,之后再进行JSON编码。比如使用GBK编码的简体字(一般来自比较早的Windows的文件等会偶尔变成非UTF8编码),先进性如下转换:
+
+```
+use JSON;
+use Encode 'from_to';
+
+# 假设$json是GBK编码的
+my $json = '{"test" : "我是GBK编码的哦"}';
+
+from_to($json, 'GBK', 'UTF-8');
+
+my $data = decode_json($json);
+```
+
+其他的,如果追求更高的JSON转换性能的话,可以试试看[JSON::XS](http://search.cpan.org/~mlehmann/JSON-XS-2.33/XS.pm)之类的附加模块~
diff --git a/_posts/2012-11-16-memory-in-unity3d.markdown b/_posts/2012-11-16-memory-in-unity3d.markdown
new file mode 100644
index 00000000..ea0e247b
--- /dev/null
+++ b/_posts/2012-11-16-memory-in-unity3d.markdown
@@ -0,0 +1,66 @@
+---
+layout: post
+title: Unity 3D中的内存管理
+date: 2012-11-16 00:16:54.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+本文欢迎转载,但烦请保留此行出处信息:[http://www.onevcat.com/2012/11/memory-in-unity3d/](http://www.onevcat.com/2012/11/memory-in-unity3d/)
+
+Unity3D在内存占用上一直被人诟病,特别是对于面向移动设备的游戏开发,动辄内存占用飙上一两百兆,导致内存资源耗尽,从而被系统强退造成极差的体验。类似这种情况并不少见,但是绝大部分都是可以避免的。虽然理论上Unity的内存管理系统应当为开发者分忧解难,让大家投身到更有意义的事情中去,但是对于Unity对内存的管理方式,官方文档中并没有太多的说明,基本需要依靠自己摸索。最近在接手的项目中存在严重的内存问题,在参照文档和Unity Answer众多猜测和证实之后,稍微总结了下Unity中的内存的分配和管理的基本方式,在此共享。
+
+虽然Unity标榜自己的内存使用全都是“Managed Memory”,但是事实上你必须正确地使用内存,以保证回收机制正确运行。如果没有做应当做的事情,那么场景和代码很有可能造成很多非必要内存的占用,这也是很多Unity开发者抱怨内存占用太大的原因。接下来我会介绍Unity使用内存的种类,以及相应每个种类的优化和使用的技巧。遵循使用原则,可以让非必要资源尽快得到释放,从而降低内存占用。
+
+---
+
+### Unity中的内存种类
+
+实际上Unity游戏使用的内存一共有三种:程序代码、托管堆(Managed Heap)以及本机堆(Native Heap)。
+
+程序代码包括了所有的Unity引擎,使用的库,以及你所写的所有的游戏代码。在编译后,得到的运行文件将会被加载到设备中执行,并占用一定内存。这部分内存实际上是没有办法去“管理”的,它们将在内存中从一开始到最后一直存在。一个空的Unity默认场景,什么代码都不放,在iOS设备上占用内存应该在17MB左右,而加上一些自己的代码很容易就飙到20MB左右。想要减少这部分内存的使用,能做的就是减少使用的库,稍后再说。
+
+托管堆是被Mono使用的一部分内存。[Mono](http://www.mono-project.com/Main_Page)项目一个开源的.net框架的一种实现,对于Unity开发,其实充当了基本类库的角色。托管堆用来存放类的实例(比如用new生成的列表,实例中的各种声明的变量等)。“托管”的意思是Mono“应该”自动地改变堆的大小来适应你所需要的内存,并且定时地使用垃圾回收(Garbage Collect)来释放已经不需要的内存。关键在于,有时候你会忘记清除对已经不需要再使用的内存的引用,从而导致Mono认为这块内存一直有用,而无法回收。
+
+最后,本机堆是Unity引擎进行申请和操作的地方,比如贴图,音效,关卡数据等。Unity使用了自己的一套内存管理机制来使这块内存具有和托管堆类似的功能。基本理念是,如果在这个关卡里需要某个资源,那么在需要时就加载,之后在没有任何引用时进行卸载。听起来很美好也和托管堆一样,但是由于Unity有一套自动加载和卸载资源的机制,让两者变得差别很大。自动加载资源可以为开发者省不少事儿,但是同时也意味着开发者失去了手动管理所有加载资源的权力,这非常容易导致大量的内存占用(贴图什么的你懂的),也是Unity给人留下“吃内存”印象的罪魁祸首。
+
+---
+
+### 优化程序代码的内存占用
+
+这部分的优化相对简单,因为能做的事情并不多:主要就是减少打包时的引用库,改一改build设置即可。对于一个新项目来说不会有太大问题,但是如果是已经存在的项目,可能改变会导致原来所需要的库的缺失(虽说一般来说这种可能性不大),因此有可能无法做到最优。
+
+
+
+当使用Unity开发时,默认的Mono包含库可以说大部分用不上,在Player Setting(Edit->Project Setting->;Player或者Shift+Ctrl(Command)+B里的Player Setting按钮)面板里,将最下方的Optimization栏目中“Api Compatibility Level”选为.NET 2.0 Subset,表示你只会使用到部分的.NET 2.0 Subset,不需要Unity将全部.NET的Api包含进去。接下来的“Stripping Level”表示从build的库中剥离的力度,每一个剥离选项都将从打包好的库中去掉一部分内容。你需要保证你的代码没有用到这部分被剥离的功能,选为“Use micro mscorlib”的话将使用最小的库(一般来说也没啥问题,不行的话可以试试之前的两个)。库剥离可以极大地降低打包后的程序的尺寸以及程序代码的内存占用,唯一的缺点是这个功能只支持Pro版的Unity。
+
+这部分优化的力度需要根据代码所用到的.NET的功能来进行调整,有可能不能使用Subset或者最大的剥离力度。如果超出了限度,很可能会在需要该功能时因为找不到相应的库而crash掉(iOS的话很可能在Xcode编译时就报错了)。比较好地解决方案是仍然用最强的剥离,并辅以较小的第三方的类库来完成所需功能。一个最常见问题是最大剥离时Sysytem.Xml是不被Subset和micro支持的,如果只是为了xml,完全可以导入一个轻量级的xml库来解决依赖(Unity官方推荐[这个](http://unity3d.com/support/documentation/Images/manual/Mono.Xml.zip))。
+
+关于每个设定对应支持的库的详细列表,可以在[这里](http://docs.unity3d.com/Documentation/ScriptReference/MonoCompatibility.html)找到。关于每个剥离级别到底做了什么,[Unity的文档](http://unity3d.com/support/documentation/Manual/iphone-playerSizeOptimization.html)也有说明。实际上,在游戏开发中绝大多数被剥离的功能使用不上的,因此不管如何,库剥离的优化方法都值得一试。
+
+---
+
+### 托管堆优化
+
+Unity有一篇不错的关于[托管堆代码如何写比较好](http://unity3d.com/support/documentation/Manual/Understanding%20Automatic%20Memory%20Management.html)的说明,在此基础上我个人有一些补充。
+
+首先需要明确,托管堆中存储的是你在你的代码中申请的内存(不论是用js,C#还是Boo写的)。一般来说,无非是new或者Instantiate两种生成object的方法(事实上Instantiate中也是调用了new)。在接收到alloc请求后,托管堆在其上为要新生成的对象实例以及其实例变量分配内存,如果可用空间不足,则向系统申请更多空间。
+
+当你使用完一个实例对象之后,通常来说在脚本中就不会再有对该对象的引用了(这包括将变量设置为null或其他引用,超出了变量的作用域,或者对Unity对象发送Destory())。在每隔一段时间,Mono的垃圾回收机制将检测内存,将没有再被引用的内存释放回收。总的来说,你要做的就是在尽可能早的时间将不需要的引用去除掉,这样回收机制才能正确地把不需要的内存清理出来。但是需要注意在内存清理时有可能造成游戏的短时间卡顿,这将会很影响游戏体验,因此如果有大量的内存回收工作要进行的话,需要尽量选择合适的时间。
+
+如果在你的游戏里,有特别多的类似实例,并需要对它们经常发送Destroy()的话,游戏性能上会相当难看。比如小熊推金币中的金币实例,按理说每枚金币落下台子后都需要对其Destory(),然后新的金币进入台子时又需要Instantiate,这对性能是极大的浪费。一种通常的做法是在不需要时,不摧毁这个GameObject,而只是隐藏它,并将其放入一个重用数组中。之后需要时,再从重用数组中找到可用的实例并显示。这将极大地改善游戏的性能,相应的代价是消耗部分内存,一般来说这是可以接受的。关于对象重用,可以参考[Unity关于内存方面的文档中Reusable Object Pools部分](http://docs.unity3d.com/Documentation/Manual/UnderstandingAutomaticMemoryManagement.html),或者Prime31有一个是用Linq来建立重用池的视频教程(Youtube,需要翻墙,[上半部分](http://www.youtube.com/watch?v=IX041ZvgQKE),[下半部分](http://www.youtube.com/watch?v=d9078u8ft58))。
+
+如果不是必要,应该在游戏进行的过程中尽量减少对GameObject的Instantiate()和Destroy()调用,因为对计算资源会有很大消耗。在便携设备上短时间大量生成和摧毁物体的话,很容易造成瞬时卡顿。如果内存没有问题的话,尽量选择先将他们收集起来,然后在合适的时候(比如按暂停键或者是关卡切换),将它们批量地销毁并且回收内存。Mono的内存回收会在后台自动进行,系统会选择合适的时间进行垃圾回收。在合适的时候,也可以手动地调用System.GC.Collect()来建议系统进行一次垃圾回收。要注意的是这里的调用真的仅仅只是建议,可能系统会在一段时间后在进行回收,也可能完全不理会这条请求,不过在大部分时间里,这个调用还是靠谱的。
+
+---
+
+### 本机堆的优化
+
+当你加载完成一个Unity的scene的时候,scene中的所有用到的asset(包括Hierarchy中所有GameObject上以及脚本中赋值了的的材质,贴图,动画,声音等素材),都会被自动加载(这正是Unity的智能之处)。也就是说,当关卡呈现在用户面前的时候,所有Unity编辑器能认识的本关卡的资源都已经被预先加入内存了,这样在本关卡中,用户将有良好的体验,不论是更换贴图,声音,还是播放动画时,都不会有额外的加载,这样的代价是内存占用将变多。Unity最初的设计目的还是面向台式机,几乎无限的内存和虚拟内存使得这样的占用似乎不是问题,但是这样的内存策略在之后移动平台的兴起和大量移动设备游戏的制作中出现了弊端,因为移动设备能使用的资源始终非常有限。因此在面向移动设备游戏的制作时,尽量减少在Hierarchy对资源的直接引用,而是使用Resource.Load的方法,在需要的时候从硬盘中读取资源,在使用后用Resource.UnloadAsset()和Resources.UnloadUnusedAssets()尽快将其卸载掉。总之,这里是一个处理时间和占用内存空间的trade off,如何达到最好的效果没有标准答案,需要自己权衡。
+
+在关卡结束的时候,这个关卡中所使用的所有资源将会被卸载掉(除非被标记了DontDestroyOnLoad)的资源。注意不仅是DontDestroyOnLoad的资源本身,其相关的所有资源在关卡切换时都不会被卸载。DontDestroyOnLoad一般被用来在关卡之间保存一些玩家的状态,比如分数,级别等偏向文本的信息。如果DontDestroyOnLoad了一个包含很多资源(比如大量贴图或者声音等大内存占用的东西)的话,这部分资源在场景切换时无法卸载,将一直占用内存,这种情况应该尽量避免。
+
+另外一种需要注意的情况是脚本中对资源的引用。大部分脚本将在场景转换时随之失效并被回收,但是,在场景之间被保持的脚本不在此列(通常情况是被附着在DontDestroyOnLoad的GameObject上了)。而这些脚本很可能含有对其他物体的Component或者资源的引用,这样相关的资源就都得不到释放,这绝对是不想要的情况。另外,static的单例(singleton)在场景切换时也不会被摧毁,同样地,如果这种单例含有大量的对资源的引用,也会成为大问题。因此,尽量减少代码的耦合和对其他脚本的依赖是十分有必要的。如果确实无法避免这种情况,那应当手动地对这些不再使用的引用对象调用Destroy()或者将其设置为null。这样在垃圾回收的时候,这些内存将被认为已经无用而被回收。
+
+需要注意的是,Unity在一个场景开始时,根据场景构成和引用关系所自动读取的资源,只有在读取一个新的场景或者reset当前场景时,才会得到清理。因此这部分内存占用是不可避免的。在小内存环境中,这部分初始内存的占用十分重要,因为它决定了你的关卡是否能够被正常加载。因此在计算资源充足或是关卡开始之后还有机会进行加载时,尽量减少Hierarchy中的引用,变为手动用Resource.Load,将大大减少内存占用。在Resource.UnloadAsset()和Resources.UnloadUnusedAssets()时,只有那些真正没有任何引用指向的资源会被回收,因此请确保在资源不再使用时,将所有对该资源的引用设置为null或者Destroy。同样需要注意,这两个Unload方法仅仅对Resource.Load拿到的资源有效,而不能回收任何场景开始时自动加载的资源。与此类似的还有AssetBundle的Load和Unload方法,灵活使用这些手动自愿加载和卸载的方法,是优化Unity内存占用的不二法则~
diff --git a/_posts/2012-12-18-xuporter.markdown b/_posts/2012-12-18-xuporter.markdown
new file mode 100644
index 00000000..c6d2ac20
--- /dev/null
+++ b/_posts/2012-12-18-xuporter.markdown
@@ -0,0 +1,76 @@
+---
+layout: post
+title: Unity编译至Xcode工程后自动添加文件和库的方法
+date: 2012-12-18 00:17:55.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+
+废话之前
+
+[XUPorter项目Github链接][3]
+
+ [3]: https://github.com/onevcat/XUPorter
+
+### 为什么想要自动添加
+
+由于Unity是全平台的游戏开发环境,在开发中针对特定平台的特定功能时,很难避免根据对象平台的不同而引入不同的依赖。包括源码,需要的库和框架等。在使用各种插件后这种情况愈发严重:比如想加入内购功能,StroreKit.framework必不可少,而且也需要相应的处理代码。按照一般的Unity插件开发流程,在完成.cs的接口声明和Unity侧的调用实现后,最重要的当然是在iOS native侧完成实现。而在以前,包括依赖库和所有源码文件,都只有在Unity生成Xcode工程之后,再手动添加。如果工程小依赖少的话花不了太多时间,但是如果项目很大,很可能折腾一次就要十来分钟,严重影响了工作效率,必须加以解决。
+
+### 怎么办
+
+Unity开发团队也意识到了这个问题,在Unity编译的最后加入了一个脚本调用的命令,会自动搜索Editor文件夹下的PostprocessBuildPlayer,并进行调用,在该文件中可以自己加入脚本来向Xcode中添加库和文件。关于PostprocessBuildPlayer的详细信息,可以参看[官方文档][5],关于向Xcode中添加文件或库,gonzoua的[xcs][6]也许是不错的选择。但是似乎xcs只能针对Xcode3来添加,在Xcode4中,主工程文件的结构发生了改变,导致xcs失效,而这个项目也迟迟没有更新(也许有时间我会考虑接手继续这个项目,但肯定不是现在...)。因此不得不打其他主意。
+
+ [5]: http://docs.unity3d.com/Documentation/Manual/BuildPlayerPipeline.html
+ [6]: https://github.com/gonzoua/xcs
+
+在Unity3.5中,加入了一个很棒的标签——[[PostProcessBuild]][7],被该标签标注的函数将自动在build player后被调用,这为大家提供了一个不需要用脚本和命令行就能添加或修改编译得到的工程的绝好的入口。darktable用python实现了一个Xcode4工程文件读写的接口[Mod PBXProj][8],但是对于Unity来说,更需要的是C#的实现。Cariola完成了[一部分实现][9],但是存在一些错误和不太好用的地方,代码也很乱。我在其基础上进行了一些改进和整理。但是因为变动的还是比较大,很难merge回去,所以决定自己开一个项目来继续推进这个项目。
+
+ [7]: http://docs.unity3d.com/Documentation/ScriptReference/PostProcessBuildAttribute.html
+ [8]: https://bitbucket.org/darktable/mod-pbxproj/overview
+ [9]: https://github.com/dcariola/XCodeEditor-for-Unity
+
+### XUPorter
+
+我把它叫做XUPorter,a dependency porter from Unity to Xcode。XUPorter可以读取Xcode工程文件并进行解析(再次感谢darktable的工作),之后在Unity工程的Assets目录下寻找所有的.projmods文件,并根据文件内容向工程中添加文件或库。
+
+#### 使用方法
+
+将Github项目中的所有文件copy到Unity工程文件夹下的/Assets/Editor目录中,XUPorter使用一个[改良版的MiniJSON][10]来进行。如果你的项目中已经在使用这个MiniJSON了的话,可以直接将XUPorter文件夹下的MiniJSON文件夹删掉;如果不一样的话,你可以选择其中一个重构一下或者加上命名空间来解决类名冲突。接下来,Mods文件夹下是示例文件以及需要导入Xcode的文件。在看完以后你需要把Mods文件夹下的所有.projmods文件以及Mods/iOS文件夹下的内容删除或者替换为你所需要的内容。
+
+ [10]: https://github.com/prime31/UIToolkit/blob/master/Assets/Plugins/MiniJSON.cs
+
+在[这里][11]提供了.unitypackege格式文件的下载,你也可以选择下载打包好的文件并导入你的工程,之后的步骤和上面一样。
+
+ [11]: http://d.pr/f/HAzc
+
+.projmods文件是一个JSON格式的配置patch文件,定义了要如何设置Xcode工程。举个基本的例子,比如KKKeychain.projmods:
+
+```json
+{
+ "group": "KKKeychain",
+ "libs": [],
+ "frameworks": ["Security.framework"],
+ "headerpaths": [],
+ "files": [],
+ "folders": ["iOS/KKKeychain/"],
+ "linker_flags": [],
+ "excludes": ["^.*.meta$", "^.*.mdown$", "^.*.pdf$"]
+}
+```
+
+各参数定义如下:
+
+* group:所有由该projmods添加的文件和文件夹所属的Xcode中的group名称
+* libs:在Xcode Build Phases中需要添加的动态链接库的名称,比如libz.dylib
+* frameworks:在Xcode Build Phases中需要添加的框架的名称,比如Security.framework
+* headerpaths:Xcode中编译设置中的Header Search Paths路径
+* files:加入工程的文件名
+* folders:加入工程的文件夹,其中所有的文件和文件夹都将被加入工程中
+* linker_flags:添加到工程linker flag中的链接配置,比如-ObjC
+* excludes:忽略的文件的正则表达式,匹配的文件将不会被加入工程中
+
+更多的例子可以参看Mods文件夹中的其他projmods文件。所有的定义路径都是基于当前projmods文件位置的相对路径。 最后,在完成projmods后,Unity会在编译完成后,调用XCodePostProcess的OnPostProcessBuild来对编译得到的Xcode工程进行修改。
+
+之后进一步要做的是为MiniJSON添加一个namespace,这样可以避免不必要的冲突。另外如果您有什么好的想法,也欢迎fork这个项目并给我pull request。项目的github链接请[猛击这里](https://github.com/onevcat/XUPorter)。
diff --git a/_posts/2013-01-26-do_not_pause_me.markdown b/_posts/2013-01-26-do_not_pause_me.markdown
new file mode 100644
index 00000000..f66f75ba
--- /dev/null
+++ b/_posts/2013-01-26-do_not_pause_me.markdown
@@ -0,0 +1,60 @@
+---
+layout: post
+title: Unity3D中暂停时的动画及粒子效果实现
+date: 2013-01-26 00:23:34.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+
+暂停是游戏中经常出现的功能,而Unity3D中对于暂停的处理并不是很理想。一般的做法是将`Time.timeScale`设置为0。Unity的文档中对于这种情况有以下描述;
+
+> The scale at which the time is passing. This can be used for slow motion effects….When timeScale is set to zero the game is basically paused …
+
+timeScale表示游戏中时间流逝快慢的尺度。文档中明确表示,这个参数是用来做慢动作效果的。对于将timeScale设置为0的情况,仅只有一个补充说明。在实际使用中,通过设置timeScale来实现慢动作特效,是一种相当简洁且不带任何毒副作用的方法,但是当将timeScale设置为0来实现暂停时,由于时间不再流逝,所有和时间有关的功能痘将停止,有些时候这正是我们想要的,因为毕竟是暂停。但是副作用也随之而来,在暂停时各种动画和粒子效果都将无法播放(因为是时间相关的),FixedUpdate也将不再被调用。
+
+**换句话说,最大的影响是,在timeScale=0的暂停情况下,你将无法实现暂停菜单的动画以及各种漂亮的点击效果。**
+
+但是并非真的没办法,关于timeScale的文档下就有提示:
+
+> Except for realtimeSinceStartup, timeScale affects all the time and delta time measuring variables of the Time class.
+
+因为 `realtimeSinceStartup` 和 `timeScale` 无关,因此也就成了解决在暂停下的动画和粒子效果的救命稻草。对于Unity动画,在每一帧,根据实际时间寻找相应帧并采样显示的方法来模拟动画:
+
+```csharp
+AnimationState _currState = animation[clipName];
+
+bool isPlaying = true;
+float _progressTime = 0F;
+float _timeAtLastFrame = 0F;
+float _timeAtCurrentFrame = 0F;
+bool _inReversePlaying = false;
+float _deltaTime = 0F;
+
+animation.Play(clipName);
+_timeAtLastFrame = Time.realtimeSinceStartup;
+
+while (isPlaying) {
+ _timeAtCurrentFrame = Time.realtimeSinceStartup;
+ _deltaTime = _timeAtCurrentFrame - _timeAtLastFrame;
+ _timeAtLastFrame = _timeAtCurrentFrame;
+ _progressTime += _deltaTime;
+ _currState.normalizedTime = _inReversePlaying ? 1.0f - (_progressTime / _currState.length) : _progressTime / _currState.length;
+ animation.Sample();
+ //…repeat or over by wrap mode
+}
+```
+
+对于粒子效果,同样进行计时,并通过粒子系统的Simulate方法来模拟对应时间的粒子状态来完成效果,比如对于Legacy粒子,使Emitter在`timeScale=0`暂停时继续有效发射并显示效果:
+
+```
+_deltaTime = Time.realtimeSinceStartup - _timeAtLastFrame;
+_timeAtLastFrame = Time.realtimeSinceStartup;
+if (Time.timeScale == 0 ){
+ _emitter.Simulate(_deltaTime);
+ _emitter.emit = true;
+}
+```
+
+核心的代码基本都在上面了,可以根据这个思路完成实现。[完整的代码和示例工程](https://github.com/onevcat/UnpauseMe)我放到了github上,有需要的朋友可以去查看,也欢迎大家指正。
diff --git a/_posts/2013-02-02-xcode-plugin.markdown b/_posts/2013-02-02-xcode-plugin.markdown
new file mode 100644
index 00000000..fb9ee332
--- /dev/null
+++ b/_posts/2013-02-02-xcode-plugin.markdown
@@ -0,0 +1,406 @@
+---
+layout: post
+title: Xcode 4 插件制作入门
+date: 2013-02-02 00:32:39.000000000 +09:00
+tags: 能工巧匠集
+---
+
+本文欢迎转载,但烦请保留此行出处信息:[http://www.onevcat.com/2013/02/xcode-plugin/](http://www.onevcat.com/2013/02/xcode-plugin/)
+
+## 2014.5.4更新
+
+对于 Xcode 5,本文有些地方显得过时了。Xcode 5 现在已经全面转向了 ARC,因此在插件初始化设置方面其实有所改变。另外由于一大批优秀插件的带动(可以参看文章底部链接),很多大神们逐渐加入了插件开发的行列,因此,一个简单的 Template 就显得很必要了。在 Github 上的[这个 repo](https://github.com/kattrali/Xcode5-Plugin-Template) 里,包含了一个 Xcode 5 的插件的 Template 工程,省去了每次从头开始建立插件工程的麻烦,大家可以直接下载使用。
+
+另外值得一提的是,在 Xcode 5 中, Apple 为了防止过期插件导致的在 Xcode 升级后 IDE 的崩溃,添加了一个 UUID 的检查机制。只有包含声明了适配 UUID,才能够被 Xcode 正确加载。上面那个项目中也包含了这方面的更详细的说明,可以参考。
+
+文中其他关于插件开发的思想和常用方法在新的 Xcode 中依然是奏效的。
+
+---
+
+本文将介绍创建一个Xcode4插件所需要的基本步骤以及一些常用的方法。请注意为Xcode创建插件并没有任何的官方支持,因此本文所描述的方法和提供的信息可能会随Apple在Xcode上做的变化而失效。另外,由于创建插件会使用到私有API,因此Xcode插件也不可能被提交到Mac App Store上进行出售。
+
+本文内容是基于Xcode 4.6(4H127)完成的,但是应该可以适用于任意的Xcode 4.X版本。VVPlugInDemo的工程文件我放到了github上,有需要的话您可以从[这里下载](https://github.com/onevcat/VVPluginDemo)并作为参考和起始来使用。
+
+## 综述
+
+Xcode本身作为一个IDE来说已经可以算上优秀,但是依然会有很多缺失的功能,另外在开发中针对自己的开发需求,创建一些便利的IDE插件,必定将大为加快开发速度。由于苹果官方并不对Xcode插件提供任何技术和文档支持,因此对于大部分开发者来说可能难于上手。虽然没有官方支持,但是在Xcode中开发并使用插件是可能的,并且也是被默许的。在Xcode启动的时候,Xcode将会寻找位于~/Library/Application Support/Developer/Shared/Xcode/Plug-ins文件夹中的后缀名为.xcplugin的bundle作为插件进行加载(运行其中的可执行文件),这就可以令我们光明正大合法合理地将我们的代码注入(虽然这个词有点不好听)Xcode,并得到运行。因此,想要创建Xcode插件,**我们需要创建Bundle工程并将编译的bundle放到上面所说的插件目录中去**,这就是Xcode插件的原理。
+
+需要特别说明的是,因为Xcode会在启动时加载你的插件,这样就相当于你的代码有机会注入Xcode。只要你的插件加载成功,那么它将和Xcode共用一个进程,也就是说当你的代码crash的时候,Xcode也会随之crash。同样的情况也可能在Xcode版本更新的时候,由于兼容性问题而出现(因为插件可能使用私有API,Apple没有义务去维护这些API的可用性)。在出现这种情况的时候,可以直接删除插件目录下的导致问题的xcplugin文件即可。
+
+## 你的第一个插件
+
+我将通过制作一个简单的demo插件来说明一般Xcode插件的编写方法,这个插件将在Xcode的Edit菜单中加入一个叫做“What is selected”的项目,当你点击这个菜单命令的时候,将弹出一个警告框,提示你现在在编辑器中所选中的内容。我相信这个例子能包含绝大部分在插件创建中所必须的步骤和一些有用的方法。由于我自己也只是个半吊子开发者,水平十分有限,因此错误和不当之处还恳请大家轻喷多原谅,并帮助我改正。那么开始..
+
+### 创建Bundle工程
+
+![image][5] 创建工程,OSX,Framework & Library,选择Bundle,点击Next。
+
+ [5]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-1.png
+
+![image][6]
+
+ [6]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-2.png
+
+在Project信息页面中,填入插件名字,在这个例子里,就叫做DemoPlugin,Framework使用默认的Cocoa就行。另外一定记住将Use Automatic Reference Counting前的勾去掉,由于插件只能使用GC来进行内存管理,因此不需要使用ARC。
+
+### 工程设置
+
+插件工程有别于一般工程,需要进行一些特别的设置,以确保能正确编译插件bundle。
+
+![image][7]
+
+ [7]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-3.png
+
+首先,在编辑工程的Info.plist文件(直接编辑plist文件或者是修改TARGETS下对应target的Info都行),加入以下三个布尔值:
+
+```
+XCGCReady = YES
+XCPluginHasUI = NO
+XC4Compatible = YES
+```
+
+这将告诉编译器工程已经使用了GC,没有另外的UI并且是Xcode4适配的,否则你的插件将不会被加载。接下来,对Bundle Setting进行一些设置:
+
+![image][8]
+
+ [8]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-4.png
+
+ * Installation Build Products Location 设置为 ${HOME}
+
+ * Product的根目录
+
+ * Installation Directory 设置为
+
+ * /Library/Application Support/Developer/Shared/Xcode/Plug-ins
+ * 这里指定了插件安装的位置,这样build之后就会将插件直接扔到Plug-ins的目录了。当然不嫌麻烦的话也可以每次自己复制粘贴过去。注意这里不是绝对路径,而是基于上面的${HOME}的路径。
+
+ * Deployment Location 设置为 YES
+
+ * 告诉Xcode不要用设置里的build location,而是用Installation Directory来确定build后放哪儿
+
+ * Wrapper extension 设置为 xcplugin
+
+ * 把产品后缀名改为xcplugin,否则Xcode不会加载插件
+
+如一开始说的那样,Xcode会在每次启动的时候搜索插件目录并进行加载,做如上设置的目的是每次build之后你只需要重新启动Xcode就能看到重新编译后的插件的效果,而避免了自己再去寻找Product然后copy&paste的步骤。
+另外,还需要自己在User-Defined里添加一个键值对:
+
+![image][9]
+
+ [9]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-5.png
+
+ * GCC_ENABLE_OBJC_GC 设置为 supported
+
+至此所有配置工作完成,接下来终于可以开始实现插件了~
+
+### Hello World
+
+新建一个类,取名叫做VVPluginDemo(当然只要不重,随便什么名字都是可以的),继承自NSObject(做iOS开发的童鞋请不要忘记现在是写Xcode插件,您需要通过OS X的Cocoa里的Objective-C class模版,而不要用Cocoa Touch的模版..)。打开VVPluginDemo.m,加入以下代码:
+
+```objc
++(void)pluginDidLoad:(NSBundle *)plugin {
+ NSLog(@"Hello World");
+}
+```
+
+Build(对于OS X 10.8的SDK可能会有提示GC已经废弃的警告,不用管,Xcode本身是GC的,ARC的插件是无法load的),打开控制台(Control+空格 输入console),重新启动Xcode。应该能控制台中看到我们的插件的输出:
+
+![image][10]
+
+ [10]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-6.png
+
+太好了。有句话叫做,写出一个Hello World,就说明你已经掌握了一半…那么,剩下的一半内容,将对开发插件时可能面临的问题和一些常用的手段进行介绍。
+
+### 创建插件单例,监听事件
+
+继续我们的插件,还记得我们的目的么?在Xcode的Edit菜单中加入一个叫做“What is selected”的项目,当你点击这个菜单命令的时候,将弹出一个警告框,提示你现在在编辑器中所选中的内容。一般来说,我们希望插件能够在整个Xcode的生命周期中都存在(不要忘记其实用来写Cocoa的Xcode本身也是一个Cocoa程序)。最好的办法就是在+pluginDidLoad:中初始化单例,如下:
+
+```objc
++ (void) pluginDidLoad: (NSBundle*) plugin {
+ [self shared];
+}
+
+
++(id) shared {
+ static dispatch_once_t once;
+ static id instance = nil;
+ dispatch_once(&once, ^{
+ instance = [[self alloc] init];
+ });
+ return instance;
+}
+```
+
+这样,以后我们在别的类中,就可以简单地通过[VVPluginDemo shared]来访问到插件的实例了。
+
+在init中,加入一个程序启动完成的事件监听,并在程序完成启动后,在菜单栏的Edit中添加我们所需要的菜单项,这个操作最好是在Xcode完全启动以后再进行,以避免一些潜在的危险和冲突。另外,由于想要在按下按钮时显示编辑器中显示的内容,我们可能需要监听NSTextViewDidChangeSelectionNotification事件(WTF,你为什么会知道要监听什么。别着急,后面会再说,先做demo先做demo)
+
+```objc
+- (id)init {
+ if (self = [super init]) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(applicationDidFinishLaunching:)
+ name:NSApplicationDidFinishLaunchingNotification
+ object:nil];
+ }
+ return self;
+}
+
+- (void) applicationDidFinishLaunching: (NSNotification*) noti {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(selectionDidChange:)
+ name:NSTextViewDidChangeSelectionNotification
+ object:nil];
+ NSMenuItem *editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
+ if (editMenuItem) {
+ [[editMenuItem submenu] addItem:[NSMenuItem separatorItem]];
+ NSMenuItem *newMenuItem = [[NSMenuItem alloc] initWithTitle:@"What is selected" action:@selector(showSelected:) keyEquivalent:@""];
+ [newMenuItem setTarget:self];
+ [newMenuItem setKeyEquivalentModifierMask: NSAlternateKeyMask];
+ [[editMenuItem submenu] addItem:newMenuItem];
+ [newMenuItem release];
+ }
+}
+
+-(void) selectionDidChange:(NSNotification *)noti {
+ //Nothing now. Just in case of crash.
+}
+
+-(void) showSelected:(NSNotification *)noti {
+ //Nothing now. Just in case of crash.
+}
+```
+
+现在build,重启Xcode,如果一切顺利的话,你应该能看到菜单栏上的变化了:
+
+![image][11]
+
+ [11]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-8.png
+
+### 完成Demo插件
+
+剩下的事情就很简单了,在接收到TextView的ChangeSelection通知后把现在选中的文本更新一下,在点击按钮时显示一个含有储存文字的对话框就行了。Let's do it~
+
+首先在.m文件中加上property声明(个人习惯,喜欢用ivar也可以)。在#import和@implementation之间加上:
+
+```objc
+@interface VVPluginDemo()
+@property (nonatomic,copy) NSString *selectedText;
+@end
+```
+
+得益于新的属性自动绑定,synthesis已经不需要写了(对此还不太了解的童鞋可以参看我的[这篇博文](http://www.onevcat.com/2012/06/modern-objective-c/))。然后完成- selectionDidChange:和-showSelected:如下:
+
+```objc
+-(void) selectionDidChange:(NSNotification *)noti {
+ if ([[noti object] isKindOfClass:[NSTextView class]]) {
+ NSTextView* textView = (NSTextView *)[noti object];
+
+ NSArray* selectedRanges = [textView selectedRanges];
+ if (selectedRanges.count==0) {
+ return;
+ }
+
+ NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
+ NSString* text = textView.textStorage.string;
+ self.selectedText = [text substringWithRange:selectedRange];
+ }
+ //Hello, welcom to OneV's Den
+}
+
+-(void) showSelected:(NSNotification *)noti {
+ NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ [alert setMessageText: self.selectedText];
+ [alert runModal];
+}
+```
+
+Build,重启Xcode,随便选中一段文本,然后点击Edit中的What is selected。OY~完成~
+
+![image][13]
+
+ [13]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-7.png
+
+至此,您应该已经掌握了基本的Xcode插件制作方法了。接下来的就是根据您的需求实践了~但是在此之前,还有一些重要的技巧和常用方法可能您会有兴趣。
+
+## 开发插件时有用的技巧
+
+由于没有文档指导插件开发,调试也只能用打log的方式,因此会十分艰难。掌握一些常用的技巧和方法,将会很有帮助。
+
+### I Need All Notifications!
+
+一种很好的方法是监听需要的消息,并针对消息作出反应。就像demo里的NSTextViewDidChangeSelectionNotification。对于熟悉iOS或者Mac开发的童鞋来说,应该在日常开发里也接触过很多类型的Notification了,而因为插件开发没有文档,因此我们需要自己去寻找想要监听和接收的Notification。[NSNotificationCenter文档][14]中,关于加入Observer的方法-addObserver:selector:name:object:,当给name参数赋值nil时,将可以监听到所有的notification:
+
+ [14]: https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
+
+> notificationName: The name of the notification for which to register the observer; that is, only notifications with this name are delivered to the observer. If you pass nil, the notification center doesn’t use a notification’s name to decide whether to deliver it to the observer.
+
+因此可以用它来监测所有的Notification,并从中找到自己所需要的来进行处理:
+
+```objc
+-(id)init {
+ if (self = [super init]) {
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(notificationListener:)
+ name:nil object:nil];
+ }
+ return self;
+}
+
+-(void)notificationListener:(NSNotification *)noti {
+ NSLog(@" Notification: %@", [noti name]);
+}
+```
+
+编译重启后在控制台得到的输出:
+
+![image][15]
+
+ [15]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-9.png
+
+当然如果只是打印名字的话可能帮助不大,也许你需要从noti的object或者userinfo中获得更多的信息。按条件打印,配合控制台的搜索进行寻找会是一个不错的方法。
+
+### Hack私有API
+
+用OC的动态特性可以做很多事,比如在运行时替换掉某个Xcode的方法。记住Xcode本身也是Cocoa程序,本质上和我们用Xcode所开发的程序没有太大区别。因此如果可以知道Xcode在进行某些操作时候的方法的话,就可以将该方法与我们自己实现的方法进行运行时调换,从而改为执行我们自己的方法。这便是运行时的Method Swizzling(或者叫Monkey patch,管他呢),这在smalltalk类语言中是一种很有趣和方便的做法,关于这方面更详细的,我以前写过一篇关于[OC运行时特性的文章][16]。当时提到的method swizzling方法并没有对交换的函数进行检查等工作,通用性也比较差。现在针对OC已经有比较成熟的一套方法交换机制了,其中比较有名的有[rentzsch的jrswizzle][17]以及[OC社区的MethodSwizzling实现][18]。
+
+ [16]: http://www.onevcat.com/2012/04/objective-c-runtime/
+ [17]: https://github.com/rentzsch/jrswizzle
+ [18]: http://cocoadev.com/wiki/MethodSwizzling
+
+有了方法交换的办法,接下来需要寻找要交换的方法。Xcode所使用的所有库都包含在Xcode.app/Contents/的Frameworks,SharedFrameworks和OtherFrameworks三个文件夹下。其中和Xcode关系最为直接以及最为重要的是Frameworks中的IDEKit和IDEFoundation,以及SharedFrameworks中的DVTKit和DVTFoundation四个。其中DVT前缀表示Developer Toolkit,IDE和IDEFoundation中的类基本是DVT中类的子类。这四个framework将是我们在开发改变Xcode默认行为的Xcode插件时最主要要打交道的。另外如果想对IB进行注入,可能还需要用到Frameworks下的IBAutolayoutFoundation(待确定)。关于这些framework中的私有API,可以使用[class-dump][19]很简单地将头文件提取出来。当然,也有人为懒人们完成了这个工作,[probablycorey的xcode-class-dump][20]中有绝大部分类的头文件。
+
+ [19]: http://stevenygard.com/projects/class-dump/
+ [20]: https://github.com/probablycorey/xcode-class-dump
+
+作为Demo,我们将简单地完成一个方法交换:在补全代码时,我们简单地输出一句log。
+
+#### MethodSwizzle
+
+为了交换方法,可以直接用现成的MethodSwizzle实现。MethodSwizzle可以在[这里][21]找到。将.h和.m导入插件工程即可~
+
+ [21]: https://gist.github.com/4696790
+
+#### 寻找对应API
+
+通过搜索,补全代码的功能定义在DVKit中的DVTTextCompletionController类,其中有一个方法为- (BOOL)acceptCurrentCompletion,猜测返回的布尔值是否接受当前的补全结果。由于这些都是私有API,因此需要在我们的工程中自己进行声明。在新建文件中的C and C++中选Header File,为工程加入一个Header文件,并加入一下代码:
+
+```objc
+@interface DVTTextCompletionController : NSObject
+- (BOOL)acceptCurrentCompletion;
+@end
+```
+
+然后需要将DVKit.framework添加到工程中,在/Applications/Xcode.app/Contents/SharedFrameworks中找到DVTKit.framework,拷贝到任意正常能访问到的目录下,然后在插件工程的Build Phases中加入framework。嗯?你说找不到DVTKit.framework?亲,私有框架当然找不到,点击Add Other...然后去刚才copy出来的地方去找吧..
+
+![image][22]
+
+ [22]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-10.png
+
+最后便是加入方法交换了~新建一个DVTTextCompletionController的Category,命名为PluginDemo
+
+![image][23]
+
+ [23]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-13.png
+
+import之前定义的header和MethodSwizzle.h,在DVTTextCompletionController+PluginDemo.m中加入下面实现:
+
+```objc
++ (void)load
+{
+ MethodSwizzle(self,
+ @selector(acceptCurrentCompletion),
+ @selector(swizzledAcceptCurrentCompletion));
+}
+
+- (BOOL)swizzledAcceptCurrentCompletion {
+NSLog(@"acceptCurrentCompletion is called by %@", self);
+return [self swizzledAcceptCurrentCompletion];
+}
+
+```
+
++load方法在每个NSObject类或子类被调用时都会被执行,可以用来在runtime配置当前类。这里交换了DVTTextCompletionController的acceptCurrentCompletion方法和我们自己实现的swizzledAcceptCurrentCompletion方法。在swizzledAcceptCurrentCompletion中,先打印了一句log,输出相应该方法的实例。接下来似乎是调用了自己,但是实际上swizzledAcceptCurrentCompletion的方法已经和原来的acceptCurrentCompletion交换,因此这里实际调用的将是原来的方法。那么这段代码所做的就是将Xcode想调用原来的acceptCurrentCompletion的行为,改变成了先打印一个log,之后再进行原来的acceptCurrentCompletion调用。
+
+编译,重启Xcode,打开一个工程随便输入点东西,让补全出现。控制台中的输出符合我们的预期:
+
+![image][24]
+
+ [24]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-12.png
+
+太棒了,有了对私有API的注入,能做的事情大为扩展了。
+
+### 研究Xcode的View Hierarchy
+
+另外一种常见的插件行为是修改某些界面。再一次说明,Xcode是一个标准Cocoa程序,一切都是那么熟悉(如果你为Cocoa或者CocoaTouch开发的话,应该是很熟悉)。拿到整个App的Window,然后依次递归打印subview。stackoverflow上有[一个UIView的版本](http://stackoverflow.com/questions/2715534/where-does-a-uialertview-live-while-not-dismissed/2715772#2715772),稍微改变一下就可以得到一个NSView版本。新建一个NSView的Dumping Category,加入如下实现:
+
+```objc
+-(void)dumpWithIndent:(NSString *)indent {
+ NSString *class = NSStringFromClass([self class]);
+ NSString *info = @"";
+ if ([self respondsToSelector:@selector(title)]) {
+ NSString *title = [self performSelector:@selector(title)];
+ if (title != nil && [title length] > 0) {
+ info = [info stringByAppendingFormat:@" title=%@", title];
+ }
+ }
+ if ([self respondsToSelector:@selector(stringValue)]) {
+ NSString *string = [self performSelector:@selector(stringValue)];
+ if (string != nil && [string length] > 0) {
+ info = [info stringByAppendingFormat:@" stringValue=%@", string];
+ }
+ }
+ NSString *tooltip = [self toolTip];
+ if (tooltip != nil && [tooltip length] > 0) {
+ info = [info stringByAppendingFormat:@" tooltip=%@", tooltip];
+ }
+
+ NSLog(@"%@%@%@", indent, class, info);
+
+ if ([[self subviews] count] > 0) {
+ NSString *subIndent = [NSString stringWithFormat:@"%@%@", indent, ([indent length]/2)%2==0 ? @"| " : @": "];
+ for (NSView *subview in [self subviews]) {
+ [subview dumpWithIndent:subIndent];
+ }
+ }
+}
+```
+
+在合适的时候(比如点击某个按钮时),调用下面一句代码,便可以打印当前Xcode的结构,非常方便。这对了解Xcode的构成和如何搭建一个如Xcode般复杂的程序很有帮助~
+
+
+ [[[NSApp mainWindow] contentView] dumpWithIndent:@""];
+
+在结果控制台中的输出结果类似这样:
+
+![image][26]
+
+ [26]: http://i758.photobucket.com/albums/xx224/onevcat/QQ20130202-14.png
+
+根据自己需要去去相应的view吧~然后配合方法交换,基本可以做到尽情做想做的事情了。
+
+## 最后的小bonus
+
+/Applications/Xcode.app/Contents/Frameworks/IDEKit.framework/Versions/A/Resources中有不少Xcode界面用的图片,pdf,png和tiff格式都有,想要自定义run,stop按钮或者想要让断点标记从蓝色块变成机器猫头像什么的…应该是可能的~
+
+/Applications/Xcode.app/Contents/PlugIns目录里有很多Xcode自带的“官方版”外挂插件,显然通过class-dump和注入的方法,你可以为Xcode的插件写插件...嗯~比如改变debugger的行为或者让plist编辑器更聪明,就是这样的。
+
+希望Apple能提供为Xcode编写插件的支持,所有东西都需要摸索虽然很有趣,但是也比较花时间。
+
+另外,github等代码托管网站上有不少大神们写的插件,都开源放出。这些必须是学习插件编写的最优秀的教材和参考:
+
+ * [mneorr / Alcatraz](https://github.com/mneorr/Alcatraz) Xcode的包管理插件,管理其他插件的插件
+ * [onevcat / VVDocumenter-Xcode](https://github.com/onevcat/VVDocumenter-Xcode) 帮助快速写文档注释的插件,自动提取参数返回值等
+ * [omz / ColorSense-for-Xcode](https://github.com/omz/ColorSense-for-Xcode) 在UIColor/NSColor上显示出对应的颜色
+ * [omz / Dash-Plugin-for-Xcode](https://github.com/omz/Dash-Plugin-for-Xcode) 在Xcode中集成Dash,方便看文档
+ * [omz / MiniXcode](https://github.com/omz/MiniXcode) 隐藏Xcode臃肿的工具栏,获得更大的可视空间
+ * [ksuther / KSImageNamed-Xcode](https://github.com/ksuther/KSImageNamed-Xcode) 输入imageNamed的时候自动补完图片名称
+ * [JugglerShu / XVim](https://github.com/JugglerShu/XVim) 将Xcode编辑器改造成Vim
+ * [davekeck / Xcode-4-Fixins](https://github.com/davekeck/Xcode-4-Fixins) 修正一些Xcode的bugs(应该已经没有太大用了)
+ * [0xced / CLITool-InfoPlist](https://github.com/0xced/CLITool-InfoPlist) 方便修改Info.plist为CLI目标的插件
+ * [questbeat / Lin](https://github.com/questbeat/Lin) 为NSLocalizedString显示补全
+ * [stefanceriu / SCXcodeMiniMap](https://github.com/stefanceriu/SCXcodeMiniMap) 在侧边显示代码小地图
+
+好了,就到这里吧。VVPlugInDemo的工程文件我放到了github上,有需要的话您可以从[这里下载][35]并作为参考和起始来使用。谢谢您看完这么长的文。正如一开始所说的,我自己水平十分有限,因此错误和不当之处还恳请大家轻喷多原谅,并帮助我改正,再次谢谢~
+
+ [35]: https://github.com/onevcat/VVPluginDemo
diff --git a/_posts/2013-03-24-mgtwitterengine.markdown b/_posts/2013-03-24-mgtwitterengine.markdown
new file mode 100644
index 00000000..bd890f18
--- /dev/null
+++ b/_posts/2013-03-24-mgtwitterengine.markdown
@@ -0,0 +1,52 @@
+---
+layout: post
+title: MGTwitterEngine中Twitter API 1.1的使用
+date: 2013-03-24 00:34:48.000000000 +09:00
+tags: 能工巧匠集
+---
+在iOS5中使用Twitter framework或者在iOS6中使用Social framework来完成Twitter的集成是非常简单和轻松的,但是如果应用要针对iOS5之前的系统版本,那么就不能使用iOS提供的框架了。一个比较常见也是使用最广泛的选择是[MGTwitterEngine](https://github.com/mattgemmell/MGTwitterEngine),比如[PomodoroDo](http://www.onevcat.com/showcase/pomodoro_do/)选择使用的就是该框架。
+
+但是今天在对PomodoroDo作更新的时候,发现Twitter的分享无法使用了,在查阅Twitter文档说明之后,发现这是Twitter采用了新版API的原因。默认状况下MGTwitterEngine采用的是v1版的API,并且使用XML的版本进行请求,而在1.1中,将[只有JSON方式的API可以使用](https://dev.twitter.com/docs/api/1.1/overview#JSON_support_only)。v1.0版本的API已经于2013年3月5日被完全废弃,因此想要继续使用MGTwitterEngine来适配iOS5之前的Twitter集成需求,就需要将MGTwitterEngine的请求改为JSON方式。MGTwitterEngine也考虑到了这一点,但是因为时间比较古老了,MGTwitterEngine使用了YAJL来作为JSON的Wrapper,因此还需要将YAJL集成进来。下午的时候尝试了一会儿,成功地让MGTwitterEngine用上了1.1的Twitter API,为了以防之后别人或是自己可能遇到同样的问题,将更新的方法在此留底备忘。
+
+1. 导入YAJL Framework
+ * YAJL的OC实现,从[该地址下载该框架](https://github.com/gabriel/yajl-objc/download)。(2013年3月24日的最新版本为YAJL 0.3.1 for iOS)
+ * 解压下载得到的zip,将解压后的YAJLiOS.framework加入项目工程
+ * 在Xcode的Build Setting里在Other Linker Flags中添加-ObjC和-all_load标记
+
+2. 加入MGTwitterEngine的JSON相关代码
+ * 从[MGTwitterEngine的页面](https://github.com/mattgemmell/MGTwitterEngine)down下该项目。当然如果有新版或者有别的branch可以用的话更省事儿,但是鉴于MGTwitterEngine现在的活跃度来说估计可能性不大,所以还是乖乖自己更新吧。
+ * 解开下载的zip,用Xcode打开MGTwitterEngine.xcodeproj工程文件,将其中Twitter YAJL Parsers组下的所有文件copy到自己的项目中。
+
+3. YAJL头文件集成
+ * 接下来是C和OC接口头文件的导入,从下面下载YAJL库:[https://github.com/thinglabs/yajl-objc](https://github.com/thinglabs/yajl-objc)
+ * 在下载得到的文件夹中,寻找并将以下h文件拷贝到自己的工程中:
+ * yajl_common.h
+ * yajl_gen.h
+ * yajl_parse.h
+ * NSObject+YAJL.h
+ * YAJL.h
+ * YAJLDocument.h
+ * YAJLGen.h
+ * YAJLParser.h
+
+4. 最后是在MGTwitterEngine设定为使用v1.1 API以及JSON方式请求
+
+在MGTwitterEngine.m中,将对应代码修改为以下:
+
+```objc
+#define USE_LIBXML 0
+#define TWITTER_DOMAIN @"api.twitter.com/1.1"
+```
+
+在MGbTwitader.h,启用YAJL
+
+```objc
+#define define YAJL_AVAILABLE 1
+```
+
+本文参考:
+
+
+[MGTwitterEngine issues 107](https://github.com/mattgemmell/MGTwitterEngine/issues/107)
+
+[http://damienh.org/2009/06/20/setting-up-mgtwitterengine-with-yajl-106-for-iphone-development/](http://damienh.org/2009/06/20/setting-up-mgtwitterengine-with-yajl-106-for-iphone-development/)
diff --git a/_posts/2013-04-01-half-year-in-japan.markdown b/_posts/2013-04-01-half-year-in-japan.markdown
new file mode 100644
index 00000000..cc62d7e2
--- /dev/null
+++ b/_posts/2013-04-01-half-year-in-japan.markdown
@@ -0,0 +1,59 @@
+---
+layout: post
+title: 赴日半年的一些杂感
+date: 2013-04-01 00:35:58.000000000 +09:00
+tags: 胡言乱语集
+---
+
+
+
+
+来日本已经足足有半年了,在这半年里见识了许多,也经历了许多。学生生涯的结束和职场生涯的开始,在这样的转变中积极投入到新的生活中去,大概也算是自己努力的一种方式。今天到公司很早,有机会整理一下这半年的一些体会和感想吧。
+
+## 关于日本
+
+其实日本对于中国和中国人来说,一直是个又爱又恨的国家。爱大抵是因为日本既有着无数的中国文化元素输入,同时又有着一大堆类似ACG的输出。前者拉近了中国与日本的距离,后者让世界有了解日本的窗口。而恨,基本都来源于七十多年前的那场战争。中国人的这种仇恨其实也并非与生俱来,而日本人也确实很难理解这种仇恨,我想这大抵和两个国家国民所受到的教育和舆论的导向不无关系。说到教育和舆论,中国的洗脑教育和言论管制估计在全球知名大国中是无出其右的。包括我在内,从小接受的就是长期而持续的仇恨教育灌输,所有能接触到的历史书籍中也都是宣扬两国民族仇恨的,我想这对于国人于日本的理解上造成了很大偏差。加上当代中国走了一些弯路,导致普遍性的国民信仰丢失和是非观的扭曲,导致了这种本不该存在的误解又进一步加深。
+
+相反地,在日本不管是电视新闻还是报纸,我都极少见到有针对中国的宣传。其实基本上电视新闻都很少会报道日本国外的消息。经常见到的都是本地哪个居民楼发生了火灾,或者谁家走失的猫狗被发现并寻找失主这样的消息。而唯一有的政治节目的形式一般是一大堆人坐成个圆桌讨论的形式,即使这样还是会请来不同方面的人,更像是一种讨论。比如之前说到钓鱼岛的问题,人员构成是两个主持+两个日本政界+两个中国人+一个美国人这样的组合,一群人都站在自己的利益角度吵得不亦乐乎。这在国内现在的请“砖家”出来唠叨教导大众的媒体模式下,应该是不可能出现的。
+
+但是同时,日本国民对于政治的不关心远远超出了我的想象,但是却正是一个这样对政治不关心的国家,却有着整个亚洲最民主的制度,这是一个很奇怪的现象。选举前几乎每天在车站都会有议员拿个喇叭宣扬自己党派和个人的理念思想,但路人匆匆都无人理睬(我想如果有人停下来和他辩论的话他也许会很开心);到现在选举已经尘埃落定后也每周会有不同的政治家到处演说。在中国,就算在北京,你也绝不可能看到国家财政部或者人事部的部长在做街头演说,也没有可能直面总书记或者国家主席,但是这些事情我却都在日本经历了,而且是作为一个外国人在不经意间就都经历了。中日两国在政治上的差距,还很大很大,而中国想要走的民主道路(希望如此),也还很长很长。
+
+
+
+其他的来说,印象最深刻的大抵就是和传说中一样的日本人的礼貌和以“耻文化”为基础的道德理念。虽然是在礼仪之邦长大的孩子,但是却是在这里感受到了更多的礼仪。服务行业就不用多说了,就算是普通生活中也会有很多的讲究。有时候真的不得不感慨是环境造就人的行为,在一个所有人都很互相尊重(至少是表面上互相尊重)的环境下,你也不得不学会去尊重别人。同样的,当人们都互相信任的时候,你也不由地变得愿意信任别人,这是一件让人感觉很好的事情。
+
+
+
+另外就是日本真的是一个很喜欢读书的国家,这一点虽然不让我吃惊,但是当走在街上很容易就看到很多书店的时候还是有些赞叹的。在电子书籍和信息时代的今天,实体书可能更多的已经成为一种符号了,至少在快餐文化的中国是如此。实体书在日本的畅销,一方面是因为地铁和文库本的贡献,另一方面大概是因为日本本身文化封闭的特性吧(之后会展开说这点)。
+
+## 关于工作
+
+
+
+工作上并没有什么特别值得称道的地方,本来也是作为漫漫人生中修行的一站来到这里的,所以说更多的还是希望用心体会这边的工作的精髓,而并不是去刻意地达成某些目标。虽然日本是一个游戏制作的传统强国,但是可以感受到在当今欧美大作不断频现和日本游戏固步自封的双重作用下,日本的游戏产业正在逐渐没落。虽然在社交领域有DeNA或者GREE,在手游上有去年风光满满的Puzzle And Dragon,但是给人的印象就是这些大抵都是only for Japan的东西。日本游戏界可以说看不到和外界交流的意愿,现在的日本游戏越来越难走向世界,世界的优秀游戏也越来越难进入日本市场,这大抵还是和当今主流文化是英语文化圈的欧美文化但是日本从业人员整体英语水平并不够有一定关系吧。
+
+除此之外,工作上还是很开心的。Kayac是一个很不错的公司,至少到现在我很享受在这里的工作。虽然加班是多了点,但是和最地狱的那段时间来说简直就是天堂(所以说趁着年轻过一些苦逼的日子是很有好处的,之后都会觉得比起以前不算什么)。不仅可以穿拖鞋汗衫进出公司,更可以每天面朝大海或者富士山写代码,这点比较惬意。
+
+不过工作上需要特别提的,最好的地方是,可以和其他国家(不单单日本的,还有法国越南印尼什么)的程序员一起工作,这对我来说是一种非常奇妙的体验。以前很多时候认为是非常正常的事情,以及非常正常的写法,有时候却在不同文化背景下会发生了一些奇妙的变化。会发现原来每个国家的coder写出来的东西真的是会带有coder个人的文化背景和思考方法的,这是以前完全没有想到的事情。比如日本的程序员写出来的东西总是很工整,每个类的格式甚至是申明变量的顺序都很规范,但是往往却在很多地方写的很啰嗦复杂。在你完全了解他的结构之前,读这样的代码很是痛苦,无尽的跳转和条件经常让人崩溃,有时候甚至不得不佩服在如此复杂的代码下居然没有出错。而法国人的代码却完全不一样,写的结构那个飘逸那个散,还时不时带上几句法语注释,虽说配合Google Translate可以猜个大概,但还是让人哭笑不得。
+
+Kayac的话据说有全日本最好的Perl程序员(或者说之一),但是很可惜我并不会也不想以Perl作为自己的开发语言,所以说基本没有交流,算是比较可惜。这边的话也有一些还算厉害的OC程序员和iOS开发者,有时候可以在网上看到一些他们的技术博客,也算不错。和其中一个在Kayac待了几年的大大玩的比较好,他居然还送了一本他写的OC的入门书给我,虽然说内容太基础对我没什么用处,但是这份情谊还是很珍贵的。
+
+技术力上的话,Kayac或者是大部分日本企业(猜测)并不是具有很强的技术能力。不管是在选用框架和编码能力上大部分员工都还很入门的感觉。不过这大抵是因为重视的方面不同,我们可能更看重个人能力和解决问题的速度质量,但是他们更多的是喜欢在范式和规则之下完成任务。这样一来,制定规则的人,或者说是项目的负责人的业务能力也就直接导致了项目的质量和进度。不过正如@钟亮所说,很多时候跳出技术的层面来看这些就会豁然开朗,无非就是遵循的规则和追求目标的手段的不一致,绝大部分最终的产品不会有太多人在意其中的技术细节。
+
+不过不管怎么样,技术强力还是很有好处的,一开始和同事互相不太认识的时候经常各种“被教导”和“被使唤”,后来逐渐实力被认可以后就转变成了总是“被请教”和“被提建议”。日本社会和日本人心态确实是会从骨子里尊敬强者,所以说想要立足以及赢得他们的尊重,只能迫使自己变得更强。
+
+每天很快就能搞定自己的任务,但是这边整个公司或者国家的氛围就是要加班到很晚,所以自己也不好到点走人。于是就有了以前不敢想象的大把时间用于学习和提高。闲着没事儿会琢磨学一些新的语言,或者是想办法将现有知识更深入,也会有时间经常关心一些业界的最新动态,这些都会很有帮助,也希望它们最终能成为自己人生的积淀。
+
+同时也在向日本人学习。不得不说一下现在在做的项目的Leader,是一个很有趣的人。年轻时候干的是潜水员,负责挖沉船探宝那种,后来体力逐渐跟不上,也考虑到相对危险,转行当了程序员。半路出家但是水平还不赖,更难能可贵的是一把年纪了却每天也还坚持学技术。从git到进阶C#再到模型和贴图入门什么的,我入职半年间,他案头的书都换了三四本。这种精神很让人佩服,也应当成为学习的榜样。
+
+## 关于生活
+
+关于这一点,嘛,至少可以不用待在北京吸毒气。在北京的时候因为空气的问题,经常咳嗽不舒服,每次沙尘的时候也完全不能出门。那时候雾霾还不叫雾霾,但是劣质空气不需要命名大家也心知肚明。一年中能见到蓝天的日子也屈指可数。别的不说,这边至少天蓝蓝,水蓝蓝,空气清新,多年的咳嗽到这边完全没有复发,这就比一切都强了。
+
+
+
+另外不需要因为户口什么的各种看派出所的脸色。印象里在日本我所得到的微笑和尊重比在国内加起来都多——不管是来自服务行业、政府部门还是平时接触的日本人。有时候仔细想想确实,纳税人辛辛苦苦创造的价值,却很大一部分得不到有效的利用。而去和自己供养的人打交道的时候,还要遭遇种种不便和蔑视。愤愤不平倒是没有,但心却拔凉拔凉。深知自己并不是二代,靠自己改变不了现状,剩下的选择就只有逃离(XD 当然没这么夸张的凄凉,只不过用脚投票也是现在的流行趋势是不)。
+
+暂时就写这么多吧,之后的生活,再慢慢体会。顺便送上一副京都的红叶。顺便提一句,本文照片都是自己拍摄的,版权所有。因此如果想要借作他用的话,还请麻烦知会一声,如果合适,会考虑给您高清版本的图片。
+
+
diff --git a/_posts/2013-04-06-our-money-app.markdown b/_posts/2013-04-06-our-money-app.markdown
new file mode 100644
index 00000000..66da67d6
--- /dev/null
+++ b/_posts/2013-04-06-our-money-app.markdown
@@ -0,0 +1,43 @@
+---
+layout: post
+title: 两个人一起记账吧~ Our Money
+date: 2013-04-06 00:37:13.000000000 +09:00
+tags: 南箕北斗集
+---
+
+[](https://itunes.apple.com/cn/app/our-money/id562520527?ls=1&mt=8)
+
+Our Money是一款能够协助多人在云端记账的iOS应用,可以帮助您简单地记录和整理日常开销,您可以邀请您的朋友和家人与您一起记账,免去每日汇报总结之苦。
+
+* [App Store地址](https://itunes.apple.com/cn/app/our-money/id562520527?ls=1&mt=8)
+* [Our Money app的首页](http://ourmoney.onevcat.com)
+
+大概但凡从按月领生活费开始花钱以来,都会兴起记账的念头,至于是否能够坚持,就各凭本事了。说到自己,则是多次付诸行动,然后不了了之。从一开始记在小本本上自己用计算器加加减减,到建个Excel文档自动求和,再到手机上的记账应用,时代在进步,咱的手段也在进步,却总还觉得没有找到最合适的工具。尤其是用手机记账以来,有的软件,每次对非得给一笔开销定义出两层的分类,让我头疼不已,家庭小帐非得整成个公司帐簿,改动标签也颇为麻烦;有的软件,记录条目倒是简单,但其他诸如统计等功能却也一起被简化了。不过,最让我郁闷的是,记账总成为我一个人的事情,谁让是用我的手机在记呢。
+
+现在,终于等到了一款操作简单但是功能齐全,尤其是,**可以多人共同记账的应用**。这款叫做Our Money 的应用,最大的亮点当然就在于“Our”。它可以实现多人一起记账,只要人手一个应用,就可以和家人一起记录家庭开销,和朋友一起整理出游费用,不同的帐本可以选择和不同的人分享,每个人都能参与,条目更新实时同步,再不用一个人负责所有的帐目。
+
+好啦,废话不多说,让我们一起来体验一下这个软件吧。[下载应用](https://itunes.apple.com/cn/app/our-money/id562520527?ls=1&mt=8)并打开,用邮箱注册用户,就可以开始记账啦。请记住你的邮箱是你邀请别人或者别人邀请你共同记账的标识哦~
+
+
+Our Money的主界面相当简洁,最上方列出列表名称,收入(预算)、支出、结余也一目了然,条目的时间、分类、备注都一目了然。那么其他其他内容被藏在哪里呢?左边一拉,当前列表的按月总计;右边一拉,列表编辑,数据统计,就是这么简单~
+
+
+
+
+首先我们新建一个列表, 在右边的界面下拉一下,就可以新建自己的列表了。选中的列表下方能够修改列表名称或者删除,中间的邀请就是重头戏啦,输入希望一同记账的朋友的邮箱,他就可以收到邀请并加入你的列表。当邀请了朋友或家人加入列表后,列表信息中就会显示多人同为列表用户。当然,在记账时随时可以邀请新的用户加入。
+
+
+
+选定刚才新建的列表,回到主界面,随便记下一点东西,在同一列表中的用户将通过推送(如果允许的话)收到您更改了列表的消息。而对方打开应用时,马上就可以同步地看到您所记录的信息,这便于双方更迅速地各自完成记账,免去了回家后苦苦思索或者汇总的麻烦,确实十分方便。
+
+
+
+记错了,找不到修改的地方怎么办?点一下,记录被选中,下面就出现了编辑或者删除的选项,还可以分享条目到社交网络,秀一下收到的礼物什么的哦~
+
+在消费和记账时难免会出现没有网络的尴尬时候,这时候Our Money还能正常工作么?当然,Our Money具有完善的离线模式处理,没有网络时照常使用,当之后连上网络的时候会自动为您完成所有同步,完全不用自己操心。
+
+
+
+总的来说Our Money是一款功能强大但又简单高效的记账软件,其云端记账和共同记账的理念很符合当今多人记账的需求。从今天开始就和家人朋友用Our Money一起记账吧~
+
+您可以从[App Store中下载Our Money](https://itunes.apple.com/cn/app/our-money/id562520527?ls=1&mt=8),还可以进一步通过应用内的赠送系统将您的记账和心得分享给家人朋友。
diff --git a/_posts/2013-04-13-ios-interview.markdown b/_posts/2013-04-13-ios-interview.markdown
new file mode 100644
index 00000000..d256b91a
--- /dev/null
+++ b/_posts/2013-04-13-ios-interview.markdown
@@ -0,0 +1,29 @@
+---
+layout: post
+title: 上级向的十个iOS面试问题
+date: 2013-04-13 00:39:32.000000000 +09:00
+tags: 南箕北斗集
+---
+
+
+
+不管对于招聘和应聘来说,面试都是很重要的一个环节,特别对于开发者来说,面试中的技术问题环节不仅是企业对应聘者技能和积累的考察,也是一个开发者自我检验的好机会。对于iOS和Mac开发来说,因为本事还算比较新,企业对于这方面的开发者需求也比较大,所以面试时的要求可能并不是很高,一般能知道一些Cocoa和OC的基本知识也就认为可以了。但是对于一个希望拥有技术力基础的企业的iOS或者Mac开发来说,两到三个顶尖的熟练技术人员,带领一些还较为初级的开发者,共同完成项目应该是比较常见的构成。
+
+Cocoa特别是CocoaTouch的开发,上手可以说十分容易,但是背后隐藏的细节和原理却很丰富。一方面对于基础不够熟练和清晰(比如从一个AppDelegate开始用代码构建ViewController,或者清晰地说明栈和堆之类的概念),另一方面对于更进阶的开发知之甚少(比如多线程、网络异步处理或者Core开头的各种框架等等)。这些内容十分重要,但是可能现在一般的iOS开发者或多或少都在这些问题上存在薄弱。在这里我整理了一份面向于较高层级的iOS开发者的面试题目的问题清单,列出了十个应聘Leader级别的高级Cocoa/CocoaTouch开发工程师所应该掌握和理解的技术。这份列表没有提供标准答案,因为这些问题本身就没有标准答案。随每个人对这些内容的认识的不同和理解的差异,可以有不一样的答案。但是最基本地,如果面对的是一名资深的Cocoa开发者,至少期望能得到的答案都是“接触过”,并且能结合自己的经验说个七七八八,达到互相能明白意图和方法的地步。能够在其中两三个领域有不错的见解和具体的阐述的话,那是更好。这种对于知识覆盖面和深度的考察很能真实反映出开发者的技术水平。如果清单里的很大部分内容都是完全没接触过和没听过的话,那可能距离资深Cocoa开发这样一个阶段还尚有距离了。
+
+那么,面试开始。
+
+1. 你使用过Objective-C的运行时编程(Runtime Programming)么?如果使用过,你用它做了什么?你还能记得你所使用的相关的头文件或者某些方法的名称吗?
+2. 你实现过多线程的Core Data么?NSPersistentStoreCoordinator,NSManagedObjectContext和NSManagedObject中的哪些需要在线程中创建或者传递?你是用什么样的策略来实现的?
+3. Core开头的系列的内容。是否使用过CoreAnimation和CoreGraphics。UI框架和CA,CG框架的联系是什么?分别用CA和CG做过些什么动画或者图像上的内容。(有需要的话还可以涉及Quartz的一些内容)
+4. 是否使用过CoreText或者CoreImage等?如果使用过,请谈谈你使用CoreText或者CoreImage的体验。
+5. NSNotification和KVO的区别和用法是什么?什么时候应该使用通知,什么时候应该使用KVO,它们的实现上有什么区别吗?如果用protocol和delegate(或者delegate的Array)来实现类似的功能可能吗?如果可能,会有什么潜在的问题?如果不能,为什么?(虽然protocol和delegate这种东西面试已经面烂了...)
+6. 你用过NSOperationQueue么?如果用过或者了解的话,你为什么要使用NSOperationQueue,实现了什么?请描述它和GCD的区别和类似的地方(提示:可以从两者的实现机制和适用范围来描述)。
+7. 既然提到GCD,那么问一下在使用GCD以及block时要注意些什么?它们两是一回事儿么?block在ARC中和传统的MRC中的行为和用法有没有什么区别,需要注意些什么?
+8. 您是否做过异步的网络处理和通讯方面的工作?如果有,能具体介绍一些实现策略么?
+9. 对于Objective-C,你认为它最大的优点和最大的不足是什么?对于不足之处,现在有没有可用的方法绕过这些不足来实现需求。如果可以的话,你有没有考虑或者实践过重新实现OC的一些功能,如果有,具体会如何做?
+10. 你实现过一个框架或者库以供别人使用么?如果有,请谈一谈构建框架或者库时候的经验;如果没有,请设想和设计框架的public的API,并指出大概需要如何做、需要注意一些什么方面,来使别人容易地使用你的框架。
+
+以上10个问题对于初级或者刚接触iOS的开发者来说,肯定是过于难了。想要答出全部问题,可能需要至少两到三年的Cocoa/CocoaTouch开发经验。而如果想要有所见地的回答,可能需要更长的时间和经验。这些问题对于技术的积累会是一个很好的考察,因为如果没有对这些问题中涉及的内容有过实际使用和体会的话,是很难较完整和全面回答这些问题的。同时,因为这些问题并不像ABCD的客观题有标准答案,表现的是应聘者的理解,所以提问者也必须具备必要的材料或者知识,以应对可能的讨论。
+
+在为团队寻求高级别的开发工程师或者Leader类的职位时,这些问题的回答会是对应聘者技术深度和广度的一个有效的考察。同样地,如果你的团队在Cocoa/CocoaTouch上比较偏重,但是技术团队的No.1的工程师却不能很好地回答这些问题的话,可能也会是需要检讨技术层的一个信号。
diff --git a/_posts/2013-04-28-itc-special-characters.markdown b/_posts/2013-04-28-itc-special-characters.markdown
new file mode 100644
index 00000000..5554a163
--- /dev/null
+++ b/_posts/2013-04-28-itc-special-characters.markdown
@@ -0,0 +1,20 @@
+---
+layout: post
+title: 苹果应用描述中不能使用特殊字符的对应方法
+date: 2014-04-28 00:40:39.000000000 +09:00
+tags: 南箕北斗集
+---
+
+
+
+### 该文章内容在iOS7中已经失效,请乖乖遵循苹果的规则写吧
+
+虽然很早Apple就说过从5月1日开始就不再允许UDID以及没有对iPhone5优化的应用上架,但是这次iTunes Connect的对于描述字符的限制还是让很多开发者措手不及。毕竟事先完全没有和大家打过招呼,Apple想要统一应用市场的风格和体验的心态可以理解,但是在开发者难得还有一点自由发挥的应用描述的地方突然作出这样的限制,确实不太厚道。相关的新闻报道可以参看[这里](http://www.cnbeta.com/articles/234799.htm)
+
+但是,难道我们真的没法使用更漂亮的描述了么?答案是,有办法!解决办法就一句话,**直接使用`字符值引用`来写iTC的描述就可以了~**
+
+比如,想使用`★`这个字符,在描述中将`★`的地方都换成`★`就可以了。
+
+一句废话,对于字符转换,当然也有在线将特殊字符转换为字符值引用的服务:[传送门](http://yasu.asuka.net/orkut/conv.html)
+
+最后,祝大家五一快乐,假期好心情~ :)
diff --git a/_posts/2013-04-29-using-blending-in-ios.markdown b/_posts/2013-04-29-using-blending-in-ios.markdown
new file mode 100644
index 00000000..df095366
--- /dev/null
+++ b/_posts/2013-04-29-using-blending-in-ios.markdown
@@ -0,0 +1,212 @@
+---
+layout: post
+title: iOS中使用blend改变图片颜色
+date: 2013-04-29 00:41:33.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+最近对`Core Animation`和`Core Graphics`的内容东西比较感兴趣,自己之前也在这块相对薄弱,趁此机会也想补习一下这块的内容,所以之后几篇可能都会是对CA和CG学习的记录的文章。
+
+在应用里一个很常见的需求是主题变换:同样的图标,同样的素材,但是需要按照用户喜爱变为不同的颜色。在iOS5和6的SDK里部分标准控件引入了`tintColor`,来满足个性化界面的需求,但是Apple在这方面还远远做的不够。一是现在用默认控件根本难以做出界面优秀的应用,二是`tintColor`所覆盖的并不够全面,在很多情况下开发者都无法使用其来完成个性化定义。解决办法是什么?最简单当然是拜托设计师大大出图,想要蓝色主题?那好,开PS盖个蓝色图层出一套蓝色的UI;又想加粉色UI,那好,再出一套粉色的图然后导入Xcode。代码上的话根据颜色需求使用image-blue或者image-pink这样的名字来加载图片。
+
+如果有一丁点重构的意识,就会知道这不是一个很好的解决方案。工程中存在大量的冗余和重复(就算你要狡辩这些图片颜色不同不算重复,你也会在内心里知道这种狡辩是多么无力),这是非常致命的。想象一下如果你有10套主题界面,先不论应用的体积会膨胀到多少,光是想做一点修改就会痛苦万分,比如希望改一下某个按钮的形状,很好,设计师大大请重复地修改10遍,并出10套UI,然后一系列的重命名,文件移动和导入…一场灾难。
+
+当然有其他办法,因为说白了就是tint不同的颜色到图片上而已,如果我们能实现改变UIImage的颜色,那我们就只需要一套UI,然后用代码来改变UI的颜色就可以了,生活有木有一下光明起来呀。嗯,让我们先从一张图片开始吧~下面是一张带有alpha通道的图片,原始颜色是纯的灰色(当然什么颜色都可以,只不过我这个人现在暂时比较喜欢灰色而已)。
+
+
+
+我们将用blending给这张图片加上另一个纯色作为tint,并保持原来的alpha通道。用Core Graphics来做的话,大概的想法很直接:
+
+1. 创建一个上下文用以画新的图片
+2. 将新的tintColor设置为填充颜色
+3. 将原图片画在创建的上下文中,并用新的填充色着色(注意保持alpha通道)
+4. 从当前上下文中取得图片并返回
+
+最麻烦的部分可能就是保持alpha通道了。[UIImage的文档](https://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIImage_Class/Reference/Reference.html)中提供了使用blend绘图的方法`drawInRect:blendMode:alpha:`,`rect`和`alpha`都没什么问题,但是`blendMode`是个啥玩意儿啊…继续看文档,关于[`CGBlendMode`的文档](https://developer.apple.com/library/ios/#documentation/GraphicsImaging/Reference/CGContext/Reference/reference.html#//apple_ref/doc/c_ref/CGBlendMode),里面有一大堆看不懂的枚举值,比如这样:
+
+```
+kCGBlendModeDestinationOver
+R = S*(1 - Da) + D
+Available in iOS 2.0 and later.
+Declared in CGContext.h.
+```
+
+完全不懂..直接看之后的Discussion部分:
+
+>The blend mode constants introduced in OS X v10.5 represent the Porter-Duff blend modes. The symbols in the equations for these blend modes are:
+R is the premultiplied result
+S is the source color, and includes alpha
+D is the destination color, and includes alpha
+Ra, Sa, and Da are the alpha components of R, S, and D
+
+原来如此,R表示结果,S表示包含alpha的原色,D表示包含alpha的目标色,Ra,Sa和Da分别是三个的alpha。明白了这些以后,就可以开始寻找我们所需要的blend模式了。相信你可以和我一样,很快找到这个模式:
+
+```
+kCGBlendModeDestinationIn
+R = D*Sa
+Available in iOS 2.0 and later.
+Declared in CGContext.h.
+```
+
+结果 = 目标色和原色透明度的加成,看起来正式所需要的。啦啦啦,还等什么呢,开始动手实现看看对不对吧~
+
+为了以后使用方便,当然是祭出Category,先创建一个UIImage的类别:
+
+```objc
+// UIImage+Tint.h
+
+#import
+
+@interface UIImage (Tint)
+
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor;
+
+@end
+```
+暂时先这样,当然我们也可以创建一个类方法直接完成从bundle读取图片然后加tintColor,但是很多时候并不如上面一个实例方法方便(比如想要从非bundle的地方获取图片),这个问题之后再说。那么就按照之前设想的步骤来实现吧:
+
+```objc
+// UIImage+Tint.m
+
+#import "UIImage+Tint.h"
+
+@implementation UIImage (Tint)
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor
+{
+ //We want to keep alpha, set opaque to NO; Use 0.0f for scale to use the scale factor of the device’s main screen.
+ UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
+ [tintColor setFill];
+ CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
+ UIRectFill(bounds);
+
+ //Draw the tinted image in context
+ [self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];
+
+ UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return tintedImage;
+}
+@end
+```
+
+简单明了,没有任何难点。测试之:`[[UIImage imageNamed:@"image"] imageWithTintColor:[UIColor orangeColor]];`,得到的结果为:
+
+
+
+嗯...怎么说呢,虽然tintColor的颜色是变了,但是总觉得怪怪的。仔细对比一下就会发现,原来灰色图里星星和周围的灰度渐变到了橙色的图里好像都消失了:星星整个变成了橙色,周围的一圈漂亮的光晕也没有了,这是神马情况啊…这种图能交差的话那算见鬼了,会被设计和产品打死的吧。对于无渐变的纯色图的图来说直接用上面的方法是没问题的,但是现在除了Metro的大色块以外哪里无灰度渐变的设计啊…检查一下使用的blend,`R = D * Sa`,恍然大悟,我们虽然保留了原色的透明度,但是却把它的所有的灰度信息弄丢了。怎么办?继续刨`CGBlendMode`的文档吧,那么多blend模式应该总有我们需要的。功夫不负有心人,`kCGBlendModeOverlay`一副嗷嗷待选的样子:
+
+
+```
+kCGBlendModeOverlay
+Either multiplies or screens the source image samples with the background image samples, depending on the background color. The result is to overlay the existing image samples while preserving the highlights and shadows of the background. The background color mixes with the source image to reflect the lightness or darkness of the background.
+Available in iOS 2.0 and later.
+Declared in CGContext.h.
+```
+
+kCGBlendModeOverlay可以保持背景色的明暗,也就是灰度信息,听起来正是我们需要的。加入到声明中,并且添加相应的实现( 顺便重构一下原来的代码 :) ):
+
+```objc
+// UIImage+Tint.h
+
+#import
+
+@interface UIImage (Tint)
+
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor;
+- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor;
+
+@end
+```
+
+```objc
+// UIImage+Tint.m
+
+#import "UIImage+Tint.h"
+
+@implementation UIImage (Tint)
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor
+{
+ return [self imageWithTintColor:tintColor blendMode:kCGBlendModeDestinationIn];
+}
+
+- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor
+{
+ return [self imageWithTintColor:tintColor blendMode:kCGBlendModeOverlay];
+}
+
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor blendMode:(CGBlendMode)blendMode
+{
+ //We want to keep alpha, set opaque to NO; Use 0.0f for scale to use the scale factor of the device’s main screen.
+ UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
+ [tintColor setFill];
+ CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
+ UIRectFill(bounds);
+
+ //Draw the tinted image in context
+ [self drawInRect:bounds blendMode:blendMode alpha:1.0f];
+
+ UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return tintedImage;
+}
+
+@end
+```
+
+完成,测试之…好吧,好尴尬,虽然颜色和周围的光这次对了,但是透明度又没了啊魂淡..一点不奇怪啊,因为`kCGBlendModeOverlay`本来就没承诺给你保留原图的透明度的说。
+
+
+
+那么..既然我们用`kCGBlendModeOverlay`能保留灰度信息,用`kCGBlendModeDestinationIn`能保留透明度信息,那就两个blendMode都用不就完事儿了么~尝试之,如果在blend绘图时不是`kCGBlendModeDestinationIn`模式的话,则再用`kCGBlendModeDestinationIn`画一次:
+
+```objc
+// UIImage+Tint.m
+
+#import "UIImage+Tint.h"
+
+@implementation UIImage (Tint)
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor
+{
+ return [self imageWithTintColor:tintColor blendMode:kCGBlendModeDestinationIn];
+}
+
+- (UIImage *) imageWithGradientTintColor:(UIColor *)tintColor
+{
+ return [self imageWithTintColor:tintColor blendMode:kCGBlendModeOverlay];
+}
+
+- (UIImage *) imageWithTintColor:(UIColor *)tintColor blendMode:(CGBlendMode)blendMode
+{
+ //We want to keep alpha, set opaque to NO; Use 0.0f for scale to use the scale factor of the device’s main screen.
+ UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0f);
+ [tintColor setFill];
+ CGRect bounds = CGRectMake(0, 0, self.size.width, self.size.height);
+ UIRectFill(bounds);
+
+ //Draw the tinted image in context
+ [self drawInRect:bounds blendMode:blendMode alpha:1.0f];
+
+ if (blendMode != kCGBlendModeDestinationIn) {
+ [self drawInRect:bounds blendMode:kCGBlendModeDestinationIn alpha:1.0f];
+ }
+
+ UIImage *tintedImage = UIGraphicsGetImageFromCurrentImageContext();
+ UIGraphicsEndImageContext();
+
+ return tintedImage;
+}
+
+@end
+```
+
+结果如下:
+
+
+
+已经很完美了,这样的话只要在代码里设定一下颜色,我们就能够很轻易地使用同样一套UI,将其blend为需要的颜色,来实现素材的重用了。唯一需要注意的是,因为每次使用`UIImage+Tint`的方法绘图时,都使用了CG的绘制方法,这就意味着每次调用都会是用到CPU的Offscreen drawing,大量使用的话可能导致性能的问题(主要对于iPhone 3GS或之前的设备,可能同时处理大量这样的绘制调用的能力会有不足)。关于CA和CG的性能的问题,打算在之后用一篇文章来介绍一下。对于这里的`UIImage+Tint`的实现,可以写一套缓存的机制,来确保大量重复的元素只在load的时候blend一次,之后将其缓存在内存中以快速读取。当然这是一个权衡的问题,在时间和空间中做出正确的平衡和选择,也正是程序设计的乐趣所在。
+
+这篇文章中作为示例的工程和UIImage+Tint可以在[Github](https://github.com/onevcat/VVImageTint)上找到,您可以随意玩弄..我相信也会是个来研究每种blend的特性的好机会~
diff --git a/_posts/2013-05-24-talk-about-warning.markdown b/_posts/2013-05-24-talk-about-warning.markdown
new file mode 100644
index 00000000..ce5dc8ce
--- /dev/null
+++ b/_posts/2013-05-24-talk-about-warning.markdown
@@ -0,0 +1,78 @@
+---
+layout: post
+title: 谈谈Objective-C的警告
+date: 2013-05-24 00:43:19.000000000 +09:00
+tags: 胡言乱语集
+---
+> 一个有节操的程序员会在乎自己的代码的警告,就像在乎饭碗边上有只死蟑螂那样。
+——[@onevcat](http://weibo.com/onevcat)
+
+### 重视编译警告
+
+现在编译器有时候会很吵,而编译器给出的警告对开发者来说是很有用的信息。警告不会阻止继续编译和链接,也不会导致程序不能运行,但是很多时候编译器会先你一步发现问题所在,对于Objective-C来说特别如此。[Clang](http://clang.llvm.org/)不仅对于明显的错误能够提出警告(比如某方法或者接口未实现),也能对很多潜在可能的问题做出提示(比如方法已经废弃或者有问题的转换),而这些问题在很多时候都可能成为潜在的致命错误,必须加以重视。
+
+像Ruby或者PHP这样的动态语言没有所谓的编译警告,而C#或者Java这类语言的警告很多都是不得不照顾的废弃方法什么的,很多开发者已经习惯于忽略警告进行开发。OC由于现在由苹果负责维护,Clang的LLVM也同时是苹果在做,可以说从语言到编译器到SDK全局都在掌握之中,因此做OC开发时的警告往往比其他语言的警告更有参考价值。打开尽可能多的警告提示,并且在程序开发中尽量避免生成警告,对于构建一个健壮高效的程序来说,是必须的。
+
+### 在Xcode中开启额外警告提示
+Xcode的工程模板已经为我们设置开启了一些默认和常用的警告提示,这些默认设置为了兼容一些上年头的项目,并没有打开很多,仅是指对最危险和最常见的部分进行了警告。这对于一个新项目来说这是不够用的(至少对我来说是不够用的),在无数前辈大牛的教导下,首先要做的事情就是打开尽可能多的警告提示。
+
+最简单的方法是通过UI来打开警告。在Xcode中,Build Setting选项里为我们预留了一些打开警告的开关,找到并直接勾选相应的选项就可以打开警告。大部分时间里选项本身已经足够能描述警告的作用和产生警告的时机,如果不是很明白的话,在右侧的Quick Help面板里有更详细的说明。对于OC开发来说特有的警告都在`Apple LLVM compiler 4.2 - Warnings - Objective C`一栏中,不管您是不是决定打开它们,都是值得花时间看一看加以了解的,因为它们都是写OC程序时最应该避免的情况。另外几个`Apple LLVM compiler 4.2 - Warnings - …`(All languages和C++)也包含了大量的选项,以方便控制警告产生。
+
+
+
+当然在UI里一个一个点击激活警告虽然简单,但每次都这样来一回是一种一点也不有趣的做法,特别是在你已经了解它们的内容并决定打开它们的时候。在编译选项中加入合适的flag能够打开或者关闭警告:在Build Setting中的Other C Flags里添加形似`-W...`的编译标识。你可以在其中填写任意多的`-W...`以开关某些警告,比如,填写为`-Wall -Wno-unused-variable`即可打开“全部”警告(其实并不是全部,只是一大部分严重警告而已),但是不启用“未使用变量”的警告。使用`-W...`的形式,而不是在UI上勾选的一大好处是,在编译器版本更新时,新加入的警告如果包含在`-Wall`中的话,不需要对工程做任何修改,新的警告即可以生效。这样立即可以察觉到同一个工程由于编译器版本更新时可能带来的隐患。另外一个更重要的原因是..Xcode的UI并没有提供所有的警告 =_=||..
+
+刚才提到的,需要注意的是,`-Wall`的名字虽然是all,但是这真的只是一个迷惑人的词语,实际上`-Wall`涵盖的仅只是所有警告中的一个子集。在[StackExchange](http://programmers.stackexchange.com/questions/122608/clang-warning-flags-for-objective-c-development/124574#124574)上有一个在Google工作的Clang开发者进行的回答,其中解释了有一些重要的警告组:
+
+* -Wall 并**不是**所有警告。这一个警告组开启的是编译器开发者对于“你所写的代码中有问题”这一命题有着很高的自信的那些警告。要是在这一组设定下你的代码出现了警告,那基本上就是你的代码真的存在严重问题了。但是同时,并不是说打开Wall就万事大吉了,因为Wall所针对的仅仅只是经典代码库中的为数不多的问题,因此有一些致命的警告并不能被其捕捉到。但是不论如何,因为Wall的警告提供的都是可信度和优先级很高的警告,所以为所有项目(至少是所有新项目)打开这组警告,应该成为一种良好的习惯。
+* -Wextra 如其所名,`-Wextra`组提供“额外的”警告。这个组和`-Wall`组几乎一样有用,但是有些情况下对于代码相对过于严苛。一个很常见的例子是,`-Wextra`中包含了`-Wsign-compare`,这个警告标识会开启比较时候对signed和unsigned的类型检查,当比较符两边一边是signed一边是unsigned时,产生警告。其实很多代码并没有特别在意这样的比较,而且绝大多数时候,比较signed和unsigned也是没有太大问题的(当然不排除会有致命错误出现的情况)。需要注意,`-Wextra`和`-Wall`是相互独立的两个警告组,虽然里面打开的警告标识有个别是重复的,但是两组并没有包含的关系。想要同时使用的话必须在Other C Flags中都加上
+* -Weverything 这个是真正的所有警告。但是一般开发者不会选择使用这个标识,因为它包含了那些还正在开发中的可能尚存bug的警告提示。这个标识一般是编译器开发者用来调试时使用的,如果你想在自己的项目里开启的话,警告一定会爆棚导致你想开始撞墙..
+
+
+
+关于某个组开启了哪些警告的说明,在GCC的手册中有[一个参考](http://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html)。虽然苹果现在用的都是LLVM了,但是这部分内容应该是继承了GCC的设定。
+
+### 控制警告,局部加入或关闭
+Clang提供了我们自己加入警告或者暂时关闭警告的办法。
+
+强制加入一个警告:
+
+```objc
+//Generate a warning
+#pragma message "Warning 1"
+
+//Another way to generate a warning
+#warning "Warning 2"
+```
+
+两种强制警告的方法在视觉效果上结果是一样的,但是警告类型略有不同,一个是`-W#pragma-messages`,另一个是`-W#warnings`。对于第二种写法,把warning换成error,可以强制使编译失败。比如在发布一些需要API Key之类的类库时,可以使用这个方法来提示别的开发者别忘了输入必要的信息。
+
+```objc
+//Generate an error to fail the build.
+#error "Something wrong"
+```
+
+对于关闭某个警告,如果需要全局关闭的话,直接在Other C Flags里写`-Wno-...`就行了,比如`-Wextra -Wno-sign-compare`就是一个常见的组合。如果相对某几个文件开启或禁用警告,在Build Phases的Compile Source相应的文件中加入对应的编译标识即可。如果只是想在某几行关闭某个警告的话,可以通过临时改变诊断编译标记来抑制指定类型的警告,具体如下:
+
+```objc
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-variable"
+
+int a;
+
+#pragma clang diagnostic pop
+```
+
+如果a之后没有被使用,也不会出未使用变量的警告了。对于想要抑制的警告类型的标识名,可以在build产生该警告后的build log中看到。Xcode中的话,快捷键Cmd+7然后点击最近的build log中,进入详细信息中就能看到了。
+
+
+
+### 我应该开启哪些警告提示
+
+个人喜好(代码洁癖)不同,会有不同的需求。我的建议是对于所有项目,特别是新开的项目,首先开启`-Wall`和`-Wextra`,然后在此基础上构建项目并且避免一切警告。如果在开发过程中遇到了某些确实无法解决或者确信自己的做法是正确的话(其实这种情况,你的做法一般即使不是错误的,也会是不那么正确的),可以有选择性地关闭某些警告。一般来说,关闭的警告项目不应该超过一只手能数出来的数字,否则一定哪儿出问题了..
+
+### 是否要让警告等于错误
+
+一种很常见的做法和代码洁癖是将警告标识为错误,从而中断编译过程。这让开发者不得不去修复这些警告,从而保持代码干净整洁。在Xcode中,可以通过勾选相应的Treat Warnings as Errors来开启,或者加入`-Werror`标识。我个人来说不喜欢使用这个设定,因为它总是打断开发流程。很多时候并不可能把代码全写完再编译调试,相反地,我更喜欢写一点就编译运行一下看看结果,这样在中间debug编译的时候会出现警告也不足为奇。另外,如果做TDD开发时,也可能会有大量正常的警告出现,如果有警告就不让编译的话,开发效率可能会打折扣。一个比较好的做法是只在Release Build时将警告视为错误,因为Xcode中是可以为Debug和Release分别指定标识的,所以这很容易做到。
+
+另外也可以只把某些警告当作错误,`-Werror=...`即可,同样地,也可以在`-Werror`被激活时使用`-Wno-error=...`来使某些警告不成为错误。结合使用这些编译标识可以达到很好的控制。
diff --git a/_posts/2013-06-11-developer-should-know-about-ios7.markdown b/_posts/2013-06-11-developer-should-know-about-ios7.markdown
new file mode 100644
index 00000000..09eb0a11
--- /dev/null
+++ b/_posts/2013-06-11-developer-should-know-about-ios7.markdown
@@ -0,0 +1,115 @@
+---
+layout: post
+title: 开发者所需要知道的iOS7 SDK新特性
+date: 2013-06-11 00:45:02.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+春风又绿加州岸,物是人非又一年。WWDC 2013 keynote落下帷幕,新的iOS开发旅程也由此开启。在iOS7界面重大变革的背后,开发者们需要知道的又有哪些呢。同去年一样,我会先简单纵览地介绍iOS7中我个人认为开发者需要着重关注和学习的内容,之后再陆续对自己感兴趣章节进行探索。计划继承类似[WWDC2012的笔记](http://onevcat.com/2012/06/%E5%BC%80%E5%8F%91%E8%80%85%E6%89%80%E9%9C%80%E8%A6%81%E7%9F%A5%E9%81%93%E7%9A%84ios6-sdk%E6%96%B0%E7%89%B9%E6%80%A7/)的形式,希望对国内开发者有所帮助。
+
+相关笔记整理如下:
+
+* 总览 [开发者所需要知道的iOS7 SDK新特性](http://onevcat.com/2013/06/developer-should-know-about-ios7/)
+* 工具 [WWDC2013笔记 Xcode5和ObjC新特性](http://onevcat.com/2013/06/new-in-xcode5-and-objc/)
+* UIKit动力学 [WWDC2013笔记 UIKit力学模型入门](http://onevcat.com/2013/06/uikit-dynamics-started/)
+* SpriteKit入门 [WWDC2013笔记 SpriteKit快速入门和新时代iOS游戏开发指南](http://onevcat.com/2013/06/sprite-kit-start/)
+* 后台应用运行和多任务新特性 [WWDC2013笔记 iOS7中的多任务](http://onevcat.com/2013/08/ios7-background-multitask/)
+* iOS7中弹簧式列表的制作 [WWDC 2013 Session笔记 - iOS7中弹簧式列表的制作](http://onevcat.com/2013/09/spring-list-like-ios7-message)
+* iOS7中自定义ViewController切换效果 [WWDC 2013 Session笔记 - iOS7中的ViewController切换](http://onevcat.com/2013/10/vc-transition-in-ios7/)
+
+---
+
+### UI相关
+#### 全新UI设计
+iOS7最大的变化莫过于UI设计,也许你会说UI设计“这是设计师大大们应该关注的事情,不关开发者的事,我们只需要替换图片就行了”。那你就错了。UI的变化必然带来使用习惯和方式的转变,如何运用iOS7的UI,如何是自己的应用更切合新的系统,都是需要考虑的事情。另外值得注意的是,使用iOS7 SDK(现在只有Xcode5预览版提供)打包的应用在iOS7上运行时将会自动使用iOS7的新界面,所以原有应用可能需要对新界面进行重大调整。具体的iOS7中所使用的UI元素的人际交互界面文档,可以从[这里](https://developer.apple.com/library/prerelease/ios/design/index.html#//apple_ref/doc/uid/TP40013289)找到(应该是需要开发者账号才能看)。
+
+
+简单总结来说,以现在上手体验看来新的UI变化改进有如下几点:
+
+* 状态栏,导航栏和应用实际展示内容不再界限:系统自带的应用都不再区分状态栏和navigation bar,而是用统一的颜色力求简洁。这也算是一种趋势。
+* BarItem的按钮全部文字化:这点做的相当坚决,所有的导航和工具条按钮都取消了拟物化,原来的文字(比如“Edit”,“Done”之类)改为了简单的文字,原来的图标(比如新建或者删除)也做了简化。
+* 程序打开加入了动画:从主界面到图标所在位置的一个放大,同时显示应用的载入界面。
+
+自己实验了几个现有的AppStore应用在iOS7上的运行情况:
+
+* [Pomodoro Do](https://itunes.apple.com/app/id533469911?mt=8): 这是我自己开发的应用,运行正常,但是因为不是iOS7 SDK打包,所以在UI上使用了之前系统的,问题是导航栏Tint颜色丢失,导致很难看,需要尽快更新。
+* Facebook:因为使用了图片自定义导航栏,而没有直接使用系统提供的材质,所以没什么问题。
+* 面包旅行:直接Crash,无法打开,原因未知。
+
+这次UI大改可以说是一次对敏捷开发的检验,原来的应用(特别是拟物化用得比较重的应用)虽然也能运行,但是很多UI自定义的地方需要更改不说,还容易让用户产生一种“来到了另一个世界”的感觉,同时可以看到也有部分应用无法运行。而对于苹果的封闭系统和只升不降的特性,开发者以及其应用必须要尽快适应这个新系统,这对于迭代快速,还在继续维护的应用来说会是一个机会。相信谁先能适应新的UI,谁就将在iOS7上占到先机。
+
+#### UIKit的力学模型(UIKit Dynamics)
+
+这个专题的相关笔记
+
+> UIKit动力学 [WWDC2013笔记 UIKit力学模型入门](http://onevcat.com/2013/06/uikit-dynamics-started/) http://onevcat.com/2013/06/uikit-dynamics-started/
+
+新增了`UIDynamicItem`委托,用来为UIView制定力学模型行为,当然其他任何对象都能通过实现这组接口来定义动力学行为,只不过在UIKit中可能应用最多。所谓动力学行为,是指将现实世界的我们常见的力学行为或者特性引入到UI中,比如重力等。通过实现UIDynamicItem,UIKit现在支持如下行为:
+
+* UIAttachmentBehavior 连接两个实现了UIDynamicItem的物体(以下简称动力物体),一个物体移动时,另一个跟随移动
+* UICollisionBehavior 指定边界,使两个动力物体可以进行碰撞
+* UIGravityBehavior 顾名思义,为动力物体增加重力模拟
+* UIPushBehavior 为动力物体施加持续的力
+* UISnapBehavior 为动力物体指定一个附着点,想象一下类似挂一幅画在图钉上的感觉
+
+如果有开发游戏的童鞋可能会觉得这些很多都是做游戏时候的需求,一种box2d之类的2D物理引擎的既视感跃然而出。没错的亲,动态UI,加上之后要介绍的Sprite Kit,极大的扩展了使用UIKit进行游戏开发的可能性。另外要注意UIDynamicItem不仅适用于UIKit,任何对象都可以实现接口来获得动态物体的一些特性,所以说用来做一些3D的或者其他奇怪有趣的事情也不是没有可能。如果觉得Cocos2D+box2d这样的组合使用起来不方便的话,现在动态UIKit+SpriteKit给出了新的选择。
+
+### 游戏方面
+
+这个专题的相关笔记
+
+> SpriteKit入门 [WWDC2013笔记 SpriteKit快速入门和新时代iOS游戏开发指南](http://onevcat.com/2013/06/sprite-kit-start/) http://onevcat.com/2013/06/sprite-kit-start/
+
+iOS7 SDK极大加强了直接使用iOS SDK制作和分发游戏的体验,最主要的是引入了专门的游戏制作框架。
+
+#### Sprite Kit Framework
+这是个人认为iOS7 SDK最大的亮点,也是最重要的部分,iOS SDK终于有自己的精灵系统了。Sprite Kit Framework使用硬件加速的动画系统来表现2D和2.5D的游戏,它提供了制作游戏所需要的大部分的工具,包括图像渲染,动画系统,声音播放以及图像模拟的物理引擎。可以说这个框架是iOS SDK自带了一个较完备的2D游戏引擎,力图让开发者专注于更高层的实现和内容。和大多数游戏引擎一样,Sprite Kit内的内容都按照场景(Scene)来分开组织,一个场景可以包括贴图对象,视频,形状,粒子效果甚至是CoreImage滤镜等等。相对于现有的2D引擎来说,由于Sprite Kit是在系统层级进行的优化,渲染时间等都由框架决定,因此应该会有比较高的效率。
+
+另外,Xcode还提供了创建粒子系统和贴图Atlas的工具。使用Xcode来管理粒子效果和贴图atlas,可以迅速在Sprite Kit中反应出来。
+
+#### Game Controller Framework
+为Made-for-iPhone/iPod/iPad (MFi) game controller设计的硬件的对应的框架,可以让用户用来连接和控制专门的游戏硬件。参考WWDC 2013开场视频中开始的赛车演示。现在想到的是,也许这货不仅可以用于游戏…或者苹果之后会扩展其应用,因为使用普及率很高的iPhone作为物联网的入口,似乎会是很有前途的事情。
+
+#### GameCenter改进
+GameCenter一直是苹果的败笔...虽然每年都在改进,但是一直没看到大的起色。今年也不例外,都是些小改动,不提也罢。
+
+### 多任务强化
+
+这个专题的相关笔记
+
+> 后台应用运行和多任务新特性 [WWDC2013笔记 iOS7中的多任务](http://onevcat.com/2013/08/ios7-background-multitask/) http://onevcat.com/2013/08/ios7-background-multitask/
+
+* 经常需要下载新内容的应用现在可以通过设置`UIBackgroundModes`为`fetch`来实现后台下载内容了,需要在AppDelegate里实现`setMinimumBackgroundFetchInterval:`以及`application:performFetchWithCompletionHandler: `来处理完成的下载,这个为后台运行代码提供了又一种选择。不过考虑到Apple如果继续严格审核的话,可能只有杂志报刊类应用能够取得这个权限吧。另外需要注意开发者仅只能指定一个最小间隔,最后下没下估计就得看系统娘的心情了。
+* 同样是后台下载,以前只能推送提醒用户进入应用下载,现在可以接到推送并在后台下载。UIBackgroundModes设为remote-notification,并实现`application:didReceiveRemoteNotification:fetchCompletionHandler:`
+
+为后台下载,开发者必须使用一个新的类`NSURLSession`,其实就是在NSURLConnection上加了个后台处理,使用类似,API十分简单,不再赘述。
+
+### AirDrop
+这个是iOS7的重头新功能,用户可以用它来分享照片,文档,链接,或者其他数据给附近的设备。但是不需要特别的实现,被集成在了标准的UIActivityViewController里,并没有单独的大块API提供。数据的话,可以通过实现UIActivityItemSource接口后进行发送。大概苹果也不愿意看到超出他们控制的文件分享功能吧,毕竟这和iOS设计的初衷不一样。如果你不使用UIActivityViewController的话,可能是无法在应用里实装AirDrop功能了。
+
+另外,结合自定义的应用文件类型,可以容易地实现在后台接收到特定文件后使用自己的应用打开,也算是增加用户留存和回访的一个办法。但是这样做现在看来比较讨厌的是会有将所有文件都注册为可以打开的应用(比如Evernote或者Dropbox之类),导致接收到AirDrop发送的内容的时候会弹出很长一排选项,体验较差,只能说希望Apple以后能改进吧
+
+### 地图
+Apple在继续在地图应用上的探索,MapKit的改进也乏善可陈。我一直相信地图类应用的瓶颈一定在于数据,但是对于数据源的建立并不是一年两年能够完成的。Google在这一块凭借自己的搜索引擎有着得天独厚的优势,苹果还差的很远很远。看看有哪些新东西吧:
+
+* MKMapCamera,可以将一个MKMapCamera对象添加到地图上,在指明位置,角度和方向后将呈现3D的样子…大概可以想象成一个数字版的Google街景..
+* MKDirections 获取Apple提供的基于方向的路径,然后可以用来将路径绘制在自己的应用中。这可能对一些小的地图服务提供商产生冲击,但是还是那句话,地图是一个数据的世界,在拥有完备数据之前,Apple不是Google的对手。这个状况至少会持续好几年(也有可能是永远)。
+* MKGeodesicPolyline 创建一个随地球曲率的线,并附加到地图上,完成一些视觉效果。
+* MKMapSnapshotter 使用其拍摄基于地图的照片,也许各类签到类应用会用到
+* 改变了overlay物件的渲染方式
+
+### Inter-App Audio 应用间的音频
+AudioUnit框架中加入了在同一台设备不同应用之间发送MIDI指令和传送音频的能力。比如在一个应用中使用AudioUnit录音,然后在另一个应用中打开以处理等。在音源应用中声明一个AURemoteIO实例来标为Inter-App可用,在目标应用中使用新的发现接口来发现并获取音频。
+
+想法很好,也算是在应用内共享迈出了一步,不过我对现在使用AudioUnit这样的低层级框架的应用数量表示不乐观。也许今后会有一些为更高层级设计的共享API提供给开发者使用。毕竟要从AudioUnit开始处理音频对于大多数开发者来说并不是一件很容易的事情。
+
+### 点对点连接 Peer-to-Peer Connectivity
+可以看成是AirDrop不能直接使用的补偿,代价是需要自己实现。MultipeerConnectivity框架可以用来发现和连接附近的设备,并传输数据,而这一切并不需要有网络连接。可以看到Apple逐渐在文件共享方面一步步放开限制,但是当然所有这些都还是被限制在sandbox里的。
+
+### Store Kit Framework
+Store Kit在内购方面采用了新的订单系统,这将可以实现对订单的本机验证。这是一次对应内购破解和有可能验证失败导致内购失败的更新,苹果希望藉此减少内购的实现流程,减少出错,同时遏制内购破解泛滥。前者可能没有问题,但是后者的话,因为objc的动态特性,决定了只要有越狱存在,内购破解也是早晚的事情。不过这一点确实方便了没有能力架设验证服务器的小开发者,这方面来说还是很好的。
+
+### 最后
+当然还有一些其他小改动,包括MessageUI里添加了附件按钮,Xcode开始支持模块了等等。完整的iOS7新特性列表可以在[这里](https://developer.apple.com/library/prerelease/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS7.html#//apple_ref/doc/uid/TP40013162-SW1)找到(暂时应该也需要开发者账号)。最后一个好消息是,苹果放慢了废弃API的速度,这个版本并没有特别重要的API被标为Deprecated,Cheers。
diff --git a/_posts/2013-06-13-new-in-xcode5-and-objc.markdown b/_posts/2013-06-13-new-in-xcode5-and-objc.markdown
new file mode 100644
index 00000000..27ca18e4
--- /dev/null
+++ b/_posts/2013-06-13-new-in-xcode5-and-objc.markdown
@@ -0,0 +1,161 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - Xcode5和ObjC新特性
+date: 2013-06-13 00:48:32.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 400 What's New in Xcode 5
+* Session 401 Xcode Core Concepts
+* Session 407 Debugging with Xcode
+* Session 404 Advances in Objective-C
+
+
+等Tools模块下的内容
+
+随着iOS7 SDK的beta放出,以及Xcode 5 DP版本的到来,很多为iOS7开发应用的方式已经逐渐浮现。可以豪不夸张地讲,由于iOS7的UI发生了重大变革,此次的升级不同于以往,我们将会迎来iOS开发诞生以来最剧烈的变动,如何拥抱变化,快速适应新的世界和平台,值得每个Cocoa和CocoaTouch开发者研究。工欲善其事,必先利其器。想做iOS7的开发,就必须切换到Xcode5和新的ObjC体系(包括新引入的语法和编译器),在这里我简要地对新添加或重大变化的功能做一个小结。
+
+## 说说新的Xcode
+
+Xcode4刚出的时候存在茫茫多似乎无穷无尽的bug(如果是一路走来的同仁可能对此还记忆犹新),好消息是这次Xcode5 DP版本似乎相当稳定,如果你遇到了开启新Xcode就报错强退的话,多半原因是因为你在使用为Xcode4制作的插件,不同版本的Xcode是共用同一个文件夹下的插件的,请将`~/Library/Application Support/Developer/Shared/Xcode/Plug-ins`目录下的内容清理一下,应该就能顺利进入Xcode5了。
+
+
+
+Xcode 5现在使用了ARC,取代了原来的垃圾回收(Garbage collection)机制,因此不论从启动速度和使用速度上来说都比之前快了不少。现在大部分的AppStore提交应用也都使用了ARC,新SDK中加入的系统框架也全都是ARC的了。另外,在Xcode5中新建工程也不再提供是否使用ARC的选项(虽然也还是可以在Build Setting中关掉)。如果你还在使用手动内存管理的话,现在是时候抛弃release什么的了,如果你还在迷茫应该应该怎么使用ARC,可以参看一下去年这个时候我发的一篇[ARC的教程文章](http://onevcat.com/2012/06/arc-hand-by-hand/)。
+
+### 界面变化
+
+
+
+首先值得称赞的是顶部工具栏的变化,新版中贯彻了精简的原则,将顶栏砍掉了30%左右的宽度,对于小屏幕来说绝对是福音。另外,在外观上界面也向平面和简洁的方向迈进了一大步,可算是对iOS7的遥相呼应吧。
+
+### 更易用的版本管理
+
+
+
+虽然在Xcode 4里就集成了版本管理的内容,但是一直被藏的很深,很多时候开发者不得不打开Organizer才能找到对应操作的地方。与之相比,Xcode5为版本管理留出了专门的一个`Source Control`菜单,从此以后妈妈再也不用担心我找不到git放哪儿了。集成的版本管理可以方便地完成大部分初级功能,包括Check Out,Pull,Commit,Push,Merge等,特别是在建立仓库和检出仓库时十分方便。但是在遇到稍微复杂的git操作时还是感到力不从心(比如rebase或摘樱桃的时候),这点上毕竟Xcode并不是一个版本管理app,而最基本的几个操作在日常工作中也算能快速地应付绝大部分情况(在不将工程文件添加到版本管理的情况下)。
+
+值得称赞的是在编辑代码的时候,可以直接对某一行进行blame了,在该行点击右键选Show Blame for Line,就能看到最后改动的人的信息。另外,Version Editor(View->Version Editor)也除了之前就有的版本对比之外,还新加了Blame和Log两种视图。在对代码历史追溯这块,Xcode5现在已经做的足够好了.
+
+结论是,虽然有所进步,但是Xcode的内置版本管理仍然不堪大任,命令行或者一个专业的git管理工具还是必要的。
+
+### 方便的工程配置
+
+与版本管理的强化相比较,工程配置方面也进行了很多加强,简化了之前开发者的需要做的一些配置工作。首先是在Build Setting的General里,加入了Team的设置,只要填写对应的Apple ID和应用Bundle ID,Xcode就将自动去寻找对应的Provisioning Profile,并使用合适的Provisioning来进行应用打包。因为有了自动配置和将集成的版本管理放到了菜单栏中,Organizer的地位被大大削弱了。至少我现在在Organizer中没有找到本机的证书管理和Provisioning Profile管理的地方,唯一开Organizer的理由大概就是应用打包发布时了。想想从远古时代的Application Loader一步一步走到现在,Xcode可以说在简化流程,帮助开发者快速发布应用方面做了很大努力。
+
+另一个重要改进是在Build选项中加入了`Capabilities`标签,如下图
+
+
+
+想想看以前为app配置iCloud要花的步骤吧:到Apple Developer里找到应用的ID,打开对应的app的iCloud功能,生成对应的Provisioning文件,回到Xcode创建一个Entitlements文件,定义Key-Value Store,Ubiquity Containers和Keychain Groups,然后你才能开始为应用创建UIDocument并且继续开发。哦天啊…作为学习来说做一次还能接受,但是如果每次开发应用都要来一遍这个过程,只能用枯燥乏味四个字来形容了。于是,正如你所看到的,现在你需要做的是,点一下iCloud的开关,然后…开始编程吧~轻松惬意。同样的方法也适用于Apple提供的其他服务,包括打开和配置GameCenter,Passbook,IAP,Maps,Keychain,后台模式和Data Protection,当然还有iOS7新加入的Inter-app Audio。这些小开关做的事情都很简单,但确实十分贴心。
+
+### 资源管理,Asset Catalog和Image Slicing
+
+资源目录(Asset Catalog)和图像切片(Image Slicing)是Xcode5新加入的功能。资源目录可以方便开发者管理工程中使用的图片素材,利用开发中的命名规则(比如高清图的@2x,图标的Icon,Splash的Default等),来筛选和分类图片。建立一个资源目录十分简单,如果是老版本导入的工程,在工程设置中图标或者splash图的设置中点击`Use Asset Catalog`,Xcode将建立新的资源目录;如果是直接使用Xcode 5建立的工程的话,那么资源目录应该已经默认躺在工程中了。
+
+
+
+添加资源目录后,在工程中会新加一个.xcassets后缀的目录用以整理和存放图片,该文件夹中存放了图片和对应的json文件来保存图片信息。为了能够使用资源目录的特性,以及更好的前向兼容性,建议将所有的图片资源都加入资源目录中:在工程中选择.xcassets文件,然后在资源目录中点击加号即可添加图片。另外,直接从工程外的Finder中将图片拖动到Xcode的资源目录界面中,也将把拖进来的图片拷贝并添加到资源目录中。对的,不再会有讨厌的弹窗出来,问你要拷贝还是要引用了。
+
+
+
+Asset Catalog的意义在于为工程中的图片提供了一个存储信息的地方,不仅可以描述资源对应的设备,资源的版本和更新信息等,更重要的在于可以为Image Slicing服务。所谓Image Slicing,相当于一个可视化的`resizableImageWithCapInsets:resizingMode:`,可以用于指定在图片缩放时用来填充的像素。在资源目录中选择要slicing的图片,点击图片界面右下方的Show Slicing按钮,在想要设定切片的图片上点击`Start Slicing`,将出现左中右(或者上中下)三条可以拖动的指示线,通过拖动它们来设定实际的缩放范围。
+
+
+
+在左侧线(或者上方线)和中间线之间的像素将在缩放时被填充,在中间线和右侧线(或者下方线)之间的像素将被隐藏。比如上面的例子,实际运行中如果对这张图片进行拉伸的话,会是下面的样子:
+
+
+
+Image Slicing可以帮助开发者用可视化的方式完成resizable image,之后通过拖拖线就可以完成sliced image,而不必再写代码,也不用再一次次尝试输入的insets合不合适了。slicing可缩放的图片大量用于UI中可以节省打包的占用空间,而在Xcode 5中引入和加强图片资源管理的目的,很大一部分是为了配合SpriteKit将游戏引擎加入到SDK中,并将Xcode逐渐打造为一个全面的IDE工具。
+
+### 新的调试和辅助功能
+
+这应该是Xcode5最值得称赞的改进了,在调试中现在在编辑框内鼠标悬浮在变量名上,Xcode将会根据类型进行猜测,并输出最合适的结果以帮助观察。就像这样:
+
+
+
+以前版本的Xcode虽然也有鼠标悬浮提示,但是想从中找到想要的value确实还是比较麻烦的事情,很多时候我们不得不参考下面Variables View的值或者直接p或者po它们,现在如果只是需要知道变量情况的话,在断到代码后一路用鼠标跟着代码走一遍,就差不多了然于胸了。如果你认为鼠标悬停只能打打字符串或者数字的话你就错了,数组,字典什么的也不在话下,更过分的是设计图像的也能很好地显示,只需要点击预览按钮,就像这样:
+
+
+
+Xcode5集成了一个Debug面板,用来实现一个简单的Profiler,可以在调试时直接看到应用的CPU消耗,内存使用等情况(其他的还有iCloud情况,功耗和图形性能等)。在Debug运行时Cmd+6即可切换到该Debug界面。监测的内容简单明了,CPU使用用来检查是否有高占用或者尖峰(特别是主线程中),内存检测用来检查内存使用和释放的情况是否符合预期。
+
+
+
+如果养成开发过程的调试中就一直打开这个Profiler面板的话(至少我从之后会坚持这个做法了),相信是有助于在开发过程中就迅速的监测到潜在的问题,并迅速解决的。当然,对于明显的问题可以在Debug面板中发现后立即寻找对应代码解决,但是如果比较复杂的问题,想要知道详细情况的话,还是要使用Instruments,在Debug面板中提供了一个“Profile In Instruments”按钮,可以快速跳转到Instruments。
+
+最后,Xcode在注释式文档方面也有进步,现在如下格式的注释将在Xcode中直接被检测到并集成进代码提示中了:
+
+```objc
+/**
+ * Setup a recorder for a specified file path. After setting it, you can use the other control method to control the shared recorder.
+ *
+ * @param talkingPath An NSString indicates in which path the recording should be created
+ * @returns YES if recorder setup correctly, NO if there is an error
+ */
+- (BOOL)recordWithFilePath:(NSString *)talkingPath;
+```
+
+得到的结果是这样的
+
+
+
+以及Quick Help中会有详细信息
+
+
+
+Xcode现在可以识别Javadoc格式(类似于上面例子)的注释文档,可用的标识符除了上面的`@param`和`@return`外,还有例如`@see`,`@discussion`等,关于Javadoc的更多格式规则,可以参考[Wiki](http://en.wikipedia.org/wiki/Javadoc)。
+
+## 关于Objective-C,Modules和Autolinking
+
+OC自从Apple接手后,一直在不断改进。随着移动开发带来的OC开发者井喷式增加,客观上也要求Apple需要提供各种良好特性来支持这样一个庞大的开发者社区。iOS4时代的GCD,iOS5时代的ARC,iOS6时代的各种简化,每年我们都能看到OC在成为一种先进语言上的努力。基于SmallTalk和runtime,本身是C的超集,如此“根正苗红”的一门语言,在今年也迎来的新的变化。
+
+今年OC的最大变化就是加入了Modules和Autolinking。
+
+### 什么是Modules呢
+
+在了解Modules之前我们需要先了解一下OC的import机制。`#import `,我相信每个开发者都写过这样的代码,用来引用其他的头文件。熟悉C或者C++的童鞋可能会知道,在C和C++里是没有#import的,只有#include(虽然GCC现在为C和C++做了特殊处理使得import可以被编译),用来包含头文件。#include做的事情其实就是简单的复制粘贴,将目标.h文件中的内容一字不落地拷贝到当前文件中,并替换掉这句include,而#import实质上做的事情和#include是一样的,只不过OC为了避免重复引用可能带来的编译错误(这种情况在引用关系复杂的时候很可能发生,比如B和C都引用了A,D又同时引用了B和C,这样A中定义的东西就在D中被定义了两次,重复了),而加入了#import,从而保证每个头文件只会被引用一次。
+
+> 如果想深究,import的实现是通过#ifndef一个标志进行判断,然后在引入后#define这个标志,来避免重复引用的
+
+实质上import也还是拷贝粘贴,这样就带来一个问题:当引用关系很复杂,或者一个头文件被非常多的实现文件引用时,编译时引用所占的代码量就会大幅上升(因为被引用的头文件在各个地方都被copy了一遍)。为了解决这个问题,C系语言引入了预编译头文件(PreCompiled Header),将公用的头文件放入预编译头文件中预先进行编译,然后在真正编译工程时再将预先编译好的产物加入到所有待编译的Source中去,来加快编译速度。比如iOS开发中Supporting Files组内的.pch文件就是一个预编译头文件,默认情况下,它引用了UIKit和Foundation两个头文件--这是在iOS开发中基本每个实现文件都会用到的东西。
+
+于是理论上说,想要提高编译速度,可以把所有头文件引用都放到pch中。但是这样面临的问题是在工程中随处可用本来不应该能访问的东西,而编译器也无法准确给出错误或者警告,无形中增加了出错的可能性。
+
+于是Modules诞生了。Modules相当于将框架进行了封装,然后加入在实际编译之时加入了一个用来存放已编译添加过的Modules列表。如果在编译的文件中引用到某个Modules的话,将首先在这个列表内查找,找到的话说明已经被加载过则直接使用已有的,如果没有找到,则把引用的头文件编译后加入到这个表中。这样被引用到的Modules只会被编译一次,但是在开发时又不会被意外使用到,从而同时解决了编译时间和引用泛滥两方面的问题。
+
+稍微追根问底,Modules是什么?其实无非是对框架进行了如下封装,拿UIKit为例:
+
+```objc
+framework module UIKit {
+ umbrella header "UIKit.h"
+ module * {export *}
+ link framework "UIKit"
+}
+```
+
+这个Module定义了首要头文件(UIKit.h),需要导出的子modules(所有),以及需要link的框架名称(UIKit)。需要指出的是,现在Module还不支持第三方的框架,所以只有SDK内置的框架能够从这个特性中受益。另外,在C++的源代码中,Modules也是被禁用的。
+
+### 好了,说了那么多,这玩意儿怎么用呢
+
+关于普通开发者使用的这个新特性的方法,Apple在LLVM5.0(也就是Xcode5带的最新的编译器前端中)引入了一个新的编译符号`@import`,使用@符号将告诉编译器去使用Modules的引用形式,从而获取好处,比如想引用MessageUI,可以写成
+
+```objc
+@import MessageUI;
+```
+
+在使用上,这将等价于以前的`#import `,但是将使用Modules的特性。如果只想使用某个特性的.h文件,比如`#import `,对应写作
+
+```objc
+@import MessageUI.MFMailComposeViewController;
+```
+当然,如果对于以前的工程,想要使用新的Modules特性,如果要把所有头文件都这样一个一个改成`@import`的话,会是很大的一个工作量。Apple自然也考虑到了这一点,于是对于原来的代码,只要使用的是iOS7或者MacOS10.9的SDK,在Build Settings中将Enable Modules(C and Objective-C)打开,然后保持原来的`#import`写法就行了。是的,不需要任何代码上的改变,编译器会在编译的时候自动地把可能的地方换成Modules的写法去编译的。
+
+Autolinking是Modules的附赠小惊喜,因为在module定义的时候指定来link framework,所以在编译module时LLVM会将所涉及到的框架自动帮你写到link里去,不再需要到编译设置里去添加了。
diff --git a/_posts/2013-06-15-uikit-dynamics-started.markdown b/_posts/2013-06-15-uikit-dynamics-started.markdown
new file mode 100644
index 00000000..980a57be
--- /dev/null
+++ b/_posts/2013-06-15-uikit-dynamics-started.markdown
@@ -0,0 +1,237 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - UIKit Dynamics入门
+date: 2013-06-15 00:50:00.000000000 +09:00
+tags: 能工巧匠集
+---
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 206 Getting Started with UIKit Dynamics
+* Session 221 Advanced Techniques with UIKit Dynamics
+
+### 什么是UIKit动力学(UIKit Dynamics)
+
+其实就是UIKit的一套动画和交互体系。我们现在进行UI动画基本都是使用CoreAnimation或者UIView animations。而UIKit动力学最大的特点是将现实世界动力驱动的动画引入了UIKit,比如重力,铰链连接,碰撞,悬挂等效果。一言蔽之,即是,将2D物理引擎引入了人UIKit。需要注意,UIKit动力学的引入,并不是以替代CA或者UIView动画为目的的,在绝大多数情况下CA或者UIView动画仍然是最优方案,只有在需要引入逼真的交互设计的时候,才需要使用UIKit动力学它是作为现有交互设计和实现的一种补充而存在的。
+
+目的当然是更加自然和炫目的UI动画效果,比如模拟现实的拖拽和弹性效果,放在以前如果单用iOS SDK的动画实现起来还是相当困难的,而在UIKit Dynamics的帮助下,复杂的动画效果可能也只需要很短的代码(基本100行以内...其实现在用UIView animation想实现一个不太复杂的动画所要的代码行数都不止这个数了吧)。总之,便利多多,配合UI交互设计,以前很多不敢想和不敢写(至少不敢自己写)的效果实现起来会非常方便,也相信在iOS7的时代各色使用UIKit动力学的应用的在动画效果肯定会上升一个档次。
+
+### 那么,应该怎么做呢
+
+#### UIKit动力学实现的结构
+
+为了实现动力UI,需要注册一套UI行为的体系,之后UI便会按照预先的设定进行运动了。我们应该了解的新的基本概念有如下四个:
+
+
+
+* UIDynamicItem:用来描述一个力学物体的状态,其实就是实现了UIDynamicItem委托的对象,或者抽象为有面积有旋转的质点;
+* UIDynamicBehavior:动力行为的描述,用来指定UIDynamicItem应该如何运动,即定义适用的物理规则。一般我们使用这个类的子类对象来对一组UIDynamicItem应该遵守的行为规则进行描述;
+* UIDynamicAnimator;动画的播放者,动力行为(UIDynamicBehavior)的容器,添加到容器内的行为将发挥作用;
+* ReferenceView:等同于力学参考系,如果你的初中物理不是语文老师教的话,我想你知道这是啥..只有当想要添加力学的UIView是ReferenceView的子view时,动力UI才发生作用。
+
+光说不练假把式,来做点简单的demo吧。比如为一个view添加重力行为:
+
+```objc
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(100, 50, 100, 100)];
+ aView.backgroundColor = [UIColor lightGrayColor];
+ [self.view addSubview:aView];
+
+ UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
+ UIGravityBehavior* gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[aView]];
+ [animator addBehavior:gravityBeahvior];
+ self.animator = animator;
+}
+```
+代码很简单,
+
+1. 以现在ViewController的view为参照系(ReferenceView),来初始化一个UIDynamicAnimator。
+2. 然后分配并初始化一个动力行为,这里是UIGravityBehavior,将需要进行物理模拟的UIDynamicItem传入。`UIGravityBehavior`的`initWithItems:`接受的参数为包含id的数组,另外`UIGravityBehavior`实例还有一个`addItem:`方法接受单个的id。就是说,实现了UIDynamicItem委托的对象,都可以看作是被力学特性影响的,进而参与到计算中。UIDynamicItem委托需要我们实现`bounds`,`center`和`transform`三个属性,在UIKit Dynamics计算新的位置时,需要向Behavior内的item询问这些参数,以进行正确计算。iOS7中,UIView和UICollectionViewLayoutAttributes已经默认实现了这个接口,所以这里我们直接把需要模拟重力的UIView添加到UIGravityBehavior里就行了。
+3. 把配置好的UIGravityBehavior添加到animator中。
+4. strong持有一下animator,避免当前scope结束被ARC释放掉(后果当然就是UIView在哪儿傻站着不掉)
+
+运行结果,view开始受重力影响了:
+
+
+
+#### 碰撞,我要碰撞
+
+没有碰撞的话,物理引擎就没有任何意义了。和重力行为类似,碰撞也有一个`UIDynamicBehavior`子类来描述碰撞行为,即`UICollisionBehavior`。在上面的demo中加上几句:
+
+```objc
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ UIView *aView = [[UIView alloc] initWithFrame:CGRectMake(100, 50, 100, 100)];
+ aView.backgroundColor = [UIColor lightGrayColor];
+ [self.view addSubview:aView];
+
+ UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
+ UIGravityBehavior* gravityBeahvior = [[UIGravityBehavior alloc] initWithItems:@[aView]];
+ [animator addBehavior:gravityBeahvior];
+
+ UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[aView]];
+ collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
+ [animator addBehavior:collisionBehavior];
+ collisionBehavior.collisionDelegate = self;
+
+ self.animator = animator;
+}
+```
+
+也许聪明的你已经看到了,还是一样的,创建新的行为规则(UICollisionBehavior),然后加到animator中…唯一区别的地方是碰撞需要设定碰撞边界范围translatesReferenceBoundsIntoBoundary将整个参照view(也就是self.view)的边框作为碰撞边界(另外你还可以使用setTranslatesReferenceBoundsIntoBoundaryWithInsets:这样的方法来设定某一个区域作为碰撞边界,更复杂的边界可以使用addBoundaryWithIdentifier:forPath:来添加UIBezierPath,或者addBoundaryWithIdentifier:fromPoint:toPoint:来添加一条线段为边界,详细地还请查阅文档);另外碰撞是有回调的,可以在self中实现`UICollisionBehaviorDelegate`。
+
+最后,只是直直地掉下来的话未免太无聊了,加个角度吧:
+
+```objc
+aView.transform = CGAffineTransformRotate(aView.transform, 45);
+```
+
+结果是这样的,帅死了…这在以前只用iOS SDK的话,够写上很长时间了吧..
+
+
+
+碰撞的delegate可以帮助我们了解碰撞的具体情况,包括哪个item和哪个item开始发生碰撞,碰撞接触点是什么,是否和边界碰撞,和哪个边界碰撞了等信息。这些回调方法保持了Apple一向的命名原则,所以通俗易懂。需要多说一句的是回调方法中对于ReferenceView的Bounds做边界的情况,BoundaryIdentifier将会是nil,自行添加的其他边界的话,ID自然是添加时指定的ID了。
+
+* – collisionBehavior:beganContactForItem:withBoundaryIdentifier:atPoint:
+* – collisionBehavior:beganContactForItem:withItem:atPoint:
+* – collisionBehavior:endedContactForItem:withBoundaryIdentifier:
+* – collisionBehavior:endedContactForItem:withItem:
+
+
+#### 其他能实现的效果
+
+除了重力和碰撞,iOS SDK还预先帮我们实现了一些其他的有用的物理行为,它们包括
+
+* UIAttachmentBehavior 描述一个view和一个锚相连接的情况,也可以描述view和view之间的连接。attachment描述的是两个点之间的连接情况,可以通过设置来模拟无形变或者弹性形变的情况(再次希望你还记得这些概念,简单说就是木棒连接和弹簧连接两个物体)。当然,在多个物体间设定多个;UIAttachmentBehavior,就可以模拟多物体连接了..有了这些,似乎可以做个老鹰捉小鸡的游戏了- -…
+* UISnapBehavior 将UIView通过动画吸附到某个点上。初始化的时候设定一下UISnapBehavior的initWithItem:snapToPoint:就行,因为API非常简单,视觉效果也很棒,估计它是今后非游戏app里会被最常用的效果之一了;
+* UIPushBehavior 可以为一个UIView施加一个力的作用,这个力可以是持续的,也可以只是一个冲量。当然我们可以指定力的大小,方向和作用点等等信息。
+* UIDynamicItemBehavior 其实是一个辅助的行为,用来在item层级设定一些参数,比如item的摩擦,阻力,角阻力,弹性密度和可允许的旋转等等
+
+UIDynamicItemBehavior有一组系统定义的默认值,
+
+* allowsRotation YES
+* density 1.0
+* elasticity 0.0
+* friction 0.0
+* resistance 0.0
+
+所有的UIDynamicBehavior都是可以独立作用的,同时作用时也遵守力的合成。也就是说,组合使用行为可以达到一些较复杂的效果。举个例子,希望模拟一个drag物体然后drop后下落的过程,可以用如下代码:
+
+```objc
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+
+ UIDynamicAnimator* animator = [[UIDynamicAnimator alloc] initWithReferenceView:self.view];
+ UICollisionBehavior* collisionBehavior = [[UICollisionBehavior alloc] initWithItems:@[self.square1]];
+
+ collisionBehavior.translatesReferenceBoundsIntoBoundary = YES;
+ [animator addBehavior:collisionBehavior];
+
+ UIGravityBehavior *g = [[UIGravityBehavior alloc] initWithItems:@[self.square1]];
+ [animator addBehavior:g];
+
+ self.animator = animator;
+}
+
+
+-(IBAction)handleAttachmentGesture:(UIPanGestureRecognizer*)gesture
+{
+ if (gesture.state == UIGestureRecognizerStateBegan){
+
+ CGPoint squareCenterPoint = CGPointMake(self.square1.center.x, self.square1.center.y - 100.0);
+ CGPoint attachmentPoint = CGPointMake(-25.0, -25.0);
+
+ UIAttachmentBehavior* attachmentBehavior = [[UIAttachmentBehavior alloc] initWithItem:self.square1 point:attachmentPoint attachedToAnchor:squareCenterPoint];
+
+ self.attachmentBehavior = attachmentBehavior;
+ [self.animator addBehavior:attachmentBehavior];
+
+ } else if ( gesture.state == UIGestureRecognizerStateChanged) {
+
+ [self.attachmentBehavior setAnchorPoint:[gesture locationInView:self.view]];
+
+ } else if (gesture.state == UIGestureRecognizerStateEnded) {
+ [self.animator removeBehavior:self.attachmentBehavior];
+ }
+}
+```
+
+viewDidiLoad时先在现在环境中加入了重力,然后监测到pan时附加一个UIAttachmentBehavior,并在pan位置更新更新其锚点,此时UIAttachmentBehavior和UIGravityBehavior将同时作用(想象成一根木棒连着手指处和view)。在手势结束时将这个UIAttachmentBehavior移除,view将在重力作用下下落。整个过程如下图:
+
+
+
+### UIKit力学的物理学分析
+
+既然是力学,那显然各种单位是很重要的。在现实世界中,理想情况下物体的运动符合牛顿第二运动定理,在国际单位制中,力的单位是牛顿(N),距离单位是米(m),时间单位是秒(s),质量单位是千克(kg)。根据地球妈妈的心情,我们生活在这样一套体制中:重力加速度约为9.8m/s^2 ,加速度的单位是m/s^2 ,速度单位是m/s,牛顿其实是kg·m/s^2 ,即1牛顿是让质量为1千克的物体产生1米每二次方秒的加速度所需要的力。
+
+以上是帮助您回忆初中知识,而现在这一套体系在UIKit里又怎么样呢?这其实是每一个物理引擎都要讨论和明白的事情,UIKit的单位体制里由于m这个东西太过夸张,因此用等量化的点(point,之后简写为p)来代替。具体是这样的:UI重力加速度定义为1000p/s^2 ,这样的定义有两方面的考虑,一时为了简化,好记,确实1000比9.8来的只观好看,二是也算符合人们的直感:一个UIView从y=0开始自由落体落到屏幕底部所需的时间,在3.5寸屏幕上为0.98秒,4寸屏幕上为1.07秒,1秒左右的自由落体的视觉效果对人眼来说是很舒服能进行判断的。
+
+那么UIView的质量又如何定义呢,这也是很重要的,并涉及到力作用和加速度下UIView的表现。苹果又给出了自己的“UIKit牛顿第二定律”,定义了1单位作用力相当于将一个100px100p的默认密度的UIView以100p/s^2 的加速度移动。这里需要注意默认密度这个假设,因为在UIDynamicItem的委托中并没有实现任何密度相关的定义,而是通过UIDynamicItemBehavior来附加指定的。默认情况下,密度值为1,这就相当于质量是10000单位的UIView在1单位的作用力下可以达到1/10的UI重力加速度。
+
+这样类比之后的结论是,如果将1单位的UI力学中的力等同于1牛顿的话:
+
+* 1000单位的UI质量,与现实世界中1kg的质量相当,即一个点等同一克;
+* 屏幕的100像素的长度,约和现实世界中0.99米相当(完全可以看为1米)
+* UI力学中的默认密度,约和现实世界的0.1kg/m^2 相当
+
+可以说UIKit为我们构建了一套适应iOS屏幕的相当优雅的力学系统,不仅让人过目不忘,在实际的物理情景和用户体验中也近乎完美。在开发中,我们可以参照这些关系寻找现实中的例子,然后将其带入UIKit的力学系统中,以得到良好的模拟效果。
+
+### UIKit动力学自定义
+
+除了SDK预先定义好的行为以外,我们还可以自己定义想要的行为。这种定义可以发生在两个层级上,一种是将官方的行为打包,以简化实现。另一种是完全定义新的计算规则。
+
+对于第一种,其实考虑一下上面的重力+边界碰撞,或者drag & drop行为,其实都是两个甚至多个行为的叠加。要是每次都这样设定一次的话,不是很辛苦么,还容易遗忘出错。于是一种好的方式是将它们打包封装一下。具体地,如下步骤:
+
+1. 继承一下UIDynamicBehavior(在这里UIDynamicBehavior类似一个抽象类,并没有具体实现什么行为)
+2. 在子类中实现一个类似其他内置行为初始化方法`initWithItems:`,用以添加物体和想要打包的规则。当然你如果喜欢用其他方式也行..只不过和自带的行为保持API统一对大家都有好处..添加item的话就用默认规则的initWithItems:就行,对于规则UIDynamicBehavior提供了一个addChildBehavior:的方法,来将其他规则加入到当前规则里
+3. 没有第三步了,使用就行了。
+
+一个例子,打包了碰撞和重力两种行为,定义之后使用时就只需要写一次了。当然这只是最简单的例子和运用,当行为复杂以后,这样的使用方法是不可避免的,否则管理起来会让人有想死的心。另外,将手势等交互的方式也集成之中,进一步封装调用细节会是不错的实践。
+
+```objc
+//GravityWithCollisionBehavior.h
+@interface GravityWithCollisionBehavior : UIDynamicBehavior
+
+-(instancetype) initWithItems:(NSArray *)items;
+
+@end
+
+
+//GravityWithCollisionBehavior.m
+@implementation GravityWithCollisionBehavior
+
+-(instancetype) initWithItems:(NSArray *)items
+{
+ if (self = [super init]) {
+ UIGravityBehavior *gb = [[UIGravityBehavior alloc] initWithItems:items];
+ UICollisionBehavior *cb = [[UICollisionBehavior alloc] initWithItems:items];
+ cb.translatesReferenceBoundsIntoBoundary = YES;
+ [self addChildBehavior:gb];
+ [self addChildBehavior:cb];
+ }
+ return self;
+}
+
+@end
+```
+
+另一种比较高级一点,需要对计算完全定义。在默认的行为或者它们组合不能满足禽兽般的产品经理/设计师的需求是,亲爱的骚年..开始自己写吧..其实说简单也简单,UIDynamicBehavior里提供了一个`@property(nonatomic, copy) void (^action)(void)`,animator将在每次animation step(就是需要计算动画时)调用这个block。就是说,你可以通过设定这个block来实现自己的行为。基本思路就是在这个block中向所有item询问它们当前的center和transform状态,然后开始计算,然后把计算后的相应值再赋予item,从而改变在屏幕上的位置,大小,角度等。
+
+### UIKit动力学的性能分析和限制
+
+使用物理引擎不是没有代价的的,特别是在碰撞检测这块,是要耗费一定CPU资源的。但是以测试的情况来看,如果只是UI层面上的碰撞检测还是没有什么问题的,我自己实测iPhone4上同时进行数十个碰撞计算完全没有掉帧的情况。因此如果只是把其用在UI特效上,应该不用太在意资源的耗费。但是如果同时有成百上千的碰撞需要处理的情况,可能会出现卡顿吧。
+
+对于UIDynamicItem来说,当它们被添加到动画系统后,我们只能通过动画系统来改变位置,而外部的对于UIDynamicItem的center,transform等设定是被忽略的(其实这也是大多数2D引擎的实现策略,算不上限制)。
+
+主要的限制是在当计算迭代无法得到有效解的时候,动画将无法正确呈现。这对于绝大多数物理引擎都是一样的。迭代不能收敛时整个物理系统处于不确定的状态,比如初始时就设定了碰撞物体位于边界内部,或者在狭小空间内放入了过多的非弹性碰撞物体等。另外,这个引擎仅仅只是用来呈现UI效果,它并没有保证物理上的精确度,因此如果要用它来做UI以外的事情,有可能是无法得到很好的结果的。
+
+### 总结
+
+总之就是一套全新的UI交互的视觉体验和效果,但是并非处处适用。在合适的地方使用可以增加体验,但是也会有其他方式更适合的情况。所以拉上你的设计师好基友去开拓新的大陆吧…
diff --git a/_posts/2013-06-16-sprite-kit-start.markdown b/_posts/2013-06-16-sprite-kit-start.markdown
new file mode 100644
index 00000000..2ecce905
--- /dev/null
+++ b/_posts/2013-06-16-sprite-kit-start.markdown
@@ -0,0 +1,533 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - SpriteKit快速入门和新时代iOS游戏开发指南
+date: 2013-06-16 00:51:18.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 502 Introduction to Sprite Kit
+* Session 503 Designing Games with Sprite Kit
+
+SpriteKit的加入绝对是iOS 7/OSX 10.9的SDK最大的亮点。从此以后官方SDK也可以方便地进行游戏制作了。
+
+如果你在看这篇帖子,那我估计你应该稍微知道一些iOS平台上2D游戏开发的东西,比如cocos2d,那很好,因为SpriteKit的很多概念其实和cocos2d非常类似,你应该能很快掌握。如果上面这张图你看着眼熟,或者自己动手实践过,那更好,因为这篇文章的内容就是通过使用SpriteKit来一步一步带你重新实践一遍这个经典教程。如果你既不知道cocos2d,更没有使用游戏引擎开发iOS游戏的经验,只是想一窥游戏开发的天地,那现在,SpriteKit将是一个非常好的入口,因为是iOS SDK自带的框架,因此思想和用法上和现有的其他框架是统一的,这极大地降低了学习的难度和门槛。
+
+### 什么是SpriteKit
+
+首先要知道什么是`Sprite`。Sprite的中文译名就是精灵,在游戏开发中,精灵指的是以图像方式呈现在屏幕上的一个图像。这个图像也许可以移动,用户可以与其交互,也有可能仅只是游戏的一个静止的背景图。塔防游戏中敌方源源不断涌来的每个小兵都是一个精灵,我方防御塔发出的炮弹也是精灵。可以说精灵构成了游戏的绝大部分主体视觉内容,而一个2D引擎的主要工作,就是高效地组织,管理和渲染这些精灵。SpriteKit是在iOS7 SDK中Apple新加入的一个2D游戏引擎框架,在SpriteKit出现之前,iOS开发平台上已经出现了像cocos2d这样的比较成熟的2D引擎解决方案。SpriteKit展现出的是Apple将Xcode和iOS/Mac SDK打造成游戏引擎的野心,但是同时也确实与IDE有着更好的集成,减少了开发者的工作。
+
+### Hello SpriteKit
+
+废话不多说,本文直接上实例教程来说明SpriteKit的基本用法。
+
+
+
+好吧,我要做的是将非常风靡流行妇孺皆知的[raywenderlich的经典cocos2d教程](http://www.raywenderlich.com/25736/how-to-make-a-simple-iphone-game-with-cocos2d-2-x-tutorial)使用全新的SpriteKit重新实现一遍。重做这个demo的主要原因是cocos2d的这个入门实在是太经典了,包括了精灵管理,交互检测,声音播放和场景切换等等方面的内容,麻雀虽小,却五脏俱全。这个小demo讲的是一个无畏的英雄抵御外敌侵略的故事,英雄在画面左侧,敌人源源不断从右侧涌来,通过点击屏幕发射飞镖来消灭敌人,阻止它们越过屏幕左侧边缘。在示例中用到的素材,可以从[这里下载](/assets/images/2013/ResourcePackSpriteKit.zip)。另外为了方便大家,整个工程示例我也放在了github上,[传送门在此](https://github.com/onevcat/SpriteKitSimpleGame)。
+
+### 配置工程
+
+首先当然是建立工程,Xcode5提供了SpriteKit模板,使用该模板建立新工程,名字就叫做SpriteKitSimpleGame好了。
+
+
+
+因为我们需要一个横屏游戏,所以在新建工程后,在工程设定的General标签中,把Depoyment Info中Device Orientation中的Portrait勾去掉,使应用只在横屏下运行。另外,为了使之后的工作轻松一些,我们可以选择在初始的view显示完成,尺寸通过rotation计算完毕之后再添加新的Scene,这样得到的Scene的尺寸将是宽480(或者568)高320的size。如果在appear之前就使用bounds.size添加的话,将得到宽320 高480(568)的size,会很麻烦。将ViewController.m中的`-viewDidLoad:`方法全部替换成下面的`-viewDidAppear:`。
+
+```objc
+- (void)viewDidAppear:(BOOL)animated
+{
+ [super viewDidAppear:animated];
+
+ // Configure the view.
+ SKView * skView = (SKView *)self.view;
+ skView.showsFPS = YES;
+ skView.showsNodeCount = YES;
+
+ // Create and configure the scene.
+ SKScene * scene = [MyScene sceneWithSize:skView.bounds.size];
+ scene.scaleMode = SKSceneScaleModeAspectFill;
+
+ // Present the scene.
+ [skView presentScene:scene];
+}
+```
+
+然后编译运行,应如果一切正常,该显示类似于下面的画面,每点击画面时,会出现一架不停旋转的飞机。
+
+
+
+### 加入精灵
+
+SpriteKit是基于场景(Scene)来组织的,每个SKView(专门用来呈现SpriteKit的View)中可以渲染和管理一个SKScene,每个Scene中可以装载多个精灵(或者其他Node,之后会详细说明),并管理它们的行为。
+
+现在让我们在这个Scene里加一个精灵吧,先从我们的英雄开始。首先要做的是把刚才下载的素材导入到工程中。我们这次用资源目录(Asset Catalog)来管理资源吧。点击工程中的`Images.xcassets`,以打开Asset Catalog。将下载解压后Art文件夹中的图片都拖入到打开的资源目录中,资源目录会自动根据文件的命名规则识别图片,1x的图片将用于iPhone4和iPad3之前的非retina设备,2x的图片将用于retina设备。当然,如果你对设备性能有信心的话,也可以把1x的图片删除掉,这样在非retina设备中也将使用2x的高清图(画面上的大小自然会帮你缩小成2x的一半),以获取更好的视觉效果。做完这一步后,工程的资源目录会是这个样子的:
+
+
+
+开始coding吧~默认的SpriteKit模板做的事情就是在ViewController的self.view(这个view是一个SKView,可以到storyboard文件中确认一下)中加入并显示了一个SKScene的子类实例MyScene。正如在做app开发时我们主要代码量会集中在ViewController一样,在用SpriteKit进行游戏开发时,因为所有游戏逻辑和精灵管理都会在Scene中完成,我们的代码量会集中在SKScene中。在MyScene.m中,把原来的`-initWithSize`替换成这样:
+
+```objc
+-(id)initWithSize:(CGSize)size {
+ if (self = [super initWithSize:size]) {
+ /* Setup your scene here */
+
+ //1 Set background color for this scene
+ self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
+
+ //2 Create a new sprite
+ SKSpriteNode *player = [SKSpriteNode spriteNodeWithImageNamed:@"player"];
+
+ //3 Set it's position to the center right edge of screen
+ player.position = CGPointMake(player.size.width/2, size.height/2);
+
+ //4 Add it to current scene
+ [self addChild:player];
+ }
+ return self;
+}
+```
+
+1. 因为默认工程的Scene背景偏黑,而我们的主角和怪物也都是黑色的,所以先设定为白色。SKColor只是一个define定义而已,在iOS平台下被定义为UIColor,在Mac下被定义为NSColor。在SpriteKit开发时,尽量使用SK开头的对应的UI类可以统一代码而减少跨iOS和Mac平台的成本。类似的定义还有SKView,它在iOS下是UIView的子类,在Mac下是NSView的子类。
+2. 在SpriteKit中初始化一个精灵很简单,直接用`SKSpriteNode`的`+spriteNodeWithImageNamed:`,指定图片名就行。实际上一个SKSpriteNode中包含了贴图(SKTexture对象),颜色,尺寸等等参数,这个简便方法为我们读取图片,生成SKTexture,并设定精灵尺寸和图片大小一致。在实际使用中,绝大多数情况这个简便方法就足够了。
+3. 设定精灵的位置。SpriteKit中的坐标系和其他OpenGL游戏坐标系是一致的,屏幕左下角为(0,0)。不过需要注意的是不论是横屏还是竖屏游戏,view的尺寸都是按照竖屏进行计算的,即对于iPhone来说在这里传入的sizewidth是320,height是480或者568,而不会因为横屏而发生交换。因此在开发时,请千万不要使用绝对数值来进行位置设定及计算(否则你会死的很难看啊很难看)。
+4. 把player加入到当前scene中,addChild接受SKNode对象(SKSprite是SKNode的子类),关于SKNode稍后再说。
+
+运行游戏,yes~主角出现在屏幕上了。
+
+
+
+### 源源不断涌来的怪物大军
+
+没有怪物的陪衬,主角再潇洒也是寂寞。添加怪物精灵的方法和之前添加主角没什么两样,生成精灵,设定位置,加到scene中。区别在于怪物是会移动的 & 怪物是每隔一段时间就会出现一个的。
+
+在MyScene.m中,加入一个方法`-addMonster`
+
+```objc
+- (void) addMonster {
+
+ SKSpriteNode *monster = [SKSpriteNode spriteNodeWithImageNamed:@"monster"];
+
+ //1 Determine where to spawn the monster along the Y axis
+ CGSize winSize = self.size;
+ int minY = monster.size.height / 2;
+ int maxY = winSize.height - monster.size.height/2;
+ int rangeY = maxY - minY;
+ int actualY = (arc4random() % rangeY) + minY;
+
+ //2 Create the monster slightly off-screen along the right edge,
+ // and along a random position along the Y axis as calculated above
+ monster.position = CGPointMake(winSize.width + monster.size.width/2, actualY);
+ [self addChild:monster];
+
+ //3 Determine speed of the monster
+ int minDuration = 2.0;
+ int maxDuration = 4.0;
+ int rangeDuration = maxDuration - minDuration;
+ int actualDuration = (arc4random() % rangeDuration) + minDuration;
+
+ //4 Create the actions. Move monster sprite across the screen and remove it from scene after finished.
+ SKAction *actionMove = [SKAction moveTo:CGPointMake(-monster.size.width/2, actualY)
+ duration:actualDuration];
+ SKAction *actionMoveDone = [SKAction runBlock:^{
+ [monster removeFromParent];
+ }];
+ [monster runAction:[SKAction sequence:@[actionMove,actionMoveDone]]];
+
+}
+```
+
+1. 计算怪物的出生点(移动开始位置)的Y值。怪物从右侧屏幕外随机的高度处进入屏幕,为了保证怪物图像都在屏幕范围内,需要指定最小和最大Y值。然后从这个范围内随机一个Y值作为出生点。
+2. 设定出生点恰好在屏幕右侧外面,然后添加怪物精灵。
+3. 怪物要是匀速过来的话太死板了,加一点随机量,这样怪物有快有慢不会显得单调
+4. 建立SKAction。SKAction可以操作SKNode,完成诸如精灵移动,旋转,消失等等。这里声明了两个SKAction,`actionMove`负责将精灵在`actualDuration`的时间间隔内移动到结束点(直线横穿屏幕);`actionMoveDone`负责将精灵移出场景,其实是run一段接受到的block代码。`runAction`方法可以让精灵执行某个操作,而在这里我们要做的是先将精灵移动到结束点,当移动结束后,移除精灵。我们需要的是一个顺序执行,这里sequence:可以让我们顺序执行多个action。
+
+然后尝试在上面的`-initWithSize:`里调用这个方法看看结果
+
+```objc
+-(id)initWithSize:(CGSize)size {
+ if (self = [super initWithSize:size]) {
+ //...
+ [self addChild:player];
+ [self addMonster];
+ }
+ return self;
+}
+```
+
+
+Cool,我们的游戏有个能动的图像。知道么,游戏的本质是什么?就是一堆能动的图像!
+
+只有一个怪物的话,英雄大大还是很寂寞,所以我们说好了会有源源不断的怪物..在`-initWithSize:`的4之后加入以下代码
+
+```objc
+ //...
+ //5 Repeat add monster to the scene every 1 second.
+ SKAction *actionAddMonster = [SKAction runBlock:^{
+ [self addMonster];
+ }];
+ SKAction *actionWaitNextMonster = [SKAction waitForDuration:1];
+ [self runAction:[SKAction repeatActionForever:[SKAction sequence:@[actionAddMonster,actionWaitNextMonster]]]];
+ //...
+```
+
+这里声明了一个SKAction的序列,run一个block,然后等待1秒。用这个动作序列用`-repeatActionForever:`生成一个无限重复的动作,然后让scene执行。这样就可以实现每秒调用一次`-addMonster`来向场景中不断添加敌人了。如果你对Cocoa(Touch)开发比较熟悉的话,可能会说,为什么不用一个NSTimer来做同样的事情,而要写这样的SKAction呢?能不能用NSTimer来达到同样的目的?答案是在对场景或者精灵等SpriteKit对象进行类似操作时,尽量不要用NSTimer。因为NSTimer将不受SpriteKit的影响和管理,使用SKAction可以不加入其它任何代码就获取如下好处:
+
+* 自动暂停和继续,当设定一个SKNode的`paused`属性为YES时,这个SKNode和它管理的子node的action都会自动被暂停。这里详细说明一下SKNode的概念:SKNode是SpriteKit中要素的基本组织方式,它代表了SKView中的一种游戏资源的组织方式。我们现在接触到的SKScene和SKSprite都是SKNode的子类,而一个SKNode可以有很多的子Node,从而构成一个SKNode的树。在我们的例子中,MyScene直接加在SKView中作为最root的node存在,而英雄或者敌人的精灵都作为Scene这个node的子node被添加进来。SKAction和node上的各种属性的的作用范围是当前这个node和它的所有子node,在这里我们如果设定MySecnen这个node(也就是self)的`paused`属性被设为YES的话,所有的Action都会被暂停,包括这个每隔一秒调用一次的action,而如果你用NSTimer的话,恭喜,你必须自行维护它的状态。
+* 当SKAction依附的结点被从结点树中拿掉的时候,这个action会自动结束并停止,这是符合一般逻辑的。
+
+编译,运行,一切如我们所预期的那样,每个一秒有一个怪物从右侧进入,并以不同的速度穿过屏幕。
+
+
+
+### 奥特曼打小怪兽是天经地义的
+
+有了英雄,有了怪兽,就差一个“打”了。我们打算做的是在用户点击屏幕某个位置时,就由英雄所在的位置向点击位置发射一枚固定速度的飞镖。然后这每飞镖要是命中怪物的话,就把怪物从屏幕中移除。
+
+先来实现发射飞镖吧。检测点击,然后让一个精灵朝向点击的方向以某个速度移动,有很多种SKAction可以实现,但是为了尽量保持简单,我们使用上面曾经使用过的`moveTo:duration:`吧。在发射之前,我们先要来做一点基本的数学运算,希望你还能记得相似三角形之类的概念。我们的飞镖是由英雄发出的,然后经过手指点击的点,两点决定一条直线。简单说我们需要求解出这条直线和屏幕右侧边缘外的交点,以此来确定飞镖的最终目的。一旦我们得到了这个终点,就可以控制飞镖moveTo到这个终点,从而模拟出发射飞镖的action了。如图所示,很简单的几何学,关于具体的计算就不再讲解了,要是算不出来的话,请考虑call你的中学数学老师并负荆请罪以示诚意。
+
+
+
+然后开始写代码吧,还记得我们之前点击会出现一个飞机的精灵么,找到相应的地方,MyScene.m里的`-touchesBegan:withEvent:`:,用下面的代码替换掉原来的。
+
+```objc
+-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ /* Called when a touch begins */
+
+ for (UITouch *touch in touches) {
+ //1 Set up initial location of projectile
+ CGSize winSize = self.size;
+ SKSpriteNode *projectile = [SKSpriteNode spriteNodeWithImageNamed:@"projectile.png"];
+ projectile.position = CGPointMake(projectile.size.width/2, winSize.height/2);
+
+ //2 Get the touch location tn the scene and calculate offset
+ CGPoint location = [touch locationInNode:self];
+ CGPoint offset = CGPointMake(location.x - projectile.position.x, location.y - projectile.position.y);
+
+ // Bail out if you are shooting down or backwards
+ if (offset.x <= 0) return;
+ // Ok to add now - we've double checked position
+ [self addChild:projectile];
+
+ int realX = winSize.width + (projectile.size.width/2);
+ float ratio = (float) offset.y / (float) offset.x;
+ int realY = (realX * ratio) + projectile.position.y;
+ CGPoint realDest = CGPointMake(realX, realY);
+
+ //3 Determine the length of how far you're shooting
+ int offRealX = realX - projectile.position.x;
+ int offRealY = realY - projectile.position.y;
+ float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));
+ float velocity = self.size.width/1; // projectile speed.
+ float realMoveDuration = length/velocity;
+
+ //4 Move projectile to actual endpoint
+ [projectile runAction:[SKAction moveTo:realDest duration:realMoveDuration] completion:^{
+ [projectile removeFromParent];
+ }];
+ }
+}
+```
+
+1. 为飞镖设定初始位置。
+2. 将点击的位置转换为node的坐标系的坐标,并计算点击位置和飞镖位置的偏移量。如果点击位置在飞镖初始位置的后方,则直接返回
+3. 根据相似三角形计算屏幕右侧外的结束位置。
+4. 移动飞镖,并在移动结束后将飞镖从场景中移除。注意在移动怪物的时候我们用了两个action(actionMove和actionMoveDone来做移动+移除),这里只使用了一个action并用带completion block移除精灵。这里对飞镖的这种做法是比较简明常见高效的,之前的做法只是为了说明action的`sequence:`的用法。
+
+运行看看现在的游戏吧,我们有英雄有怪物还有打怪物的小飞镖,好像气氛上已经开始有趣了!
+
+
+
+### 飞镖击中的检测
+
+但是一个严重的问题是,现在的飞镖就算接触到了怪物也是直穿而过,完全就是空气一般的存在。为什么?因为我们还没有写任何检测飞镖和怪物的接触的代码(废话)。我们想要做的是在飞镖和怪物接触到的时候,将它们都移出场景,这样看起来就像是飞镖打中了怪物,并且把怪物消灭了。
+
+基本思路是在每隔一个小的时间间隔,就扫描一遍场景中现存的飞镖和怪物。这里就需要提到SpriteKit中最基本的每一帧的周期概念。
+
+
+
+在iOS传统的view的系统中,view的内容被渲染一次后就将一直等待,直到需要渲染的内容发生改变(比如用户发生交互,view的迁移等)的时候,才进行下一次渲染。这主要是因为传统的view大多工作在静态环境下,并没有需要频繁改变的需求。而对于SpriteKit来说,其本身就是用来制作大多数时候是动态的游戏的,为了保证动画的流畅和场景的持续更新,在SpriteKit中view将会循环不断地重绘。
+
+动画和渲染的进程是和SKScene对象绑定的,只有当场景被呈现时,这些渲染以及其中的action才会被执行。SKScene实例中,一个循环按执行顺序包括
+
+* 每一帧开始时,SKScene的`-update:`方法将被调用,参数是从开始时到调用时所经过的时间。在该方法中,我们应该实现一些游戏逻辑,包括AI,精灵行为等等,另外也可以在该方法中更新node的属性或者让node执行action
+* 在update执行完毕后,SKScene将会开始执行所有的action。因为action是可以由开发者设定的(还记得runBlock:么),因此在这一个阶段我们也是可以执行自己的代码的。
+* 在当前帧的action结束之后,SKScene的`-didEvaluateActions`将被调用,我们可以在这个方法里对结点做最后的调整或者限制,之后将进入物理引擎的计算阶段。
+* 然后SKScene将会开始物理计算,如果在结点上添加了SKPhysicsBody的话,那么这个结点将会具有物理特性,并参与到这个阶段的计算。根据物理计算的结果,SpriteKit将会决定结点新的状态。
+* 然后`-didSimulatePhysics`会被调用,这类似之前的`-didEvaluateActions`。这里是我们开发者能参与的最后的地方,是我们改变结点的最后机会。
+* 一帧的最后是渲染流程,根据之前设定和计算的结果对整个呈现的场景进行绘制。完成之后,SpriteKit将开始新的一帧。
+
+在了解了一些SpriteKit的基础概念后,回到我们的demo。检测场景上每个怪物和飞镖的状态,如果它们相撞就移除,这是对精灵的计算的和操作,我们可以将其放到`-update:`方法中来处理。在此之前,我们需要保存一下添加到场景中的怪物和飞镖,在MyScene.m的@implementation之前加入下面的声明:
+
+```objc
+@interface MyScene()
+@property (nonatomic, strong) NSMutableArray *monsters;
+@property (nonatomic, strong) NSMutableArray *projectiles;
+@end
+```
+
+然后在`-initWithSize:`中配置场景之前,初始化这两个数组:
+
+```objc
+-(id)initWithSize:(CGSize)size {
+ if (self = [super initWithSize:size]) {
+ /* Setup your scene here */
+ self.monsters = [NSMutableArray array];
+ self.projectiles = [NSMutableArray array];
+
+ //...
+ }
+ return self;
+}
+```
+在将怪物或者飞镖加入场景中的同时,分别将它们加入到数组中,
+```objc
+-(void) addMonster {
+ //...
+
+ [self.monsters addObject:monster];
+}
+```
+
+```objc
+-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ for (UITouch *touch in touches) {
+ //...
+
+ [self.projectiles addObject:projectile];
+ }
+}
+```
+
+同时,在将它们移除场景时,将它们移出所在数组,分别在`[monster removeFromParent]`和`[projectile removeFromParent]`后加入`[self.monsters removeObject:monster]`和`[self.projectiles removeObject:projectile]`。接下来终于可以在`-update:`中检测并移除了:
+
+```objc
+-(void)update:(CFTimeInterval)currentTime {
+ /* Called before each frame is rendered */
+ NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
+ for (SKSpriteNode *projectile in self.projectiles) {
+
+ NSMutableArray *monstersToDelete = [[NSMutableArray alloc] init];
+ for (SKSpriteNode *monster in self.monsters) {
+
+ if (CGRectIntersectsRect(projectile.frame, monster.frame)) {
+ [monstersToDelete addObject:monster];
+ }
+ }
+
+ for (SKSpriteNode *monster in monstersToDelete) {
+ [self.monsters removeObject:monster];
+ [monster removeFromParent];
+ }
+
+ if (monstersToDelete.count > 0) {
+ [projectilesToDelete addObject:projectile];
+ }
+ }
+
+ for (SKSpriteNode *projectile in projectilesToDelete) {
+ [self.projectiles removeObject:projectile];
+ [projectile removeFromParent];
+ }
+}
+```
+
+代码比较简单,不多解释了。直接运行看结果
+
+
+
+### 播放声音
+
+音效绝对是游戏的一个重要环节,还记得一开始下载的那个资源文件压缩包么?里面除了Art文件夹外还有个Sounds文件夹,我们把Sounds加入工程里,整个文件夹拖到工程导航里面,然后勾上“Copy item”。
+
+我们想在发射飞镖时播出一个音效,对于音效的播放是十分简单的,SpriteKit为我们提供了一个action,用来播放单个音效。因为每次的音效是相同的,所以只需要在一开始加载一次action,之后就一直使用这个action,以提高效率。先在MyScene.m的@interface中加入
+
+```objc
+@property (nonatomic, strong) SKAction *projectileSoundEffectAction;
+```
+
+然后在`-initWithSize:`一开始的地方加入
+
+```objc
+self.projectileSoundEffectAction = [SKAction playSoundFileNamed:@"pew-pew-lei.caf" waitForCompletion:NO];
+```
+
+最后,修改发射飞镖的action,使播放音效的action和移动精灵的action同时执行。将`-touchesBegan:withEvent:`最后runAction的部分改为
+
+```objc
+//...
+//4 Move projectile to actual endpoint and play the throw sound effect
+SKAction *moveAction = [SKAction moveTo:realDest duration:realMoveDuration];
+SKAction *projectileCastAction = [SKAction group:@[moveAction,self.projectileSoundEffectAction]];
+[projectile runAction:projectileCastAction completion:^{
+ [projectile removeFromParent];
+ [self.projectiles removeObject:projectile];
+}];
+
+//...
+```
+
+之前我们介绍了用`-sequence:`连接不同的action,使它们顺序串行执行。在这里我们用了另一个方便的方法,`-group:`可以范围一个新的action,这个action将并行同时开始执行传入的所有action。在这里我们在飞镖开始移动的同时,播放了一个pew-pew-lei的音效(音效效果请下载demo试听,或者自行脑补…)。
+
+游戏中音效一般来说至少会有效果音(SE)和背景音(BGM)两种,SE可以用SpriteKit的action来解决,而BGM就要惨一些,至少写这篇教程的时候,SpriteKit还没有一个BGM的专门的对应方案(如果之后新加了的话我会更新本教程)。所以现在我们使用传统的播放较长背景音乐的方法来实现背景音,那就是用`AVAudioPlayer`。在@interface MyScene()中加入一个bgmPlayer的声明,然后在`-initWithSize:`中加载背景音并一直播放。
+
+```objc
+@interface MyScene()
+//...
+@property (nonatomic, strong) AVAudioPlayer *bgmPlayer;
+//...
+@end
+
+@implementation MyScene
+
+-(id)initWithSize:(CGSize)size {
+//...
+ NSString *bgmPath = [[NSBundle mainBundle] pathForResource:@"background-music-aac" ofType:@"caf"];
+ self.bgmPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:bgmPath] error:NULL];
+ self.bgmPlayer.numberOfLoops = -1;
+ [self.bgmPlayer play];
+//...
+}
+```
+
+AVAudioPlayer用来播放背景音乐相当的合适,唯一的问题是有可能你想在暂停的时候停止这个背景音乐的播放。因为使用的是SpriteKit以外的框架,而并非action,因此BGM的播放不会随着设置Scene为暂停或者移除这个Scene而停止。想要停止播放,必须手动显式地调用`[self.bgmPlayer stop]`,可以说是比较麻烦,不过有时候你不并不想在暂停或者场景切换的时候中断背景音乐的话,这反倒是一个好的选择。
+
+### 结果计算和场景切换
+
+到现在为止,整个关卡作为一个demo来说已经比较完善了。最后,我们可以为这个关卡设定一些条件,毕竟不是每个人都喜欢一直无意义地消灭怪物直到手机没电。我们设定规则,当打死30个怪物后切换到新的场景,以成功结束战斗的结果;另外,要是有任何一个怪物到达了屏幕左侧边缘,则本场战斗失败。另外我们在显示结果的场景中还需要一个交互按钮,以便我们重新开始一轮游戏。
+
+首先是检测被打死的怪物数,在MyScene里添加一个`monstersDestroyed`,然后在打中怪物时使这个值+1,并在随后判断如果消灭怪物数量大于等于30,则切换场景(暂时没有实现,现在留了两个TODO,一会儿我们再实装场景切换)
+
+```objc
+@interface MyScene()
+//...
+@property (nonatomic, assign) int monstersDestroyed;
+//...
+@end
+
+
+-(void)update:(CFTimeInterval)currentTime {
+//...
+ for (SKSpriteNode *monster in monstersToDelete) {
+ [self.monsters removeObject:monster];
+ [monster removeFromParent];
+
+ self.monstersDestroyed++;
+ if (self.monstersDestroyed >= 30) {
+ //TODO: Show a win scene
+ }
+ }
+//...
+```
+
+另外,在怪物到达屏幕边缘的时候也触发场景的切换:
+
+```objc
+- (void) addMonster {
+ //...
+ SKAction *actionMoveDone = [SKAction runBlock:^{
+ [monster removeFromParent];
+ [self.monsters removeObject:monster];
+
+ //TODO: Show a lose scene
+ }];
+ //...
+}
+
+```
+
+接下来就是制作新的表示结果的场景了。新建一个SKScene的子类很简单,和平时我们新建Cocoa或者CocoaTouch的类没有什么区别。菜单中File->New->File...,选择Objective-C class,然后将新建的文件取名为ResultScene,父类填写为SKScene,并在新建的时候选择合适的Target即可。在新建的ResultScene.m的@implementation中加入如下代码:
+
+```objc
+-(instancetype)initWithSize:(CGSize)size won:(BOOL)won
+{
+ if (self = [super initWithSize:size]) {
+ self.backgroundColor = [SKColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
+
+ //1 Add a result label to the middle of screen
+ SKLabelNode *resultLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
+ resultLabel.text = won ? @"You win!" : @"You lose";
+ resultLabel.fontSize = 30;
+ resultLabel.fontColor = [SKColor blackColor];
+ resultLabel.position = CGPointMake(CGRectGetMidX(self.frame),
+ CGRectGetMidY(self.frame));
+ [self addChild:resultLabel];
+
+ //2 Add a retry label below the result label
+ SKLabelNode *retryLabel = [SKLabelNode labelNodeWithFontNamed:@"Chalkduster"];
+ retryLabel.text = @"Try again";
+ retryLabel.fontSize = 20;
+ retryLabel.fontColor = [SKColor blueColor];
+ retryLabel.position = CGPointMake(resultLabel.position.x, resultLabel.position.y * 0.8);
+ //3 Give a name for this node, it will help up to find the node later.
+ retryLabel.name = @"retryLabel";
+ [self addChild:retryLabel];
+ }
+ return self;
+}
+```
+
+我们在ResultScene中自定义了一个含有结果的初始化方法初始化,之后我们将使用这个方法来初始化ResultScene。在这个init方法中我们做了以下这些事:
+
+1. 根据输入添加了一个SKLabelNode来显示游戏的结果。SKLabelNode也是SKNode的子类,可以用来方便地显示不同字体、颜色或者样式的文字标签。
+2. 在结果标签的下方加入了一个重开一盘的标签
+3. 我们为这个node进行了命名,通过对node命名,我们可以在之后方便地拿到这个node的参照,而不必新建一个变量来持有它。在实际运用中,这个命名即可以用来存储一个唯一的名字,来帮助我们之后找到特定的node(使用`-childNodeWithName:`),也可以一堆特性类似的node共用一个名字,这样可以方便枚举(使用`-enumerateChildNodesWithName:usingBlock:`方法)。不过这次的demo中,我们只是简单地用字符串比较来确定node,稍后会看到具体的用法。
+
+最后不要忘了这个方法名写到.h文件中去,这样我们才能在游戏场景中调用到。
+
+回到游戏场景,在MyScene.m的加入对ResultScene.h的引用,然后在实现中加入一个切换场景的方法
+
+```objc
+#import "ResultScene.h"
+
+//...
+-(void) changeToResultSceneWithWon:(BOOL)won
+{
+ [self.bgmPlayer stop];
+ self.bgmPlayer = nil;
+ ResultScene *rs = [[ResultScene alloc] initWithSize:self.size won:won];
+ SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionUp duration:1.0];
+ [self.scene.view presentScene:rs transition:reveal];
+}
+```
+
+`SKTransition`是专门用来做不同的Scene之前切换的类,这个类为我们提供了很多“廉价”的场景切换效果(相信我,你如果想要自己实现它们的话会颇费一番功夫)。在这里我们建立了一个将当前场景上推的切换效果,来显示新的ResultScene。另外注意我们在这里停止了BGM的播放。之后,将刚才留下来的两个TODO的地方,分别替换为以相应参数对这个方法的调用。
+
+最后,我们想要在ResultScene中点击Retry标签时,重开一盘游戏。在ResultScene.m中加入代码
+
+```objc
+-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
+{
+ for (UITouch *touch in touches) {
+ CGPoint touchLocation = [touch locationInNode:self];
+ SKNode *node = [self nodeAtPoint:touchLocation];
+
+ if ([node.name isEqualToString:@"retryLabel"]) {
+ [self changeToGameScene];
+ }
+ }
+}
+
+-(void) changeToGameScene
+{
+ MyScene *ms = [MyScene sceneWithSize:self.size];
+ SKTransition *reveal = [SKTransition revealWithDirection:SKTransitionDirectionDown duration:1.0];
+ [self.scene.view presentScene:ms transition:reveal];
+}
+```
+
+运行游戏,消灭足够多的敌人(或者漏过一个敌人),应该能够可能到场景切换和结果显示。然后点击再来一次的话,将重新开始新的游戏。
+
+
+
+### 关于Sprite的一些个人补充
+
+至此,整个Demo的主体部分结束。接下来对于当前的SpriteKit(iOS SDK beta1)说一些我个人的看法和理解。如果之后这部分内容有巨大变化的话,我会尽量更新。首先是性能问题,如果有在iOS平台下使用cocos2d开发的经验的话,很容易看出来SpriteKit在很多地方借鉴了cocos2d。作为SDK内置的框架来说,又有cocos2d的开源实现进行参考,效率方面超越cocos2d应该是理所当然的。在现有的一系列benchmark上来看,实际上SpriteKit在图形渲染方面也有着很不错的表现。另外,在编写过程中,也有不少技巧可以使用,以进一步进行优化,比如在内存中保持住常用的action,预先加载资源,使用Atlas等等。在进行比较全面和完整的优化后,SpriteKit的表现应该是可以期待的。
+
+使用SpriteKit一个很明显的优点在于,SKView其实是基于UIKit的UIView的一套实现,而其中的所有SKNode对象都UIResponder的子类,并且实现了NSCoding等接口。也就是说,其实在SpriteKit中是可以很容易地使用其他的非游戏Cocoa/CocoaTouch框架的。比如可以使用UIKit或者Cocoa来简单地制作UI,然后只在需要每帧演算的时候使用SpriteKit,藉此来达到快速开发的目的。这点上cocos2d是无法与之比拟的。另外,因为SKSprite同时兼顾了iOS和Mac两者,因此在我们进行开发时如果能稍加注意,理论上可以比较容易地完成iOS和Mac的跨平台。
+
+由于SKNode是UIResponder的子类,因此在真正制作游戏的时候,对于相应用户点击等操作我们是不必(也不应该)像demo中一样全部放在Scene点击事件中,而是应该尽量封装游戏中用到的node,并在node中处理用户的点击,并且委托到Scene中进行处理,可能逻辑上会更加清晰。关于用户交互事件的处理,另外一个需要注意的地方在于,使用UIResponder监测的用户交互事件和SKScene的事件循环是相互独立的。如果像我们的demo中那样直接处理用户点击并和SpriteKit交互的话,我们并不能确定这个执行时机在SKScene循环中的状态。比如点击的相关代码也许会在`-update`后执行,也可能在`-didSimulatePhysics`后被调用,这引入了执行顺序的不确定性。对于上面的这个简单的demo来说这没有什么太大关系,但是在对于时间敏感的游戏逻辑或者带有物理模拟的游戏中,也许时序会很关键。由于点击事件的时序和精灵动画和物理等的时序不确定,有可能造成奇怪的问题。对此现在暂时的解决方法是仅在点击事件中设置一个标志位记录点击状态,然后在接下来的`-update:`中进行检测并处理(苹果给出的官方SpriteKit的“Adventure”是这样处理的),以此来保证时序的正确性。代价是点击事件会延迟一帧才会被处理,虽然在绝大多数情况下并不是什么问题,但是其实这点上并不优雅,至少在现在的beta版中,算不上优雅。
diff --git a/_posts/2013-07-21-what-i-did-recently.markdown b/_posts/2013-07-21-what-i-did-recently.markdown
new file mode 100644
index 00000000..116c180c
--- /dev/null
+++ b/_posts/2013-07-21-what-i-did-recently.markdown
@@ -0,0 +1,48 @@
+---
+layout: post
+title: 近期做的两三事
+date: 2013-07-21 00:53:10.000000000 +09:00
+tags: 胡言乱语集
+---
+夏日炎炎,无心睡眠。
+
+虽然已经有一段时间没有更新博客了,但是我确实是一直在努力干活儿的。这一个月以来大部分视线都在WWDC上,也写了几篇博文介绍个人觉得iOS7中需要深入挖掘和研究的API。但是因为NDA加上现在人在国外的缘故,还是不太好肆无忌惮地发出来。等到iOS7和Xcode5的NDA结束的时候(大概是9月中旬吧),我会一并把写的WWDC2013的笔记发出来,到时候还要请大家多多捧场。
+
+另外在工作之外,也自己做了一些小项目,基本都是一些个人兴趣所致。虽然不值一提,但是还是想写下来主要作为记录。另外如果恰好能帮助到两三个同仁的话,那是最好不过。
+
+### 一个Xcode插件,VVDocumenter
+
+虽然ObjC代码因为其可读性极强,而不太需要时常查阅文档,但是其实对于大多数人(包括我自己)来说,可能为方法或变量取一个好名字并不是那么简单的事情。这时候可能就需要文档或者注释来帮助之后的开发者(包括大家自己)尽快熟悉和方便修改。但是用Xcode写文档是一件让人很头疼的事情,没有像VS之类的成熟IDE的方便的方法,一直以来都是依靠像Snippet这样的东西,然后自己辛苦填写大量已有的内容。
+
+
+之前看到一个用[Ruby+系统服务来生成注释的方案](http://blog.chukong-inc.com/index.php/2012/05/16/xcode4_fast_doxygen/),但是每次要自己去选还要按快捷键,总觉得是很麻烦的事情。借鉴其他平台IDE一般都是采用三个斜杠(`///`)来生成文档注释的方法,所以也为Xcode写了一个类似的。用法很简单,在要写文档的代码上面连打三个斜杠,就能自动提取参数等生成规范的Javadoc格式文档注释。**VVDocumenter**整个项目MIT开源,并且扔在github上了,有兴趣的童鞋可以[在这里](https://github.com/onevcat/VVDocumenter-Xcode)找到,欢迎大家加星fork以及给我发pull request来改善这个插件。
+
+
+
+### 一个Unity插件,UniRate
+
+做了一个叫**UniRate**的Unity插件,可以完全解决Unity移动端游戏请求用户评价的需求。对于一款应用/游戏来说,一般都会在你使用若干次/天之后弹一个邀请你评价的窗口,你可以选择是否到AppStore/Android Market进行评价或者稍后提醒。分别在iOS或者Android中实现这样的功能可以说是小菜一碟,但是Unity里现在暂时没有很好的方案。很可能你会需要花不少时间来实现一个类似功能,又或者要是你对native plugin这方面不太熟悉的话,可能就比较头疼了。
+
+现在可以用UniRate来解决,添加的方法很简单,导入资源包,将里面的UniRateManager拖拽到scene中,就可以了..是的..没有第三步,这时候你已经有一个会监测用户使用并在安装3天并且使用10次后弹出一个提示评价的框,用户可以选择评价并跳转到相应页面了。如果你想做更多细节的调整和控制,可以参看这里的[用户手册](https://github.com/onevcat/UniRate/wiki/UniRate-Manual)和[在线文档](http://unirate.onevcat.com/reference/class_uni_rate.html)。
+
+
+
+如果你感兴趣并且希望支持一下的话,UniRate现在可以在Unity Asset Store购买,[传送门在这里](https://www.assetstore.unity3d.com/#/content/10116)。
+
+### Oculus VR Rift
+
+如果你不知道Oculus的话,这里有张我的近照可以帮助你了解。
+
+
+
+其实就是一个虚拟现实用的眼镜,可以直接在眼前塞满屏幕的设备。之前也有索尼之类的厂家出过类似的眼镜,但是Oculus最大的特点是全屏无黑边,可以说提供了和以往完全不同的沉浸式游戏体验。难能可贵的是,在此同时还能做到价格厚道(坊间传闻今后希望能做到本体免费)。
+
+回到主题,自从体验过Oculus VR Rift以后,我就相信这会是游戏的未来和方向。于是之前就下了订单预定了开发者版本,今天总算是到货。Oculus对于我来说最大的优点是支持Unity3D,所以自己可以用它来做一些好玩儿的东西,算是门槛比较低。相信之后会有一段时间来学习适配Oculus的Unity开发,并且每天沉浸在创造自娱自乐的虚拟现实之中,希望这段时光能成为自己之后美好的回忆。我在之后也会找机会在博客里分享一些关于Unity和Oculus集成,以及开发Oculus适配的游戏的一些经验和方法。
+
+**如果有可能的话,真希望自己能够做一款好玩的Oculus的游戏,或者找到一个做Oculus游戏的企业,去创造这个未来,改变世界。**
+
+### XUPorter更新
+
+[XUPorter](https://github.com/onevcat/XUPorter)最早是写出来自己用的。因为每次从Unity build工程出来的时候,在Xcode里把各种依赖库拖来拖去简直是一件泯灭人性的事情。两年多前刚开始Unity的时候没有post build script这种东西,于是每次都要花上五到十分钟来配置Xcode的工程,时间一长就直接忘了需要依赖哪些文件和框架才能编译通过。后来有个post build脚本,但是每次写起来也很麻烦。XUPorter利用Unity3.5新加入的`PostProcessBuild`来根据配置修改Xcode工程文件,具体的介绍可以[看这里](http://onevcat.com/2012/12/xuporter/)。之前就是往Github上一扔而已,很高兴的是,有一些项目开始使用XUPorter做管理了,也有热心人在Github上帮助维护这个项目。于是最近对其进行了一些更新,添加了第三方库的添加等一些功能。
+
+如果有需要的朋友可以了解一下并使用,可以节省不少时间。如果觉得好,也欢迎帮助推荐和支持,让更多人知道并受益。最简单的方法就是在[项目的Github页面](https://github.com/onevcat/XUPorter)加个星星~ :)
diff --git a/_posts/2013-07-23-shader-tutorial-1.markdown b/_posts/2013-07-23-shader-tutorial-1.markdown
new file mode 100644
index 00000000..bb43d5a0
--- /dev/null
+++ b/_posts/2013-07-23-shader-tutorial-1.markdown
@@ -0,0 +1,248 @@
+---
+layout: post
+title: 猫都能学会的Unity3D Shader入门指南(一)
+date: 2013-07-23 00:54:38.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+## 动机
+
+自己使用Unity3D也有一段时间了,但是很多时候是流于表面,更多地是把这个引擎简单地用作脚本控制,而对更深入一些的层次几乎没有了解。虽然说Unity引擎设计的初衷就是创建简单的不需要开发者操心的谁都能用的3D引擎,但是只是肤浅的使用,可能是无法达到随心所欲的境地的,因此,这种状况必须改变!从哪里开始呢,貌似有句话叫做会写Shader的都是高手,于是,想大概看看从Shader开始能不能使自己到达的层次能再深入一些吧,再于是,有了这个系列(希望我能坚持写完它,虽然应该会拖个半年左右)。
+
+Unity3D的所有渲染工作都离不开着色器(Shader),如果你和我一样最近开始对Shader编程比较感兴趣的话,可能你和我有着同样的困惑:如何开始?Unity3D提供了一些Shader的手册和文档(比如[这里](http://docs.unity3d.com/Documentation/Manual/Shaders.html),[这里](http://docs.unity3d.com/Documentation/Components/Built-inShaderGuide.html)和[这里](http://docs.unity3d.com/Documentation/Components/SL-Reference.html)),但是一来内容比较分散,二来学习阶梯稍微陡峭了些。这对于像我这样之前完全没有接触过有关内容的新人来说是相当不友好的。国内外虽然也有一些Shader的介绍和心得,但是也同样存在内容分散的问题,很多教程前一章就只介绍了基本概念,接下来马上就搬出一个超复杂的例子,对于很多基本的用法并没有解释。也许对于Shader熟练使用的开发者来说是没有问题,但是我相信像我这样的入门者也并不在少数。在多方寻觅无果后,我觉得有必要写一份教程,来以一个入门者的角度介绍一些Shader开发的基本步骤。其实与其说是教程,倒不如说是一份自我总结,希望能够帮到有需要的人。
+
+所以,本“教程”的对象是
+
+* 总的来说是新接触Shader开发的人:也许你知道什么是Shader,也会使用别人的Shader,但是仅限于知道一些基本的内建Shader名字,从来没有打开它们查看其源码。
+* 想要更多了解Shader和有需求要进行Shader开发的开发者,但是之前并没有Shader开发的经验。
+
+当然,因为我本身在Shader开发方面也是一个不折不扣的大菜鸟,本文很多内容也只是在自己的理解加上一些可能不太靠谱的求证和总结。本文中的示例应该会有更好的方式来实现,因此您是高手并且恰巧路过的话,如果有好的方式来实现某些内容,恳请您不吝留下评论,我会对本文进行不断更新和维护。
+
+## 一些基本概念
+
+### Shader和Material
+
+如果是进行3D游戏开发的话,想必您对着两个词不会陌生。Shader(着色器)实际上就是一小段程序,它负责将输入的Mesh(网格)以指定的方式和输入的贴图或者颜色等组合作用,然后输出。绘图单元可以依据这个输出来将图像绘制到屏幕上。输入的贴图或者颜色等,加上对应的Shader,以及对Shader的特定的参数设置,将这些内容(Shader及输入参数)打包存储在一起,得到的就是一个Material(材质)。之后,我们便可以将材质赋予合适的renderer(渲染器)来进行渲染(输出)了。
+
+所以说Shader并没有什么特别神奇的,它只是一段规定好输入(颜色,贴图等)和输出(渲染器能够读懂的点和颜色的对应关系)的程序。而Shader开发者要做的就是根据输入,进行计算变换,产生输出而已。
+
+Shader大体上可以分为两类,简单来说
+
+* 表面着色器(Surface Shader) - 为你做了大部分的工作,只需要简单的技巧即可实现很多不错的效果。类比卡片机,上手以后不太需要很多努力就能拍出不错的效果。
+* 片段着色器(Fragment Shader) - 可以做的事情更多,但是也比较难写。使用片段着色器的主要目的是可以在比较低的层级上进行更复杂(或者针对目标设备更高效)的开发。
+
+因为是入门文章,所以之后的介绍将主要集中在表面着色器上。
+
+### Shader程序的基本结构
+
+因为着色器代码可以说专用性非常强,因此人为地规定了它的基本结构。一个普通的着色器的结构应该是这样的:
+
+
+首先是一些属性定义,用来指定这段代码将有哪些输入。接下来是一个或者多个的子着色器,在实际运行中,哪一个子着色器被使用是由运行的平台所决定的。子着色器是代码的主体,每一个子着色器中包含一个或者多个的Pass。在计算着色时,平台先选择最优先可以使用的着色器,然后依次运行其中的Pass,然后得到输出的结果。最后指定一个回滚,用来处理所有Subshader都不能运行的情况(比如目标设备实在太老,所有Subshader中都有其不支持的特性)。
+
+需要提前说明的是,在实际进行表面着色器的开发时,我们将直接在Subshader这个层次上写代码,系统将把我们的代码编译成若干个合适的Pass。废话到此为止,下面让我们真正实际进入Shader的世界吧。
+
+## Hello Shader
+
+百行文档不如一个实例,下面给出一段简单的Shader代码,然后根据代码来验证下上面说到的结构和阐述一些基本的Shader语法。因为本文是针对Unity3D来写Shader的,所以也使用Unity3D来演示吧。首先,新建一个Shader,可以在Project面板中找到,Create,选择Shader,然后将其命名为`Diffuse Texture`:
+
+
+
+随便用个文本编辑器打开刚才新建的Shader:
+
+```glsl
+Shader "Custom/Diffuse Texture" {
+ Properties {
+ _MainTex ("Base (RGB)", 2D) = "white" {}
+ }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ LOD 200
+
+ CGPROGRAM
+ #pragma surface surf Lambert
+
+ sampler2D _MainTex;
+
+ struct Input {
+ float2 uv_MainTex;
+ };
+
+ void surf (Input IN, inout SurfaceOutput o) {
+ half4 c = tex2D (_MainTex, IN.uv_MainTex);
+ o.Albedo = c.rgb;
+ o.Alpha = c.a;
+ }
+ ENDCG
+ }
+ FallBack "Diffuse"
+}
+
+```
+
+如果您之前没怎么看过Shader代码的话,估计细节上会看不太懂。但是有了上面基本结构的介绍,您应该可以识别出这个Shader的构成,比如一个Properties部分,一个SubShader,以及一个FallBack。另外,第一行只是这个Shader的声明并为其指定了一个名字,比如我们的实例Shader,你可以在材质面板选择Shader时在对应的位置找到这个Shader。
+
+
+
+**接下来我们讲逐句讲解这个Shader,以期明了每一个语句的意义。**
+
+### 属性
+
+在`Properties{}`中定义着色器属性,在这里定义的属性将被作为输入提供给所有的子着色器。每一条属性的定义的语法是这样的:
+
+`_Name("Display Name", type) = defaultValue[{options}]`
+
+* _Name - 属性的名字,简单说就是变量名,在之后整个Shader代码中将使用这个名字来获取该属性的内容
+* Display Name - 这个字符串将显示在Unity的材质编辑器中作为Shader的使用者可读的内容
+* type - 这个属性的类型,可能的type所表示的内容有以下几种:
+ * Color - 一种颜色,由RGBA(红绿蓝和透明度)四个量来定义;
+ * 2D - 一张2的阶数大小(256,512之类)的贴图。这张贴图将在采样后被转为对应基于模型UV的每个像素的颜色,最终被显示出来;
+ * Rect - 一个非2阶数大小的贴图;
+ * Cube - 即Cube map texture(立方体纹理),简单说就是6张有联系的2D贴图的组合,主要用来做反射效果(比如天空盒和动态反射),也会被转换为对应点的采样;
+ * Range(min, max) - 一个介于最小值和最大值之间的浮点数,一般用来当作调整Shader某些特性的参数(比如透明度渲染的截止值可以是从0至1的值等);
+ * Float - 任意一个浮点数;
+ * Vector - 一个四维数;
+* defaultValue 定义了这个属性的默认值,通过输入一个符合格式的默认值来指定对应属性的初始值(某些效果可能需要某些特定的参数值来达到需要的效果,虽然这些值可以在之后在进行调整,但是如果默认就指定为想要的值的话就省去了一个个调整的时间,方便很多)。
+ * Color - 以0~1定义的rgba颜色,比如(1,1,1,1);
+ * 2D/Rect/Cube - 对于贴图来说,默认值可以为一个代表默认tint颜色的字符串,可以是空字符串或者"white","black","gray","bump"中的一个
+ * Float,Range - 某个指定的浮点数
+ * Vector - 一个4维数,写为 (x,y,z,w)
+* 另外还有一个{option},它只对2D,Rect或者Cube贴图有关,在写输入时我们最少要在贴图之后写一对什么都不含的空白的{},当我们需要打开特定选项时可以把其写在这对花括号内。如果需要同时打开多个选项,可以使用空白分隔。可能的选择有ObjectLinear, EyeLinear, SphereMap, CubeReflect, CubeNormal中的一个,这些都是OpenGL中TexGen的模式,具体的留到后面有机会再说。
+
+所以,一组属性的申明看起来也许会是这个样子的
+
+```glsl
+//Define a color with a default value of semi-transparent blue
+_MainColor ("Main Color", Color) = (0,0,1,0.5)
+//Define a texture with a default of white
+_Texture ("Texture", 2D) = "white" {}
+```
+
+现在看懂上面那段Shader(以及其他所有Shader)的Properties部分应该不会有任何问题了。接下来就是SubShader部分了。
+
+### Tags
+
+表面着色器可以被若干的标签(tags)所修饰,而硬件将通过判定这些标签来决定什么时候调用该着色器。比如我们的例子中SubShader的第一句
+
+`Tags { "RenderType"="Opaque" }`
+
+告诉了系统应该在渲染非透明物体时调用我们。Unity定义了一些列这样的渲染过程,与RenderType是Opaque相对应的显而易见的是`"RenderType" = "Transparent"`,表示渲染含有透明效果的物体时调用。在这里Tags其实暗示了你的Shader输出的是什么,如果输出中都是非透明物体,那写在Opaque里;如果想渲染透明或者半透明的像素,那应该写在Transparent中。
+
+另外比较有用的标签还有`"IgnoreProjector"="True"`(不被[Projectors](http://docs.unity3d.com/Documentation/Components/class-Projector.html)影响),`"ForceNoShadowCasting"="True"`(从不产生阴影)以及`"Queue"="xxx"`(指定渲染顺序队列)。这里想要着重说一下的是Queue这个标签,如果你使用Unity做过一些透明和不透明物体的混合的话,很可能已经遇到过不透明物体无法呈现在透明物体之后的情况。这种情况很可能是由于Shader的渲染顺序不正确导致的。Queue指定了物体的渲染顺序,预定义的Queue有:
+
+* Background - 最早被调用的渲染,用来渲染天空盒或者背景
+* Geometry - 这是默认值,用来渲染非透明物体(普通情况下,场景中的绝大多数物体应该是非透明的)
+* AlphaTest - 用来渲染经过[Alpha Test](http://docs.unity3d.com/Documentation/Components/SL-AlphaTest.html)的像素,单独为AlphaTest设定一个Queue是出于对效率的考虑
+* Transparent - 以从后往前的顺序渲染透明物体
+* Overlay - 用来渲染叠加的效果,是渲染的最后阶段(比如镜头光晕等特效)
+
+这些预定义的值本质上是一组定义整数,Background = 1000, Geometry = 2000, AlphaTest = 2450, Transparent = 3000,最后Overlay = 4000。在我们实际设置Queue值时,不仅能使用上面的几个预定义值,我们也可以指定自己的Queue值,写成类似这样:`"Queue"="Transparent+100"`,表示一个在Transparent之后100的Queue上进行调用。通过调整Queue值,我们可以确保某些物体一定在另一些物体之前或者之后渲染,这个技巧有时候很有用处。
+
+### LOD
+
+LOD很简单,它是Level of Detail的缩写,在这里例子里我们指定了其为200(其实这是Unity的内建Diffuse着色器的设定值)。这个数值决定了我们能用什么样的Shader。在Unity的Quality Settings中我们可以设定允许的最大LOD,当设定的LOD小于SubShader所指定的LOD时,这个SubShader将不可用。Unity内建Shader定义了一组LOD的数值,我们在实现自己的Shader的时候可以将其作为参考来设定自己的LOD数值,这样在之后调整根据设备图形性能来调整画质时可以进行比较精确的控制。
+
+* VertexLit及其系列 = 100
+* Decal, Reflective VertexLit = 150
+* Diffuse = 200
+* Diffuse Detail, Reflective Bumped Unlit, Reflective Bumped VertexLit = 250
+* Bumped, Specular = 300
+* Bumped Specular = 400
+* Parallax = 500
+* Parallax Specular = 600
+
+### Shader本体
+
+前面杂项说完了,终于可以开始看看最主要的部分了,也就是将输入转变为输出的代码部分。为了方便看,请容许我把上面的SubShader的主题部分抄写一遍
+
+```glsl
+CGPROGRAM
+#pragma surface surf Lambert
+
+sampler2D _MainTex;
+
+struct Input {
+ float2 uv_MainTex;
+};
+
+void surf (Input IN, inout SurfaceOutput o) {
+ half4 c = tex2D (_MainTex, IN.uv_MainTex);
+ o.Albedo = c.rgb;
+ o.Alpha = c.a;
+}
+ENDCG
+```
+
+还是逐行来看,首先是CGPROGRAM。这是一个开始标记,表明从这里开始是一段CG程序(我们在写Unity的Shader时用的是Cg/HLSL语言)。最后一行的ENDCG与CGPROGRAM是对应的,表明CG程序到此结束。
+
+接下来是是一个编译指令:`#pragma surface surf Lambert`,它声明了我们要写一个表面Shader,并指定了光照模型。它的写法是这样的
+
+`#pragma surface surfaceFunction lightModel [optionalparams]`
+
+* surface - 声明的是一个表面着色器
+* surfaceFunction - 着色器代码的方法的名字
+* lightModel - 使用的光照模型。
+
+所以在我们的例子中,我们声明了一个表面着色器,实际的代码在surf函数中(在下面能找到该函数),使用Lambert(也就是普通的diffuse)作为光照模型。
+
+接下来一句`sampler2D _MainTex;`,sampler2D是个啥?其实在CG中,sampler2D就是和texture所绑定的一个数据容器接口。等等..这个说法还是太复杂了,简单理解的话,所谓加载以后的texture(贴图)说白了不过是一块内存存储的,使用了RGB(也许还有A)通道,且每个通道8bits的数据。而具体地想知道像素与坐标的对应关系,以及获取这些数据,我们总不能一次一次去自己计算内存地址或者偏移,因此可以通过sampler2D来对贴图进行操作。更简单地理解,sampler2D就是GLSL中的2D贴图的类型,相应的,还有sampler1D,sampler3D,samplerCube等等格式。
+
+解释通了sampler2D是什么之后,还需要解释下为什么在这里需要一句对`_MainTex`的声明,之前我们不是已经在`Properties`里声明过它是贴图了么。答案是我们用来实例的这个shader其实是由两个相对独立的块组成的,外层的属性声明,回滚等等是Unity可以直接使用和编译的ShaderLab;而现在我们是在`CGPROGRAM...ENDCG`这样一个代码块中,这是一段CG程序。对于这段CG程序,要想访问在`Properties`中所定义的变量的话,**必须使用和之前变量相同的名字进行声明**。于是其实`sampler2D _MainTex;`做的事情就是再次声明并链接了_MainTex,使得接下来的CG程序能够使用这个变量。
+
+终于可以继续了。接下来是一个struct结构体。相信大家对于结构体已经很熟悉了,我们先跳过之,直接看下面的的surf函数。上面的#pragma段已经指出了我们的着色器代码的方法的名字叫做surf,那没跑儿了,就是这段代码是我们的着色器的工作核心。我们已经说过不止一次,着色器就是给定了输入,然后给出输出进行着色的代码。CG规定了声明为表面着色器的方法(就是我们这里的surf)的参数类型和名字,因此我们没有权利决定surf的输入输出参数的类型,只能按照规定写。这个规定就是第一个参数是一个Input结构,第二个参数是一个inout的SurfaceOutput结构。
+
+它们分别是什么呢?Input其实是需要我们去定义的结构,这给我们提供了一个机会,可以把所需要参与计算的数据都放到这个Input结构中,传入surf函数使用;SurfaceOutput是已经定义好了里面类型输出结构,但是一开始的时候内容暂时是空白的,我们需要向里面填写输出,这样就可以完成着色了。先仔细看看INPUT吧,现在可以跳回来看上面定义的INPUT结构体了:
+
+```glsl
+struct Input {
+ float2 uv_MainTex;
+};
+```
+
+作为输入的结构体必须命名为Input,这个结构体中定义了一个float2的变量…你没看错我也没打错,就是float2,表示浮点数的float后面紧跟一个数字2,这又是什么意思呢?其实没什么魔法,float和vec都可以在之后加入一个2到4的数字,来表示被打包在一起的2到4个同类型数。比如下面的这些定义:
+
+```
+//Define a 2d vector variable
+vec2 coordinate;
+//Define a color variable
+float4 color;
+//Multiply out a color
+float3 multipliedColor = color.rgb * coordinate.x;
+```
+
+在访问这些值时,我们即可以只使用名称来获得整组值,也可以使用下标的方式(比如.xyzw,.rgba或它们的部分比如.x等等)来获得某个值。在这个例子里,我们声明了一个叫做`uv_MainTex`的包含两个浮点数的变量。
+
+如果你对3D开发稍有耳闻的话,一定不会对uv这两个字母感到陌生。UV mapping的作用是将一个2D贴图上的点按照一定规则映射到3D模型上,是3D渲染中最常见的一种顶点处理手段。在CG程序中,我们有这样的约定,在一个贴图变量(在我们例子中是`_MainTex`)之前加上uv两个字母,就代表提取它的uv值(其实就是两个代表贴图上点的二维坐标 )。我们之后就可以在surf程序中直接通过访问uv_MainTex来取得这张贴图当前需要计算的点的坐标值了。
+
+如果你坚持看到这里了,那要恭喜你,因为离最后成功读完一个Shader只有一步之遥。我们回到surf函数,它的两有参数,第一个是Input,我们已经明白了:在计算输出时Shader会多次调用surf函数,每次给入一个贴图上的点坐标,来计算输出。第二个参数是一个可写的SurfaceOutput,SurfaceOutput是预定义的输出结构,我们的surf函数的目标就是根据输入把这个输出结构填上。SurfaceOutput结构体的定义如下
+
+```glsl
+struct SurfaceOutput {
+ half3 Albedo; //像素的颜色
+ half3 Normal; //像素的法向值
+ half3 Emission; //像素的发散颜色
+ half Specular; //像素的镜面高光
+ half Gloss; //像素的发光强度
+ half Alpha; //像素的透明度
+};
+```
+
+这里的half和我们常见float与double类似,都表示浮点数,只不过精度不一样。也许你很熟悉单精度浮点数(float或者single)和双精度浮点数(double),这里的half指的是半精度浮点数,精度最低,运算性能相对比高精度浮点数高一些,因此被大量使用。
+
+在例子中,我们做的事情非常简单:
+
+```glsl
+half4 c = tex2D (_MainTex, IN.uv_MainTex);
+o.Albedo = c.rgb;
+o.Alpha = c.a;
+```
+
+这里用到了一个`tex2d`函数,这是CG程序中用来在一张贴图中对一个点进行采样的方法,返回一个float4。这里对_MainTex在输入点上进行了采样,并将其颜色的rbg值赋予了输出的像素颜色,将a值赋予透明度。于是,着色器就明白了应当怎样工作:即找到贴图上对应的uv点,直接使用颜色信息来进行着色,over。
+
+## 接下来...
+
+我想现在你已经能读懂一些最简单的Shader了,接下来我推荐的是参考Unity的[Surface Shader Examples](http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderExamples.html)多接触一些各种各样的基本Shader。在这篇教程的基础上,配合一些google的工作,完全看懂这个shader示例页面应该不成问题。如果能做到无压力看懂,那说明你已经有良好的基础可以前进到Shader的更深的层次了(也许等不到我的下一篇教程就可以自己开始动手写些效果了);如果暂时还是有困难,那也没有关系,Shader学习绝对是一个渐进的过程,因为有很多约定和常用技巧,多积累和实践自然会进步并掌握。
+
+在接下来的教程里,打算通过介绍一些实际例子以及从基础开始实际逐步动手实现一个复杂一点的例子,让我们能看到shader在真正使用中的威力。我希望能尽快写完这个系列,但是无奈时间确实有限,所以我也不知道什么时候能出炉...写好的时候我会更改这段内容并指向新的文章。您要是担心错过的话,也可以使用[邮件订阅](http://eepurl.com/wNSkj)或者[订阅本站的rss](http://onevcat.com/atom.xml)(虽然Google Reader已经关了- -)。
diff --git a/_posts/2013-08-17-ios7-background-multitask.markdown b/_posts/2013-08-17-ios7-background-multitask.markdown
new file mode 100644
index 00000000..d208ead8
--- /dev/null
+++ b/_posts/2013-08-17-ios7-background-multitask.markdown
@@ -0,0 +1,263 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - iOS7中的多任务
+date: 2013-08-17 00:56:02.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 204 What's New with Multitasking
+* Session 705 What’s New in Foundation Networking
+
+## iOS7以前的Multitasking
+
+iOS的多任务是在iOS4的时候被引入的,在此之前iOS的app都是按下Home键就被干掉了。iOS4虽然引入了后台和多任务,但是实际上是伪多任务,一般的app后台并不能执行自己的代码,只有少数几类服务在通过注册后可以真正在后台运行,并且在提交到AppStore的时候也会被严格审核是否有越权行为,这种限制主要是出于对于设备的续航和安全两方面进行的考虑。之后经过iOS5和6的逐渐发展,后台能运行的服务的种类虽然出现了增加,但是iOS后台的本质并没有变化。在iOS7之前,系统所接受的应用多任务可以大致分为几种:
+
+* 后台完成某些花费时间的特定任务
+* 后台播放音乐等
+* 位置服务
+* IP电话(VoIP)
+* Newsstand
+
+在WWDC 2013的keynote上,iOS7的后台多任务改进被专门拿出来向开发者进行了介绍,到底iOS7里多任务方面有什么新的特性可以利用,如何使用呢?简单来说,iOS7在后台特性方面有很大改进,不仅改变了以往的一些后台任务处理方式,还加入了全新的后台模式,本文将针对iOS7中新的后台特性进行一些学习和记录。大体来说,iOS7后台的变化在于以下四点:
+
+* 改变了后台任务的运行方式
+* 增加了后台获取(Background Fetch)
+* 增加了推送唤醒(静默推送,Silent Remote Notifications)
+* 增加了后台传输(Background Transfer Service)
+
+
+
+## iOS7的多任务
+
+### 后台任务
+
+首先看看后台任务的变化,先说这方面的改变,而不是直接介绍新的API,是因为这个改变很典型地代表了iOS7在后台任务管理和能耗控制上的大体思路。从上古时期开始(其实也就4.0),UIApplication提供了`-beginBackgroundTaskWithExpirationHandler:`方法来使app在被切到后台后仍然能保持运行一段时间,app可以用这个方法来确保一些很重很慢的工作可以在急不可耐的用户将你的应用扔到后台后还能完成,比如编码视频,上传下载某些重要文件或者是完成某些数据库操作等,虽然时间不长,但在大多数情况下勉强够用。如果你之前没有使用过这个API的话,它使用起来大概是长这个样子的:
+
+```objc
+- (void) doUpdate
+
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+
+ [self beginBackgroundUpdateTask];
+
+ NSURLResponse * response = nil;
+ NSError * error = nil;
+ NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error];
+
+ // Do something with the result
+
+ [self endBackgroundUpdateTask];
+ });
+}
+
+- (void) beginBackgroundUpdateTask
+{
+ self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
+ [self endBackgroundUpdateTask];
+ }];
+}
+
+- (void) endBackgroundUpdateTask
+{
+ [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
+ self.backgroundUpdateTask = UIBackgroundTaskInvalid;
+}
+```
+
+在`beginBackgroundTaskWithExpirationHandler:`里写一个超时处理(系统只给app分配了一定时间来进行后台任务,超时之前会调用这个block),然后进行开始进行后台任务处理,在任务结束或者过期的时候call一下`endBackgroundTask:`使之与begin方法配对(否则你的app在后台任务超时的时候会被杀掉)。同时,你可以使用UIApplication实例的backgroundTimeRemaining属性来获取剩余的后台执行时间。
+
+具体的执行时间来说,在iOS6和之前的系统中,系统在用户退出应用后,如果应用正在执行后台任务的话,系统会保持活跃状态直到后台任务完成或者是超时以后,才会进入真正的低功耗休眠状态。
+
+
+
+而在iOS7中,后台任务的处理方式发生了改变。系统将在用户锁屏后尽快让设备进入休眠状态,以节省电力,这时后台任务是被暂停的。之后在设备在特定时间进行系统应用的操作被唤醒(比如检查邮件或者接到来电等)时,之前暂停的后台任务将一起进行。就是说,系统不会专门为第三方的应用保持设备处于活动状态。如下图示
+
+
+
+这个变化在不减少应用的后台任务时间长度的情况下,给设备带来了更多的休眠时间,从而延长了续航。对于开发者来说,这个改变更多的是系统层级的变化,对于非网络传输的任务来说,保持原来的用法即可,新系统将会按照新的唤醒方式进行处理;而对于原来在后台做网络传输的应用来说,苹果建议在iOS7中使用`NSURLSession`,创建后台的session并进行网络传输,这样可以很容易地利用更好的后台传输API,而不必受限于原来的时长,关于这个具体的我们一会儿再说。
+
+### 后台获取(Background Fetch)
+
+现在的应用无法在后台获取信息,比如社交类应用,用户一定需要在打开应用之后才能进行网络连接,获取新的消息条目,然后才能将新内容呈现给用户。说实话这个体验并不是很好,用户在打开应用后必定会有一段时间的等待,每次皆是如此。iOS7中新加入的后台获取就是用来解决这个不足的:后台获取干的事情就是在用户打开应用之前就使app有机会执行代码来获取数据,刷新UI。这样在用户打开应用的时候,最新的内容将已然呈现在用户眼前,而省去了所有的加载过程。想想看,没有加载的网络体验的世界,会是怎样一种感觉。这已经不是smooth,而是真的amazing了。
+
+那具体应该怎么做呢?一步一步来:
+
+#### 启用后台获取
+
+首先是修改应用的Info.plist,在`UIBackgroundModes`中加入fetch,即可告诉系统应用需要后台获取的权限。另外一种更简单的方式,得益于Xcode5的Capabilities特性(参见可以参见我之前的一篇[WWDC2013笔记 Xcode5和ObjC新特性](http://onevcat.com/2013/06/new-in-xcode5-and-objc/)),现在甚至都不需要去手动修改Info.plist来进行添加了,打开Capabilities页面下的Background Modes选项,并勾选Background fetch选项即可(如下图)。
+
+
+
+笔者写这篇文章的时候iOS7还没有上市,也没有相关的审核资料,所以不知道如果只是在这里打开了fetch选项,但却没有实现的话,应用会不会无法通过审核。但是依照苹果一贯的做法来看,如果声明了需要某项后台权限,但是结果却没有相关实现的话,被拒掉的可能性还是比较大的。因此大家尽量不要拿上线产品进行实验,而应当是在demo项目里研究明白以后再到上线产品中进行实装。
+
+#### 设定获取间隔
+
+对应用的UIApplication实例设置获取间隔,一般在应用启动的时候调用以下代码即可:
+
+```objc
+[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
+```
+
+如果不对最小后台获取间隔进行设定的话,系统将使用默认值`UIApplicationBackgroundFetchIntervalNever`,也就是永远不进行后台获取。当然,`-setMinimumBackgroundFetchInterval:`方法接受的是NSTimeInterval,因此你也可以手动指定一个以秒为单位的最小获取间隔。需要注意的是,我们都已经知道iOS是一个非常霸道为我独尊的系统,因此自然也不可能让一介区区第三方应用来控制系统行为。这里所指定的时间间隔只是代表了“在上一次获取或者关闭应用之后,在这一段时间内一定不会去做后台获取”,而真正具体到什么时候会进行后台获取,那~~~完全是要看系统娘的心情的~~~我们是无从得知的。系统将根据你的设定,选择比如接收邮件的时候顺便为你的应用获取一下,或者也有可能专门为你的应用唤醒一下设备。作为开发者,我们应该做的是为用户的电池考虑,尽可能地选择合适自己应用的后台获取间隔。设置为UIApplicationBackgroundFetchIntervalMinimum的话,系统会尽可能多尽可能快地为你的应用进行后台获取,但是比如对于一个天气应用,可能对实时的数据并不会那么关心,就完全不必设置为UIApplicationBackgroundFetchIntervalMinimum,也许1小时会是一个更好的选择。新的Mac OSX 10.9上已经出现了功耗监测,用于让用户确定什么应用是能耗大户,有理由相信同样的东西也可能出现在iOS上。如果不想让用户因为你的应用是耗电大户而怒删的话,从现在开始注意一下应用的能耗还是蛮有必要的(做绿色环保低碳的iOS app,从今天开始~)。
+
+#### 实现后台获取代码并通知系统
+
+在完成了前两步后,只需要在AppDelegate里实现`-application:performFetchWithCompletionHandler:`就行了。系统将会在执行fetch的时候调用这个方法,然后开发者需要做的是在这个方法里完成获取的工作,然后刷新UI,并通知系统获取结束,以便系统尽快回到休眠状态。获取数据这是应用相关的内容,在此不做赘述,应用在前台能完成的工作在这里都能做,唯一的限制是系统不会给你很长时间来做fetch,一般会小于一分钟,而且fetch在绝大多数情况下将和别的应用共用网络连接。这些时间对于fetch一些简单数据来说是足够的了,比如微博的新条目(大图除外),接下来一小时的天气情况等。如果涉及到较大文件的传输的话,用后台获取的API就不合适了,而应该使用另一个新的文件传输的API,我们稍后再说。类似前面提到的后台任务完成时必须通知系统一样,在在获取完成后,也必须通知系统获取完成,方法是调用`-application:performFetchWithCompletionHandler:`的handler。这个CompletionHandler接收一个`UIBackgroundFetchResult`作为参数,可供选择的结果有`UIBackgroundFetchResultNewData`,`UIBackgroundFetchResultNoData`,`UIBackgroundFetchResultFailed`三种,分别表示获取到了新数据(此时系统将对现在的UI状态截图并更新App Switcher中你的应用的截屏),没有新数据,以及获取失败。写一个简单的例子吧:
+
+```objc
+//File: YourAppDelegate.m
+-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
+{
+ UINavigationController *navigationController = (UINavigationController*)self.window.rootViewController;
+
+ id fetchViewController = navigationController.topViewController;
+ if ([fetchViewController respondsToSelector:@selector(fetchDataResult:)]) {
+ [fetchViewController fetchDataResult:^(NSError *error, NSArray *results){
+ if (!error) {
+ if (results.count != 0) {
+ //Update UI with results.
+ //Tell system all done.
+ completionHandler(UIBackgroundFetchResultNewData);
+ } else {
+ completionHandler(UIBackgroundFetchResultNoData);
+ }
+ } else {
+ completionHandler(UIBackgroundFetchResultFailed);
+ }
+ }];
+ } else {
+ completionHandler(UIBackgroundFetchResultFailed);
+ }
+}
+```
+
+当然,实际情况中会比这要复杂得多,用户当前的ViewController是否合适做获取,获取后的数据如何处理都需要考虑。另外要说明的是上面的代码在获取成功后直接在appDelegate里更新UI,这只是为了能在同一处进行说明,但却是不正确的结构。比较好的做法是将获取和更新UI的业务逻辑都放到fetchViewController里,然后向其发送获取消息的时候将completionHandler作为参数传入,并在fetchViewController里完成获取结束的报告。
+
+另一个比较神奇的地方是系统将追踪用户的使用习惯,并根据对每个应用的使用时刻给一个合理的fetch时间。比如系统将记录你在每天早上9点上班的电车上,中午12点半吃饭时,以及22点睡觉前会刷一下微博,只要这个习惯持续个三四天,系统便会将应用的后台获取时刻调节为9点,12点和22点前一点。这样在每次你打开应用都直接有最新内容的同时,也节省了电量和流量。
+
+#### 后台获取的调试
+
+既然是系统决定的fetch,那我们要如何测试写的代码呢?难道是将应用退到后台,然后安心等待系统进行后台获取么?当然不是...Xcode5为我们提供了两种方法来测试后台获取的代码。一种是从后台获取中启动应用,另一种是当应用在后台时模拟一次后台推送。
+
+对于前者,我们可以新建一个Scheme来专门调试从后台启动。点击Xcode5的Product->Scheme->Edit Scheme(或者直接使用快捷键`⌘<`)。在编辑Scheme的窗口中点Duplicate Scheme按钮复制一个当前方案,然后在新Scheme的option中将Background Fetch打上勾。从这个Scheme来运行应用的时候,应用将不会直接启动切入前台,而是调用后台获取部分代码并更新UI,这样再点击图标进入应用时,你应该可以看到最新的数据和更新好的UI了。
+
+
+
+另一种是当应用在后台时,模拟一次后台获取。这个比较简单,在app调试运行时,点击Xcode5的Debug菜单中的Simulate Background Fetch,即可模拟完成一次获取调用。
+
+### 推送唤醒(Remote Notifications)
+
+远程推送(Remote Push Notifications)可以说是增加用户留存率的不二法则,在iOS6和之前,推送的类型是很单一的,无非就是显示标题内容,指定声音等。用户通过解锁进入你的应用后,appDelegate中通过推送打开应用的回调将被调用,然后你再获取数据,进行显示。这和没有后台获取时的打开应用后再获取数据刷新的问题是一样的。在iOS7中这个行为发生了一些改变,我们有机会使设备在接收到远端推送后让系统唤醒设备和我们的后台应用,并先执行一段代码来准备数据和UI,然后再提示用户有推送。这时用户如果解锁设备进入应用后将不会再有任何加载过程,新的内容将直接得到呈现。
+
+实装的方法和刚才的后台获取比较类似,还是一步步来:
+
+#### 启用推送唤醒
+
+和上面的后台获取类似,更改Info.plist,在`UIBackgroundModes`下加入`remote-notification`即可开启,当然同样的更简单直接的办法是使用Capabilities。
+
+#### 更改推送的payload
+
+在iOS7中,如果想要使用推送来唤醒应用运行代码的话,需要在payload中加入`content-available`,并设置为1。
+
+```javascript
+aps {
+ content-available: 1
+ alert: {...}
+ }
+```
+
+#### 实现推送唤醒代码并通知系统
+最后在appDelegate中实现`-application:didReceiveRemoteNotification:fetchCompletionHandle:`。这部分内容和上面的后台获取部分完全一样,在此不再重复。
+#### 一些限制和应用的例子
+因为一旦推送成功,用户的设备将被唤醒,因此这类推送不可能不受到限制。Apple将限制此类推送的频率,当频率超过一定限制后,带有content-available标志的推送将会被阻塞,以保证用户设备不被频繁唤醒。按照Apple的说法,这个频率在一小时内个位数次的推送的话不会有太大问题。
+
+Apple给出了几个典型的应用情景,比如一个电视节目类的应用,当用户标记某些剧目为喜爱时,当这些剧有更新时,可以给用户发送静默的唤醒推送通知,客户端在接到通知后检查更新并开始后台下载(注意后台下载的部分绝对不应该在推送回调中做,而是应该使用新的后台传输服务,后面详细介绍)。下载完成后发送一个本地推送告知用户新的内容已经准备完毕。这样在用户注意到推送并打开应用的时候,所有必要的内容已经下载完毕,UI也将切换至用户喜爱的剧目,用户只需要点击播放即可开始真正使用应用,这绝对是无比顺畅和优秀的体验。另一种应用情景是文件同步类,比如用户标记了一些文件为需要随时同步,这样用户在其他设备或网页服务上更改了这些文件时,可以发送静默推送然后使用后台传输来保持这些文件随时是最新。
+
+如果您是一路看下来的话,不难发现其实后台获取和静默推送在很多方面是很类似的,特别是实现和处理的方式,但是它们适用的情景是完全不同的。后台获取更多地使用在泛数据模式下,也即用户对特定数据并不是很关心,数据应该被更新的时间也不是很确定,典型的有社交类应用和天气类应用;而静默推送或者是推送唤醒更多地应该是用户感兴趣的内容发生更新时被使用,比如消息类应用和内容型服务等。根据不同的应用情景,选择合适的后台策略(或者混合使用两者),以带给用户绝佳体验,这是Apple所期望iOS7开发者做到的。
+
+### 后台传输(Background Transfer Service)
+
+iOS6和之前,iOS应用在大块数据的下载这一块限制是比较多的:只有应用在前台时能保持下载(用户按Home键切到后台或者是等到设备自动休眠都可能中止下载),在后台只有很短的最多十分钟时间可以保持网络连接。如果想要完成一个较大数据的下载,用户将不得不打开你的app并且基本无所事事。很多这种时候,用户会想要是在下载的时候能切到别的应用刷刷微博或者玩玩游戏,然后再切回来的就已经下载完成了的话,该有多好。iOS7中,这可以实现了。iOS7引入了后台传输的相关方式,用来保证应用退出后数据下载或者上传能继续进行。这种传输是由iOS系统进行管理的,没有时间限制,也不要求应用运行在前台。
+
+想要实现后台传输,就必须使用iOS7的新的网络连接的类,NSURLSession。这是iOS7中引入用以替代陈旧的NSURLConnection的类,著名的AFNetworking甚至不惜从底层开始完全重写以适配iOS7和NSURLSession(参见[这里](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide)),NSURLSession的重要性可见一斑。在这里我主要只介绍NSURLSession在后台传输中的一些使用,关于这个类的其他用法和对原有NSURLConnection的加强,只做稍微带过而不展开,有兴趣深入挖掘和使用的童鞋可以参看Apple的文档(或者更简单的方式是使用AFNetworking来处理网络相关内容,而不是直接和CFNetwork框架打交道)。
+
+#### 步骤和例子
+
+后台传输的的实现也十分简单,简单说分为三个步骤:创建后台传输用的NSURLSession对象;向这个对象中加入对应的传输的NSURLSessionTask,并开始传输;在实现appDelegate里实现`-application:handleEventsForBackgroundURLSession:completionHandler:`方法,以刷新UI及通知系统传输结束。接下来结合代码来看一看实际的用法吧~
+
+首先我们需要一个用于后台下载的session:
+
+```objc
+- (NSURLSession *)backgroundSession
+{
+ //Use dispatch_once_t to create only one background session. If you want more than one session, do with different identifier
+ static NSURLSession *session = nil;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.yourcompany.appId.BackgroundSession"];
+ session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
+ });
+ return session;
+}
+```
+这里创建并配置了NSURLSession,将其指定为后台session并设定delegate。
+
+接下来向其中加入对应的传输用的NSURLSessionTask,并启动下载。
+
+```objc
+//@property (nonatomic) NSURLSession *session;
+//@property (nonatomic) NSURLSessionDownloadTask *downloadTask;
+
+- (NSURLSession *)backgroundSession
+{
+ //...
+}
+
+- (void) beginDownload
+{
+ NSURL *downloadURL = [NSURL URLWithString:DownloadURLString];
+ NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL];
+ self.session = [self backgroundSession];
+ self.downloadTask = [self.session downloadTaskWithRequest:request];
+ [self.downloadTask resume];
+}
+```
+
+最后一步是在appDelegate中实现`-application:handleEventsForBackgroundURLSession:completionHandler:`
+
+```objc
+//AppDelegate.m
+- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier
+ completionHandler:(void (^)())completionHandler
+{
+ //Check if all transfers are done, and update UI
+ //Then tell system background transfer over, so it can take new snapshot to show in App Switcher
+ completionHandler();
+
+ //You can also pop up a local notification to remind the user
+ //...
+}
+```
+
+NSURLSession和对应的NSURLSessionTask有以下重要的delegate方法可以使用:
+
+```objc
+- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
+ didFinishDownloadingToURL:(NSURL *)location;
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
+ didCompleteWithError:(NSError *)error;
+```
+
+一旦后台传输的状态发生变化(包括正常结束和失败)的时候,应用将被唤醒并运行appDelegate中的回调,接下来NSURLSessionTask的委托方法将在后台被调用。虽然上面的例子中直接在appDelegate中call了completionHandler,但是实际上更好的选择是在appDelegate中暂时持有completionHandler,然后在NSURLSessionTask的delegate方法中检查是否确实完成了传输并更新UI后,再调用completionHandler。另外,你的应用到现在为止只是在后台运行,想要提醒用户传输完成的话,也许你还需要在这个时候发送一个本地推送(记住在这个时候你的应用是可以执行代码的,虽然是在后台),这样用户可以注意到你的应用的变化并回到应用,并开始已经准备好数据和界面。
+
+#### 一些限制
+
+首先,后台传输只会通过wifi来进行,用户大概也不会开心蜂窝数据的流量被后台流量用掉。后台下载的时间与以前的关闭应用后X分钟的模式不一样,而是为了节省电力变为离散式的下载,并与其他后台任务并发(比如接收邮件等)。另外还需要注意的是,对于下载后的内容不要忘记写到应用的目录下(一般来说这种可以重复获得的内容应该放到cache目录下),否则如果由于应用完全退出的情况导致没有保存到可再次访问的路径的话,那可就白做工了。
+
+后台传输非常适合用于文件,照片或者追加游戏内容关卡等的下载,如果配合后台获取或者静默推送的话,相信可以完全很多很有趣,并且以前被限制而无法实现的功能。
diff --git a/_posts/2013-08-31-shader-tutorial-2.markdown b/_posts/2013-08-31-shader-tutorial-2.markdown
new file mode 100644
index 00000000..eed8587a
--- /dev/null
+++ b/_posts/2013-08-31-shader-tutorial-2.markdown
@@ -0,0 +1,224 @@
+---
+layout: post
+title: 猫都能学会的Unity3D Shader入门指南(二)
+date: 2013-08-31 00:57:34.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+## 关于本系列
+
+这是Unity3D Shader入门指南系列的第二篇,本系列面向的对象是新接触Shader开发的Unity3D使用者,因为我本身自己也是Shader初学者,因此可能会存在错误或者疏漏,如果您在Shader开发上有所心得,很欢迎并恳请您指出文中纰漏,我会尽快改正。在[之前的开篇](http://onevcat.com/2013/07/shader-tutorial-1/)中介绍了一些Shader的基本知识,包括ShaderLab的基本结构和语法,以及简单逐句地讲解了一个基本的shader。在具有这些基础知识后,阅读简单的shader应该不会有太大问题,在继续教程之前简单阅读一下Unity的[Surface Shader Example](http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaderExamples.html),以检验您是否掌握了上一节的内容。如果您对阅读大部分示例Shader并没有太大问题,可以正确地指出Shader的结构,声明和使用的话,就说明您已经准备好继续阅读本节的内容了。
+
+## 法线贴图(Normal Mapping)
+
+法线贴图是凸凹贴图(Bump mapping)的一种常见应用,简单说就是在不增加模型多边形数量的前提下,通过渲染暗部和亮部的不同颜色深度,来为原来的贴图和模型增加视觉细节和真实效果。简单原理是在普通的贴图的基础上,再另外提供一张对应原来贴图的,可以表示渲染浓淡的贴图。通过将这张附加的表示表面凸凹的贴图的因素于实际的原贴图进行运算后,可以得到新的细节更加丰富富有立体感的渲染效果。在本节中,我们将首先实现一个法线贴图的Shader,然后对Unity Shader的光照模型进行一些讨论,并实现一个自定义的光照模型。最后再通过更改shader模拟一个石头上的积雪效果,并对模型顶点进行一些修改使积雪效果看起来比较真实。在本节结束的时候,我们就会有一个比较强大的可以满足一些真实开发工作时可用的shader了,而且更重要的是,我们将会掌握它是如何被创造出来的。
+
+关于法线贴图的效果图,可以对比看看下面。模型面数为500,左侧只使用了简单的Diffuse着色,右侧使用了法线贴图。比较两张图片不难发现,使用了法线贴图的石头在暗部和亮部都有着更好的表现。整体来说,凸凹感比Diffuse的结果增强许多,石头看起来更真实也更具有质感。
+
+
+
+本节中需要用到的上面的素材可以[在这里下载](http://vdisk.weibo.com/s/y-NNpUsxhYhZI),其中包括上面的石块的模型,一张贴图以及对应的法线贴图。将下载的package导入到工程中,并新建一个material,使用简单的Diffuse的Shader(比如上一节我们实现的),再加上一个合适的平行光光源,就可以得到我们左图的效果。另外,本节以及以后都会涉及到一些Unity内建的Shader的内容,比如一些标准常用函数和常量定义等,相关内容可以在Unity的内建Shader中找到,内建Shader可以在[Unity下载页面](http://unity3d.com/unity/download/archive)的版本右侧找到。
+
+接下来我们实现法线贴图。在实现之前,我们先简单地稍微多了解一些法线贴图的基本知识。大多数法线图一般都和下面的图类似,是一张以蓝紫色为主的图。这张法线图其实是一张RGB贴图,其中红,绿,蓝三个通道分别表示由高度图转换而来的该点的法线指向:Nx、Ny、Nz。在其中绝大部分点的法线都指向z方向,因此图更偏向于蓝色。在shader进行处理时,我们将光照与该点的法线值进行点积后即可得到在该光线下应有的明暗特性,再将其应用到原图上,即可反应在一定光照环境下物体的凹凸关系了。关于法向贴图的更多信息,可以参考[wiki上的相关条目](http://en.wikipedia.org/wiki/Normal_mapping)。
+
+
+
+回到正题,我们现在考虑的主要是Shader入门,而不是图像学的原理。再上一节我们写的Shader的基础上稍微做一些修改,就可以得到适应并完成法线贴图渲染的新Shader。新加入的部分进行了编号并在之后进行说明。
+
+```glsl
+Shader "Custom/Normal Mapping" {
+ Properties {
+ _MainTex ("Base (RGB)", 2D) = "white" {}
+
+ //1
+ _Bump ("Bump", 2D) = "bump" {}
+ }
+ SubShader {
+ Tags { "RenderType"="Opaque" }
+ LOD 200
+
+ CGPROGRAM
+ #pragma surface surf Lambert
+
+ sampler2D _MainTex;
+
+ //2
+ sampler2D _Bump;
+
+ struct Input {
+ float2 uv_MainTex;
+
+ //3
+ float2 uv_Bump;
+ };
+
+ void surf (Input IN, inout SurfaceOutput o) {
+ half4 c = tex2D (_MainTex, IN.uv_MainTex);
+
+ //4
+ o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump);
+
+ o.Albedo = c.rgb;
+ o.Alpha = c.a;
+ }
+ ENDCG
+ }
+ FallBack "Diffuse"
+}
+```
+
+1. 声明并加入一个显示名称为`Bump`的贴图,用于放置法线图
+2. 为了能够在CG程序中使用这张贴图,必须加入一个sample,希望你还记得~
+3. 获取Bump的uv信息作为输入
+4. 从法线图中提取法线信息,并将其赋予相应点的输出的Normal属性。`UnpackNormal`是定义在UnityCG.cginc文件中的方法,这个文件中包含了一系列常用的CG变量以及方法。`UnpackNormal`接受一个fixed4的输入,并将其转换为所对应的法线值(fixed3)。在解包得到这个值之后,将其赋给输出的Normal,就可以参与到光线运算中完成接下来的渲染工作了。
+
+现在保存并且编译这个Shader,创建新的material并使用这个shader,将石头的材质贴图和法线图分别拖放到Base和Bump里,再将其应用到石头模型上,应该就可以看到右侧图的效果了。
+
+## 光照模型
+
+在我们之前的看到的Shader中(其实也就上一节的基本diffuse和这里的normal mapping),都只使用了Lambert的光照模型(#pragma surface surf Lambert),这是一个很经典的漫反射模型,光强与入射光的方向和反射点处表面法向夹角的余弦成正比。关于Lambert和漫反射的一些详细的计算和推论,可以参看wiki([Lambert](http://en.wikipedia.org/wiki/Lambertian_reflectance),[漫反射](http://en.wikipedia.org/wiki/Diffuse_reflection))或者其他地方的介绍。一句话的简单解释就是一个点的反射光强是和该点的法线向量和入射光向量和强度和夹角有关系的,其结果就是这两个向量的点积。既然已经知道了光照计算的原理,我们先来看看如何实现一个自己的光照模型吧。
+
+在刚才的Shader上进行如下修改。
+
+* 首先将原来的`#pragma`行改为这样
+
+```glsl
+#pragma surface surf CustomDiffuse
+```
+
+* 然后在SubShader块中添加如下代码
+
+```glsl
+inline float4 LightingCustomDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) {
+ float difLight = max(0, dot (s.Normal, lightDir));
+ float4 col;
+ col.rgb = s.Albedo * _LightColor0.rgb * (difLight * atten * 2);
+ col.a = s.Alpha;
+ return col;
+}
+```
+
+* 最后保存,回到Unity。Shader将编译,如果一切正常,你将不会看到新的shader和之前的在材质表现上有任何不同。但是事实上我们现在的shader已经与Unity内建的diffuse光照模型撇清了关系,而在使用我们自己设定的光照模型了。
+
+喵的,这些代码都干了些什么!相信你一定会有这样的疑惑...没问题,没有疑惑的话那就不叫初学了,还是一行行讲来。首先正像我们上一篇所说,`#pragma`语句在这里声明了接下来的Shader的类型,计算调用的方法名,以及指定光照模型。在之前我们一直指定Lambert为光照模型,而现在我们将其换为了CustomDiffuse。
+
+接下来添加的代码是计算光照的实现。shader中对于方法的名称有着比较严格的约定,想要创建一个光照模型,首先要做的是按照规则声明一个光照计算的函数名字,即`Lighting`。对于我们的光照模型CustomDiffuse,其计算函数的名称自然就是`LightingCustomDiffuse`了。光照模型的计算是在surf方法的表面颜色之后,根据输入的光照条件来对原来的颜色在这种光照下的表现进行计算,最后输出新的颜色值给渲染单元完成在屏幕的绘制。
+
+也许你已经猜到了,我们之前用的Lambert光照模型是不是也有一个名字叫LightingLambert的光照计算函数呢?Bingo。在Unity的内建Shader中,有一个Lighting.cginc文件,里面就包含了LightingLambert的实现。也许你也注意到了,我们所实现的LightingCustomDiffuse的内容现在和Unity内建中的LightingLambert是完全一样的,这也就是使用新的shader的原来视觉上没有区别的原因,因为实现确实是完全一样的。
+
+首先来看输入量,`SurfaceOutput s`这个就是经过表面计算函数surf处理后的输出,我们讲对其上的点根据光线进行处理,`fixed3 lightDir`是光线的方向,`fixed atten`表示光衰减的系数。在计算光照的代码中,我们先将输入的s的法线值(在Normal mapping中的话这个值已经是法线图中的对应量了)和输入光线进行点积(dot函数是CG中内置的数学函数,希望你还记得,可以[参考这里](http://http.developer.nvidia.com/CgTutorial/cg_tutorial_chapter05.html))。点积的结果在-1至1之间,这个值越大表示法线与光线间夹角越小,这个点也就应该越亮。之后使用max来将这个系数结果限制在0到1之间,是为了避免负数情况的存在而导致最终计算的颜色变为负数,输出一团黑,一般来说这是我们不愿意看到的。接下来我们将surf输出的颜色与光线的颜色`_LightColor0.rgb`(由Unity根据场景中的光源得到的,它在Lighting.cginc中有声明)进行乘积,然后再与刚才计算的光强系数和输入的衰减系数相乘,最后得到在这个光线下的颜色输出(关于difLight * atten * 2中为什么有个乘2,这是一个历史遗留问题,主要是为了进行一些光强补偿,可以参见[这里的讨论](http://forum.unity3d.com/threads/94711-Why-(atten-*-2)))。
+
+在了解了基本实现方式之后,我们可以看看做一些修改玩玩儿。最简单的比如将这个Lambert模型改亮一些,比如换成Half Lambert模型。Half Lambert是由Valve创造的可以使物体在低光线条件下增亮的技术,最早被用于半条命(Half Life)中以避免在低光下物体的走形。简单说就是把光强系数先取一半,然后在加0.5,代码如下:
+
+```glsl
+inline float4 LightingCustomDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) {
+ float difLight = dot (s.Normal, lightDir);
+ float hLambert = difLight * 0.5 + 0.5;
+ float4 col;
+ col.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2);
+ col.a = s.Alpha;
+ return col;
+}
+```
+
+这样一来,原来光强0的点,现在对应的值变为了0.5,而原来是1的地方现在将保持为1。也就是说模型贴图的暗部被增强变亮了,而亮部基本保持和原来一样,防止过曝。使用Half Lambert前后的效果图如下,注意最右侧石头下方的阴影处细节更加明显了,而这一切都只是视觉效果的改变,不涉及任何贴图和模型的变化。
+
+
+
+## 表面贴图的追加效果
+
+OK,对于光线和自定义光照模型的讨论暂时到此为止,因为如果展开的话这将会一个庞大的图形学和经典光学的话题了。我们回到Shader,并且一起实现一些激动人心的效果吧。比如,在你的游戏场景中有一幕是雪地场景,而你希望做一些石头上白雪皑皑的覆盖效果,应该怎么办呢?难道让你可爱的3D设计师再去出一套覆雪的贴图然后使用新的贴图?当然不,不是不能,而是不该。因为新的贴图不仅会增大项目的资源包体积,更会增大之后修改和维护的难度,想想要是有好多石头需要实现同样的覆雪效果,或者是要随着游戏时间堆积的雪逐渐变多的话,你应该怎么办?难道让设计师再把所有的石头贴图都盖上雪,然后再按照雪的厚度出5套不同的贴图么?相信我,他们会疯的。
+
+于是,我们考虑用Shader来完成这件工作吧!先考虑下我们需要什么,积雪效果的话,我们需要积雪等级(用来表示积雪量),雪的颜色,以及积雪的方向。基本思路和实现自定义光照模型类似,通过计算原图的点在世界坐标中的法线方向与积雪方向的点积,如果大于设定的积雪等级的阈值的话则表示这个方向与积雪方向是一致的,其上是可以积雪的,显示雪的颜色,否则使用原贴图的颜色。废话不再多说,上代码,在上面的Shader的基础上,更改Properties里的内容为
+
+```glsl
+Properties {
+ _MainTex ("Base (RGB)", 2D) = "white" {}
+ _Bump ("Bump", 2D) = "bump" {}
+ _Snow ("Snow Level", Range(0,1) ) = 0
+ _SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0)
+ _SnowDirection ("Snow Direction", Vector) = (0,1,0)
+}
+```
+
+没有太多值得说的,唯一要提一下的是_SnowDirection设定的默认值为(0,1,0),这表示我们希望雪是垂直落下的。对应地,在CG程序中对这些变量进行声明:
+
+```glsl
+sampler2D _MainTex;
+sampler2D _Bump;
+float _Snow;
+float4 _SnowColor;
+float4 _SnowDirection;
+```
+
+接下来改变Input的内容:
+
+```glsl
+struct Input {
+ float2 uv_MainTex;
+ float2 uv_Bump;
+ float3 worldNormal; INTERNAL_DATA
+};
+```
+
+相对于上面的Shader输入来说,加入了一个`float3 worldNormal; INTERNAL_DATA`,如果SurfaceOutput中设定了Normal值的话,通过worldNormal可以获取当前点在世界中的法线值。详细的解说可以参见[Unity的Shader文档](http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaders.html)。接下来可以改变surf函数,实装积雪效果了。
+
+```glsl
+void surf (Input IN, inout SurfaceOutput o) {
+ half4 c = tex2D (_MainTex, IN.uv_MainTex);
+ o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump));
+
+ if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1,-1,_Snow)) {
+ o.Albedo = _SnowColor.rgb;
+ } else {
+ o.Albedo = c.rgb;
+ }
+
+ o.Alpha = c.a;
+}
+```
+
+和上面相比,加入了一个if…else…的判断。首先看这个条件的不等式的左侧,我们对雪的方向和和输入点的世界法线方向进行点积。`WorldNormalVector`通过输入的点及这个点的法线值,来计算它在世界坐标中的方向;右侧的lerp函数相信只要对插值有概念的同学都不难理解:当_Snow取最小值0时,这个函数将返回1,而_Snow取最大值时,返回-1。这样我们就可以通过设定_Snow的值来控制积雪的阈值,要是积雪等级_Snow是0时,不等式左侧不可能大于右侧,因此完全没有积雪;相反要是_Snow取最大值1时,由于左侧必定大于-1,所以全模型积雪。而随着取中间值的变化,积雪的情况便会有所不同。
+
+应用这个Shader,并且适当地调节一下积雪等级和颜色,可以得到如下右边的效果。
+
+
+
+## 更改顶点模型
+
+到现在位置,我们还仅指是在原贴图上进行操作,不管是用法线图使模型看起来凸凹有致,还是加上积雪,所有的计算和颜色的输出都只是“障眼法”,并没有对模型有任何实质的改动。但是对于积雪效果来说,实际上积雪是附加到石头上面,而不应当简单替换掉原来的颜色。但是具体实施起来,最简单的办法还是直接替换颜色,但是我们可以稍微变更一下模型,使原来的模型在积雪的方向稍微变大一些,这样来达到一种雪是附加到石头上的效果。
+
+我们继续修改之前的Shader,首先我们需要告诉surface shadow我们要改变模型的顶点。首先将#param行改为
+
+`#pragma surface surf CustomDiffuse vertex:vert`
+
+这告诉Shader我们想要改变模型顶点,并且我们会写一个叫做`vert`的函数来改变顶点。接下来我们再添加一个参数,在Properties中声明一个`_SnowDepth`变量,表示积雪的厚度,当然我们也需要在CG段中进行声明:
+
+```glsl
+//In Properties{…}
+_SnowDepth ("Snow Depth", Range(0,0.3)) = 0.1
+
+//In CG declare
+float _SnowDepth;
+```
+
+接下来实现vert方法,和之前积雪的运算其实比较类似,判断点积大小来决定是否需要扩大模型以及确定模型扩大的方向。在CG段中加入以下vert方法
+
+```glsl
+void vert (inout appdata_full v) {
+ float4 sn = mul(transpose(_Object2World) , _SnowDirection);
+ if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow * 2) / 3)) {
+ v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow;
+ }
+}
+```
+
+和surf的原理差不多,系统会输入一个当前的顶点的值,我们根据需要计算并填上新的值作为返回即可。上面第一行中使用`transpose`方法输出原矩阵的转置矩阵,在这里_Object2World是Unity ShaderLab的内建值,它表示将当前模型转换到世界坐标中的矩阵,将其与积雪方向做矩阵乘积得到积雪方向在物体的世界空间中的投影(把积雪方向转换到世界坐标中)。之后我们计算了这个世界坐标中实际的积雪方向和当前点的法线值的点积,并将结果与使用积雪等级的2/3进行比较lerp后的阈值比较。这样,当前点如果和积雪方向一致,并且积雪较为完整的话,将改变该点的模型顶点高度。
+
+加入模型更改前后的效果对比如下图,加入模型调整的右图表现要更为丰满真实。
+
+
+
+这节就到这里吧。本节中实现的Shader可以[在这里找到完整版本](https://gist.github.com/onevcat/6396814)进行参考,希望大家周末愉快~
diff --git a/_posts/2013-09-01-spring-list-like-ios7-message.markdown b/_posts/2013-09-01-spring-list-like-ios7-message.markdown
new file mode 100644
index 00000000..85ae9d24
--- /dev/null
+++ b/_posts/2013-09-01-spring-list-like-ios7-message.markdown
@@ -0,0 +1,199 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - iOS7中弹簧式列表的制作
+date: 2013-09-01 00:58:48.000000000 +09:00
+tags: 能工巧匠集
+---
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 206 Getting Started with UIKit Dynamics
+* Session 217 Exploring Scroll Views in iOS7
+
+UIScrollView可以说是UIKit中最重要的类之一了,包括UITableView和UICollectionView等重要的数据容器类都是UIScrollView的子类。在历年的WWDC上,UIScrollView和相关的API都有专门的主题进行介绍,也可以看出这个类的使用和变化之快。今年也不例外,因为iOS7完全重新定义了UI,这使得UIScrollView里原来不太会使用的一些用法和实现的效果在新的系统中得到了很好的表现。另外,由于引入了UIKit Dynamics,我们还可以结合ScrollView做出一些以前不太可能或者需要花费很大力气来实现的效果,包括带有重力的swipe或者是类似新的信息app中的带有弹簧效果聊天泡泡等。如果您还不太了解iOS7中信息app的效果,这里有一张gif图可以帮您大概了解一下:
+
+
+
+这次笔记的内容主要就是实现一个这样的效果。为了避免重复造轮子,我对这个效果进行了一些简单的封装,并连同这篇笔记的demo一起扔在了Github上,有需要的童鞋可以[到这里](https://github.com/onevcat/VVSpringCollectionViewFlowLayout)自取。
+
+iOS7的SDK中Apple最大的野心其实是想用SpriteKit来结束iOS平台游戏开发(至少是2D游戏开发)的乱战,统一游戏开发的方式并建立良性社区。而UIKit Dynamics,个人猜测Apple在花费力气为SpriteKit开发了物理引擎的同时,发现在UIKit中也可以使用,并能得到不错的效果,于是顺便革新了一下设计理念,在UI设计中引入了不少物理的概念。在iOS系统中,最为典型的应用是锁屏界面打开相机时中途放弃后的重力下坠+反弹的效果,另一个就是信息应用中的加入弹性的消息列表了。弹性列表在我自己上手试过以后觉得表现形式确实很生动,可以消除原来列表那种冷冰冰的感觉,是有可能在今后的设计中被大量使用的,因此决定学上一学。
+
+首先我们需要知道要如何实现这样一种效果,我们会用到哪些东西。毋庸置疑,如果不使用UIKit Dynamics的话,自己从头开始来完成会是一件非常费力的事情,你可能需要实现一套位置计算和物理模拟来使效果看起来真实滑润。而UIKit Dynamics中已经给我们提供了现成的弹簧效果,可以用`UIAttachmentBehavior`进行实现。另外,在说到弹性效果的时候,我们其实是在描述一个列表中的各个cell之间的关系,对于传统的UITableView来说,描述UITableViewCell之间的关系是比较复杂的(因为Apple已经把绝大多数工作做了,包括计算cell位置和位移等。使用越简单,定制就会越麻烦在绝大多数情况下都是真理)。而UICollectionView则通过layout来完成cell之间位置关系的描述,给了开发者较大的空间来实现布局。另外,UIKit Dynamics为UICollectionView做了很多方便的Catagory,可以很容易地“指导”UICollectionView利用加入物理特性计算后的结果,在实现弹性效果的时候,UICollectionView是我们不二的选择。
+
+如果您在阅读这篇笔记的时候遇到困难的话,建议您可以看看我之前的一些笔记,包括今年的[UIKit Dynamics的介绍](http://onevcat.com/2013/06/uikit-dynamics-started/)和去年的[UICollectionView介绍](http://onevcat.com/2012/06/introducing-collection-views/)。
+
+话不多说,我们开工。首先准备一个UICollectionViewFlowLayout的子类(在这里叫做`VVSpringCollectionViewFlowLayout`),然后在ViewController中用这个layout实现一个简单的collectionView:
+
+```objc
+//ViewController.m
+
+@interface ViewController ()
+@property (nonatomic, strong) VVSpringCollectionViewFlowLayout *layout;
+@end
+
+static NSString *reuseId = @"collectionViewCellReuseId";
+
+@implementation ViewController
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view, typically from a nib.
+
+ self.layout = [[VVSpringCollectionViewFlowLayout alloc] init];
+ self.layout.itemSize = CGSizeMake(self.view.frame.size.width, 44);
+ UICollectionView *collectionView = [[UICollectionView alloc] initWithFrame:self.view.frame collectionViewLayout:self.layout];
+
+ collectionView.backgroundColor = [UIColor clearColor];
+
+ [collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:reuseId];
+
+ collectionView.dataSource = self;
+ [self.view insertSubview:collectionView atIndex:0];
+}
+
+#pragma mark - UICollectionViewDataSource
+- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
+{
+ return 50;
+}
+
+- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
+{
+ UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseId forIndexPath:indexPath];
+
+ //Just give a random color to the cell. See https://gist.github.com/kylefox/1689973
+ cell.contentView.backgroundColor = [UIColor randomColor];
+ return cell;
+}
+@end
+```
+
+这部分没什么可以多说的,现在我们有一个标准的FlowLayout的UICollectionView了。通过使用UICollectionViewFlowLayout的子类来作为开始的layout,我们可以节省下所有的初始cell位置计算的代码,在上面代码的情况下,这个collectionView的表现和一个普通的tableView并没有太大不同。接下来我们着重来看看要如何实现弹性的layout。对于弹性效果,我们需要的是连接一个item和一个锚点间弹性连接的`UIAttachmentBehavior`,并能在滚动时设置新的锚点位置。我们在scroll的时候,只要使用UIKit Dynamics的计算结果,替代掉原来的位置更新计算(其实就是简单的scrollView的contentOffset的改变),就可以模拟出弹性的效果了。
+
+首先在`-prepareLayout`中为cell添加`UIAttachmentBehavior`。
+
+```objc
+//VVSpringCollectionViewFlowLayout.m
+@interface VVSpringCollectionViewFlowLayout()
+@property (nonatomic, strong) UIDynamicAnimator *animator;
+@end
+
+@implementation VVSpringCollectionViewFlowLayout
+//...
+
+-(void)prepareLayout {
+ [super prepareLayout];
+
+ if (!_animator) {
+ _animator = [[UIDynamicAnimator alloc] initWithCollectionViewLayout:self];
+ CGSize contentSize = [self collectionViewContentSize];
+ NSArray *items = [super layoutAttributesForElementsInRect:CGRectMake(0, 0, contentSize.width, contentSize.height)];
+
+ for (UICollectionViewLayoutAttributes *item in items) {
+ UIAttachmentBehavior *spring = [[UIAttachmentBehavior alloc] initWithItem:item attachedToAnchor:item.center];
+
+ spring.length = 0;
+ spring.damping = 0.5;
+ spring.frequency = 0.8;
+
+ [_animator addBehavior:spring];
+ }
+ }
+}
+@end
+```
+
+prepareLayout将在CollectionView进行排版的时候被调用。首先当然是call一下super的prepareLayout,你肯定不会想要全都要自己进行设置的。接下来,如果是第一次调用这个方法的话,先初始化一个UIDynamicAnimator实例,来负责之后的动画效果。iOS7 SDK中,UIDynamicAnimator类专门有一个针对UICollectionView的Category,以使UICollectionView能够轻易地利用UIKit Dynamics的结果。在`UIDynamicAnimator.h`中能够找到这个Category:
+
+```objc
+@interface UIDynamicAnimator (UICollectionViewAdditions)
+
+// When you initialize a dynamic animator with this method, you should only associate collection view layout attributes with your behaviors.
+// The animator will employ thecollection view layout’s content size coordinate system.
+- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout*)layout;
+
+// The three convenience methods returning layout attributes (if associated to behaviors in the animator) if the animator was configured with collection view layout
+- (UICollectionViewLayoutAttributes*)layoutAttributesForCellAtIndexPath:(NSIndexPath*)indexPath;
+- (UICollectionViewLayoutAttributes*)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;
+- (UICollectionViewLayoutAttributes*)layoutAttributesForDecorationViewOfKind:(NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath;
+
+@end
+```
+
+于是通过`-initWithCollectionViewLayout:`进行初始化后,这个UIDynamicAnimator实例便和我们的layout进行了绑定,之后这个layout对应的attributes都应该由绑定的UIDynamicAnimator的实例给出。就像下面这样:
+
+```objc
+//VVSpringCollectionViewFlowLayout.m
+@implementation VVSpringCollectionViewFlowLayout
+
+//...
+
+-(NSArray *)layoutAttributesForElementsInRect:(CGRect)rect {
+ return [_animator itemsInRect:rect];
+}
+
+-(UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath {
+ return [_animator layoutAttributesForCellAtIndexPath:indexPath];
+}
+@end
+```
+
+让我们回到`-prepareLayout`方法中,在创建了UIDynamicAnimator实例后,我们对于这个layout中的每个attributes对应的点,都创建并添加一个添加一个`UIAttachmentBehavior`(在iOS7 SDK中,UICollectionViewLayoutAttributes已经实现了UIDynamicItem接口,可以直接参与UIKit Dynamic的计算中去)。创建时我们希望collectionView的每个cell就保持在原位,因此我们设定了锚点为当前attribute本身的center。
+
+接下来我们考虑滑动时的弹性效果的实现。在系统的信息app中,我们可以看到弹性效果有两个特点:
+
+* 随着滑动的速度增大,初始的拉伸和压缩的幅度将变大
+* 随着cell距离屏幕触摸位置越远,拉伸和压缩的幅度
+
+对于考虑到这两方面的特点,我们所期望的滑动时的各cell锚点的变化应该是类似这样的:
+
+
+
+现在我们来实现这个锚点的变化。既然都是滑动,我们是不是可以考虑在UIScrollView的`–scrollViewDidScroll:`委托方法中来设定新的Behavior锚点值呢?理论上来说当然是可以的,但是如果这样的话我们大概就不得不面临着将刚才的layout实例设置为collectionView的delegate这样一个事实。但是我们都知道layout应该做的事情是给collectionView提供必要的布局信息,而不应该负责去处理它的委托事件。处理collectionView的回调更恰当地应该由处于collectionView的controller层级的类来完成,而不应该由一个给collectionView提供数据和信息的类来响应。在`UICollectionViewLayout`中,我们有一个叫做`-shouldInvalidateLayoutForBoundsChange:`的方法,每次layout的bounds发生变化的时候,collectionView都会询问这个方法是否需要为这个新的边界和更新layout。一般情况下只要layout没有根据边界不同而发生变化的话,这个方法直接不做处理地返回NO,表示保持现在的layout即可,而每次bounds改变时这个方法都会被调用的特点正好可以满足我们更新锚点的需求,因此我们可以在这里面完成锚点的更新。
+
+```objc
+//VVSpringCollectionViewFlowLayout.m
+@implementation VVSpringCollectionViewFlowLayout
+
+//...
+
+-(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds {
+ UIScrollView *scrollView = self.collectionView;
+ CGFloat scrollDelta = newBounds.origin.y - scrollView.bounds.origin.y;
+
+ //Get the touch point
+ CGPoint touchLocation = [scrollView.panGestureRecognizer locationInView:scrollView];
+
+ for (UIAttachmentBehavior *spring in _animator.behaviors) {
+ CGPoint anchorPoint = spring.anchorPoint;
+
+ CGFloat distanceFromTouch = fabsf(touchLocation.y - anchorPoint.y);
+ CGFloat scrollResistance = distanceFromTouch / 500;
+
+ UICollectionViewLayoutAttributes *item = [spring.items firstObject];
+ CGPoint center = item.center;
+
+ //In case the added value bigger than the scrollDelta, which leads an unreasonable effect
+ center.y += (scrollDelta > 0) ? MIN(scrollDelta, scrollDelta * scrollResistance)
+ : MAX(scrollDelta, scrollDelta * scrollResistance);
+ item.center = center;
+
+ [_animator updateItemUsingCurrentState:item];
+ }
+ return NO;
+}
+
+@end
+```
+
+首先我们计算了这次scroll的距离`scrollDelta`,为了得到每个item与触摸点的之间的距离,我们当然还需要知道触摸点的坐标`touchLocation`。接下来,可以根据距离对每个锚点进行设置了:简单地计算了原来锚点与触摸点之间的距离`distanceFromTouch`,并由此计算一个系数。接下来,对于当前的item,我们获取其当前锚点位置,然后将其根据`scrollDelta`的数值和刚才计算的系数,重新设定锚点的位置。最后我们需要告诉UIDynamicAnimator我们已经完成了对冒点的更新,现在可以开始更新物理计算,并随时准备collectionView来取LayoutAttributes的数据了。
+
+也许你还没有缓过神来?但是我们确实已经做完了,让我们来看看实际的效果吧:
+
+
+
+当然,通过调节`damping`,`frequency`和`scrollResistance`的系数等参数,可以得到弹性不同的效果,比如更多的震荡或者更大的幅度等等。
+
+这个layout实现起来非常简单,我顺便封装了一下放到了Github上,大家有需要的话可以[点击这里下载](https://github.com/onevcat/VVSpringCollectionViewFlowLayout)并直接使用。
+
diff --git a/_posts/2013-10-11-vc-transition-in-ios7.markdown b/_posts/2013-10-11-vc-transition-in-ios7.markdown
new file mode 100644
index 00000000..fbcfb49e
--- /dev/null
+++ b/_posts/2013-10-11-vc-transition-in-ios7.markdown
@@ -0,0 +1,418 @@
+---
+layout: post
+title: WWDC 2013 Session笔记 - iOS7中的ViewController切换
+date: 2013-10-11 00:59:56.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这是我的WWDC2013系列笔记中的一篇,完整的笔记列表请参看[这篇总览](http://onevcat.com/2013/06/developer-should-know-about-ios7/)。本文仅作为个人记录使用,也欢迎在[许可协议](http://creativecommons.org/licenses/by-nc/3.0/deed.zh)范围内转载或使用,但是还烦请保留原文链接,谢谢您的理解合作。如果您觉得本站对您能有帮助,您可以使用[RSS](http://onevcat.com/atom.xml)或[邮件](http://eepurl.com/wNSkj)方式订阅本站,这样您将能在第一时间获取本站信息。
+
+本文涉及到的WWDC2013 Session有
+
+* Session 201 Building User Interfaces for iOS 7
+* Session 218 Custom Transitions Using View Controllers
+* Session 226 Implementing Engaging UI on iOS
+
+毫无疑问,ViewController(在本文中简写为VC)是使用MVC构建Cocoa或者CocoaTouch程序时最重要的一个类,我们的日常工作中一般来说最花费时间和精力的也是在为VC部分编写代码。苹果产品是注重用户体验的,而对细节进行琢磨也是苹果对于开发者一直以来的要求和希望。在用户体验中,VC之间的关系,比如不同VC之间迁移和转换动画效果一直是一个值得不断推敲的重点。在iOS7中,苹果给出了一套完整的VC制作之间迁移效果的方案,可以说是为现在这部分各种不同实现方案指出了一条推荐的统一道路。
+
+### iOS 7 SDK之前的VC切换解决方案
+
+在深入iOS 7的VC切换效果的新API实现之前,先让我们回顾下现在的一般做法吧。这可以帮助理解为什么iOS7要对VC切换给出新的解决方案,如果您对iOS 5中引入的VC容器比较熟悉的话,可以跳过这节。
+
+在iOS5和iOS6中,除了标准的Push,Tab和PresentModal之外,一般是使用ChildViewController的方式来完成VC之间切换的过渡效果。ChildViewController和自定义的Controller容器是iOS 5 SDK中加入的,可以用来生成自定义的VC容器,简单来说典型的一种用法类似这样:
+
+```objc
+//ContainerVC.m
+
+[self addChildViewController:toVC];
+[fromVC willMoveToParentViewController:nil];
+[self.view addSubview:toVC.view];
+
+__weak id weakSelf = self;
+[self transitionFromViewController:fromVC
+ toViewController:toVC duration:0.3
+ options:UIViewAnimationOptionTransitionCrossDissolve
+ animations:^{}
+ completion:^(BOOL finished) {
+ [fromVC.view removeFromSuperView];
+ [fromVC removeFromParentViewController];
+ [toVC didMoveToParentViewController:weakSelf];
+}];
+```
+
+在自己对view进行管理的同时,可以使用transitionFromViewController:toViewController:...的Animation block中可以实现一些简单的切换效果。去年年初我写的[UIViewController的误用](http://onevcat.com/2012/02/uiviewcontroller/)一文中曾经指出类似`[viewController.view addSubview:someOtherViewController.view];`这样的代码的存在,一般就是误用VC。这个结论适用于非Controller容器,对于自定义的Controller容器来说,向当前view上添加其他VC的view是正确的做法(当然不能忘了也将VC本身通过`addChildViewController:`方法添加到容器中)。
+
+VC容器的主要目的是解决将不同VC添加到同一个屏幕上的需求,以及可以提供一些简单的自定义切换效果。使用VC容器可以使view的关系正确,使添加的VC能够正确接收到例如屏幕旋转,viewDidLoad:等VC事件,进而进行正确相应。VC容器确实可以解决一部分问题,但是也应该看到,对于自定义切换效果来说,这样的解决还有很多不足。首先是代码高度耦合,VC切换部分的代码直接写在container中,难以分离重用;其次能够提供的切换效果比较有限,只能使用UIView动画来切换,管理起来也略显麻烦。iOS 7提供了一套新的自定义VC切换,就是针对这两个问题的。
+
+### iOS 7 自定义ViewController动画切换
+
+#### 自定义动画切换的相关的主要API
+
+在深入之前,我们先来看看新SDK中有关这部分内容的相关接口以及它们的关系和典型用法。这几个接口和类的名字都比较相似,但是还是能比较好的描述出各自的职能的,一开始的话可能比较迷惑,但是当自己动手实现一两个例子之后,它们之间的关系就会逐渐明晰起来。(相关的内容都定义在UIKit的[UIViewControllerTransitioning.h](https://gist.github.com/onevcat/6944809)中了)
+
+
+#### @protocol [UIViewControllerContextTransitioning](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerContextTransitioning_protocol/Reference/Reference.html)
+
+这个接口用来提供切换上下文给开发者使用,包含了从哪个VC到哪个VC等各类信息,一般不需要开发者自己实现。具体来说,iOS7的自定义切换目的之一就是切换相关代码解耦,在进行VC切换时,做切换效果实现的时候必须要需要切换前后VC的一些信息,**系统在新加入的API的比较的地方都会提供一个实现了该接口的对象**,以供我们使用。
+
+**对于切换的动画实现来说**(这里先介绍简单的动画,在后面我会再引入手势驱动的动画),这个接口中最重要的方法有:
+
+* -(UIView *)containerView; VC切换所发生的view容器,开发者应该将切出的view移除,将切入的view加入到该view容器中。
+* -(UIViewController *)viewControllerForKey:(NSString *)key; 提供一个key,返回对应的VC。现在的SDK中key的选择只有UITransitionContextFromViewControllerKey和UITransitionContextToViewControllerKey两种,分别表示将要切出和切入的VC。
+* -(CGRect)initialFrameForViewController:(UIViewController *)vc; 某个VC的初始位置,可以用来做动画的计算。
+* -(CGRect)finalFrameForViewController:(UIViewController *)vc; 与上面的方法对应,得到切换结束时某个VC应在的frame。
+* -(void)completeTransition:(BOOL)didComplete; 向这个context报告切换已经完成。
+
+
+#### @protocol [UIViewControllerAnimatedTransitioning](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIViewControllerAnimatedTransitioning_Protocol/Reference/Reference.html)
+
+这个接口负责切换的具体内容,也即“切换中应该发生什么”。开发者在做自定义切换效果时大部分代码会是用来实现这个接口。它只有两个方法需要我们实现:
+
+* -(NSTimeInterval)transitionDuration:(id < UIViewControllerContextTransitioning >)transitionContext; 系统给出一个切换上下文,我们根据上下文环境返回这个切换所需要的花费时间(一般就返回动画的时间就好了,SDK会用这个时间来在百分比驱动的切换中进行帧的计算,后面再详细展开)。
+
+* -(void)animateTransition:(id < UIViewControllerContextTransitioning >)transitionContext; 在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。
+
+#### @protocol [UIViewControllerTransitioningDelegate](https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewControllerTransitioningDelegate_protocol/Reference/Reference.html)
+
+这个接口的作用比较简单单一,在需要VC切换的时候系统会像实现了这个接口的对象询问是否需要使用自定义的切换效果。这个接口共有四个类似的方法:
+
+* -(id< UIViewControllerAnimatedTransitioning >)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
+
+* -(id< UIViewControllerAnimatedTransitioning >)animationControllerForDismissedController:(UIViewController *)dismissed;
+
+* -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForPresentation:(id < UIViewControllerAnimatedTransitioning >)animator;
+
+* -(id< UIViewControllerInteractiveTransitioning >)interactionControllerForDismissal:(id < UIViewControllerAnimatedTransitioning >)animator;
+
+前两个方法是针对动画切换的,我们需要分别在呈现VC和解散VC时,给出一个实现了UIViewControllerAnimatedTransitioning接口的对象(其中包含切换时长和如何切换)。后两个方法涉及交互式切换,之后再说。
+
+### Demo
+
+还是那句话,一百行的讲解不如一个简单的小Demo,于是..it's demo time~ 整个demo的代码我放到了github的[这个页面](https://github.com/onevcat/VCTransitionDemo)上,有需要的朋友可以参照着看这篇文章。
+
+我们打算做一个简单的自定义的modalViewController的切换效果。普通的present modal VC的效果大家都已经很熟悉了,这次我们先实现一个自定义的类似的modal present的效果,与普通效果不同的是,我们希望modalVC出现的时候不要那么乏味的就简单从底部出现,而是带有一个弹性效果(这里虽然是弹性,但是仅指使用UIView的模拟动画,而不设计iOS 7的另一个重要特性UIKit Dynamics。用UIKit Dynamics当然也许可以实现更逼真华丽的效果,但是已经超出本文的主题范畴了,因此不在这里展开了。关于UIKit Dynamics,可以参看我之前关于这个主题的[一篇介绍](http://onevcat.com/2013/06/uikit-dynamics-started/))。我们首先实现简单的ModalVC弹出吧..这段非常基础,就交待了一下背景,非初级人士请跳过代码段..
+
+先定义一个ModalVC,以及相应的protocal和delegate方法:
+
+```objc
+//ModalViewController.h
+
+@class ModalViewController;
+@protocol ModalViewControllerDelegate
+
+-(void) modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController;
+
+@end
+
+@interface ModalViewController : UIViewController
+@property (nonatomic, weak) id delegate;
+@end
+
+//ModalViewController.m
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view.
+ self.view.backgroundColor = [UIColor lightGrayColor];
+
+ UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
+ button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
+ [button setTitle:@"Dismiss me" forState:UIControlStateNormal];
+ [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:button];
+}
+
+-(void) buttonClicked:(id)sender
+{
+ if (self.delegate && [self.delegate respondsToSelector:@selector(modalViewControllerDidClickedDismissButton:)]) {
+ [self.delegate modalViewControllerDidClickedDismissButton:self];
+ }
+}
+
+```
+
+这个是很标准的modalViewController的实现方式了。需要多嘴一句的是,在实际使用中有的同学喜欢在-buttonClicked:中直接给self发送dismissViewController的相关方法。在现在的SDK中,如果当前的VC是被显示的话,这个消息会被直接转发到显示它的VC去。但是这并不是一个好的实现,违反了程序设计的哲学,也很容易掉到坑里,具体案例可以参看[这篇文章的评论](http://onevcat.com/2011/11/objc-block/)。
+
+所以我们用标准的方式来呈现和解散这个VC:
+
+```objc
+//MainViewController.m
+
+- (void)viewDidLoad
+{
+ [super viewDidLoad];
+ // Do any additional setup after loading the view.
+ UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
+ button.frame = CGRectMake(80.0, 210.0, 160.0, 40.0);
+ [button setTitle:@"Click me" forState:UIControlStateNormal];
+ [button addTarget:self action:@selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
+ [self.view addSubview:button];
+}
+
+-(void) buttonClicked:(id)sender
+{
+ ModalViewController *mvc = [[ModalViewController alloc] init];
+ mvc.delegate = self;
+ [self presentViewController:mvc animated:YES completion:nil];
+}
+
+-(void)modalViewControllerDidClickedDismissButton:(ModalViewController *)viewController
+{
+ [self dismissViewControllerAnimated:YES completion:nil];
+}
+```
+
+测试一下,没问题,然后我们可以开始实现自定义的切换效果了。首先我们需要一个实现了UIViewControllerAnimatedTransitioning的对象..嗯,新建一个类来实现吧,比如BouncePresentAnimation:
+
+```objc
+//BouncePresentAnimation.h
+@interface BouncePresentAnimation : NSObject
+
+@end
+
+//BouncePresentAnimation.m
+- (NSTimeInterval)transitionDuration:(id )transitionContext
+{
+ return 0.8f;
+}
+
+- (void)animateTransition:(id )transitionContext
+{
+ // 1. Get controllers from transition context
+ UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
+
+ // 2. Set init frame for toVC
+ CGRect screenBounds = [[UIScreen mainScreen] bounds];
+ CGRect finalFrame = [transitionContext finalFrameForViewController:toVC];
+ toVC.view.frame = CGRectOffset(finalFrame, 0, screenBounds.size.height);
+
+ // 3. Add toVC's view to containerView
+ UIView *containerView = [transitionContext containerView];
+ [containerView addSubview:toVC.view];
+
+ // 4. Do animate now
+ NSTimeInterval duration = [self transitionDuration:transitionContext];
+ [UIView animateWithDuration:duration
+ delay:0.0
+ usingSpringWithDamping:0.6
+ initialSpringVelocity:0.0
+ options:UIViewAnimationOptionCurveLinear
+ animations:^{
+ toVC.view.frame = finalFrame;
+ } completion:^(BOOL finished) {
+ // 5. Tell context that we completed.
+ [transitionContext completeTransition:YES];
+ }];
+}
+```
+
+解释一下这个实现:
+
+1. 我们首先需要得到参与切换的两个ViewController的信息,使用context的方法拿到它们的参照;
+2. 对于要呈现的VC,我们希望它从屏幕下方出现,因此将初始位置设置到屏幕下边缘;
+3. 将view添加到containerView中;
+4. 开始动画。这里的动画时间长度和切换时间长度一致,都为0.8s。usingSpringWithDamping的UIView动画API是iOS7新加入的,描述了一个模拟弹簧动作的动画曲线,我们在这里只做使用,更多信息可以参看[相关文档](https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/occ/clm/UIView/animateWithDuration:delay:usingSpringWithDamping:initialSpringVelocity:options:animations:completion:);(顺便多说一句,iOS7中对UIView动画添加了一个很方便的Category,UIViewKeyframeAnimations。使用其中方法可以为UIView动画添加关键帧动画)
+5. 在动画结束后我们必须向context报告VC切换完成,是否成功(在这里的动画切换中,没有失败的可能性,因此直接pass一个YES过去)。系统在接收到这个消息后,将对VC状态进行维护。
+
+接下来我们实现一个UIViewControllerTransitioningDelegate,应该就能让它工作了。简单来说,一个比较好的地方是直接在MainViewController中实现这个接口。在MainVC中声明实现这个接口,然后加入或变更为如下代码:
+
+```objc
+@interface MainViewController ()
+@property (nonatomic, strong) BouncePresentAnimation *presentAnimation;
+@end
+
+@implementation MainViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
+ if (self) {
+ // Custom initialization
+ _presentAnimation = [BouncePresentAnimation new];
+ }
+ return self;
+}
+
+-(void) buttonClicked:(id)sender
+{
+ //...
+ mvc.transitioningDelegate = self;
+ //...
+}
+
+- (id )animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
+{
+ return self.presentAnimation;
+}
+```
+
+Believe or not, we have done. 跑一下,应该可以得到如下效果:
+
+
+
+### 手势驱动的百分比切换
+
+iOS7引入了一种手势驱动的VC切换的方式(交互式切换)。如果你使用系统的各种应用,在navViewController里push了一个新的VC的话,返回时并不需要点击左上的Back按钮,而是通过从屏幕左侧划向右侧即可完成返回操作。而在这个操作过程中,我们甚至可以撤销我们的手势,以取消这次VC转移。在新版的Safari中,我们甚至可以用相同的手势来完成网页的后退功能(所以很大程度上来说屏幕底部的工具栏成为了摆设)。如果您还不知道或者没太留意过这个改动,不妨现在就拿手边的iOS7这辈试试看,手机浏览的朋友记得切回来哦 :)
+
+我们这就动手在自己的VC切换中实现这个功能吧,首先我们需要在刚才的知识基础上补充一些东西:
+
+首先是UIViewControllerContextTransitioning,刚才提到这个是系统提供的VC切换上下文,如果您深入看了它的头文件描述的话,应该会发现其中有三个关于InteractiveTransition的方法,正是用来处理交互式切换的。但是在初级的实际使用中我们其实可以不太理会它们,而是使用iOS 7 SDK已经给我们准备好的一个现成转为交互式切换而新加的类:UIPercentDrivenInteractiveTransition。
+
+#### [UIPercentDrivenInteractiveTransition](https://developer.apple.com/library/ios/documentation/uikit/reference/UIPercentDrivenInteractiveTransition_class/Reference/Reference.html)是什么
+
+这是一个实现了UIViewControllerInteractiveTransitioning接口的类,为我们预先实现和提供了一系列便利的方法,可以用一个百分比来控制交互式切换的过程。一般来说我们更多地会使用某些手势来完成交互式的转移(当然用的高级的话用其他的输入..比如声音,iBeacon距离或者甚至面部微笑来做输入驱动也无不可,毕竟想象无极限嘛..),这样使用这个类(一般是其子类)的话就会非常方便。我们在手势识别中只需要告诉这个类的实例当前的状态百分比如何,系统便根据这个百分比和我们之前设定的迁移方式为我们计算当前应该的UI渲染,十分方便。具体的几个重要方法:
+
+* -(void)updateInteractiveTransition:(CGFloat)percentComplete 更新百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
+* -(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
+* –(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态
+
+#### @protocol [UIViewControllerInteractiveTransitioning](https://developer.apple.com/library/ios/documentation/uikit/reference/UIViewControllerInteractiveTransitioning_protocol/Reference/Reference.html)
+
+就如上面提到的,UIPercentDrivenInteractiveTransition只是实现了这个接口的一个类。为了实现交互式切换的功能,我们需要实现这个接口。因为大部分时候我们其实不需要自己来实现这个接口,因此在这篇入门中就不展开说明了,有兴趣的童鞋可以自行钻研。
+
+还有就是上面提到过的UIViewControllerTransitioningDelegate中的返回Interactive实现对象的方法,我们同样会在交互式切换中用到它们。
+
+### 继续Demo
+
+Demo time again。在刚才demo的基础上,这次我们用一个向上划动的手势来吧之前呈现的ModalViewController给dismiss掉~当然是交互式的切换,可以半途取消的那种。
+
+首先新建一个类,继承自UIPercentDrivenInteractiveTransition,这样我们可以省不少事儿。
+
+```objc
+//SwipeUpInteractiveTransition.h
+@interface SwipeUpInteractiveTransition : UIPercentDrivenInteractiveTransition
+
+@property (nonatomic, assign) BOOL interacting;
+
+- (void)wireToViewController:(UIViewController*)viewController;
+
+@end
+
+//SwipeUpInteractiveTransition.m
+@interface SwipeUpInteractiveTransition()
+@property (nonatomic, assign) BOOL shouldComplete;
+@property (nonatomic, strong) UIViewController *presentingVC;
+@end
+
+@implementation SwipeUpInteractiveTransition
+-(void)wireToViewController:(UIViewController *)viewController
+{
+ self.presentingVC = viewController;
+ [self prepareGestureRecognizerInView:viewController.view];
+}
+
+- (void)prepareGestureRecognizerInView:(UIView*)view {
+ UIPanGestureRecognizer *gesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
+ [view addGestureRecognizer:gesture];
+}
+
+-(CGFloat)completionSpeed
+{
+ return 1 - self.percentComplete;
+}
+
+- (void)handleGesture:(UIPanGestureRecognizer *)gestureRecognizer {
+ CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view.superview];
+ switch (gestureRecognizer.state) {
+ case UIGestureRecognizerStateBegan:
+ // 1. Mark the interacting flag. Used when supplying it in delegate.
+ self.interacting = YES;
+ [self.presentingVC dismissViewControllerAnimated:YES completion:nil];
+ break;
+ case UIGestureRecognizerStateChanged: {
+ // 2. Calculate the percentage of guesture
+ CGFloat fraction = translation.y / 400.0;
+ //Limit it between 0 and 1
+ fraction = fminf(fmaxf(fraction, 0.0), 1.0);
+ self.shouldComplete = (fraction > 0.5);
+
+ [self updateInteractiveTransition:fraction];
+ break;
+ }
+ case UIGestureRecognizerStateEnded:
+ case UIGestureRecognizerStateCancelled: {
+ // 3. Gesture over. Check if the transition should happen or not
+ self.interacting = NO;
+ if (!self.shouldComplete || gestureRecognizer.state == UIGestureRecognizerStateCancelled) {
+ [self cancelInteractiveTransition];
+ } else {
+ [self finishInteractiveTransition];
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+@end
+
+```
+
+有点长,但是做的事情还是比较简单的。
+
+1. 我们设定了一个BOOL变量来表示是否处于切换过程中。这个布尔值将在监测到手势开始时被设置,我们之后会在调用返回这个InteractiveTransition的时候用到。
+2. 计算百分比,我们设定了向下划动400像素或以上为100%,每次手势状态变化时根据当前手势位置计算新的百分比,结果被限制在0~1之间。然后更新InteractiveTransition的百分数。
+3. 手势结束时,把正在切换的标设置回NO,然后进行判断。在2中我们设定了手势距离超过设定一半就认为应该结束手势,否则就应该返回原来状态。在这里使用其进行判断,已决定这次transition是否应该结束。
+
+接下来我们需要添加一个向下移动的UIView动画,用来表现dismiss。这个十分简单,和BouncePresentAnimation很相似,写一个NormalDismissAnimation的实现了UIViewControllerAnimatedTransitioning接口的类就可以了,本文里略过不写了,感兴趣的童鞋可以自行查看源码。
+
+最后调整MainViewController的内容,主要修改点有三个地方:
+
+```objc
+//MainViewController.m
+@interface MainViewController ()
+//...
+// 1. Add dismiss animation and transition controller
+@property (nonatomic, strong) NormalDismissAnimation *dismissAnimation;
+@property (nonatomic, strong) SwipeUpInteractiveTransition *transitionController;
+@end
+
+@implementation MainViewController
+
+- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
+{
+ //...
+ _dismissAnimation = [NormalDismissAnimation new];
+ _transitionController = [SwipeUpInteractiveTransition new];
+ //...
+}
+
+-(void) buttonClicked:(id)sender
+{
+ //...
+ // 2. Bind current VC to transition controller.
+ [self.transitionController wireToViewController:mvc];
+ //...
+}
+
+// 3. Implement the methods to supply proper objects.
+-(id)animationControllerForDismissedController:(UIViewController *)dismissed
+{
+ return self.dismissAnimation;
+}
+
+-(id)interactionControllerForDismissal:(id)animator {
+ return self.transitionController.interacting ? self.transitionController : nil;
+}
+
+```
+
+1. 在其中添加dismiss时候的动画和交互切换Controller
+2. 在初始化modalVC的时候为交互切换的Controller绑定VC
+3. 为UIViewControllerTransitioningDelegate实现dismiss时候的委托方法,包括返回对应的动画以及交互切换Controller
+
+完成了,如果向下划动时,效果如下:
+
+
+
+
+### 关于iOS 7中自定义VC切换的一些总结
+
+demo中只展示了对于modalVC的present和dismiss的自定义切换效果,当然对与Navigation Controller的Push和Pop切换也是有相应的一套方法的。实现起来和dismiss十分类似,只不过对应UIViewControllerTransitioningDelegate的询问动画和交互的方法换到了UINavigationControllerDelegate中(为了区别push或者pop,看一下这个接口应该能马上知道)。另外一个很好的福利是,对于标准的navController的Pop操作,苹果已经替我们实现了手势驱动返回,我们不用再费心每个去实现一遍了,cheers~
+
+另外,可能你会觉得使用VC容器其提供的transition动画方法来进行VC切换就已经够好够方便了,为什么iOS7中还要引入一套自定义的方式呢。其实从根本来说它们所承担的是两类完全不同的任务:自定义VC容器可以提供自己定义的VC结构,并保证系统的各类方法和通知能够准确传递到合适的VC,它提供的transition方法虽然可以实现一些简单的UIView动画,但是难以重用,可以说是和containerVC完全耦合在一起的;而自定义切换并不改变VC的组织结构,只是负责提供view的效果,因为VC切换将动画部分、动画驱动部分都使用接口的方式给出,因此重用性非常优秀。在绝大多数情况下,精心编写的一套UIView动画是可以轻易地用在不同的VC中,甚至是不同的项目中的。
+
+需要特别一提的是,Github上的[ColinEberhardt的VCTransitionsLibrary](https://github.com/ColinEberhardt/VCTransitionsLibrary)已经为我们提供了一系列的VC自定义切换动画效果,正是得益于iOS7中这一块的良好设计(虽然这几个接口的命名比较相似,在弄明白之前会有些confusing),因此这些效果使用起来非常方便,相信一般项目中是足够使用的了。而其他更复杂或者炫目的效果,亦可在其基础上进行扩展改进得到。可以说随着越来越多的应用转向iOS7,自定义VC切换将成为新的用户交互实现的基础和重要部分,对于今后会在其基础上会衍生出怎样让人眼前一亮的交互设计,不妨让我们拭目以待(或者自己努力去创造)。
diff --git a/_posts/2013-11-18-ios-iap-checklist.markdown b/_posts/2013-11-18-ios-iap-checklist.markdown
new file mode 100644
index 00000000..20dd0755
--- /dev/null
+++ b/_posts/2013-11-18-ios-iap-checklist.markdown
@@ -0,0 +1,43 @@
+---
+layout: post
+title: iOS内购实现及测试Check List
+date: 2013-11-18 01:01:16.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+免费+应用内购买的模式已经被证明了是最有效的盈利模式,所以实现内购功能可能是很多开发者必做的工作和必备的技能了。但是鉴于内购这块坑不算少,另外因为sandbox测试所需要特定的配置也很多,所以对于经验不太多的开发者来说很容易就遇到各种问题,并且测试时出错Apple给出的也只有“Can not connect iTunes Store”或者"Invalid Product IDs"之类毫无价值的错误提示,并没有详细的错误说明,因此调试起来往往没有方向。有老前辈在[这里](http://troybrant.net/blog/2010/01/invalid-product-ids/)整理过一个相对完整的check list了,但是因为年代已经稍微久远,所以内容上和现在的情况已经有一些出入。趁着在最近两个项目里做内购这块遇到的新问题,顺便在此基础上总结整理了一份比较新的中文Check list,希望能帮到后来人。
+
+如果您在实现和测试iOS应用内购的时候遇到问题,可以逐一对照下面所列出的条目,并逐一进行检查。相信可以排除大部分的错误。如果您遇到的问题不在这个列表范围内,欢迎在评论中指出,我会进行更新。
+
+* 您是否在iOS Dev Center中打开了对应应用AppID的`In-App Purchases`功能?登陆iOS Dev Center的Certificates, Identifiers & Profiles下,在Identifiers中找到正在开发的App,In-App Purchase一项应当显示Enabled(如果使用Xcode5,可以直接在Xcode的Capabilities页面中打开In-App Purchases)。
+* 您是否在iTunes Connect中注册了您的IAP项目,并将其设为Cleared for Sale?
+* 您的plist中的`Bundle identifier`的内容是否和您的AppID一致?
+* 您是否正确填写了Version(CFBundleVersion)和Build(CFBuildNumber)两个数字?两者缺一不可。
+* 您用代码向Apple申请售卖物品列表时是否使用了完整的在iTC注册的Product ID?(使用在IAP管理中内购项目的Product ID一栏中的字符串)
+* 您是否在打开IAP以后重新生成过包含IAP许可的provisioning profile?
+* 你是否重新导入了新的包含IAP的provisioning profile?建议在Organizer中先删掉原来设备上的老的provisioning profile。
+* 您是否在用包含IAP的provisioning profile在部署测试程序?在Xcode5中,建议使用General中的Team选项来自动管理。
+* 您是否是在模拟器中测试IAP?虽然理论上说模拟器在某些情况下可以测试IAP,但是条件很多也不让人安心,因此您确实需要一台真机来做IAP测试。
+* 您是在企业版发布中测试IAP么?因为企业版没有iTC进行内购项目管理,也无法发布AppStore应用,所以您在企业版的build中不能使用IAP。
+* 您是否将设备上原来的app删除了,并重新进行了安装?记得在安装前做一下Clean和Clean Build Folder。
+* 您是否在运行应用前将设备上实际的Apple ID登出了?建议在设置->iTunes Store和App Stroe中将使用中的Apple ID登出,以未登录状态进入应用进行测试。
+* 你是否使用的是Test User?如果你还没有创建Test User,你需要到iTC中创建。
+* 您使用的测试账号是否是美国区账号?虽然不是一定需要,但是鉴于其他地区的测试账号经常抽风,加上美国区账号一直很稳定,因此强烈建议使用美国区账号。正常情况下IAP不需要进行信用卡绑定和其他信息填写,如果你遇到了这种情况,可以试试删除这个测试账号再新建一个其他地区的。
+* 您是否有新建账户进行测试?可能的话,可以使用新建测试账户试试看,因为某些特定情况下测试账户会被Apple锁定。
+* 您的应用是否是被拒状态(Rejected)或自己拒绝(Developer Rejected)了?被拒绝状态的应用的话对应还未通过的内购项目也会一起被拒,因此您需要重新将IAP项目设为Cleared for Sale。
+* 您的应用是否处于等待开发者发布(Pending Developer Release)状态?等待发布状态的IAP是无法测试的。
+* 您的内购项目是否是最近才新建的,或者进行了更改?内购项目需要一段时间才能反应到所有服务器上,这个过程一般是一两小时,也可能再长一些达到若干小时。
+* 您在iTC中Contracts, Tax, and Banking Information项目中是否有还没有设置或者过期了的项目?不完整的财务信息无法进行内购测试。
+* 您是在越狱设备上进行内购测试么?越狱设备不能用于正常内购,您需要重装或者寻找一台没有越狱的设备。
+* 您是否能正常连接到Apple的服务器,你可以访问[Apple开发者论坛关于IAP的板块](https://devforums.apple.com/community/ios/connected/purchase),如果苹果服务器正down掉,那里应该有热烈的讨论。
+
+---
+
+如果您正在寻找一份手把手教你实现IAP的教程的话,这篇文章不是您的菜。关于IAP的实现和步骤,可以参考下面的教程:
+
+* 苹果的[官方IAP指南](https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Introduction.html)和相应的[Technical Note](https://developer.apple.com/library/mac/technotes/tn2259/_index.html)
+* Ray Wenderlich的[iOS6 IAP教程](http://www.raywenderlich.com/23266/in-app-purchases-in-ios-6-tutorial-consumables-and-receipt-validation)
+* 一篇图文并茂的[中文教程](http://blog.csdn.net/xiaominghimi/article/details/6937097)
+* 直接使用大神们封好的Store有关的库,比如[mattt/CargoBay](https://github.com/mattt/CargoBay),[robotmedia/RMStore](https://github.com/robotmedia/RMStore)或者[MugunthKumar/MKStoreKit](https://github.com/MugunthKumar/MKStoreKit)。推荐前两个,因为MKStoreKit有一些恼人的小bug。
diff --git a/_posts/2013-12-31-code-vs-xib-vs-storyboard.markdown b/_posts/2013-12-31-code-vs-xib-vs-storyboard.markdown
new file mode 100644
index 00000000..0d2a3ba0
--- /dev/null
+++ b/_posts/2013-12-31-code-vs-xib-vs-storyboard.markdown
@@ -0,0 +1,80 @@
+---
+layout: post
+title: 代码手写UI,xib和StoryBoard间的博弈,以及Interface Builder的一些小技巧
+date: 2013-12-31 01:02:21.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+最近接触了几个刚入门的iOS学习者,他们之中存在一个普遍和困惑和疑问,就是应该如何制作UI界面。iOS应用是非常重视用户体验的,可以说绝大多数的应用成功与否与交互设计以及UI是否漂亮易用有着非常大的关系。而随着iOS开发发展至今,可以说在UI制作上大家逐渐分化为了三种主要流派:使用代码手写UI及布局;使用单个xib文件组织viewController或者view;使用StoryBoard来通过单个或很少的几个(关于这点稍后会进行展开)文件构建全部UI。应该使用哪种方式来制作UI已经是iOS开发中亘古不变的争论话题了,或许永远不会有一个统一的结论。但是首先需要知道的是三种方式各有优劣,所以也各有自己最适用的场合,而不会有完全的孰优孰劣。对于初学iOS开发来说,一时间其实是很难判定最适合自己的UI架构方式的。在这篇文章里我希望能够通过自己的经验给出一些意见,以期能帮助入门者来挑选最适合自己应用场景的方案。对于老鸟的话,也不妨对照自己平日的使用习惯和运用场景,看看有没有可以改进或变化的地方。最后,因为我本人现在最习惯和喜欢的是用Interface Builder(之后简称IB)及xib来做UI,所以文末附上了一些IB使用时候的小技巧,算是做个总结。
+
+### 代码手写UI
+
+这种方法经常被学院派的极客或者依赖多人合作的大型项目大规模使用。Geek们喜欢用代码构建UI,是因为代码是键盘敲出来的,这样可以做到不开IB,手不离开键盘就完成工作,可以专注于编码环境,看起来很cool很高效,而且不到运行时大家都不知道会是什么样子,也显出了程序员这一职业的高大上及神秘气息(这个真的不是在黑..想想大家一起在设计师背后指点江山的场景吧)。大型多人合作项目使用代码构建UI,主要是看中纯代码在版本管理时的优势,检查追踪改动以及进行代码合并相对容易一些。
+
+另外,代码UI可以说具有最好的代码重用性。如果你的目的是写一些可以高度重用的控件提供给其他开发者使用,那毫无疑问最好的选择应该是使用代码来完成UIView的子类。这样进一步的修改和其他开发者在使用时,都会方便不少。使用代码也是最为强大的,会有xib或者StoryBoard做不了的事情,但是使用代码最终一定能够完成所要的需求。
+
+但是代码手写UI的劣势同时也是最明显的,主要就是一个字:慢。首先相比可视化的IB来说,完成一个并不太复杂的界面,你可能需要写上数百行的UI代码。不论是初始化一个Label,还是设定一个frame或者添加一个target-action,都需要写代码,这不仅在前期极为浪费时间,在之后维护时代码定位和寻找也会很痛苦。其次,因为你无法直观地看到你能得到的结果,所以你很可能需要不断地`Cmd+R`/`Cmd+.`来修改各个视图的位置大小。即使你用上了[Reveal](http://revealapp.com)或者[RestartLessOften](https://github.com/mikr/RestartLessOften)之类的工具,也还是无法特别方便地完成需要的布局。另外加上如果需要利用AutoLayout来进行尺寸适配的话,使用代码进行约束就更加头疼了。很多时候一个无法满足的约束的问题就够来回运行修改调试很长时间了。
+
+### Xibs
+
+相对于代码,使用IB和xib文件来组织UI,可以省下大量代码和时间,从而得到更快的开发速度。如果你曾经受到过微软家Visual Basic或者其他Visual系的可视化界面的荼毒与残害,因此怀疑Interface Builder的纯正血统和工作能力,建议可以看看这些资料以纠正三观:[Jean-Marie Hullot的Interface Builder神话](http://www.programmer.com.cn/9234/)以及[西装革履的青涩乔帮主在NeXT时亲手用IB构建应用](http://www.youtube.com/watch?v=viLnOVBbcsE)(需要翻墙)。另外,不妨打开你的Mac上的Application文件夹中或者iPhone上Apple家的各种应用。你会惊奇地发现,IB远比你看到的要强大:小至计算器取色器这类小工具,大至iWork三件套,Aperture或Final Cut这样的专业级应用,无一不是使用IB来完成UI制作的。
+
+其实IB和xib是从iOS SDK初次面世开始就是捆绑在开发者工具套装内的内容了,而到了Xcode 4之后更被直接集成到了Xcode中成为了IDE的一部分。xib设计的一大目的其实是为了良好的MVC:一般来说,单个的xib文件对应一个ViewController,而对于一些自定义的view,往往也会使用单个xib并从main bundle进行加载的方式来载入。IB帮助完成view的创建,布局和与file owner的关系映射等一些列工作。对于初学者来说,牢记xib的文件都是view的内容,有助于建立起较好的MVC的概念,从而在开发中避免或少走弯路。
+
+xib文件之前一大被诟病的问题是文件内容过于复杂,可读性很差,即使只是简单打开没有编辑也有可能造成变化而导致合并和提交的苦难。在Xcode 5中Apple大幅简化了xib文件的格式,使其变得易读易维护。可以说现在对于xib文件在版本管理上其实和纯代码已经没有太大差异,只要仔细看过一遍xib的文件内容,自然能理解绝大部分,并很好地追踪并查找过往的修改记录了。
+
+当然xib也不是完美的。最大的问题在于xib中的设置往往并非最终设置,在代码中你将有机会覆盖你在xib文件中进行的UI设计。在不同的地方对同一个属性进行设置,这在之后的维护中将会是噩梦般的存在。因为其实IB还是有所局限的,它没有逻辑判断,也很难在运行时进行配置,而反之使用代码确是无所不能的。在使用xib时,辅以部分代码来补充和完成功能几乎是不可避免的。关于这点在开发时应该予以高度重视,如果选择xib,那么要尽量将xib的工作和代码的工作隔离开来:能够使用xib完成的内容就统一使用xib来做,而不要说三个Label其中两个在xib设置了字体而另一个却在代码中完成。尽量仅保持必要的、较少的IBOutlet和IBAction会是一个好方法。
+
+### StoryBoard
+
+iOS5之后Apple提供了一种全新的方式来制作UI,那就是StoryBoard。简单理解来说,可以把StoryBoard看做是一组viewController对应的xib,以及它们之间的转换方式的集合。在StoryBoard中不仅可以看到每个ViewController的布局样式,也可以明确地知道各个ViewController之间的转换关系。相对于单个的xib,其代码需求更少,也由于集合了各个xib,使得对于界面的理解和修改的速度也得到了更大提升。减少代码量就是减少bug量,这也是程序开发中的真理之一。
+
+在Xcode5之后,StoryBoard已经成为新建项目的默认配置,这也代表了Apple对开发者的建议和未来的方向。WWDC2013的各个Sample Code中也基本都使用了StoryBoard来进行演示。可以预见到,之后Apple必定会在这方面进行继续强化,而反之纯代码或者单个xib的方式很可能不会再得到增强。
+
+如果不考虑iOS版本的支持(其实说实话现在已经很少还见到要从iOS4开始支持的app了吧),现在StoryBoard面临的最大问题就是多人协作。因为所有的UI都定义在一个文件中,因此很多开发者个人或企业的技术负责人认为StoryBoard是无法进行协作开发的,其实这更多的是一种对StoryBoard的陌生所造成的误解。虽然Apple并没有在WWDC明确提及,但是没有人规定整个项目只能有一个StoryBoard文件。一种可行的做法是将项目的不同部分分解成若干个StoryBoard,并安排开发人员对自己的部分进行负责。简单举例比如一个有4个tab功能相互独立的基于UITabBarViewController的应用,完全可以使用4个StoryBoard来分别代表4个tab,并在相互无干扰的情况下完成开发。这样一来就不会存在所谓的冲突问题了。StoryBoard的API是如此简单,现在的SDK中一共方法数量一只手就能数过来,所以具体方法在这里就不再罗嗦了。
+
+StoryBoard的另外的挑战来源于ViewController的重用和自定义的view的处理。对于前者,在正确封装接口以及良好设计的基础上,其实StoryBoard的VC重用与代码的VC重用是没有本质区别的,在StoryBoard中添加封装良好需要重用的Scene即可解决。而对于后者,因为StoryBoard中已经不允许有单个view的存在,因此很多时候我们还是需要借助于单个的xib来自定义UI。这一点可以说是由于StoryBoard的设计思路所造成的,StoryBoard更强调的是一种层次结构,是在全局的视角上来组织UI设计和迁移。而对于单个的view,更多的会注重于重用和定制,而与整个项目的流程没有太大关系。相信抓住这一要点,就能很好地了解什么时候使用xib,什么时候使用StoryBoard。
+
+关于StoryBoard最后要说的是,现在会有一些对于StoryBoard性能上的担忧。因为相对于单个xib来说,StoryBoard文件往往更大,加载速度也相应变慢。但是其实随着现在设备的更新换代,在iPhone4都难觅的今天,这点性能上的差距几乎可以忽略了。而再之后的设备,不论读取还是解析,只会越来越快。所以性能上的问题完全是没有担心的必要的。
+
+### 我的观点和选择
+
+我入门的时候是使用xib的,因为那时候还没有StoryBoard,而我也不是喜欢代码的学院派Geek。到现在,三种方式我都有尝试过,并分别得到了一些可能还并不是特别深刻体会。对于现在的我来说,xib是我的奶酪,也是我在自己的一些项目里一直使用的方式,我可以在极短短时间内用xib架起一套包括自定义要素和良好部件重用性复杂UI。但是在我尝试了几次使用StoryBoard制作demo之后,我已经决定在之后的项目转向使用StoryBoard。一方面因为确实是未来方向(每次新工程删StoryBoard很讨厌..),现在的StoryBoard专有的preview功能,以及之后AutoLayout的进一步改进等都很值得期待;另一方面也觉得奶酪放一个地方太久了会不好,趁着iOS7的大变革,也更新一下自己的观念和方式,把奶酪换个地方摆摆,也许会对以后大有裨益。
+
+对于初心者来说,我并不建议上手就直接使用代码来进行UI制作和布局,因为冗长的UI代码确实非常乏味无趣。尽快看到成品,至少尽快看到原型,是保持兴趣,继续深入和从事职业的有效动力。所以如果有可能有条件,在老鸟的指导下选择StoryBoard来进行快速构建(或者如果是单个人开发的话,可以不用考虑多个StoryBoard协作,就更容易),会是入门的好选择。而最新的教程和文档已经开始逐渐偏向StoryBoard,关于StoryBoard的问题在SO上关注度也会更高,这样在入门时会有更多的资料可以进行参考。
+
+这并不是说不需要关心代码UI或者xib,因为使用StoryBoard的时候在只能使用代码以及自定义单个view时,还是不可避免地需要接触它们的。这里想给的一点建议就是,虽然你不依赖代码来进行UI制作,但是了解并掌握如何使用纯代码来从头构建UI还是非常必要的:包括从新建Window开始,到初始化ViewController,添加必要的view,设定它们的property,以及添加和处理它们的各种响应及responser链等内容。现在iOS开发入门非常容易,Xcode和xib/StoryBoard帮助开发者隐藏了太多的细节,但是很多时候如果你不明白underhood到底是些什么,为什么这些xib/StoryBoard会这样运作的话,经常会出现卡在一些很可笑的和初级的bug上找不着北,这其实会是对时间的巨大浪费,很不值得。
+
+### 一些IB小技巧
+
+最后分享一些IB使用上的小技巧作为结束吧。其中很多方法也可以用在StoryBoard上,所以在向我自己之前xib使用者生涯致敬的同时,也算是一点小的备忘总结吧。
+
+#### 同时添加多个outlet
+
+在IB中,选中一个view并右键点击,将会出现灰色的HUD,可以在其上方便地拖拉或设定事件和outlet。你可以同时打开多个这样的面板来一次性添加所有outlet。右键点击面板,随便拖动一下面板,然后再打开另一个。你会发现前一个面板也留下来了,这样你就可以方便地进行拖拽设定了。
+
+
+
+当然,对于成组和行为类似的IBOutlet,应该直接使用IBOutletCollection来进行处理会更方便。
+
+#### 可视化坐标距离
+
+IB最烦人的问题就是对其。用代码的时候我们可以明确地指定x,y坐标,但是换到IB的时候我们更多的时候是靠拖拽UIView来布局。比如需要三个间隔相同的label,除了用强大的肉眼来估测距离是否相等以外,难道只能乖乖分别选中三个label,记下它们的坐标然后打开计算器来做加减法么?
+
+显然不要那么笨,试试看选中一个label,然后按住option键并将鼠标移动到其他label上试试?你可以发现view之间的距离都以很容易理解的方式显示出来了。不仅是同层次的view,被选中view与其他层次的view之间的距离关系也可以同样显示。
+
+
+
+#### 在一组view层次中进行选择
+
+对于一些复杂的view层级关系,我们往往直接在IB中选择会比较困难。比如view相互覆盖时,我们很难甚至不能在编辑视图中选中底层的view。这时候一般的做法是打开左侧的view层级面板,一层层展开然后选择自己需要的view。其实我们也有更简单的方法:按住`Cmd`和`Shift`,然后在需要选择的view上方按右键,就可以列出在点击位置上所有的view的列表。藉此就可以方便快速地选中想要的view了。
+
+
+
+#### 添加辅助线
+
+这么高大上的技巧必须放在最后啊...在左边的层级列表中双击某个view,然后`Cmd+_`或者`Cmd+|`即可在选中的view上添加一条水平或者垂直中心的辅助线。当然这个辅助线是可以随意移动的。如果干过设计的同学肯定明白这个的意义了,在之后的对齐和设计变更的时候都有重要的参考价值。
+
+
diff --git a/_posts/2014-01-17-black-magic-in-macro.markdown b/_posts/2014-01-17-black-magic-in-macro.markdown
new file mode 100644
index 00000000..2ffd50cf
--- /dev/null
+++ b/_posts/2014-01-17-black-magic-in-macro.markdown
@@ -0,0 +1,469 @@
+---
+layout: post
+title: 宏定义的黑魔法 - 宏菜鸟起飞手册
+date: 2014-01-17 01:03:36.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+宏定义在C系开发中可以说占有举足轻重的作用。底层框架自不必说,为了编译优化和方便,以及跨平台能力,宏被大量使用,可以说底层开发离开define将寸步难行。而在更高层级进行开发时,我们会将更多的重心放在业务逻辑上,似乎对宏的使用和依赖并不多。但是使用宏定义的好处是不言自明的,在节省工作量的同时,代码可读性大大增加。如果想成为一个能写出漂亮优雅代码的开发者,宏定义绝对是必不可少的技能(虽然宏本身可能并不漂亮优雅XD)。但是因为宏定义对于很多人来说,并不像业务逻辑那样是每天会接触的东西。即使是能偶尔使用到一些宏,也更多的仅仅只停留在使用的层级,却并不会去探寻背后发生的事情。有一些开发者确实也有探寻的动力和意愿,但却在点开一个定义之后发现还有宏定义中还有其他无数定义,再加上满屏幕都是不同于平时的代码,既看不懂又不变色,于是乎心生烦恼,怒而回退。本文希望通过循序渐进的方式,通过几个例子来表述C系语言宏定义世界中的一些基本规则和技巧,从0开始,希望最后能让大家至少能看懂和还原一些相对复杂的宏。考虑到我自己现在objc使用的比较多,这个站点的读者应该也大多是使用objc的,所以有部分例子是选自objc,但是本文的大部分内容将是C系语言通用。
+
+### 入门
+
+如果您完全不知道宏是什么的话,可以先来热个身。很多人在介绍宏的时候会说,宏嘛很简单,就是简单的查找替换嘛。嗯,只说对了的一半。C中的宏分为两类,对象宏(object-like macro)和函数宏(function-like macro)。对于对象宏来说确实相对简单,但却也不是那么简单的查找替换。对象宏一般用来定义一些常数,举个例子:
+
+```c
+//This defines PI
+#define M_PI 3.14159265358979323846264338327950288
+```
+
+
+
+`#define`关键字表明即将开始定义一个宏,紧接着的`M_PI`是宏的名字,空格之后的数字是内容。类似这样的`#define X A`的宏是比较简单的,在编译时编译器会在语义分析认定是宏后,将X替换为A,这个过程称为宏的展开。比如对于上面的`M_PI`
+
+```c
+#define M_PI 3.14159265358979323846264338327950288
+
+double r = 10.0;
+double circlePerimeter = 2 * M_PI * r;
+// => double circlePerimeter = 2 * 3.14159265358979323846264338327950288 * r;
+
+printf("Pi is %0.7f",M_PI);
+//Pi is 3.1415927
+```
+
+那么让我们开始看看另一类宏吧。函数宏顾名思义,就是行为类似函数,可以接受参数的宏。具体来说,在定义的时候,如果我们在宏名字后面跟上一对括号的话,这个宏就变成了函数宏。从最简单的例子开始,比如下面这个函数宏
+
+```c
+//A simple function-like macro
+#define SELF(x) x
+NSString *name = @"Macro Rookie";
+NSLog(@"Hello %@",SELF(name));
+// => NSLog(@"Hello %@",name);
+// => Hello Macro Rookie
+```
+
+这个宏做的事情是,在编译时如果遇到`SELF`,并且后面带括号,并且括号中的参数个数与定义的相符,那么就将括号中的参数换到定义的内容里去,然后替换掉原来的内容。 具体到这段代码中,`SELF`接受了一个name,然后将整个SELF(name)用name替换掉。嗯..似乎很简单很没用,身经百战阅码无数的你一定会认为这个宏是写出来卖萌的。那么接受多个参数的宏肯定也不在话下了,例如这样的:
+
+```c
+#define PLUS(x,y) x + y
+printf("%d",PLUS(3,2));
+// => printf("%d",3 + 2);
+// => 5
+```
+
+相比对象宏来说,函数宏要复杂一些,但是看起来也相当简单吧?嗯,那么现在热身结束,让我们正式开启宏的大门吧。
+
+### 宏的世界,小有乾坤
+
+因为宏展开其实是编辑器的预处理,因此它可以在更高层级上控制程序源码本身和编译流程。而正是这个特点,赋予了宏很强大的功能和灵活度。但是凡事都有两面性,在获取灵活的背后,是以需要大量时间投入以对各种边界情况进行考虑来作为代价的。可能这么说并不是很能让人理解,但是大部分宏(特别是函数宏)背后都有一些自己的故事,挖掘这些故事和设计的思想会是一件很有意思的事情。另外,我一直相信在实践中学习才是真正掌握知识的唯一途径,虽然可能正在看这篇博文的您可能最初并不是打算亲自动手写一些宏,但是这我们不妨开始动手从实际的书写和犯错中进行学习和挖掘,因为只有肌肉记忆和大脑记忆协同起来,才能说达到掌握的水准。可以说,写宏和用宏的过程,一定是在在犯错中学习和深入思考的过程,我们接下来要做的,就是重现这一系列过程从而提高进步。
+
+第一个题目是,让我们一起来实现一个`MIN`宏吧:实现一个函数宏,给定两个数字输入,将其替换为较小的那个数。比如`MIN(1,2)`出来的值是1。嗯哼,simple enough?定义宏,写好名字,两个输入,然后换成比较取值。比较取值嘛,任何一本入门级别的C程序设计上都会有讲啊,于是我们可以很快写出我们的第一个版本:
+
+```c
+//Version 1.0
+#define MIN(A,B) A < B ? A : B
+```
+
+Try一下
+```c
+int a = MIN(1,2);
+// => int a = 1 < 2 ? 1 : 2;
+printf("%d",a);
+// => 1
+```
+
+输出正确,打包发布!
+
+
+
+但是在实际使用中,我们很快就遇到了这样的情况
+```c
+int a = 2 * MIN(3, 4);
+printf("%d",a);
+// => 4
+```
+
+看起来似乎不可思议,但是我们将宏展开就知道发生什么了
+
+```c
+int a = 2 * MIN(3, 4);
+// => int a = 2 * 3 < 4 ? 3 : 4;
+// => int a = 6 < 4 ? 3 : 4;
+// => int a = 4;
+```
+
+嘛,写程序这个东西,bug出来了,原因知道了,事后大家就都是诸葛亮了。因为小于和比较符号的优先级是较低的,所以乘法先被运算了,修正非常简单嘛,加括号就好了。
+
+```c
+//Version 2.0
+#define MIN(A,B) (A < B ? A : B)
+```
+这次`2 * MIN(3, 4)`这样的式子就轻松愉快地拿下了。经过了这次修改,我们对自己的宏信心大增了...直到,某一天一个怒气冲冲的同事跑来摔键盘,然后给出了一个这样的例子:
+
+```c
+int a = MIN(3, 4 < 5 ? 4 : 5);
+printf("%d",a);
+// => 4
+```
+
+简单的相比较三个数字并找到最小的一个而已,要怪就怪你没有提供三个数字比大小的宏,可怜的同事只好自己实现4和5的比较。在你开始着手解决这个问题的时候,你首先想到的也许是既然都是求最小值,那写成`MIN(3, MIN(4, 5))`是不是也可以。于是你就随手这样一改,发现结果变成了3,正是你想要的..接下来,开始怀疑之前自己是不是看错结果了,改回原样,一个4赫然出现在屏幕上。你终于意识到事情并不是你想像中那样简单,于是还是回到最原始直接的手段,展开宏。
+
+```c
+int a = MIN(3, 4 < 5 ? 4 : 5);
+// => int a = (3 < 4 < 5 ? 4 : 5 ? 3 : 4 < 5 ? 4 : 5); //希望你还记得运算符优先级
+// => int a = ((3 < (4 < 5 ? 4 : 5) ? 3 : 4) < 5 ? 4 : 5); //为了您不太纠结,我给这个式子加上了括号
+// => int a = ((3 < 4 ? 3 : 4) < 5 ? 4 : 5)
+// => int a = (3 < 5 ? 4 : 5)
+// => int a = 4
+```
+
+找到问题所在了,由于展开时连接符号和被展开式子中的运算符号优先级相同,导致了计算顺序发生了变化,实质上和我们的1.0版遇到的问题是差不多的,还是考虑不周。那么就再严格一点吧,3.0版!
+
+```c
+//Version 3.0
+#define MIN(A,B) ((A) < (B) ? (A) : (B))
+```
+
+至于为什么2.0版本中的`MIN(3, MIN(4, 5))`没有出问题,可以正确使用,这里作为练习,大家可以试着自己展开一下,来看看发生了什么。
+
+经过两次悲剧,你现在对这个简单的宏充满了疑惑。于是你跑了无数的测试用例而且它们都通过了,我们似乎彻底解决了括号问题,你也认为从此这个宏就妥妥儿的哦了。不过如果你真的这么想,那你就图样图森破了。生活总是残酷的,该来的bug也一定是会来的。不出意外地,在一个雾霾阴沉的下午,我们又收到了一个出问题的例子。
+
+```c
+float a = 1.0f;
+float b = MIN(a++, 1.5f);
+printf("a=%f, b=%f",a,b);
+// => a=3.000000, b=2.000000
+```
+
+拿到这个出问题的例子你的第一反应可能和我一样,这TM的谁这么二货还在比较的时候搞++,这简直乱套了!但是这样的人就是会存在,这样的事就是会发生,你也不能说人家逻辑有错误。a是1,a++表示先使用a的值进行计算,然后再加1。那么其实这个式子想要计算的是取a和b的最小值,然后a等于a加1:所以正确的输出a为2,b为1才对!嘛,满眼都是泪,让我们这些久经摧残的程序员淡定地展开这个式子,来看看这次又发生了些什么吧:
+
+```c
+float a = 1.0f;
+float b = MIN(a++, 1.5f);
+// => float b = ((a++) < (1.5f) ? (a++) : (1.5f))
+```
+
+其实只要展开一步就很明白了,在比较a++和1.5f的时候,先取1和1.5比较,然后a自增1。接下来条件比较得到真以后又触发了一次a++,此时a已经是2,于是b得到2,最后a再次自增后值为3。出错的根源就在于我们预想的是a++只执行一次,但是由于宏展开导致了a++被多执行了,改变了预想的逻辑。解决这个问题并不是一件很简单的事情,使用的方式也很巧妙。我们需要用到一个GNU C的赋值扩展,即使用`({...})`的形式。这种形式的语句可以类似很多脚本语言,在顺次执行之后,会将最后一次的表达式的赋值作为返回。举个简单的例子,下面的代码执行完毕后a的值为3,而且b和c只存在于大括号限定的代码域中
+
+```c
+int a = ({
+ int b = 1;
+ int c = 2;
+ b + c;
+});
+// => a is 3
+```
+
+有了这个扩展,我们就能做到之前很多做不到的事情了。比如彻底解决`MIN`宏定义的问题,而也正是GNU C中`MIN`的标准写法
+
+```c
+//GNUC MIN
+#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; })
+```
+
+这里定义了三个语句,分别以输入的类型申明了`__a`和`__b`,并使用输入为其赋值,接下来做一个简单的条件比较,得到`__a`和`__b`中的较小值,并使用赋值扩展将结果作为返回。这样的实现保证了不改变原来的逻辑,先进行一次赋值,也避免了括号优先级的问题,可以说是一个比较好的解决方案了。如果编译环境支持GNU C的这个扩展,那么毫无疑问我们应该采用这种方式来书写我们的`MIN`宏,如果不支持这个环境扩展,那我们只有人为地规定参数不带运算或者函数调用,以避免出错。
+
+关于`MIN`我们讨论已经够多了,但是其实还存留一个悬疑的地方。如果在同一个scope内已经有`__a`或者`__b`的定义的话(虽然一般来说不会出现这种悲剧的命名,不过谁知道呢),这个宏可能出现问题。在申明后赋值将因为定义重复而无法被初始化,导致宏的行为不可预知。如果您有兴趣,不妨自己动手试试看结果会是什么。Apple在Clang中彻底解决了这个问题,我们把Xcode打开随便建一个新工程,在代码中输入`MIN(1,1)`,然后Cmd+点击即可找到clang中 `MIN`的写法。为了方便说明,我直接把相关的部分抄录如下:
+
+```objc
+//CLANG MIN
+#define __NSX_PASTE__(A,B) A##B
+
+#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
+
+#define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); __typeof__(B) __NSX_PASTE__(__b,L) = (B); (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); })
+```
+
+似乎有点长,看起来也很吃力。我们先美化一下这宏,首先是最后那个`__NSMIN_IMPL__`内容实在是太长了。我们知道代码的话是可以插入换行而不影响含义的,宏是否也可以呢?答案是肯定的,只不过我们不能使用一个单一的回车来完成,而必须在回车前加上一个反斜杠`\`。改写一下,为其加上换行好看些:
+
+```objc
+#define __NSX_PASTE__(A,B) A##B
+
+#define MIN(A,B) __NSMIN_IMPL__(A,B,__COUNTER__)
+
+#define __NSMIN_IMPL__(A,B,L) ({ __typeof__(A) __NSX_PASTE__(__a,L) = (A); \
+ __typeof__(B) __NSX_PASTE__(__b,L) = (B); \
+ (__NSX_PASTE__(__a,L) < __NSX_PASTE__(__b,L)) ? __NSX_PASTE__(__a,L) : __NSX_PASTE__(__b,L); \
+ })
+```
+
+但可以看出`MIN`一共由三个宏定义组合而成。第一个`__NSX_PASTE__`里出现的两个连着的井号`##`在宏中是一个特殊符号,它表示将两个参数连接起来这种运算。注意函数宏必须是有意义的运算,因此你不能直接写`AB`来连接两个参数,而需要写成例子中的`A##B`。宏中还有一切其他的自成一脉的运算符号,我们稍后还会介绍几个。接下来是我们调用的两个参数的`MIN`,它做的事是调用了另一个三个参数的宏`__NSMIN_IMPL__`,其中前两个参数就是我们的输入,而第三个`__COUNTER__`我们似乎不认识,也不知道其从何而来。其实`__COUNTER__`是一个预定义的宏,这个值在编译过程中将从0开始计数,每次被调用时加1。因为唯一性,所以很多时候被用来构造独立的变量名称。有了上面的基础,再来看最后的实现宏就很简单了。整体思路和前面的实现和之前的GNUC MIN是一样的,区别在于为变量名`__a`和`__b`添加了一个计数后缀,这样大大避免了变量名相同而导致问题的可能性(当然如果你执拗地把变量叫做__a9527并且出问题了的话,就只能说不作死就不会死了)。
+
+花了好多功夫,我们终于把一个简单的`MIN`宏彻底搞清楚了。宏就是这样一类东西,简单的表面之下隐藏了很多玄机,可谓小有乾坤。作为练习大家可以自己尝试一下实现一个`SQUARE(A)`,给一个数字输入,输出它的平方的宏。虽然一般这个计算现在都是用inline来做了,但是通过和`MIN`类似的思路我们是可以很好地实现它的,动手试一试吧 :)
+
+### Log,永恒的主题
+
+Log人人爱,它为我们指明前进方向,它为我们抓虫提供帮助。在objc中,我们最多使用的log方法就是`NSLog`输出信息到控制台了,但是NSLog的标准输出可谓残废,有用信息完全不够,比如下面这段代码:
+
+```objc
+NSArray *array = @[@"Hello", @"My", @"Macro"];
+NSLog (@"The array is %@", array);
+```
+
+打印到控制台里的结果是类似这样的
+
+```
+2014-01-20 11:22:11.835 TestProject[23061:70b] The array is (
+ Hello,
+ My,
+ Macro
+)
+```
+
+我们在输出的时候关心什么?除了结果以外,很多情况下我们会对这行log的所在的文件位置方法什么的会比较关心。在每次NSLog里都手动加上方法名字和位置信息什么的无疑是个笨办法,而如果一个工程里已经有很多`NSLog`的调用了,一个一个手动去改的话无疑也是噩梦。我们通过宏,可以很简单地完成对`NSLog`原生行为的改进,优雅,高效。只需要在预编译的pch文件中加上
+
+```objc
+//A better version of NSLog
+#define NSLog(format, ...) do { \
+ fprintf(stderr, "<%s : %d> %s\n", \
+ [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
+ __LINE__, __func__); \
+ (NSLog)((format), ##__VA_ARGS__); \
+ fprintf(stderr, "-------\n"); \
+ } while (0)
+```
+
+嘛,这是我们到现在为止见到的最长的一个宏了吧...没关系,一点一点来分析就好。首先是定义部分,第2行的`NSLog(format, ...)`。我们看到的是一个函数宏,但是它的参数比较奇怪,第二个参数是`...`,在宏定义(其实也包括函数定义)的时候,写为`...`的参数被叫做可变参数(variadic)。可变参数的个数不做限定。在这个宏定义中,除了第一个参数`format`将被单独处理外,接下来输入的参数将作为整体一并看待。回想一下NSLog的用法,我们在使用NSLog时,往往是先给一个format字符串作为第一个参数,然后根据定义的格式在后面的参数里跟上写要输出的变量之类的。这里第一个格式化字符串即对应宏里的`format`,后面的变量全部映射为`...`作为整体处理。
+
+接下来宏的内容部分。上来就是一个下马威,我们遇到了一个do while语句...想想看你上次使用do while是什么时候吧?也许是C程序设计课的大作业?或者是某次早已被遗忘的算法面试上?总之虽然大家都是明白这个语句的,但是实际中可能用到它的机会少之又少。乍一看似乎这个do while什么都没做,因为while是0,所以do肯定只会被执行一次。那么它存在的意义是什么呢,我们是不是可以直接简化一下这个宏,把它给去掉,变成这个样子呢?
+
+```objc
+//A wrong version of NSLog
+#define NSLog(format, ...) fprintf(stderr, "<%s : %d> %s\n", \
+ [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
+ __LINE__, __func__); \
+ (NSLog)((format), ##__VA_ARGS__); \
+ fprintf(stderr, "-------\n");
+```
+
+答案当然是否定的,也许简单的测试里你没有遇到问题,但是在生产环境中这个宏显然悲剧了。考虑下面的常见情况
+
+```objc
+if (errorHappend)
+ NSLog(@"Oops, error happened");
+```
+
+展开以后将会变成
+```objc
+if (errorHappend)
+ fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+(NSLog)((format), ##__VA_ARGS__); //I will expand this later
+fprintf(stderr, "-------\n");
+```
+注意..C系语言可不是靠缩进来控制代码块和逻辑关系的。所以说如果使用这个宏的人没有在条件判断后加大括号的话,你的宏就会一直调用真正的NSLog输出东西,这显然不是我们想要的逻辑。当然在这里还是需要重新批评一下认为if后的单条执行语句不加大括号也没问题的同学,这是陋习,无需理由,请改正。不论是不是一条语句,也不论是if后还是else后,都加上大括号,是对别人和自己的一种尊重。
+
+好了知道我们的宏是如何失效的,也就知道了修改的方法。作为宏的开发者,应该力求使用者在最大限度的情况下也不会出错,于是我们想到直接用一对大括号把宏内容括起来,大概就万事大吉了?像这样:
+
+```objc
+//Another wrong version of NSLog
+#define NSLog(format, ...) {
+ fprintf(stderr, "<%s : %d> %s\n", \
+ [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], \
+ __LINE__, __func__); \
+ (NSLog)((format), ##__VA_ARGS__); \
+ fprintf(stderr, "-------\n"); \
+ }
+```
+
+展开刚才的那个式子,结果是
+```objc
+//I am sorry if you don't like { in the same like. But I am a fan of this style :P
+if (errorHappend) {
+ fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+ (NSLog)((format), ##__VA_ARGS__);
+ fprintf(stderr, "-------\n");
+};
+```
+
+编译,执行,正确!因为用大括号标识代码块是不会嫌多的,所以这样一来的话我们的宏在不论if后面有没有大括号的情况下都能工作了!这么看来,前面例子中的do while果然是多余的?于是我们又可以愉快地发布了?如果你够细心的话,可能已经发现问题了,那就是上面最后的一个分号。虽然编译运行测试没什么问题,但是始终稍微有些刺眼有木有?没错,因为我们在写NSLog本身的时候,是将其当作一条语句来处理的,后面跟了一个分号,在宏展开后,这个分号就如同噩梦一般的多出来了。什么,你还没看出哪儿有问题?试试看展开这个例子吧:
+
+```objc
+if (errorHappend)
+ NSLog(@"Oops, error happened");
+else
+ //Yep, no error, I am happy~ :)
+```
+
+No! I am not haapy at all! 因为编译错误了!实际上这个宏展开以后变成了这个样子:
+
+```objc
+if (errorHappend) {
+ fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+ (NSLog)((format), ##__VA_ARGS__);
+ fprintf(stderr, "-------\n");
+}; else {
+ //Yep, no error, I am happy~ :)
+}
+```
+
+因为else前面多了一个分号,导致了编译错误,很恼火..要是写代码的人乖乖写大括号不就啥事儿没有了么?但是我们还是有巧妙的解决方法的,那就是上面的do while。把宏的代码块添加到do中,然后之后while(0),在行为上没有任何改变,但是可以巧妙地吃掉那个悲剧的分号,使用do while的版本展开以后是这个样子的
+
+```objc
+if (errorHappend)
+ do {
+ fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+ (NSLog)((format), ##__VA_ARGS__);
+ fprintf(stderr, "-------\n");
+ } while (0);
+else {
+ //Yep, no error, I am really happy~ :)
+}
+```
+
+这个吃掉分号的方法被大量运用在代码块宏中,几乎已经成为了标准写法。而且while(0)的好处在于,在编译的时候,编译器基本都会为你做好优化,把这部分内容去掉,最终编译的结果不会因为这个do while而导致运行效率上的差异。在终于弄明白了这个奇怪的do while之后,我们终于可以继续深入到这个宏里面了。宏本体内容的第一行没有什么值得多说的`fprintf(stderr, "<%s : %d> %s\n",`,简单的格式化输出而已。注意我们使用了`\`将这个宏分成了好几行来写,实际在最后展开时会被合并到同一行内,我们在刚才`MIN`最后也用到了反斜杠,希望你还能记得。接下来一行我们填写这个格式输出中的三个token,
+
+```
+[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+```
+
+这里用到了三个预定义宏,和刚才的`__COUNTER__`类似,预定义宏的行为是由编译器指定的。`__FILE__`返回当前文件的绝对路径,`__LINE__`返回展开该宏时在文件中的行数,`__func__`是改宏所在scope的函数名称。我们在做Log输出时如果带上这这三个参数,便可以加快解读Log,迅速定位。关于编译器预定义的Log以及它们的一些实现机制,感兴趣的同学可以移步到gcc文档的[PreDefine页面](http://gcc.gnu.org/onlinedocs/cpp/Predefined-Macros.html#Predefined-Macros)和clang的[Builtin Macro](http://clang.llvm.org/docs/LanguageExtensions.html#builtin-macros)进行查看。在这里我们将格式化输出的三个参数分别设定为文件名的最后一个部分(因为绝对路径太长很难看),行数,以及方法名称。
+
+接下来是还原原始的NSLog,`(NSLog)((format), ##__VA_ARGS__);`中出现了另一个预定义的宏`__VA_ARGS__`(我们似乎已经找出规律了,前后双下杠的一般都是预定义)。`__VA_ARGS__`表示的是宏定义中的`...`中的所有剩余参数。我们之前说过可变参数将被统一处理,在这里展开的时候编译器会将`__VA_ARGS__`直接替换为输入中从第二个参数开始的剩余参数。另外一个悬疑点是在它前面出现了两个井号`##`。还记得我们上面在`MIN`中的两个井号么,在那里两个井号的意思是将前后两项合并,在这里做的事情比较类似,将前面的格式化字符串和后面的参数列表合并,这样我们就得到了一个完整的NSLog方法了。之后的几行相信大家自己看懂也没有问题了,最后输出一下试试看,大概看起来会是这样的。
+
+```
+-------
+ -[AppDelegate application:didFinishLaunchingWithOptions:]
+2014-01-20 16:44:25.480 TestProject[30466:70b] The array is (
+ Hello,
+ My,
+ Macro
+)
+-------
+```
+
+带有文件,行号和方法的输出,并且用横杠隔开了(请原谅我没有质感的设计,也许我应该画一只牛,比如这样?),debug的时候也许会轻松一些吧 :)
+
+
+
+这个Log有三个悬念点,首先是为什么我们要把format单独写出来,然后吧其他参数作为可变参数传递呢?如果我们不要那个format,而直接写成`NSLog(...)`会不会有问题?对于我们这里这个例子来说的话是没有变化的,但是我们需要记住的是`...`是可变参数列表,它可以代表一个、两个,或者是很多个参数,但同时它也能代表零个参数。如果我们在申明这个宏的时候没有指定format参数,而直接使用参数列表,那么在使用中不写参数的NSLog()也将被匹配到这个宏中,导致编译无法通过。如果你手边有Xcode,也可以看看Cocoa中真正的NSLog方法的实现,可以看到它也是接收一个格式参数和一个参数列表的形式,我们在宏里这么定义,正是为了其传入正确合适的参数,从而保证使用者可以按照原来的方式正确使用这个宏。
+
+第二点是既然我们的可变参数可以接受任意个输入,那么在只有一个format输入,而可变参数个数为零的时候会发生什么呢?不妨展开看一看,记住`##`的作用是拼接前后,而现在`##`之后的可变参数是空:
+
+```
+NSLog(@"Hello");
+=> do {
+ fprintf((stderr, "<%s : %d> %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, __func__);
+ (NSLog)((@"Hello"), );
+ fprintf(stderr, "-------\n");
+ } while (0);
+
+```
+
+中间的一行`(NSLog)(@"Hello", );`似乎是存在问题的,你一定会有疑惑,这种方式怎么可能编译通过呢?!原来大神们其实早已想到这个问题,并且进行了一点特殊的处理。这里有个特殊的规则,在`逗号`和`__VA_ARGS__`之间的双井号,除了拼接前后文本之外,还有一个功能,那就是如果后方文本为空,那么它会将前面一个逗号吃掉。这个特性当且仅当上面说的条件成立时才会生效,因此可以说是特例。加上这条规则后,我们就可以将刚才的式子展开为正确的`(NSLog)((@"Hello"));`了。
+
+最后一个值得讨论的地方是`(NSLog)((format), ##__VA_ARGS__);`的括号使用。把看起来能去掉的括号去掉,写成`NSLog(format, ##__VA_ARGS__);`是否可以呢?在这里的话应该是没有什么大问题的,首先format不会被调用多次也不太存在误用的可能性(因为最后编译器会检查NSLog的输入是否正确)。另外你也不用担心展开以后式子里的NSLog会再次被自己展开,虽然展开式中NSLog也满足了我们的宏定义,但是宏的展开非常聪明,展开后会自身无限循环的情况,就不会再次被展开了。
+
+作为一个您读到了这里的小奖励,附送三个debug输出rect,size和point的宏,希望您能用上(嗯..想想曾经有多少次你需要打印这些结构体的某个数字而被折磨致死,让它们玩儿蛋去吧!当然请先加油看懂它们吧)
+
+```
+#define NSLogRect(rect) NSLog(@"%s x:%.4f, y:%.4f, w:%.4f, h:%.4f", #rect, rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)
+#define NSLogSize(size) NSLog(@"%s w:%.4f, h:%.4f", #size, size.width, size.height)
+#define NSLogPoint(point) NSLog(@"%s x:%.4f, y:%.4f", #point, point.x, point.y)
+```
+
+### 两个实际应用的例子
+
+当然不是说上面介绍的宏实际中不能用。它们相对简单,但是里面坑不少,所以显得很有特点,非常适合作为入门用。而实际上在日常中很多我们常用的宏并没有那么多奇怪的问题,很多时候我们按照想法去实现,再稍微注意一下上述介绍的可能存在的共通问题,一个高质量的宏就可以诞生。如果能写出一些有意义价值的宏,小了从对你的代码的使用者来说,大了从整个社区整个世界和减少碳排放来说,你都做出了相当的贡献。我们通过几个实际的例子来看看,宏是如何改变我们的生活,和写代码的习惯的吧。
+
+先来看看这两个宏
+
+```objc
+#define XCTAssertTrue(expression, format...) \
+ _XCTPrimitiveAssertTrue(expression, ## format)
+
+#define _XCTPrimitiveAssertTrue(expression, format...) \
+({ \
+ @try { \
+ BOOL _evaluatedExpression = !!(expression); \
+ if (!_evaluatedExpression) { \
+ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 0, @#expression),format); \
+ } \
+ } \
+ @catch (id exception) { \
+ _XCTRegisterFailure(_XCTFailureDescription(_XCTAssertion_True, 1, @#expression, [exception reason]),format); \
+ }\
+})
+```
+
+如果您常年做苹果开发,却没有见过或者完全不知道`XCTAssertTrue`是什么的话,强烈建议补习一下测试驱动开发的相关知识,我想应该会对您之后的道路很有帮助。如果你已经很熟悉这个命令了,那我们一起开始来看看幕后发生了什么。
+
+有了上面的基础,相信您大体上应该可以自行解读这个宏了。`({...})`的语法和`##`都很熟悉了,这里有三个值得注意的地方,在这个宏的一开始,我们后面的的参数是`format...`,这其实也是可变参数的一种写法,和`...`与`__VA_ARGS__`配对类似,`{NAME}...`将于`{NAME}`配对使用。也就是说,在这里宏内容的`format`指代的其实就是定义的先对`expression`取了两次反?我不是科班出身,但是我还能依稀记得这在大学程序课上讲过,两次取反的操作可以确保结果是BOOL值,这在objc中还是比较重要的(关于objc中BOOL的讨论已经有很多,如果您还没能分清BOOL, bool和Boolean,可以参看[NSHisper的这篇文章](http://nshipster.com/bool/))。然后就是`@#expression`这个式子。我们接触过双井号`##`,而这里我们看到的操作符是单井号`#`,注意井号前面的`@`是objc的编译符号,不属于宏操作的对象。单个井号的作用是字符串化,简单来说就是将替换后在两头加上"",转为一个C字符串。这里使用@然后紧跟#expression,出来后就是一个内容是expression的内容的NSString。然后这个NSString再作为参数传递给`_XCTRegisterFailure`和`_XCTFailureDescription`等,继续进行展开,这些是后话。简单一瞥,我们大概就可以想象宏帮助我们省了多少事儿了,如果各位看官要是写个断言还要来个十多行的话,想象都会疯掉的吧。
+
+另外一个例子,找了人民群众喜闻乐见的[ReactiveCocoa(RAC)](https://github.com/ReactiveCocoa/ReactiveCocoa)中的一个宏定义。对于RAC不熟悉或者没听过的朋友,可以简单地看看[Limboy的一系列相关博文](http://blog.leezhong.com)(搜索ReactiveCocoa),介绍的很棒。如果觉得“哇哦这个好酷我很想学”的话,不妨可以跟随raywenderlich上这个[系列的教程](http://www.raywenderlich.com/55384/ios-7-best-practices-part-1)做一些实践,里面简单地用到了RAC,但是都已经包含了RAC的基本用法了。RAC中有几个很重要的宏,它们是保证RAC简洁好用的基本,可以说要是没有这几个宏的话,是不会有人喜欢RAC的。其中`RACObserve`就是其中一个,它通过KVC来为对象的某个属性创建一个信号返回(如果你看不懂这句话,不要担心,这对你理解这个宏的写法和展开没有任何影响)。对于这个宏,我决定不再像上面那样展开和讲解,我会在最后把相关的宏都贴出来,大家不妨拿它练练手,看看能不能将其展开到代码的状态,并且明白其中都发生了些什么。如果你遇到什么问题或者在展开过程中有所心得,欢迎在评论里留言分享和交流 :)
+
+好了,这篇文章已经够长了。希望在看过以后您在看到宏的时候不再发怵,而是可以很开心地说这个我会这个我会这个我也会。最终目标当然是写出漂亮高效简洁的宏,这不论对于提高生产力还是~~~震慑你的同事~~~提升自己实力都会很有帮助。
+
+另外,在这里一定要宣传一下关注了很久的[@hangcom](http://weibo.com/hangcom) 吴航前辈的新书《iOS应用逆向工程》。很荣幸能够在发布之前得到前辈的允许拜读了整本书,可以说看的畅快淋漓。我之前并没有越狱开发的任何基础,也对相关领域知之甚少,在这样的前提下跟随书中的教程和例子进行探索的过程可以说是十分有趣。我也得以能够用不同的眼光和高度来审视这几年所从事的iOS开发行业,获益良多。可以说《iOS应用逆向工程》是我近期所愉快阅读到的很cool的一本好书。现在这本书还在预售中,但是距离1月28日的正式发售已经很近,有兴趣的同学可以前往[亚马逊](http://www.amazon.cn/gp/product/B00HQW9AA6/ref=s9_simh_gw_p14_d0_i6?pf_rd_m=A1AJ19PSB66TGU&pf_rd_s=center-2&pf_rd_r=1KY5VBPQDKMCCWC07ANV&pf_rd_t=101&pf_rd_p=108773272&pf_rd_i=899254051)或者[ChinaPub](http://product.china-pub.com/3769262)的相关页面预定,相信这本书将会是iOS技术人员非常棒的春节读物。
+
+最后是我们说好的留给大家玩的练习,我加了一点注释帮助大家稍微理解每个宏是做什么的,在文章后面留了一块试验田,大家可以随便填写玩弄。总之,加油!
+
+```
+//调用 RACSignal是类的名字
+RACSignal *signal = RACObserve(self, currentLocation);
+
+//以下开始是宏定义
+//rac_valuesForKeyPath:observer:是方法名
+#define RACObserve(TARGET, KEYPATH) \
+ [(id)(TARGET) rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]
+
+#define keypath(...) \
+ metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__))(keypath1(__VA_ARGS__))(keypath2(__VA_ARGS__))
+
+//这个宏在取得keypath的同时在编译期间判断keypath是否存在,避免误写
+//您可以先不用介意这里面的巫术..
+#define keypath1(PATH) \
+ (((void)(NO && ((void)PATH, NO)), strchr(# PATH, '.') + 1))
+
+#define keypath2(OBJ, PATH) \
+ (((void)(NO && ((void)OBJ.PATH, NO)), # PATH))
+
+//A和B是否相等,若相等则展开为后面的第一项,否则展开为后面的第二项
+//eg. metamacro_if_eq(0, 0)(true)(false) => true
+// metamacro_if_eq(0, 1)(true)(false) => false
+#define metamacro_if_eq(A, B) \
+ metamacro_concat(metamacro_if_eq, A)(B)
+
+#define metamacro_if_eq1(VALUE) metamacro_if_eq0(metamacro_dec(VALUE))
+
+#define metamacro_if_eq0(VALUE) \
+ metamacro_concat(metamacro_if_eq0_, VALUE)
+
+#define metamacro_if_eq0_1(...) metamacro_expand_
+
+#define metamacro_expand_(...) __VA_ARGS__
+
+#define metamacro_argcount(...) \
+ metamacro_at(20, __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
+
+#define metamacro_at(N, ...) \
+ metamacro_concat(metamacro_at, N)(__VA_ARGS__)
+
+#define metamacro_concat(A, B) \
+ metamacro_concat_(A, B)
+
+#define metamacro_concat_(A, B) A ## B
+
+#define metamacro_at2(_0, _1, ...) metamacro_head(__VA_ARGS__)
+
+#define metamacro_at20(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, ...) metamacro_head(__VA_ARGS__)
+
+#define metamacro_head(...) \
+ metamacro_head_(__VA_ARGS__, 0)
+
+#define metamacro_head_(FIRST, ...) FIRST
+
+#define metamacro_dec(VAL) \
+ metamacro_at(VAL, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+```
+
+//调用 RACSignal是类的名字
+RACSignal *signal = RACObserve(self, currentLocation);
+
+
+
diff --git a/_posts/2014-02-14-ios-test-with-kiwi.markdown b/_posts/2014-02-14-ios-test-with-kiwi.markdown
new file mode 100644
index 00000000..704fe937
--- /dev/null
+++ b/_posts/2014-02-14-ios-test-with-kiwi.markdown
@@ -0,0 +1,428 @@
+---
+layout: post
+title: TDD的iOS开发初步以及Kiwi使用入门
+date: 2014-02-14 01:06:19.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+测试驱动开发(Test Driven Development,以下简称TDD)是保证代码质量的不二法则,也是先进程序开发的共识。Apple一直致力于在iOS开发中集成更加方便和可用的测试,在Xcode 5中,新的IDE和SDK引入了XCTest来替代原来的SenTestingKit,并且取消了新建工程时的“包括单元测试”的可选项(同样待遇的还有使用ARC的可选项)。新工程将自动包含测试的target,并且相关框架也搭建完毕,可以说测试终于摆脱了iOS开发中“二等公民”的地位,现在已经变得和产品代码一样重要了。我相信每个工程师在完成自己的业务代码的同时,也有最基本的编写和维护相应的测试代码的义务,以保证自己的代码能够正确运行。更进一步,如果能够使用TDD来进行开发,不仅能保证代码运行的正确性,也有助于代码结构的安排和思考,有助于自身的不断提高。我在最开始进行开发时也曾对测试嗤之以鼻,但后来无数的惨痛教训让我明白那么多工程师痴迷于测试或者追求更完美的测试,是有其深刻含义的。如果您之前还没有开始为您的代码编写测试,我强烈建议,从今天开始,从现在开始(也许做不到的话,也请从下一个项目开始),编写测试,或者尝试一下TDD的开发方式。
+
+而[Kiwi](https://github.com/allending/Kiwi)是一个iOS平台十分好用的行为驱动开发(Behavior Driven Development,以下简称BDD)的测试框架,有着非常漂亮的语法,可以写出结构性强,非常容易读懂的测试。因为国内现在有关Kiwi的介绍比较少,加上在测试这块很能很多工程师们并没有特别留意,水平层次可能相差会很远,因此在这一系列的两篇博文中,我将从头开始先简单地介绍一些TDD的概念和思想,然后从XCTest的最简单的例子开始,过渡到Kiwi的测试世界。在下一篇中我将继续深入介绍一些Kiwi的其他稍高一些的特性,以期更多的开发者能够接触并使用Kiwi这个优秀的测试框架。
+
+### 什么是TDD,为什么我们要TDD
+
+测试驱动开发并不是一个很新鲜的概念了。软件开发工程师们(当然包括你我)最开始学习程序编写时,最喜欢干的事情就是编写一段代码,然后运行观察结果是否正确。如果不对就返回代码检查错误,或者是加入断点或者输出跟踪程序并找出错误,然后再次运行查看输出是否与预想一致。如果输出只是控制台的一个简单的数字或者字符那还好,但是如果输出必须在点击一系列按钮之后才能在屏幕上显示出来的东西呢?难道我们就只能一次一次地等待编译部署,启动程序然后操作UI,一直点到我们需要观察的地方么?这种行为无疑是对美好生命和绚丽青春的巨大浪费。于是有一些已经浪费了无数时间的资深工程师们突然发现,原来我们可以在代码中构建出一个类似的场景,然后在代码中调用我们之前想检查的代码,并将运行的结果与我们的设想结果在程序中进行比较,如果一致,则说明了我们的代码没有问题,是按照预期工作的。比如我们想要实现一个加法函数add,输入两个数字,输出它们相加后的结果。那么我们不妨设想我们真的拥有两个数,比如3和5,根据人人会的十以内的加法知识,我们知道答案是8.于是我们在相加后与预测的8进行比较,如果相等,则说明我们的函数实现至少对于这个例子是没有问题的,因此我们对“这个方法能正确工作”这一命题的信心就增加了。这个例子的伪码如下:
+
+```c
+//Product Code
+add(float num1, float num 2) {...}
+
+//Test code
+let a = 3;
+let b = 5;
+let c = a + b;
+
+if (c == 8) {
+ // Yeah, it works!
+} else {
+ //Something wrong!
+}
+
+```
+
+当测试足够全面和具有代表性的时候,我们便可以信心爆棚,拍着胸脯说,这段代码没问题。我们做出某些条件和假设,并以其为条件使用到被测试代码中,并比较预期的结果和实际运行的结果是否相等,这就是软件开发中测试的基本方式。
+
+
+
+而TDD是一种相对于普通思维的方式来说,比较极端的一种做法。我们一般能想到的是先编写业务代码,也就是上面例子中的`add`方法,然后为其编写测试代码,用来验证产品方法是不是按照设计工作。而TDD的思想正好与之相反,在TDD的世界中,我们应该首先根据需求或者接口情况编写测试,然后再根据测试来编写业务代码,而这其实是违反传统软件开发中的先验认知的。但是我们可以举一个生活中类似的例子来说明TDD的必要性:有经验的砌砖师傅总是会先拉一条垂线,然后沿着线砌砖,因为有直线的保证,因此可以做到笔直整齐;而新入行的师傅往往二话不说直接开工,然后在一阶段完成后再用直尺垂线之类的工具进行测量和修补。TDD的好处不言自明,因为总是先测试,再编码,所以至少你的所有代码的public部分都应该含有必要的测试。另外,因为测试代码实际是要使用产品代码的,因此在编写产品代码前你将有一次深入思考和实践如何使用这些代码的机会,这对提高设计和可扩展性有很好的帮助,试想一下你测试都很难写的接口,别人(或者自己)用起来得多纠结。在测试的准绳下,你可以有目的有方向地编码;另外,因为有测试的保护,你可以放心对原有代码进行重构,而不必担心破坏逻辑。这些其实都指向了一个最终的目的:让我们快乐安心高效地工作。
+
+在TDD原则的指导下,我们先编写测试代码。这时因为还没有对应的产品代码,所以测试代码肯定是无法通过的。在大多数测试系统中,我们使用红色来表示错误,因此一个测试的初始状态应该是红色的。接下来我们需要使用最小的代价(最少的代码)来让测试通过。通过的测试将被表示为安全的绿色,于是我们回到了绿色的状态。接下来我们可以添加一些测试例,来验证我们的产品代码的实现是否正确。如果不幸新的测试例让我们回到了红色状态,那我们就可以修改产品代码,使其回到绿色。如此反复直到各种边界和测试都进行完毕,此时我们便可以得到一个具有测试保证,鲁棒性超强的产品代码。在我们之后的开发中,因为你有这些测试的保证,你可以大胆重构这段代码或者与之相关的代码,最后只需要保证项目处于绿灯状态,你就可以保证代码没重构没有出现问题。
+
+简单说来,TDD的基本步骤就是“红→绿→大胆重构”。
+
+### 使用XCTest来执行TDD
+
+Xcode 5中已经集成了XCTest的测试框架(之前版本是SenTestingKit和OCUnit),所谓测试框架,就是一组让“将测试集成到工程中”以及“编写和实践测试”变得简单的库。我们之后将通过实现一个栈数据结构的例子,来用XCTest初步实践一下TDD开发。在大家对TDD有一些直观认识之后,再转到Kiwi的介绍。如果您已经在使用XCTest或者其他的测试框架了的话,可以直接跳过本节。
+
+首先我们用Xcode新建一个工程吧,选择模板为空项目,在`Product Name`中输入工程名字VVStack,当然您可以使用自己喜欢的名字。如果您使用过Xcode之前的版本的话,应该有留意到之前在这个界面是可以选择是否使用Unit Test的,但是现在这个选框已经被取消。
+
+
+
+新建工程后,可以发现在工程中默认已经有一个叫做`VVStackTests`的target了,这就是我们测试时使用的target。测试部分的代码默认放到了{ProjectName}Tests的group中,现在这个group下有一个测试文件VVStackTests.m。我们的测试例不需要向别的类暴露接口,因此不需要.h文件。另外一般XCTest的测试文件都会以Tests来做文件名结尾。
+
+
+
+运行测试的快捷键是`⌘U`(或者可以使用菜单的Product→Test),我们这时候直接对这个空工程进行测试,Xcode在编译项目后会使用你选择的设备或者模拟器运行测试代码。不出意外的话,这次测试将会失败,如图:
+
+
+
+`VVStackTests.m`是Xcode在新建工程时自动为我们添加的测试文件。因为这个文件并不长,所以我们可以将其内容全部抄录如下:
+
+```objc
+#import
+
+@interface VVStackTests : XCTestCase
+
+@end
+
+@implementation VVStackTests
+
+- (void)setUp
+{
+ [super setUp];
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+}
+
+- (void)tearDown
+{
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ [super tearDown];
+}
+
+- (void)testExample
+{
+ XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__);
+}
+
+@end
+```
+
+可以看到,`VVStackTests`是`XCTestCase`的子类,而`XCTestCase`正是XCTest测试框架中的测试用例类。XCTest在进行测试时将会寻找测试target中的所有`XCTestCase`子类,并运行其中以`test`开头的所有实例方法。在这里,默认实现的`-testExample`将被执行,而在这个方法里,Xcode默认写了一个`XCTFail`的断言,来强制这个测试失败,用以提醒我们测试还没有实现。所谓断言,就是判断输入的条件是否满足。如果不满足,则抛出错误并输出预先规定的字符串作为提示。在这个Fail的断言一定会失败,并提示没有实现该测试。另外,默认还有两个方法`-setUp`和`-tearDown`,正如它们的注释里所述,这两个方法会分别在每个测试开始和结束的时候被调用。我们现在正要开始编写我们的测试,所以先将原来的`-testExample`删除掉。现在再使用`⌘U`来进行测试,应该可以顺利通过了(因为我们已经没有任何测试了)。
+
+接下来让我们想想要做什么吧。我们要实现一个简单的栈数据结构,那么当然会有一个类来代表这种数据结构,在这个工程中我打算就叫它`VVStack`。按照常规,我们可以新建一个Cocoa Touch类,继承NSObject并且开始实现了。但是别忘了,我们现在在TDD,我们需要先写测试!那么首先测试的目标是什么呢?没错,是测试这个`VVStack`类是否存在,以及是否能够初始化。有了这个目标,我们就可以动手开始编写测试了。在文件开头加上`#import "VVStack.h"`,然后在`VVStackTests.m`的`@end`前面加上如下代码:
+
+```objc
+- (void)testStackExist {
+ XCTAssertNotNil([VVStack class], @"VVStack class should exist.");
+}
+
+- (void)testStackObjectCanBeCreated {
+ VVStack *stack = [VVStack new];
+ XCTAssertNotNil(stack, @"VVStack object can be created.");
+}
+```
+
+嘛,当然是不可能通过测试的,而且甚至连编译都无法完成,因为我们现在根本没有一个叫做`VVStack`的类。最简单的让测试通过的方法就是在产品代码中添加`VVStack`类。新建一个Cocoa Touch的Objective-C class,取名VVStack,作为NSObject的子类。注意在添加的时候,应该只将其加入产品的target中:
+
+
+
+由于`VVStack`是NSObject的子类,所以上面的两个断言应该都能通过。这时候再运行测试,成功变绿。接下来我们开始考虑这个类的功能:栈的话肯定需要能够push,并且push后的栈顶元素应该就是刚才所push进去的元素。那么建立一个push方法的测试吧,在刚才添加的代码之下继续写:
+
+```objc
+- (void)testPushANumberAndGetIt {
+ VVStack *stack = [VVStack new];
+ [stack push:2.3];
+ double topNumber = [stack top];
+ XCTAssertEqual(topNumber, 2.3, @"VVStack should can be pushed and has that top value.");
+}
+```
+
+因为我们还没有实现`-push:`和`-top`方法,所以测试毫无疑问地失败了(在ARC环境中直接无法编译)。为了使测试立即通过我们首先需要在`VVStack.h`中声明这两个方法,然后在.m的实现文件中进行实现。令测试通过的最简单的实现是一个空的push方法以及直接返回2.3这个数:
+
+```objc
+//VVStack.h
+@interface VVStack : NSObject
+- (void)push:(double)num;
+- (double)top;
+@end
+
+//VVStack.m
+@implementation VVStack
+- (void)push:(double)num {
+
+}
+
+- (double)top {
+ return 2.3;
+}
+@end
+```
+
+再次运行测试,我们顺利回到了绿灯状态。也许你很快就会说,这算哪门子实现啊,如果再增加一组测试例,比如push一个4.6,然后检查top,不就失败了么?我们难道不应该直接实现一个真正的合理的实现么?对此的回答是,在实际开发中,我们肯定不会以这样的步伐来处理像例子中这样类似的简单问题,而是会直接跳过一些error-try的步骤,实现一个比较完整的方案。但是在更多的时候,我们所关心和需要实现的目标并不是这样容易。特别是在对TDD还不熟悉的时候,我们有必要放慢节奏和动作,将整个开发理念进行充分实践,这样才有可能在之后更复杂的案例中正确使用。于是我们发扬不怕繁杂,精益求精的精神,在刚才的测试例上增加一个测试,回到`VVStackTests.m`中,在刚才的测试方法中加上:
+
+```objc
+- (void)testPushANumberAndGetIt {
+ //...
+ [stack push:4.6];
+ topNumber = [stack top];
+ XCTAssertEqual(topNumber, 4.6, @"Top value of VVStack should be the last num pushed into it");
+}
+```
+
+很好,这下子我们回到了红灯状态,这正是我们所期望的,现在是时候来考虑实现这个栈了。这个实现过于简单,也有非常多的思路,其中一种是使用一个`NSMutableArray`来存储数据,然后在`top`方法里返回最后加入的数据。修改`VVStack.m`,加入数组,更改实现:
+
+```objc
+//VVStack.m
+@interface VVStack()
+@property (nonatomic, strong) NSMutableArray *numbers;
+@end
+
+@implementation VVStack
+- (id)init {
+ if (self = [super init]) {
+ _numbers = [NSMutableArray new];
+ }
+ return self;
+}
+
+- (void)push:(double)num {
+ [self.numbers addObject:@(num)];
+}
+
+- (double)top {
+ return [[self.numbers lastObject] doubleValue];
+}
+@end
+```
+
+测试通过,注意到在`-testStackObjectCanBeCreated`和`testPushANumberAndGetIt`两个测试中都生成了一个`VVStack`对象。在这个测试文件中基本每个测试都会需要初始化对象,因此我们可以考虑在测试文件中添加一个VVStack的实例成员,并将测试中的初始化代码移到`-setUp`中,并在`-tearDown`中释放。
+
+接下来我们可以模仿继续实现`pop`等栈的方法。鉴于篇幅这里不再继续详细实现,大家可以自己动手试试看。记住先实现测试,然后再实现产品代码。一开始您可能会觉得这很无聊,效率低下,但是请记住这是起步练习不可缺少的一部分,而且在我们的例子中其实一切都是以“慢动作”在进行的。相信在经过实践和使用后,您将会逐渐掌握自己的节奏和重点测试。关于使用XCTest到这里为止的代码,可以在[github](https://github.com/onevcat/VVStack/tree/xctest)上找到。
+
+### Kiwi和BDD的测试思想
+
+`XCTest`是基于OCUnit的传统测试框架,在书写性和可读性上都不太好。在测试用例太多的时候,由于各个测试方法是割裂的,想在某个很长的测试文件中找到特定的某个测试并搞明白这个测试是在做什么并不是很容易的事情。所有的测试都是由断言完成的,而很多时候断言的意义并不是特别的明确,对于项目交付或者新的开发人员加入时,往往要花上很大成本来进行理解或者转换。另外,每一个测试的描述都被写在断言之后,夹杂在代码之中,难以寻找。使用XCTest测试另外一个问题是难以进行[mock或者stub](http://www.mockobjects.com),而这在测试中是非常重要的一部分(关于mock测试的问题,我会在下一篇中继续深入)。
+
+行为驱动开发(BDD)正是为了解决上述问题而生的,作为第二代敏捷方法,BDD提倡的是通过将测试语句转换为类似自然语言的描述,开发人员可以使用更符合大众语言的习惯来书写测试,这样不论在项目交接/交付,或者之后自己修改时,都可以顺利很多。如果说作为开发者的我们日常工作是写代码,那么BDD其实就是在讲故事。一个典型的BDD的测试用例包活完整的三段式上下文,测试大多可以翻译为`Given..When..Then`的格式,读起来轻松惬意。BDD在其他语言中也已经有一些框架,包括最早的Java的JBehave和赫赫有名的Ruby的[RSpec](http://rspec.info)和[Cucumber](http://cukes.info)。而在objc社区中BDD框架也正在欣欣向荣地发展,得益于objc的语法本来就非常接近自然语言,再加上[C语言宏的威力](http://onevcat.com/2014/01/black-magic-in-macro/),我们是有可能写出漂亮优美的测试的。在objc中,现在比较流行的BDD框架有[cedar](https://github.com/pivotal/cedar),[specta](https://github.com/specta/specta)和[Kiwi](https://github.com/allending/Kiwi)。其中个人比较喜欢Kiwi,使用Kiwi写出的测试看起来大概会是这个样子的:
+
+```objc
+describe(@"Team", ^{
+ context(@"when newly created", ^{
+ it(@"should have a name", ^{
+ id team = [Team team];
+ [[team.name should] equal:@"Black Hawks"];
+ });
+
+ it(@"should have 11 players", ^{
+ id team = [Team team];
+ [[[team should] have:11] players];
+ });
+ });
+});
+```
+
+我们很容易根据上下文将其提取为`Given..When..Then`的三段式自然语言
+
+> Given a team, when newly created, it should have a name, and should have 11 players
+
+很简单啊有木有!在这样的语法下,是不是写测试的兴趣都被激发出来了呢。关于Kiwi的进一步语法和使用,我们稍后详细展开。首先来看看如何在项目中添加Kiwi框架吧。
+
+### 在项目中添加Kiwi
+
+最简单和最推荐的方法当然是[CocoaPods](http://cocoapods.org),如果您对CocoaPods还比较陌生的话,推荐您花时间先看一看这篇[CocoaPods的简介](http://blog.devtang.com/blog/2012/12/02/use-cocoapod-to-manage-ios-lib-dependency/)。Xcode 5和XCTest环境下,我们需要在Podfile中添加类似下面的条目(记得将`VVStackTests`换成您自己的项目的测试target的名字):
+
+```
+target :VVStackTests, :exclusive => true do
+ pod 'Kiwi/XCTest'
+end
+```
+
+之后`pod install`以后,打开生成的`xcworkspace`文件,Kiwi就已经处于可用状态了。另外,为了我们在新建测试的时候能省点事儿,可以在官方repo里下载并运行安装[Kiwi的Xcode Template](https://github.com/allending/Kiwi/tree/master/Xcode%20Templates)。如果您坚持不用CocoaPods,而想要自己进行配置Kiwi的话,可以参考[这篇wiki](https://github.com/allending/Kiwi/wiki/Setting-Up-Kiwi-2.x-without-CocoaPods)。
+
+
+### 行为描述(Specs)和期望(Expectations),Kiwi测试的基本结构
+
+我们先来新建一个Kiwi测试吧。如果安装了Kiwi的Template的话,在新建文件中选择`Kiwi/Kiwi Spec`来建立一个Specs,取名为`SimpleString`,注意选择目标target为我们的测试target,模板将会在新建的文件名字后面加上Spec后缀。传统测试的文件名一般以Tests为后缀,表示这个文件中含有一组测试,而在Kiwi中,一个测试文件所包含的是一组对于行为的描述(Spec),因此习惯上使用需要测试的目标类来作为名字,并以Spec作为文件名后缀。在Xcode 5中建立测试时已经不会同时创建.h文件了,但是现在的模板中包含有对同名.h的引用,可以在创建后将其删去。如果您没有安装Kiwi的Template的话,可以直接创建一个普通的Objective-C test case class,然后将内容替换为下面这样:
+
+```objc
+#import
+
+SPEC_BEGIN(SimpleStringSpec)
+
+describe(@"SimpleString", ^{
+
+});
+
+SPEC_END
+```
+
+你可能会觉得这不是objc代码,甚至怀疑这些语法是否能够编译通过。其实`SPEC_BEGIN`和`SPEC_END`都是宏,它们定义了一个`KWSpec`的子类,并将其中的内容包装在一个函数中(有兴趣的朋友不妨点进去看看)。我们现在先添加一些描述和测试语句,并运行看看吧,将上面的代码的`SPEC_BEGIN`和`SPEC_END`之间的内容替换为:
+
+```objc
+describe(@"SimpleString", ^{
+ context(@"when assigned to 'Hello world'", ^{
+ NSString *greeting = @"Hello world";
+ it(@"should exist", ^{
+ [[greeting shouldNot] beNil];
+ });
+
+ it(@"should equal to 'Hello world'", ^{
+ [[greeting should] equal:@"Hello world"];
+ });
+ });
+});
+```
+
+`describe`描述需要测试的对象内容,也即我们三段式中的`Given`,`context`描述测试上下文,也就是这个测试在`When`来进行,最后`it`中的是测试的本体,描述了这个测试应该满足的条件,三者共同构成了Kiwi测试中的行为描述。它们是可以nest的,也就是一个Spec文件中可以包含多个`describe`(虽然我们很少这么做,一个测试文件应该专注于测试一个类);一个`describe`可以包含多个`context`,来描述类在不同情景下的行为;一个`context`可以包含多个`it`的测试例。让我们运行一下这个测试,观察输出:
+
+```
+VVStack[36517:70b] + 'SimpleString, when assigned to 'Hello world', should exist' [PASSED]
+VVStack[36517:70b] + 'SimpleString, when assigned to 'Hello world', should equal to 'Hello world'' [PASSED]
+```
+
+可以看到,这三个关键字的描述将在测试时被依次打印出来,形成一个完整的行为描述。除了这三个之外,Kiwi还有一些其他的行为描述关键字,其中比较重要的包括
+
+* `beforeAll(aBlock)` - 当前scope内部的所有的其他block运行之前调用一次
+* `afterAll(aBlock)` - 当前scope内部的所有的其他block运行之后调用一次
+* `beforeEach(aBlock)` - 在scope内的每个it之前调用一次,对于`context`的配置代码应该写在这里
+* `afterEach(aBlock)` - 在scope内的每个it之后调用一次,用于清理测试后的代码
+* `specify(aBlock)` - 可以在里面直接书写不需要描述的测试
+* `pending(aString, aBlock)` - 只打印一条log信息,不做测试。这个语句会给出一条警告,可以作为一开始集中书写行为描述时还未实现的测试的提示。
+* `xit(aString, aBlock)` - 和`pending`一样,另一种写法。因为在真正实现时测试时只需要将x删掉就是`it`,但是pending语意更明确,因此还是推荐pending
+
+可以看到,由于有`context`的存在,以及其可以嵌套的特性,测试的流程控制相比传统测试可以更加精确。我们更容易把before和after的作用区域限制在合适的地方。
+
+实际的测试写在`it`里,是由一个一个的期望(Expectations)来进行描述的,期望相当于传统测试中的断言,要是运行的结果不能匹配期望,则测试失败。在Kiwi中期望都由`should`或者`shouldNot`开头,并紧接一个或多个判断的的链式调用,大部分常见的是be或者haveSomeCondition的形式。在我们上面的例子中我们使用了should not be nil和should equal两个期望来确保字符串赋值的行为正确。其他的期望语句非常丰富,并且都符合自然语言描述,所以并不需要太多介绍。在使用的时候不妨直接按照自己的想法来描述自己的期望,一般情况下在IDE的帮助下我们都能找到想要的结果。如果您想看看完整的期望语句的列表,可以参看文档的[这个页面](https://github.com/allending/Kiwi/wiki/Expectations)。另外,您还可以通过新建`KWMatcher`的子类,来简单地自定义自己和项目所需要的期望语句。从这一点来看,Kiwi可以说是一个非常灵活并具有可扩展性的测试框架。
+
+到此为止的代码可以从[这里](https://github.com/onevcat/VVStack/tree/kiwi-start)找到。
+
+### Kiwi实际使用实例
+
+最后我们来用Kiwi完整地实现VVStack类的测试和开发吧。首先重写刚才XCTest的相关测试:新建一个VVStackSpec作为Kiwi版的测试用例,然后把describe换成下面的代码:
+
+```objc
+describe(@"VVStack", ^{
+ context(@"when created", ^{
+ __block VVStack *stack = nil;
+ beforeEach(^{
+ stack = [VVStack new];
+ });
+
+ afterEach(^{
+ stack = nil;
+ });
+
+ it(@"should have the class VVStack", ^{
+ [[[VVStack class] shouldNot] beNil];
+ });
+
+ it(@"should exist", ^{
+ [[stack shouldNot] beNil];
+ });
+
+ it(@"should be able to push and get top", ^{
+ [stack push:2.3];
+ [[theValue([stack top]) should] equal:theValue(2.3)];
+
+ [stack push:4.6];
+ [[theValue([stack top]) should] equal:4.6 withDelta:0.001];
+ });
+
+ });
+});
+```
+
+看到这里的您看这段测试应该不成问题。需要注意的有两点:首先`stack`分别是在`beforeEach`和`afterEach`的block中的赋值的,因此我们需要在声明时在其前面加上`__block`标志。其次,期望描述的should或者shouldNot是作用在对象上的宏,因此对于标量,我们需要先将其转换为对象。Kiwi为我们提供了一个标量转对象的语法糖,叫做`theValue`,在做精确比较的时候我们可以直接使用例子中直接与2.3做比较这样的写法来进行对比。但是如果测试涉及到运算的话,由于浮点数精度问题,我们一般使用带有精度的比较期望来进行描述,即4.6例子中的`equal:withDelta:`(当然,这里只是为了demo,实际在这用和上面2.3一样的方法就好了)。
+
+接下来我们再为这个context添加一个测试例,用来测试初始状况时栈是否为空。因为我们使用了一个Array来作为存储容器,根据我们之前用过的equal方法,我们很容易想到下面这样的测试代码
+
+```objc
+it(@"should equal contains 0 element", ^{
+ [[theValue([stack.numbers count]) should] equal:theValue(0)];
+});
+```
+
+这段测试在逻辑上没有太大问题,但是有非常多值得改进的地方。首先如果我们需要将原来写在Extension里的`numbers`暴露到头文件中,这对于类的封装是一种破坏,对于这个,一种常见的做法是只暴露一个`-count`方法,让其返回`numbers`的元素个数,从而保证`numbers`的私有性。另外对于取值和转换,其实theValue的存在在一定程度上是破坏了测试可读性的,我们可以想办法改善一下,比如对于0的来说,我们有`beZero`这样的期望可以使用。简单改写以后,这个`VVStack.h`和这个测试可以变成这个样子:
+
+```objc
+//VVStack.h
+//...
+- (NSUInteger)count;
+//...
+
+
+//VVStack.m
+//...
+- (NSUInteger)count {
+ return [self.numbers count];
+}
+//...
+
+it(@"should equal contains 0 element", ^{
+ [[theValue([stack count]) should] beZero];
+});
+```
+
+更进一步地,对于一个collection来说,Kiwi有一些特殊处理,比如`have`和`haveCountOf`系列的期望。如果测试的对象实现了`-count`方法的话,我们就可以使用这一系列期望来写出更好的测试语句。比如上面的测试还可以进一步写成
+
+```objc
+it(@"should equal contains 0 element", ^{
+ [[stack should] haveCountOf:0];
+});
+```
+
+在这种情况下,我们并没有显式地调用VVStack的`-count`方法,所以我们可以在头文件中将其删掉。但是我们需要保留这个方法的实现,因为测试时是需要这个方法的。如果测试对象不能响应count方法的话,如你所料,测试时会扔一个unrecognized selector的错。Kiwi的内部实现是一个大量依赖了一个个行为Matcher和objc的消息转发,对objcruntime特性比较熟悉,并想更深入的朋友不放可以看看Kiwi的源码,写得相当漂亮。
+
+其实对于这个测试,我们还可以写出更漂亮的版本,像这样:
+
+```objc
+it(@"should equal contains 0 element", ^{
+ [[stack should] beEmpty];
+});
+```
+
+好了。关于空栈这个情景下的测试感觉差不多了。我们继续用TDD的思想来完善`VVStack`类吧。栈的话,我们当然需要能够`-pop`,也就是说在(Given)给定一个栈时,(When)当栈中有元素的时候,(Then)我们可以pop它,并且得到栈顶元素。我们新建一个context,然后按照这个思路书写行为描述(测试):
+
+```objc
+ context(@"when new created and pushed 4.6", ^{
+ __block VVStack *stack = nil;
+ beforeEach(^{
+ stack = [VVStack new];
+ [stack push:4.6];
+ });
+
+ afterEach(^{
+ stack = nil;
+ });
+
+ it(@"can be poped and the value equals 4.6", ^{
+ [[theValue([stack pop]) should] equal:theValue(4.6)];
+ });
+
+ it(@"should contains 0 element after pop", ^{
+ [stack pop];
+ [[stack should] beEmpty];
+ });
+ });
+```
+
+完成了测试书写后,我们开始按照设计填写产品代码。在VVStack.h中完成申明,并在.m中加入相应实现。
+
+```objc
+- (double)pop {
+ double result = [self top];
+ [self.numbers removeLastObject];
+ return result;
+}
+```
+
+很简单吧。而且因为有测试的保证,我们在提供像Stack这样的基础类时,就不需要等到或者在真实的环境中检测了。因为在被别人使用之前,我们自己的测试代码已经能够保证它的正确性了。`VVStack`剩余的最后一个小问题是,在栈是空的时候,我们执行pop操作时应该给出一个错误,用以提示空栈无法pop。虽然在objc中异常并不常见,但是在这个情景下是抛异常的好时机,也符合一般C语言对于出空栈的行为。我们可以在之前的“when created”上下文中加入一个期望:
+
+```objc
+it(@"should raise a exception when pop", ^{
+ [[theBlock(^{
+ [stack pop];
+ }) should] raiseWithName:@"VVStackPopEmptyException"];
+});
+```
+
+和`theValue`配合标量值类似,`theBlock`也是Kiwi中的一个转换语法,用来将一段程序转换为相应的matcher,使其可以被施加期望。这里我们期望空的Stack在被pop时抛出一个叫做"VVStackPopEmptyException"的异常。我们可以重构pop方法,在栈为空时给一个异常:
+
+```objc
+- (double)pop {
+ if ([self count] == 0) {
+ [NSException raise:@"VVStackPopEmptyException" format:@"Can not pop an empty stack."];
+ }
+ double result = [self top];
+ [self.numbers removeLastObject];
+ return result;
+}
+```
+
+### 进一步的Kiwi
+
+VVStack的测试和实现就到这里吧,根据这套测试,您可以使用自己的实现来轻易地重构这个类,而不必担心破坏它的公共接口的行为。如果需要添加新的功能或者修正已有bug的时候,我们也可以通过添加或者修改相应的测试,来确保正确性。我将会在下一篇博文中继续介绍Kiwi,看看Kiwi在异步测试和mock/stub的使用和表现如何。Kiwi现在还在比较快速的发展中,官方repo的[wiki](https://github.com/allending/Kiwi/wiki)上有一些不错的资料和文档,可以参考。`VVStack`的项目代码可以在[这个repo](https://github.com/onevcat/VVStack)上找到,可以作为参考。
+
+另外,Kiwi 不仅可以用来做简单的特性测试,它也包括了完整的 mock 和 stub 测试的功能。关于这部分内容我补充了一篇[文章](http://onevcat.com/2014/05/kiwi-mock-stub-test/)专门说明,有兴趣的同学不妨继续深入看看。
diff --git a/_posts/2014-03-22-common-background-practices.markdown b/_posts/2014-03-22-common-background-practices.markdown
new file mode 100644
index 00000000..7bbfdce9
--- /dev/null
+++ b/_posts/2014-03-22-common-background-practices.markdown
@@ -0,0 +1,427 @@
+---
+layout: post
+title: 常见的后台实践
+date: 2014-03-22 01:07:50.000000000 +09:00
+tags: 能工巧匠集
+---
+## 题外
+
+[objc.io](http://www.objc.io) 是一个非常棒的iOS进阶学习的网站,上面有很多超赞的学习资源和例子。最近我和 [@方一雄](http://weibo.com/fangyixiong),[@answer-huang](http://weibo.com/u/1623064627) 和社区的另外几名小伙伴在主持做一个 objc.io 的译文整理汇总和后续翻译跟进的项目,我暂时略自我狂妄地把它叫做 `objc中国`([objccn.io](http://objccn.io)) 项目,希望它能给现在已经很红火的中国objc社区锦上添花。现在上面已经有一些文章,您可以时不时地访问我们的[首页](http://objccn.io)来查看新的动态。如果有兴趣,也可以考虑[加入我们](https://github.com/objccn/articles),来为中国objc社区的发展贡献一点力量。
+
+对objc中国上的每一篇文章,我都会至少进行一个基本的校对。在整理和收集的过程中,我发现虽然不少文章有相对完整的译文,但其中也存在对原文的理解上有一定偏差的情况。可能是译者并没有原作者对某些问题的深入理解,可能是原文也做过一些修改调整而译文没有更新,也可能是因为翻译时时间上多有仓促,导致了它们并不足以在只是稍加修改后就能发表在主站点上。对于这样的译文,我的想法是为了保证文章质量,牺牲一些个人时间来重新进行翻译。一来可以保证文章质量和站点的水准,二来也算是一次自我学习和提高的过程。
+
+题外话完毕。本文是 objc.io issue #2 的第二篇正文,这篇文章在该主题第一篇[并发编程:API 及挑战](http://objccn.io/issue-2-1/)的基础上深层次地讲了一些实践上的例子和技术,颇有难度。对多线程不熟悉的同学可以先参照看看第一篇,再来阅读本文。
+
+---
+
+本文主要探讨一些常用后台任务的最佳实践。我们将会看看如何并发地使用 Core Data ,如何并行绘制 UI ,如何做异步网络请求等。最后我们将研究如何异步处理大型文件,以保持较低的内存占用。
因为在异步编程中非常容易犯错误,所以,本文中的例子都将使用很简单的方式。因为使用简单的结构可以帮助我们看透代码,抓住问题本质。如果你最后把代码写成了复杂的嵌套回调的话,那么你很可能应该重新考虑自己当初的设计选择了。
+
+
+## 操作队列 (Operation Queues) 还是 GCD ?
+
+目前在 iOS 和 OS X 中有两套先进的同步 API 可供我们使用:[操作队列][6]和 [GCD][7] 。其中 GCD 是基于 C 的底层的 API ,而操作队列则是 GCD 实现的 Objective-C API。关于我们可以使用的并行 API 的更加全面的总览,可以参见 [并发编程:API 及挑战][8]。
+
+操作队列提供了在 GCD 中不那么容易复制的有用特性。其中最重要的一个就是可以取消在任务处理队列中的任务,在稍后的例子中我们会看到这个。而且操作队列在管理操作间的依赖关系方面也容易一些。另一面,GCD 给予你更多的控制权力以及操作队列中所不能使用的底层函数。详细介绍可以参考[底层并发 API][9] 这篇文章。
+
+扩展阅读:
+
+* [StackOverflow: NSOperation vs. Grand Central Dispatch](http://stackoverflow.com/questions/10373331/nsoperation-vs-grand-central-dispatch)
+* [Blog: When to use NSOperation vs. GCD](http://eschatologist.net/blog/?p=232)
+
+### 后台的 Core Data
+
+在着手 Core Data 的并行处理之前,最好先打一些基础。我们强烈建议通读苹果的官方文档 [Concurrency with Core Data guide][10] 。这个文档中罗列了基本规则,比如绝对不要在线程间传递 managed objects等。这并不单是说你绝不应该在另一个线程中去更改某个其他线程的 managed object ,甚至是读取其中的属性都是不能做的。要想传递这样的对象,正确做法是通过传递它的 object ID ,然后从其他对应线程所绑定的 context 中去获取这个对象。
+
+其实只要你遵循那些规则,并使用这篇文章里所描述的方法的话,处理 Core Data 的并行编程还是比较容易的。
+
+Xcode 所提供的 Core Data 标准模版中,所设立的是运行在主线程中的一个存储调度 (persistent store coordinator)和一个托管对象上下文 (managed object context) 的方式。在很多情况下,这种模式可以运行良好。创建新的对象和修改已存在的对象开销都非常小,也都能在主线程中没有困难滴完成。然后,如果你想要做大量的处理,那么把它放到一个后台上下文来做会比较好。一个典型的应用场景是将大量数据导入到 Core Data 中。
+
+我们的方式非常简单,并且可以被很好地描述:
+
+1. 我们为导入工作单独创建一个操作
+2. 我们创建一个 managed object context ,它和主 managed object context 使用同样的 persistent store coordinator
+3. 一旦导入 context 保存了,我们就通知 主 managed object context 并且合并这些改变
+
+在[示例app][11]中,我们要导入一大组柏林的交通数据。在导入的过程中,我们展示一个进度条,如果耗时太长,我们希望可以取消当前的导入操作。同时,我们显示一个随着数据加入可以自动更新的 table view 来展示目前可用的数据。示例用到的数据是采用的 Creative Commons license 公开的,你可以[在此下载][12]它们。这些数据遵守一个叫做 [General Transit Feed][13] 格式的交通数据公开标准。
+
+我们创建一个 `NSOperation` 的子类,将其叫做 `ImportOperation`,我们通过重写 `main` 方法,用来处理所有的导入工作。这里我们使用 `NSPrivateQueueConcurrencyType` 来创建一个独立并拥有自己的私有 dispatch queue 的 managed object context,这个 context 需要管理自己的队列。在队列中的所有操作必须使用 `performBlock` 或者 `performBlockAndWait` 来进行触发。这点对于保证这些操作能在正确的线程上执行是相当重要的。
+
+```objc
+NSManagedObjectContext* context = [[NSManagedObjectContext alloc]
+ initWithConcurrencyType:NSPrivateQueueConcurrencyType];
+context.persistentStoreCoordinator = self.persistentStoreCoordinator;
+context.undoManager = nil;
+[self.context performBlockAndWait:^
+{
+ [self import];
+}];
+```
+
+在这里我们重用了已经存在的 persistent store coordinator 。一般来说,初始化 managed object contexts 要么使用 `NSPrivateQueueConcurrencyType`,要么使用 `NSMainQueueConcurrencyType`。第三种并发类型 `NSConfinementConcurrencyType` 是为老旧代码准备的,我们不建议再使用它了。
+
+在导入前,我们枚举文件中的各行,并对可以解析的每一行创建 managed object :
+
+```objc
+[lines enumerateObjectsUsingBlock:
+ ^(NSString* line, NSUInteger idx, BOOL* shouldStop)
+ {
+ NSArray* components = [line csvComponents];
+ if(components.count < 5) {
+ NSLog(@"couldn't parse: %@", components);
+ return;
+ }
+ [Stop importCSVComponents:components intoContext:context];
+ }];
+```
+
+在 view controller 中通过以下代码来开始操作:
+
+```objc
+ImportOperation* operation = [[ImportOperation alloc]
+ initWithStore:self.store fileName:fileName];
+[self.operationQueue addOperation:operation];
+```
+
+至此为止,后台导入部分已经完成。接下来,我们要加入取消功能,这其实非常简单,只需要枚举的 block 中加一个判断就行了:
+
+```objc
+if(self.isCancelled) {
+ *shouldStop = YES;
+ return;
+}
+```
+
+最后为了支持进度条,我们在 operation 中创建一个叫做 `progressCallback` 的属性。需要注意的是,更新进度条必须在主线程中完成,否则会导致 UIKit 崩溃。
+
+```objc
+operation.progressCallback = ^(float progress)
+{
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^
+ {
+ self.progressIndicator.progress = progress;
+ }];
+};
+```
+
+我们在枚举中来调用这个进度条更新的 block 的操作:
+
+```objc
+self.progressCallback(idx / (float) count);
+```
+
+然而,如果你执行示例代码的话,你会发现它运行逐渐变得很慢,取消操作也有迟滞。这是因为主操作队列中塞满了要更新进度条的 block 操作。一个简单的解决方法是降低更新的频度,比如只在每导入一百行时更新一次:
+
+```objc
+NSInteger progressGranularity = lines.count / 100;
+
+if (idx % progressGranularity == 0) {
+ self.progressCallback(idx / (float) count);
+}
+```
+
+
+### 更新 Main Context
+
+在 app 中的 table view 是由一个在主线程上获取了结果的 controller 所驱动的。在导入数据的过程中和导入数据完成后,我们要在 table view 中展示我们的结果。
+
+在让一切运转起来之前之前,还有一件事情要做。现在在后台 context 中导入的数据还不能传送到主 context中,除非我们显式地让它这么去做。我们在 `Store` 类的设置 Core Data stack 的 `init` 方法中加入下面的代码:
+
+```objc
+[[NSNotificationCenter defaultCenter]
+ addObserverForName:NSManagedObjectContextDidSaveNotification
+ object:nil
+ queue:nil
+ usingBlock:^(NSNotification* note)
+{
+ NSManagedObjectContext *moc = self.mainManagedObjectContext;
+ if (note.object != moc)
+ [moc performBlock:^(){
+ [moc mergeChangesFromContextDidSaveNotification:note];
+ }];
+ }];
+}];
+```
+
+
+如果 block 在主队列中被作为参数传递的话,那么这个 block 也会在主队列中被执行。如果现在你运行程序的话,你会注意到 table view 会在完成导入数据后刷新数据,但是这个行为会阻塞用户大概几秒钟。
+
+要修正这个问题,我们需要做一些无论如何都应该做的事情:批量保存。在导入较大的数据时,我们需要定期保存,逐渐导入,否则内存很可能就会被耗光,性能一般也会更坏。而且,定期保存也可以分散主线程在更新 table view 时的工作压力。
+
+合理的保存的次数可以通过试错得到。保存太频繁的话,可能会在 I/O 操作上花太多时间;保存次数太少的话,应用会变得无响应。在经过一些尝试后,我们设定每 250 次导入就保存一次。改进后,导入过程变得很平滑,它可以适时更新 table view,也没有阻塞主 context 太久。
+
+### 其他考虑
+
+在导入操作时,我们将整个文件都读入到一个字符串中,然后将其分割成行。这种处理方式对于相对小的文件来说没有问题,但是对于大文件,最好采用惰性读取 (lazily read) 的方式逐行读入。本文最后的示例将使用输入流的方式来实现这个特性,在 [StackOverflow][14] 上 Dave DeLong 也提供了一段非常好的示例代码来说明这个问题。
+
+在 app 第一次运行时,除开将大量数据导入 Core Data 这一选择以外,你也可以在你的 app bundle 中直接放一个 sqlite 文件,或者从一个可以动态生成数据的服务器下载。如果使用这些方式的话,可以节省不少在设备上的处理事件。
+
+最后,最近对于 child contexts 有很多争议。我们的建议是不要在后台操作中使用它。如果你以主 context 的 child 的方式创建了一个后台 context 的话,保存这个后台 context 将[阻塞主线程][15]。而要是将主 context 作为后台 context 的 child 的话,实际上和与创建两个传统的独立 contexts 来说是没有区别的。因为你仍然需要手动将后台的改变合并回主 context 中去。
+
+设置一个 persistent store coordinator 和两个独立的 contexts 被证明了是在后台处理 Core Data 的好方法。除非你有足够好的理由,否则在处理时你应该坚持使用这种方式。
+
+扩展阅读:
+
+* [Core Data Programming Guide: Efficiently importing data](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/Articles/cdImporting.html)
+* [Core Data Programming Guide: Concurrency with Core Data](http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/CoreData/Articles/cdConcurrency.html#//apple_ref/doc/uid/TP40003385-SW1j)
+* [StackOverflow: Rules for working with Core Data](http://stackoverflow.com/questions/2138252/core-data-multi-thread-application/2138332#2138332)
+* [WWDC 2012 Video: Core Data Best Practices](https://developer.apple.com/videos/wwdc/2012/?id=214)
+* [Book: Core Data by Marcus Zarra](http://pragprog.com/book/mzcd/core-data)
+
+## 后台 UI 代码
+
+首先要强调:UIKit 只能在主线程上运行。而那部分不与 UIKit 直接相关,却会消耗大量时间的 UI 代码可以被移动到后台去处理,以避免其将主线程阻塞太久。但是在你将你的 UI 代码移到后台队列之前,你应该好好地测量哪一部分才是你代码中的瓶颈。这非常重要,否则你所做的优化根本是南辕北辙。
+
+如果你找到了你能够隔离出的昂贵操作的话,可以将其放到操作队列中去:
+
+```objc
+__weak id weakSelf = self;
+[self.operationQueue addOperationWithBlock:^{
+ NSNumber* result = findLargestMersennePrime();
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^{
+ MyClass* strongSelf = weakSelf;
+ strongSelf.textLabel.text = [result stringValue];
+ }];
+}];
+```
+
+如你所见,这些代码其实一点也不直接明了。我们首先声明了一个 weak 引用来参照 self,否则会形成循环引用( block 持有了 self,私有的 `operationQueue` retain 了 block,而 self 又 retain 了 `operationQueue` )。为了避免在运行 block 时访问到已被释放的对象,在 block 中我们又需要将其转回 strong 引用。
+
+编者注 这在 ARC 和 block 主导的编程范式中是解决 retain cycle 的一种常见也是最标准的方法。
+
+### 后台绘制
+
+如果你确定 `drawRect:` 是你的应用的性能瓶颈,那么你可以将这些绘制代码放到后台去做。但是在你这样做之前,检查下看看是不是有其他方法来解决,比如、考虑使用 core animation layers 或者预先渲染图片而不去做 Core Graphics 绘制。可以看看 Florian 对在真机上图像性能测量的[帖子][16],或者可以看看来自 UIKit 工程师 Andy Matuschak 对个各种方式的权衡的[评论][17]。
+
+如果你确实认为在后台执行绘制代码会是你的最好选择时再这么做。其实解决起来也很简单,把 `drawRect:` 中的代码放到一个后台操作中去做就可以了。然后将原本打算绘制的视图用一个 image view 来替换,等到操作执行完后再去更新。在绘制的方法中,使用 `UIGraphicsBeginImageContextWithOptions` 来取代 `UIGraphicsBeginImageContextWithOpertions` :
+
+```objc
+UIGraphicsBeginImageContextWithOptions(size, NO, 0);
+// drawing code here
+UIImage *i = UIGraphicsGetImageFromCurrentImageContext();
+UIGraphicsEndImageContext();
+return i;
+```
+
+通过在第三个参数中传入 0 ,设备的主屏幕的 scale 将被自动传入,这将使图片在普通设备和 retina 屏幕上都有良好的表现。
+
+如果你在 table view 或者是 collection view 的 cell 上做了自定义绘制的话,最好将塔门放入 operation 的子类中去。你可以将它们添加到后台操作队列,也可以在用户将 cell 滚动出边界时的 `didEndDisplayingCell` 委托方法中进行取消。这些技巧都在 2012 年的WWDC [Session 211 -- Building Concurrent User Interfaces on iOS][18]中有详细阐述。
+
+除了在后台自己调度绘制代码,以也可以试试看使用 `CALayer` 的 `drawsAsynchronously` 属性。然而你需要精心衡量这样做的效果,因为有时候它能使绘制加速,有时候却适得其反。
+
+## 异步网络请求处理
+
+你的所有网络请求都应该采取异步的方式完成。
+
+然而,在 GCD 下,有时候你可能会看到这样的代码
+
+```objc
+// 警告:不要使用这些代码。
+dispatch_async(backgroundQueue, ^{
+ NSData* contents = [NSData dataWithContentsOfURL:url]
+ dispatch_async(dispatch_get_main_queue(), ^{
+ // 处理取到的日期
+ });
+});
+```
+
+乍看起来没什么问题,但是这段代码却有致命缺陷。你没有办法去取消这个同步的网络请求。它将阻塞住线程直到它完成。如果请求一直没结果,那就只能干等到超时(比如 `dataWithContentsOfURL:` 的超时时间是 30 秒)。
+
+如果队列是串行执行的话,它将一直被阻塞住。假如队列是并行执行的话,GCD 需要重开一个线程来补凑你阻塞住的线程。两种结果都不太妙,所以最好还是不要阻塞线程。
+
+要解决上面的困境,我们可以使用 `NSURLConnection` 的异步方法,并且把所有操作转化为 operation 来执行。通过这种方法,我们可以从操作队列的强大功能和便利中获益良多:我们能轻易地控制并发操作的数量,添加依赖,以及取消操作。
+
+然而,在这里还有一些事情值得注意: `NSURLConnection` 是通过 run loop 来发送事件的。因为时间发送不会花多少时间,因此最简单的是就只使用 main run loop 来做这个。然后,我们就可以用后台线程来处理输入的数据了。
+
+另一种可能的方式是使用像 [AFNetworking](http://afnetworking.com) 这样的框架:建立一个独立的线程,为建立的线程设置自己的 run loop,然后在其中调度 URL 连接。但是并不推荐你自己去实现这些事情。
+
+要处理URL 连接,我们重写自定义的 operation 子类中的 `start` 方法:
+
+```objc
+- (void)start
+{
+ NSURLRequest* request = [NSURLRequest requestWithURL:self.url];
+ self.isExecuting = YES;
+ self.isFinished = NO;
+ [[NSOperationQueue mainQueue] addOperationWithBlock:^
+ {
+ self.connection = [NSURLConnectionconnectionWithRequest:request
+ delegate:self];
+ }];
+}
+```
+
+由于重写的是 `start` 方法,所以我们需要自己要管理操作的 `isExecuting` 和 `isFinished` 状态。要取消一个操作,我们需要取消 connection ,并且设定合适的标记,这样操作队列才知道操作已经完成。
+
+```objc
+- (void)cancel
+{
+ [super cancel];
+ [self.connection cancel];
+ self.isFinished = YES;
+ self.isExecuting = NO;
+}
+```
+
+当连接完成加载后,它向代理发送回调:
+
+```objc
+- (void)connectionDidFinishLoading:(NSURLConnection *)connection
+{
+ self.data = self.buffer;
+ self.buffer = nil;
+ self.isExecuting = NO;
+ self.isFinished = YES;
+}
+```
+
+
+就这么多了。完整的代码可以参见[GitHub上的示例工程][20]。
+
+总结来说,我们建议要么你玩时间来把事情做对做好,要么就直接使用像 [AFNetworking][19] 这样的框架。其实 [AFNetworking][19] 还提供了不少好用的小工具,比如有个 `UIImageView` 的 category,来负责异步地从一个 URL 加载图片。在你的 table view 里使用的话,还能自动帮你处理取消加载操作,非常方便。
+
+扩展阅读:
+
+* [Concurrency Programming Guide](http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1)
+* [NSOperation Class Reference: Concurrent vs. Non-Concurrent Operations](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperation_class/Reference/Reference.html%23//apple_ref/doc/uid/TP40004591-RH2-SW15)
+* [Blog: synchronous vs. asynchronous NSURLConnection](http://www.cocoaintheshell.com/2011/04/nsurlconnection-synchronous-asynchronous/)
+* [GitHub: `SDWebImageDownloaderOperation.m`](https://github.com/rs/SDWebImage/blob/master/SDWebImage/SDWebImageDownloaderOperation.m)
+* [Blog: Progressive image download with ImageIO](http://www.cocoaintheshell.com/2011/05/progressive-images-download-imageio/)
+* [WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS](https://developer.apple.com/videos/wwdc/2012/)
+
+## 进阶:后台文件 I/O
+
+在之前我们的后台 Core Data 示例中,我们将一整个文件加载到了内存中。这种方式对于较小的文件没有问题,但是受限于 iOS 设备的内存容量,对于大文件来说的话就不那么友好了。要解决这个问题,我们将构建一个类,它负责一行一行读取文件而不是一次将整个文件读入内存,另外要在后台队列处理文件,以保持应用相应用户的操作。
+
+为了达到这个目的,我们使用能让我们异步处理文件的 `NSInputStream` 。根据[官方文档][21]的描述:
+
+> 如果你需总是需要从头到尾来读/写文件的话,streams 提供了一个简单的接口来异步完成这个操作
+
+不管你是否使用 streams,大体上逐行读取一个文件的模式是这样的:
+
+1. 建立一个中间缓冲层以提供,当没有找到换行符号的时候可以向其中添加数据
+2. 从 stream 中读取一块数据
+3. 对于这块数据中发现的每一个换行符,取中间缓冲层,向其中添加数据,直到(并包括)这个换行符,并将其输出
+4. 将剩余的字节添加到中间缓冲层去
+5. 回到 2,直到 stream 关闭
+
+为了将其运用到实践中,我们又建立了一个[示例应用][22],里面有一个 `Reader` 类完成了这件事情,它的接口十分简单
+
+```objc
+@interface Reader : NSObject
+- (void)enumerateLines:(void (^)(NSString*))block
+ completion:(void (^)())completion;
+- (id)initWithFileAtPath:(NSString*)path;
+@end
+```
+
+注意,这个类不是 NSOperation 的子类。与 URL connections 类似,输入的 streams 通过 run loop 来传递它的事件。这里,我们仍然采用 main run loop 来分发事件,然后将数据处理过程派发至后台操作线程里去处理。
+
+```objc
+- (void)enumerateLines:(void (^)(NSString*))block
+ completion:(void (^)())completion
+{
+ if (self.queue == nil) {
+ self.queue = [[NSOperationQueue alloc] init];
+ self.queue.maxConcurrentOperationCount = 1;
+ }
+ self.callback = block;
+ self.completion = completion;
+ self.inputStream = [NSInputStream inputStreamWithURL:self.fileURL];
+ self.inputStream.delegate = self;
+ [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
+ forMode:NSDefaultRunLoopMode];
+ [self.inputStream open];
+}
+```
+
+现在,input stream 将(在主线程)向我们发送代理消息,然后我们可以在操作队列中加入一个 block 操作来执行处理了:
+
+```objc
+- (void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
+{
+ switch (eventCode) {
+ ...
+ case NSStreamEventHasBytesAvailable: {
+ NSMutableData *buffer = [NSMutableData dataWithLength:4 * 1024];
+ NSUInteger length = [self.inputStream read:[buffer mutableBytes]
+ maxLength:[buffer length]];
+ if (0 < length) {
+ [buffer setLength:length];
+ __weak id weakSelf = self;
+ [self.queue addOperationWithBlock:^{
+ [weakSelf processDataChunk:buffer];
+ }];
+ }
+ break;
+ }
+ ...
+ }
+}
+```
+
+处理数据块的过程是先查看当前已缓冲的数据,并将新加入的数据附加上去。接下来它将按照换行符分解成小的部分,并处理每一行。
+
+数据处理过程中会不断的从buffer中获取已读入的数据。然后把这些新读入的数据按行分开并存储。剩余的数据被再次存储到缓冲区中:
+
+```objc
+- (void)processDataChunk:(NSMutableData *)buffer;
+{
+ if (self.remainder != nil) {
+ [self.remainder appendData:buffer];
+ } else {
+ self.remainder = buffer;
+ }
+ [self.remainder obj_enumerateComponentsSeparatedBy:self.delimiter
+ usingBlock:^(NSData* component, BOOL last) {
+ if (!last) {
+ [self emitLineWithData:component];
+ } else if (0 < [component length]) {
+ self.remainder = [component mutableCopy];
+ } else {
+ self.remainder = nil;
+ }
+ }];
+}
+```
+
+现在你运行示例应用的话,会发现它在响应事件时非常迅速,内存的开销也保持很低(在我们测试时,不论读入的文件有多大,堆所占用的内存量始终低于 800KB)。绝大部分时候,使用逐块读入的方式来处理大文件,是非常有用的技术。
+
+延伸阅读:
+
+* [File System Programming Guide: Techniques for Reading and Writing Files Without File Coordinators](http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html)
+* [StackOverflow: How to read data from NSFileHandle line by line?](http://stackoverflow.com/questions/3707427/how-to-read-data-from-nsfilehandle-line-by-line)
+
+## 总结
+
+通过我们所列举的几个示例,我们展示了如何异步地在后台执行一些常见任务。在所有的解决方案中,我们尽力保持了代码的简单,这是因为在并发编程中,稍不留神就会捅出篓子来。
+
+很多时候为了避免麻烦,你可能更愿意在主线程中完成你的工作,在你能这么做事,这确实让你的工作轻松不少,但是当你发现性能瓶颈时,你可以尝试尽可能用最简单的策略将那些繁重任务放到后台去做。
+
+我们在上面例子中所展示的方法对于其他任务来说也是安全的选择。在主队列中接收事件或者数据,然后用后台操作队列来执行实际操作,然后回到主队列去传递结果,遵循这样的原则来编写尽量简单的并行代码,将是保证高效正确的不二法则。
+
+---
+
+[话题 #2 下的更多文章][11]
+
+ [1]: http://objccn.io/issue-2
+ [6]: http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/NSOperationQueue_class/Reference/Reference.html
+ [7]: https://developer.apple.com/library/ios/#documentation/Performance/Reference/GCD_libdispatch_Ref/Reference/reference.html
+ [8]: http://www.objc.io/issue-2-1/
+ [9]: http://www.objc.io/issue-2-3/
+ [10]: https://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html
+ [11]: https://github.com/objcio/issue-2-background-core-data
+ [12]: http://stg.daten.berlin.de/datensaetze/vbb-fahrplan-2013
+ [13]: https://developers.google.com/transit/gtfs/reference
+ [14]: http://stackoverflow.com/questions/3707427/how-to-read-data-from-nsfilehandle-line-by-line/3711079#3711079
+ [15]: http://floriankugler.com/blog/2013/4/29/concurrent-core-data-stack-performance-shootout
+ [16]: http://floriankugler.com/blog/2013/5/24/layer-trees-vs-flat-drawing-graphics-performance-across-ios-device-generations
+ [17]: https://lobste.rs/s/ckm4uw/a_performance-minded_take_on_ios_design/comments/itdkfh
+ [18]: https://developer.apple.com/videos/wwdc/2012/
+ [19]: http://afnetworking.com/
+ [20]: https://github.com/objcio/issue-2-background-networking
+ [21]: http://developer.apple.com/library/ios/#documentation/FileManagement/Conceptual/FileSystemProgrammingGUide/TechniquesforReadingandWritingCustomFiles/TechniquesforReadingandWritingCustomFiles.html
+ [22]: https://github.com/objcio/issue-2-background-file-io
+
+原文 [Common Background Practices](http://www.objc.io/issue-2/common-background-practices.html)
+
+译文 [iOS开发中一些常见的并行处理](http://blog.jobbole.com/52557/)
diff --git a/_posts/2014-05-09-kiwi-mock-stub-test.markdown b/_posts/2014-05-09-kiwi-mock-stub-test.markdown
new file mode 100644
index 00000000..71e2459e
--- /dev/null
+++ b/_posts/2014-05-09-kiwi-mock-stub-test.markdown
@@ -0,0 +1,227 @@
+---
+layout: post
+title: Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试
+date: 2014-05-09 11:48:33.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+Kiwi 是 iOS 的一个行为驱动开发 (Behavior Driven Development, BDD) 的测试框架,我们在[上一篇入门介绍](http://onevcat.com/2014/02/ios-test-with-kiwi/)中简单了解了一些 iOS 中测试的概念以及 Kiwi 的基本用法。其实 Kiwi 的强大远不止如此,它不仅包含了基本的期望和断言,也集成了一些相对高级的测试方法。在本篇中我们将在之前的基础上,来看看 Kiwi 的这些相对高级的用法,包括模拟对象 (mock),桩程序 (stub),参数捕获和异步测试等内容。这些方法都是在测试中会经常用到的,用来减少我们测试的难度的手段,特别是在耦合复杂的情况下的测试以及对于 UI 事件的测试。
+
+## Stub 和 Mock 的基本概念
+
+如果您曾经有过为代码编写测试的经验,您一定会知道其中不易。我们编写生产代码让它能够工作其实并不很难,项目中编码方面的工作难点往往在于框架搭建以及随着项目发展如何保持代码优雅可读可维护。而测试相比起业务代码的编写一般来说会更难一些,很多时候你会发现有些代码是“无法测试”的,因为代码之间存在较高的耦合程度,因此绕不开对于其他类的依赖,来对某个类单独测试其正确性。我们不能依赖于一个没有经过测试的类来对另一个需要测试的类进行测试,如果这么做了,我们便无法确定测试的结果是否正是按我们的需要得到的(不能排除测试成功,但是其实是因为未测试的依赖类恰好失败了而恰巧得到的正确结果的可能性)。
+
+### Stub
+
+解决的方法之一是我们用一种最简单的语言来“描述”那些依赖类的行为,而避免对它们进行具体实现,这样就能最大限度地避免出错。比如我们有一个复杂的算法通过输入的温度和湿度来预测明天的天气,现在我们在存储类中暴露了一个方法,它接受输入的温度和湿度,通过之前复杂算法的计算后将结果写入到数据库中。相关的代码大概是下面这个样子,假设我们有个 `WeatherRecorder` 类来做这件事:
+
+```
+//WeatherRecorder.m
+-(void) writeResultToDatabaseWithTemprature:(NSInteger)temprature
+ humidity:(NSInteger)humidity
+{
+ id result = [self.weatherForecaster resultWithTemprature:temprature humidity:humidity];
+ [self write:result];
+}
+```
+
+(虽然这个例子设计得不太好,因为服务层架构不对,但是其实) 在实际项目中是可能会有不少类似的代码。对于这样的方法和相应的 `WeatherRecorder` 应该如何测试呢?这个方法依赖了 `weatherForecaster` 的计算方法,而我们这里关心的更多的是 write 这个方法的正确性 (算法的测试应该被分开写在对应的测试中),对于计算的细节和结果我们其实并不关心。但是这个方法本身和算法耦合在了一起,我们当然可以说直接给若干组输入,运行这个方法然后检测数据库中的结果是否与我们预期的一致,但是这其实做了假设,那就是:在测试中我们自己的计算结果和预报计算方法的结果是一致的。这个假设可能在一开始是成立的,但是你无法知道在之后的开发中这个算法会不会改变,会变成怎样。也许之后有修正模型出现,结果和现在大相径庭,这时就会出现 write 数据库的测试居然因为预报的算法变更而失败。这不仅使得测试涵盖了它不应该包括的内容,违背了测试的单一性,也凭添了不少麻烦。
+
+一个完美的解决的方案是,我们人为地来指定计算的结果,然后测试数据库的写入操作。人为地让一个对象对某个方法返回我们事先规定好的值,这就叫做 `stub`。
+
+在 Kiwi 中写一个 stub 非常简单,比如我们有一个 `Person` 类的实例,我们想要 stub 让它返回一个固定的名字,可以这么写:
+
+```
+Person *person = [Person somePerson];
+[person stub:@selector(name) andReturn:@“Tom”];
+```
+
+在这个 stub 下,如下测试将会通过,而不论 person 到底具体是谁:
+
+```
+NSString *testName = [person name];
+[ testName should] equal:@“Tom”];
+```
+
+另外,对于我们之前天气预报例子中的带有参数的方法,我们可以使用 Kiwi stub 的带参数版本来进行替换,比如:
+
+```
+[weatherForecaster stub:@selector(resultWithTemprature:humidity:)
+ andReturn:someResult
+ withArguments:theValue(23),theValue(50)];
+```
+
+这时我们再给 `weatherForecaster` 发送参数为温度 `23` 和湿度 `50` 的消息时,方法会直接将 `someResult` 返回给我们,这样我们就可以不再依赖于天气预报算法的具体实现,也不用担心算法变更会破坏测试,而对数据库写入进行稳定的测试了。
+
+对于 Kiwi 的 stub,需要注意的是它不是永久有效的,在每个 `it` block 的结尾 stub 都会被清空,超出范围的方法调用将不会被 stub 截取到。
+
+### Mock
+
+`mock` 是一个非常容易和 `stub` 混淆的概念。简单来说,我们可以将 `mock` 看做是一种更全面和更智能的 `stub`。
+
+首先解释全面,我们需要明确,mock 其实就是一个对象,它是对现有类的行为一种模拟(或是对现有接口实现的模拟)。在 objc 的 OOP 中,类或者接口就是指导对象行为的蓝图,而 mock 则遵循这些蓝图并模拟它们的实例对象。从这方面来说,mock 与 stub 最大的区别在于 stub 只是简单的方法替换,而不涉及新的对象,被 stub 的对象可以是业务代码中真正的对象。而 mock 行为本身产生新的(不可能在业务代码中出现的)对象,并遵循类的定义相应某些方法。
+
+其次是更智能。基础上来说,和 stub 很相似,我们可以为创造的 mock 定义在某种输入和方法调用下的输出,更进一步,我们还可以为 mock 设定期望 (准确来说,是我们一定会为 mock 设定期望,这也是 mock 最常见的用例)。即,我们可以为一个 mock 指定这样的期望:“这个 mock **应该收到以 X 为参数的 Y 方法**,并规定它的返回为 Z”。其中"应该收到以 X 为参数的 Y 方法"这个期望会在测试与其不符合时让你的测试失败,而“返回 Z” 这个描述行为更接近于一种 stub 的定义。XCTest 框架想要实现这样的测试例可以说要费九牛之力,但是这在 Kiwi 里却十分自然。
+
+
+
+还是举上面的天气预报的例子。我们在 stub 时将 `weatherForecaster` 的方法替换处理了。细心的读者可能会有疑惑,问这个 `weatherForecaster` 是怎么来的。因为这个对象其实只是 `WeatherRecorder` 中一个属性,而且很有可能在测试时我们并不能拥有一个恰好合适的 `weatherForecaster`。`WeatherRecorder` 是不需要将 `weatherForecaster` 暴露在头文件中的,VC 是不需要知道它的实现细节的),而我们在上面的 stub 的前提是我们能在测试代码中拿到这个 `weatherForecaster`,很多时候只能修改代码将其暴露,但是这并不是好的实践,很多时候也并不现实。现在有了 mock 后,我们就可以自创一个虚拟的 `weatherForecaster`,并为其设定期望的调用来确保我们输入温度和湿度确实经过了计算然后存入了数据库中了。mock 所使用的期望和普通对象的调用期望类似:
+
+```
+id weatherForecasterMock = [WeatherForecaster mock];
+[[weatherForecasterMock should] receive:@selector(resultWithTemprature:humidity:)
+ andReturn:someResult
+ withArguments:theValue(23),theValue(50)];
+
+```
+
+然后,对于要测试的 `weatherRecorder` 实例,用 stub 将 -weatherForecaster 的返回换为我们的 mock:
+
+```
+[weatherRecorder stub:@selector(weatherForecaster) andReturn:weatherForecasterMock];
+```
+
+这样一来,在 `-writeResultToDatabaseWithTemprature:humidity:` 中我们就可以使用一个 mock 的 `weatherForecaster` 来完成工作,并检验是否确实进行了预报了。类似的组合用法在 mock/stub 测试中是比较常见的,在本文最后的例子中我们会再次见到类似的用法。
+
+## 参数捕获
+
+有时候我们会对 mock 对象的输入参数感兴趣,比如期望某个参数符合一定要求,但是对于 mock 而言一般我们是通过调用别的方法来验证 mock 是否被调用的,所以很可能无法拿到传给 mock 对象的参数。这种情况下我们就可以使用参数捕获来获取输入的参数。比如对于上面的 `weatherForecasterMock`,如果我们想捕获温度参数,可以在调用测试前使用
+
+```
+KWCaptureSpy *spy = [weatherForecasterMock captureArgument:@selector(resultWithTemprature:humidity:) atIndex:0];
+```
+
+来加一个参数捕获。这样,当我们在测试中使用 stub 将 `weatherForecaster` 替换为我们的 mock 后,再进行如下调用
+
+```
+[weatherRecorder writeResultToDatabaseWithTemprature:23 humidity:50]
+```
+
+后,我们可以通过访问 `spy.argument` 来拿到实际输入 `resultWithTemprature:humidity:` 的第一个参数。
+
+在这个例子中似乎不太有用,因为我们输入给 `-writeResultToDatabaseWithTemprature:humidity:` 的参数和 `-resultWithTemprature:humidity:` 的是一样的。但是在某些情况下确实会很有效果,我们会在之后看到一个实际的使用例。
+
+## 异步测试
+
+异步测试是为了对后台线程的结果进行期望检验时所需要的,Kiwi 可以对某个对象的未来的状况书写期望,并进行检验。通过将要检验的对象加上 `expectFutureValue`,然后使用 `shouldEventually` 即可。就像这样:
+
+```
+[[expectFutureValue(myObject) shouldEventually] beNonNil];
+
+[[expectFutureValue(theValue(myBool)) shouldEventually] beYes];
+```
+
+比如在 REST 网络测试中,我们可能大部分情况下会选择用一组 mock 来替代服务器的返回进行验证,但是也不排除会有直接访问服务器进行测试的情况。在这种情况下我们就可以使用延时来进行异步测试。这里直接照抄一个官方 Wiki 的例子进行说明:
+
+```
+ context(@"Fetching service data", ^{
+ it(@"should receive data within one second", ^{
+
+ __block NSString *fetchedData = nil;
+
+ [[LRResty client] get:@"http://www.example.com" withBlock:^(LRRestyResponse* r) {
+ NSLog(@"That's it! %@", [r asString]);
+ fetchedData = [r asString];
+ }];
+ [[expectFutureValue(fetchedData) shouldEventually] beNonNil];
+ });
+
+ });
+```
+
+这个测试保证了返回的 `LRRestyResponse` 对象可以转为一个字符串并且不是 `nil`。
+
+其实没什么神奇的,就是生成了一个延时的验证,在一定时间间隔后再对观测的对象进行检查。这个时间间隔默认是 1 秒,如果你需要其他的时间间隔的话,可以使用 `shouldEventuallyBeforeTimingOutAfter` 版本:
+
+## 一个例子:测试 ViewController
+
+举个实际一点的例子吧,我们来看看平时觉得难以测试的 `UIViewController` 的部分,包括一个 `tableView` 和对应的 `dataSource` 和 `delegate` 的测试方法。我们使用了 objc.io 第一期中的 [Lighter View Controllers](http://www.objc.io/issue-1/lighter-view-controllers.html) 和 [Clean table view code](http://www.objc.io/issue-1/table-views.html) 中的代码来实现一个简单可测试的 VC 结构,然后使用 Kiwi 替换完成了 [Testing View Controllers](http://www.objc.io/issue-1/testing-view-controllers.html) 一文中的所有测试模块。这里篇幅有限,实现的具体细节就不在复述了,有兴趣的同学可以看看 objc.io 的这三篇文章,或者也可以在 [objc 中国](http://www.objccn.io) 上找到它们的译文:[更轻量的 View Controllers](http://objccn.io/issue-1-1/),[整洁的 Table View 代码](http://objccn.io/issue-1-2/)以及[测试 View Controllers](http://objccn.io/issue-1-3/)。
+
+我们在这里结合 Kiwi 的方法对重写的测试部分进行一些说明。[objc.io 原来的项目](https://github.com/objcio/issue-1-lighter-view-controllers)使用的是 [OCMock](http://ocmock.org) 实现的解耦测试,而为了进行说明,我用 Kiwi 简单重写了测试部分的代码,这个项目也可以[在 Github 上找到](https://github.com/onevcat/PhotoData_Kiwi)。
+
+对于 `ArchiveReading` 的测试都是 Kiwi 最基本的内容,在[上一篇文章中](http://onevcat.com/2014/02/ios-test-with-kiwi/)已经详细介绍过了;对于 `PhotoCell` 的测试形式上比较新颖,其实是一个对 xib 的测试,保证了 xib 的初始化和 outlet 连接的正确性,但是测试内容也比较基本。剩下的是对于 tableView 的 dataSource 和 viewController 的测试,我们来具体看看。
+
+### Data Source 的测试
+
+首先是 `ArrayDataSourceSpec`,得益于将 array 的 dataSource 进行抽象和封装,我们可以单独对其进行测试。基本思路是我们希望在为一个 tableView 设置好数据源后,tableView 可以正确地从数据源获取组织 UI 所需要的信息,基本上来说,也就是能够得到“有多少行”以及“每行的 cell 是什么”这两个问题的答案。到这里,有写过 iOS 的开发者应该都明白我们要测试的是什么了。没错,就是 `-tableView:numberOfRowsInSection:` 以及 `-tableView:cellForRowAtIndexPath:` 这两个接口的实现。
+
+测试用例关键代码如下:
+
+```
+TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){
+ configuredCell = a;
+ configuredObject = b;
+};
+ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"] cellIdentifier:@"foo" configureCellBlock:block];
+
+id mockTableView = [UITableView mock];
+UITableViewCell *cell = [[UITableViewCell alloc] init];
+
+it(@"should be 2 items", ^{
+ NSInteger count = [dataSource tableView:mockTableView numberOfRowsInSection:0];
+ [[theValue(count) should] equal:theValue(2)];
+});
+
+__block id result = nil;
+NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
+
+it(@"should receive cell request", ^{
+ [[mockTableView should] receive:@selector(dequeueReusableCellWithIdentifier:forIndexPath:) andReturn:cell withArguments:@"foo",indexPath];
+ result = [dataSource tableView:mockTableView cellForRowAtIndexPath:indexPath];
+});
+```
+
+为了简要说明,我改变了 repo 中的代码组织结构,不过意思是一样的。我们要测试的是 `ArrayDataSource` 类,因此我们生成一个实例对象。在测试中我们不希望测试依赖于 `UITableView`,因此我们 mock 了一个对象代替之。接下来向 dataSource 发送询问元素个数的方法,这里应该毫无疑问返回数组中的元素数量。接下来我们给 `mockTableView` 设定了一个期望,当将向这个 mock 的 tableView 请求 dequeu indexPath 为 (0,0) 的 cell 时,将直接返回我们预先生成的一个 cell,并进行接下来的处理。完成设定后,我们调用要测试的方法 `[dataSource tableView:mockTableView cellForRowAtIndexPath:indexPath]`。`dataSource` 在接到这个方法后,向 `mockTableView` 请求一个 cell(这个方法已经被 mock),接下来通过之前定义的 block 来对 cell 进行配置,最后返回并赋值给 `result`。于是,我们就得到了一个可以进行期望断言的 result,它应该和我们之前做的 cell 是同一个对象,并且经过了正确的配置。至此这个 dataSource 测试完毕。
+
+您当然还可以扩展这个 dataSource 并且为其添加对应的测试,但是对于这两个 `required` 方法的测试已经揭示了测试 Data Source 的基本方法。
+
+### ViewController 的测试
+
+ViewController 一般被认为是最难测试甚至不可测试的部分。而通过 objc.io 的抽离方式可以使 MVC 更加清晰,也让 ViewController 的代码简洁不少。保持良好的 MVC 结构,尽可能精简 ViewController,对其的测试还是有可能及有意义的。在 `PhotosViewControllerSpec` 里做了对 ViewConroller 的一个简单测试。我们模拟了 tableView 中对一个 cell 的点击,然后检查 `navigationController` 的 `push` 操作是否确实被调用,以及被 `push` 的对象是否是我们想要的下一个 ViewController。
+
+要测试的是 `PhotosViewController` 的实例,因此我们生成一个。对于它的 `UINavigationController`,因为其没有在导航栈中,也这不是我们要测试的对象(保持测试的单一性),所以用一个 mock 对象来代替。然后为其设定 `-pushViewController:animated:` 需要被调用的期望。然后再用输入参数捕获将被 push 的对象抓出来,进行判断。关键部分代码如下:
+
+```
+UINavigationController *mockNavController = [UINavigationController mock];
+[photosViewController stub:@selector(navigationController) andReturn:mockNavController];
+
+[[mockNavController should] receive:@selector(pushViewController:animated:)];
+KWCaptureSpy *spy = [mockNavController captureArgument:@selector(pushViewController:animated:) atIndex:0];
+[photosViewController tableView:photosViewController.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
+
+id obj = spy.argument;
+PhotoViewController *vc = obj;
+[[vc should] beKindOfClass:[PhotoViewController class]];
+[[vc.photo shouldNot] beNil];
+```
+
+在这里我们用 stub 替换了 `photosViewController` 的 `navigationController`,这个替换进去的 `UINavigationController` 的 mock 被期望响应 `-pushViewController:animated:`。于是在点击 tableView 的 cell 时,我们期望 push 一个新的 `PhotoViewController` 实例,这一点可以通过捕获 push 消息的参数来达成。大体的步骤和原理与之前天气预报的例子的最终版本很相似,在此就不再详细展开了。
+
+关于 mock 还有一点需要补充的是,使用 `+mock` 方法生成的 mock 对象对于期望收到的方法是严格判定的,就是说它能且只能响应那些你添加了期望或者 stub 的方法。比如只为一个 mock 设定了 `should receive selector(a)` 这样的期望,那么对这个 mock 发送一个消息 b 的话,将会抛出异常 (当然,如果你没有向其发送消息 a 的话,测试会失败)。如果你的 mock 还需要相应其他方法的话,可以使用 `+nullMock` 方法来生成一个可以接受任意预定消息而不会抛出异常的空 mock。
+
+## 总结
+
+花了两篇文章的篇幅介绍了 TDD 和 BDD,相信您已经有一定基本的了解了。在最近一年一来,测试的地位在 objc 社区中可以说是直线上升,不论是 Apple 官方的维护或者是开发者们的重视程度的提高。良好的代码习惯和良好的测试应该是相辅相成,良性循环的。
+
+最后对几个常见问题做一些总结:
+
+#### 我应不应该测试,要怎么做
+
+应该,即使在你的工作中没有要求。测试会让你的生活更美好轻松。你是愿意花 10 分钟完成对你代码的自动化测试,还是在 QA 找过来以后花一整天去找 bug,并且同时制造更多的 bug?即使你现在还完全不了解也不会编写测试,你也可以从最简单的 model 测试开始,抽离并封装逻辑部分,然后用 XCTest 做简单断言。先建立测试的概念,然后有意编写可测试代码,最终掌握测试方法。
+
+#### 需要使用 TDD 么
+
+建议使用。虽然看上去这有点疯狂,虽然一开始会有不适应,但是这确实是对思维检验的非常好的时机。在实际很爽地去一通乱写之前先做好整体设计,TDD 确实可以帮助提高项目结构和质量。开始的时候建议小粒度进行,可能开发效率会有一段低谷时期 (但是相较于代码质量的提高,这点付出还是很值得的),熟悉之后可以加大步伐,并且积累一套适合自己的测试风格,这时候你就会发现开发效率像坐火箭一般提升了。
+
+#### 需要使用 BDD 么
+
+可以考虑在有一定积累后使用。因为有些情况下 XCTest 确实略显苍白,有些测试实现起来也很繁芜。BDD 在一定程度上可以将测试的目的理得更清晰,当然,前提是你需要明确知道想测试的是什么,以及尽量保证测试的单一性和无耦合。另外虽然这两篇文章介绍的是 Kiwi,但是实际上我们在 objc 的 BDD 时还有不少其他选择,比如 [specta](https://github.com/specta/specta) 或者 [cedar](https://github.com/pivotal/cedar) 都是很好的框架。我个人喜欢 Kiwi 纯粹是因为和 Kiwi 作者和维护社区里的几位大大的个人关系,大家如果要实践 BDD,在选择的时候也可以进行一些对比,选择合适自己的。
+
+## 扩展阅读
+
+* [Test-Driven iOS Development](http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183) 我的 iOS TDD 入门书
+* [Kiwi 的 Wiki](https://github.com/kiwi-bdd/Kiwi/wiki) 关于 Kiwi 你所需要知道的一切
+* [Unit Testing - NSHipster](http://nshipster.com/unit-testing/)
+* [Test Driving iOS Development with Kiwi](https://itunes.apple.com/us/book/test-driving-ios-development/id502345143?mt=11) iBook的书,中国区不让卖书,所以可能需要非中国账号 (日本账号这本书只要 5 美金)
diff --git a/_posts/2014-05-30-jin-qi-sui-xiang-he-wwdc-de-ji-hua.markdown b/_posts/2014-05-30-jin-qi-sui-xiang-he-wwdc-de-ji-hua.markdown
new file mode 100644
index 00000000..a877f835
--- /dev/null
+++ b/_posts/2014-05-30-jin-qi-sui-xiang-he-wwdc-de-ji-hua.markdown
@@ -0,0 +1,29 @@
+---
+layout: post
+title: 近期随想和 WWDC 的计划
+date: 2014-05-30 01:21:18.000000000 +09:00
+tags: 南箕北斗集
+---
+最近的博文总是写技术,本来其实是打算将这里建设成技术成长与人文关怀并重的博客的,但是现在看来思考不足。在刚被每周七天每天 18 小时的魔鬼般的封闭开发连续虐待了三周之后,我基本达到了看一眼代码就想吐的地步。每天让我坚持下来的动力可能只剩 “过完这周就可以参加的 WWDC” 这一件事情了。于是觉得,现在是时候可以写一点技术无关的博文来舒缓舒缓心情了。
+
+其实在封闭开发期间发生了不少事情,整理在一起看来,还是颇为值得思考的。
+
+首先是经历了一件很不幸的事情,我的一个非常优秀的大学同学,也是家内从高中开始的持续了十年友情的闺蜜,因为一次意外事故遇难,比我们提前了不少和这个世界道别了。一直说生命是顽强的,而我们中的绝大多数也确实是从出生开始就学着去与命运抗争。只有不断砥砺磨练,才能活出绚丽光彩的人生,这样的信念一直激励着我。但是真正当前一天还活力无限、分享生活点滴的人,第二天却只有噩耗传来,与世长辞的时候,这般无情的事实才会告诉我们,生命之脆弱远远超乎想象。虽说天下并无不散的宴席,但是风云之间,意外事故的一瞬就能让至亲至爱的人永远分离。之前无论怎样的理想抱负,亦或是雄心壮志,也就在这倏尔之间戛然而止,便再无法高歌。
+
+生命的易逝总会给人带来很多感慨,悲欢离合,阴晴圆缺,唯有惋惜,唯有叹息。
+
+接下来一件事是锤子手机的发布。对于罗永浩先生的演说(或者说是讲课),我是第一次听。之前一直听说会是一场精彩的单口相声,全场听下来(因为还要干活,所以只能听不能看),也确实是一场精彩的单口相声。当年乔帮主做演讲或者发布也鲜有过一个人讲全场的情况,而这位罗老师尽显教师风范,直接霸占讲台接近三个小时,是能说佩服了。不管如何,在这个发布会上还是有不少亮点的,虽然并没有什么革命性的东西(用这个来要求一个刚起步没太多积累的企业也确实过为苛刻了),但是在一些细节上的打磨和小的创新还是着实让人感动的。当下中国所缺少的,其实正是这样静下心来,打造一款作品的工匠精神。锤子手机的这一点,深得我心。这是一个很不错的开始,我也很期待接下来的故事,不仅是锤子手机的故事,更是整个中国制造的故事,会怎样展开。
+
+在演讲中罗先生为了说明手机系统和软件的重要性,引用了乔帮主对于日本电子设备为什么被美国大幅超越的解释,说是因为日本的软件行业水平不行。因为演讲中并没有对这句话进行出处的标注,乔帮主语录我又因为资质拙劣没有背完全,所以不知道一向亲日的帮主是不是真的说过这句话。本着对于没有出处的引用绝不相信的原则,我对这句话的真实程度持严重怀疑态度;但是,作为一个在日本工作了小两年的海漂的角度来看,我对这句话的内容双手赞成。不管是在自社工作中还是各种交流活动里,确实没有能够见到特别出彩的软件和技术。而日本开发者似乎都比较喜欢埋头苦干,不太擅长于向社区寻求帮助和进步,日益严重的孤岛效应和对与英文资料的心理抵触,使得有时候确实觉得日本的技术现状还真挺尴尬的。
+
+抛开信息技术上的具体实现不说,日本的设计或者说工业设计其实还是世界领先的。不论是建筑业对于一砖一瓦的考究,还是对食物或者衣装从用料和做法上的审慎,其实都还是挺让人赞叹的。但是有一件事情其实很让人搞不懂:拥有如此强力的设计的日本,为什么 Web 网站都做的仿佛是上个世纪的样式呢?要么是 [Yahoo Japan](http://www.yahoo.co.jp) 这种让人抓狂的铺满链接毫无美感的版面设计,要么是 [Rakuten](http://www.rakuten.co.jp) 这种让人绝望的奇葩配色全是贴纸的无重点页面。而在手机应用和游戏界面设计上也是如此,似乎他们永远希望所有的信息在同一个屏幕上展示,而不去考究信息的重要程度和挖掘更好的用户体验。在当前的软件行业中,这样的行为显然是悖逆潮流,所以被美国超过也不足为奇了。现在看来,甚至有被中国超过的趋势(如果把像 360 和助手管家之类的东西从中国软件中刨除的话)。
+
+其实这个问题的原因我想过不止一次,深层次来说是一个日本社会和中国社会的消费上的巨大区别的社会问题。在这边大叔大婶们一定都是消费主力,而小年轻因为工资低又要租房要腐败,基本每个月很难结余。所以更多的时候创造者会对更主要的目标客户做出妥协,而现状大概也就是妥协的产物了吧。
+
+其实昨天和同事一起闲扯的时候有了个新的发现,从另一个角度看,他们也许也有他们的苦衷。比如你可以试试看用 WinXP 和 IE6 打开上面提到的两个网站,相信一个像素不差的完美排版和不逊于在高端先进浏览器上的效果,会给你带来意外的惊喜。
+
+最后还是回归一下开发的话题吧。我想可能现在会关注我这个博客的朋友大部分都是 iOS 开发的爱好者或者从业者。一年一度,举世瞩目的 WWDC 将在下周进行。这次很幸运,被我抽到了一张门票,并且我顺利地办妥了各种手续,不出意外的话应该是可以成行,前往美国参加这次会议。可能一直看我的博客的朋友都知道,之前两年我都是通过一边对照 session 学习,一边结合自己的理解整理出一份笔记的方式来进行练习和巩固的。今年如果时间允许的话,应该也不会例外。但是和往常一样,因为有 NDA 的限制,我会先不将这些笔记公开进行发表,而只是整理在自己的仓库中。在 NDA 解除之前,也不会用来公开讨论和传播。所以有心的想在第一时间看到的朋友,到时候可以关注并找找我的 repo。
+
+预先报告一下之后的行程吧,我将在后天晚上飞三藩市,然后先拜会一下在当地发财的同学们。之后会在周一的 KeyNote 上用[微博](http://weibo.com/onevcat/)的方式给大家带来一些现场的消息。因为 Apple 也会有直播,所以可能会更侧重于以我自己的视角来以一个普通开发者的身份体验 WWDC 这样一个盛会。接下来肯定是会挑一些自己感兴趣的或者在新系统中举足轻重的 session 和 lab 来参加,并且整理笔记,虽然这可能是后话。另外,可能大家会不太知道的是,因为这会是一个全球 Apple 开发者聚集的时间段,所以在 WWDC 举办的同时,也会有非常多的第三方组织的 event 或者 meeting。我也会挑选其中几个参加,并且有计划在这些活动上和像 [@mattt](https://twitter.com/mattt),[@rwenderlich](https://twitter.com/rwenderlich) 以及 [@danielboedewadt](https://twitter.com/danielboedewadt) 这样的顶级开发者~~进行一些技术探讨和交流~~见面要签名和合照的计划,希望能够顺利。
+
+另外,这个博客现在采用了 [CollaMark](http://collamark.com/#/) 的还在开发中的笔记 API,你可以通过选中一段文字,然后点击弹出来的 C 的按钮来添加一段笔记。你可以设定这段笔记是只有你自己可见还是别人也能看。大家不妨可以尝试下作为一种新的和其他读者分享和交流的手段,我觉得很有意思。这是公司里一个中国同事 [@sunderls](http://weibo.com/sunderls) 业余时间做的项目,现在还在测试阶段,可能刷新会有一点点问题(如果没出来的话可能需要清页面缓存什么的),不过还是欢迎大家注册捧场 :)
diff --git a/_posts/2014-06-03-my-opinion-about-swift.markdown b/_posts/2014-06-03-my-opinion-about-swift.markdown
new file mode 100644
index 00000000..efb7d755
--- /dev/null
+++ b/_posts/2014-06-03-my-opinion-about-swift.markdown
@@ -0,0 +1,41 @@
+---
+layout: post
+title: 关于 Swift 的一点初步看法
+date: 2014-06-03 12:05:44.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+虽然四点半就起床去排队等入场,结果还是只能坐在了蛮后面的位置看着大屏幕参加了今年的 Keynote。其实今年 OS X 和 iOS 的更新亮点都不少,但是显然风头和光芒都让横空出世的 Swift 给抢走了。这部分内容因为不是 NDA,所以可以提前说一说。
+
+Swift 是 Apple 自创的一门专门为 Cocoa 和 CocoaTouch 设计的语言,意在用来替代 objc。早上发布的时候有很多朋友说其实他们已经写了很久的 Swift,而且还给了一个[网站](http://swift-lang.org),在这里首先需要说明的是,这个网站的 *Swift parallel scripting language* 和 Apple 的 [Swift](https://developer.apple.com/swift/) 并不是一个东西,两者可以说毫无关系。Apple 还在自己的 Swift 介绍页面后面很友好地放上了 Swift parallel scripting language 的网站链接,以提示那些真的想搜另一个 Swift 却被 SEO 误导过来的可怜的孩子。
+
+我个人来说,在把玩了 Swift 几个小时之后,深深地喜欢上了这门新的语言。这篇文章以一个初学者(其实现在大家都是初学者)的角度来对 Swift 做一个简单的介绍,因为现在大家其实是在同一个起跑线上,所以理解上可能会有很多不精确的地方,出错了也请大家轻喷指正!
+
+## 什么是 Swift
+
+很多人在看到 Swift 第一眼的感觉是,这丫是个脚本语言啊。因为在很多语法特性上 Swift 确实和一些脚本非常相似。但是首先需要明确的是,至少在 Apple 开发中,Swift 不是以一种脚本语言来运行的,所有的 Swift 代码都将被 LLVM 编译为 native code,以极高的效率运行。按照官方今天给出的 benchmark 数据,运行时比 Python 快 3.9 倍,比 objc 快 1.4 倍左右。我相信官方数据肯定是有些水分,但是即使这样,Swift 也给人带来很多遐想和期待。Swift 和原来的 objc 一样,是类型安全的语言,变量和方法都有明确的返回,并且变量在使用前需要进行初始化。而在语法方面,Swift 迁移到了业界公认的非常先进的语法体系,其中包含了闭包,多返回,泛型和大量的函数式编程的理念,函数也终于成为一等公民可以作为变量保存了(虽然具体实现和用法上来看和 js 那种传统意义的好像不太一样)。初步看下来语法上借鉴了很多 Ruby 的人性化的设计,但是借助于 Apple 自己手中 强大的 LLVM,性能上必须要甩开 Ruby 不止一两个量级。
+
+另一方面,Swift 的代码又是可以 Interactive 来“解释”执行的。新的 Xcode 中加入了所谓的 Playground 来对开发者输入的 Swift 代码进行交互式的相应,开发者也可是使用 swift 的命令行工具来交互式地执行 swift 语句。细心的朋友可能注意到了,我在这里把“解释”两个字打上了双引号。这是因为即使在命令行中, Swift 其实也不是被解释执行的,而是在每个指令后进对从开始以来的 swift 代码行了一遍编译,然后执行的。这样的做法下依然可以让人“感到”是在做交互解释执行,这门语言的编译速度和优化水平,可见一斑。同时 Playground 还顺便记录了每条语句的执行时候的各种情况,叫做一组 timeline。可以使用 timeline 对代码的执行逐步检查,省去了断点 debug 的时间,也非常方便。
+
+至于更详细的比如 Swift 的语法之类的,可以参见 Apple 在 iBooks 放出的 [The Swift Programming Language](https://itunes.apple.com/us/book/the-swift-programming-language/id881256329?mt=11),或者你是开发者的话,也可以看看 pre-release 的[参考文档](https://developer.apple.com/library/ios/welcome_to_swift)
+
+## Cool,我可以现在就使用 Swift 么?
+
+Swift 作为 Apple 钦定的 objc 的继承者,作为 iOS/Mac 开发者的话,是觉得必须和值得学习和使用的。现在 Swift 可以和原来的 objc 或者 c 系的代码混用(注意,不同于 objc 和 c++ 或者 c 在同一个 .mm 文件中的混编,swift 文件不能和 objc 代码写在同一个文件中,你需要将两种代码分开)。编译出来的二进制文件是可以运行在 iOS 7 和 iOS 8 的设备上的(iOS 6 及之前的是不支持的)。虽然我没有尝试过,但是使用新的 clang 对 swift 进行编译的 app 二进制包,只要你的 target 是 iOS 7 及以上的话,应该现在就可以往 App Store 进行提交。
+
+一个很好的消息是 Xcode 6 中应该是所有的文档都有 objc 和 swift 两种语言版本了,所以在文档支持上应该不是问题。而按照 Apple 开发者社区的一贯的跟进速度,有理由相信在不久的将来,Apple 很可能会果断 drop 掉 objc 的支持,而全面转向 swift。所以,关于标题里的这个问题的答案,我个人的建议是,尽快学习,尽快开始使用。如果你有一定的脚本语言的基础(Ruby 最好,Python 或者 JS 什么的也很不错),又比较了解 Cocoa 框架的思想的话,转型到新的语言应该完全不是问题。你会发现以前很多 objc 实现起来很郁闷的事情,在新语言下都易如反掌。我毫不忌讳地说,在 Apple 无数工程师和语言设计天才的努力下,Swift 吸收了众多语言的精华,应该是现在这个世界上最新(这不是废话么),也是最先进的一门编程语言(之一)了。而我认为,也正是 Apple 对这门语言有这样的自信,才会在这么一个可以说公司还在全盛的时候,不守陈规、如此大胆地进行语言的更换。因为 Apple 必定比你我都精于算计,切换语言带来的利益必须远大于弊端,才会值得冒如此大的风险。在这个意义上来说,今天的发布会就是程序开发业界的一枚重磅炸弹,也必将写入史册,而你我其实真的身在其中,变成了这段历史的见证者。
+
+## 如何开始?
+
+很简单,虽然历年的 WWDC 都在 NDA 的控制之下使得我们无法讨论过多的内容,但是这次的 Swift 破天荒地是在 NDA 之外的内容。Apple 已经放出了足够多的资源让我们开始学习。首先是官方的 Swift 的[介绍页面](https://developer.apple.com/swift/),你可以了解一些 Swift 的基本特性和细节。然后就是从 iBooks 下载 [Swift 的书籍](https://itunes.apple.com/us/book/the-swift-programming-language/id881256329?mt=11)。你可以不必通读全书,而只需要快速浏览一下 35 页之前的 Tour 部分的内容,就可以开始将其运用到开发中了。因为不受 NDA 限制,所以 StackOverflow 的 [swift 标签](http://stackoverflow.com/questions/tagged/swift-language)和 [Google 上](https://www.google.com/#q=swift)应该会马上充斥满相关的问题和内容。及时跟进,相信和其他开发者一同从零开始学习和进步,你会很快上手并熟练使用 Swift 进行开发。
+
+(因为真的,太好用了。你很难想象我在写一个漂亮的闭包或者嵌套函数或者多返回时,那种内心的激动和喜悦...)
+
+## 总结
+
+这次的 WWDC 可以说是 Apple 之前几年布局的一个汇总和爆发。从一开始的 Mac 整合电话和短信,以及无处不在的 Handoff,到后面的通知中心 widget 和系统 framework 的 extension,以及更甚的 Family Share 等等,可以说 Apple 通过自己对产业链的控制和生态圈的完善,让 iDevice 或者 Mac 的用户粘度得到了前所未有的加强。对一个人来说,可能一台苹果设备之后他会很容易购买第二台第三台;对于一家人来说,可能一个成员拥有苹果设备之后,其他人也会被宣传和便捷带动。这是一手妙招,也是 Apple 最近几年一直在做的趋势。
+
+罗马其实不是一天建成的,在开发语言方面,Apple 其实也精心打造了很多年。在语言而言,之前完全没有这方面经验的苹果,毅然决然地选择离开 GCC 阵营,另起炉灶自己弄 Clang 和 LLVM 的布局,而终于在几年来对 objc 小修小补之后来了一次革命性的爆发。在日进万金的大好时候,抛弃一个成熟开发社区,而转向一种新的编程语言,做出这种决策,只能说这家公司的魄力让人折服和钦佩。另一方面,Apple 这么做的另一个理由应该是吸引更多的开发者加入到 Apple 开发阵营,因为相对于 objc 的语法和学习曲线,Swift 显然要容易很多,对于其他阵营的开发者,这也会是一个很好的入场机会。正应了这次 WWDC 的宣传语,Apple 已经为我们提供了更好的工具,我们有什么理由不继续我们的征途,实现我们的梦想呢?
+
+**Write the code. Change the world.**
diff --git a/_posts/2014-06-07-walk-in-swift.markdown b/_posts/2014-06-07-walk-in-swift.markdown
new file mode 100644
index 00000000..f8e59163
--- /dev/null
+++ b/_posts/2014-06-07-walk-in-swift.markdown
@@ -0,0 +1,494 @@
+---
+layout: post
+title: 行走于 Swift 的世界中
+date: 2014-06-07 18:23:44.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+> 2014 年 7 月 13 日更新:根据 beta 3 的情况修正了文中过时的部分
+
+从周一 Swift 正式公布,到现在周五,这几天其实基本一直在关注和摸索 Swift 了。对于一门新语言来说,开荒阶段的探索自然是激动人心的,但是很多时候资料的缺失和细节的隐藏也让人着实苦恼。这一周,特别是最近几天的感受是,Swift 并不像我[上一篇表达自己初步看法的文章](http://onevcat.com/2014/06/my-opinion-about-swift/)里所说的那样,相对于 objc 来说有更好的学习曲线。甚至可以说 objc 在除了语法上比较特别以外,其概念还是比较容易的。而 Swift 在漂亮的语法之后其实隐藏了很多细节和实现,而如果无法理解这些细节和实现,就很难明白这门新语言在设计上的考虑。在实际编码中,也会有各种各样的为什么编译不通过,为什么运行时出错这样的问题。本文意在总结一下这几天看 Swift 时候遇到的自己觉得重要的一些概念,并重新整理一些对这门语言的想法。可能有些内容是需要您了解 Swift 的基本概念的,所以这并不是一篇教你怎么写 Swift 或者入门的文章,建议您先读读 Apple 官方给出的 Swift 的[电子书](https://itunes.apple.com/us/book/the-swift-programming-language/id881256329?mt=11),至少将第一章的 Tour 部分读完(这里也有质量很不错的但是暂时还没有完全翻译完成的[中文版本](http://numbbbbb.github.io/the-swift-programming-language-in-chinese/))。不过因为自己也才接触一周不到,肯定说不上深入,还希望大家一起探讨。
+
+### 类型?什么是类型?
+
+这是一个基础的问题,类型 (Types) 在 Swift 中是非常重要的概念,在 Swift 中类型是用来描述和定义一组数据的有效值,以及指导它们如何进行操作的一个蓝图。这个概念和其他编程语言中“类”的概念很相似。Swift 的类型分为命名类型和复合类型两种;命名类型比较简单,就是我们日常用的 `类 (class)`,`结构体 (struct)`,`枚举 (enum)` 以及`接口 (protocol)`。在 Swift 中,这四种命名类型为我们定义了所有的基本结构,它们都可以有自己的成员变量和方法,这和其他一般的语言是不太一样的(比如很少有语言的enum可以有方法,protocol可以有变量)。另外一种类型是复合类型,包括`函数 (func)` 和 `多元组 (tuple)`。它们在使用的时候不会被命名,而是由 Swift 内部自己定义。
+
+我们在实际做开发时,一般会接触很多的命名类型。在 Swift 的世界中,一切看得到的东西,都一定属于某一种类型。在 PlayGround 或者是项目中,通过在某个实际的被命名的类型上 `Cmd + 单击`,我们就能看到它的定义。比如在 Swift 世界中的所有基本型 `Int`,`String`,`Array`,`Dictionay` 等等,其实它们都是结构体。而这些基本类型通过定义本身,以及众多的 `extension`,实现了很多接口,共同提供了基本功能。这也正是 Swift 的类型的一种很常见的组织方式。
+
+而相对的,Cocoa 框架中的类,基本都被映射为了 Swift 的 `class`。如果你有比较深厚的 objc 功底的话,应该会听说过 objc 的类其实是一组包含了元数据 (metadata) 的结构体,而在 objc 中我们可以使用 `+class` 来拿到某个 Class 的 isa,从而确定类的组成和描述。而在 Swift 的 native 层面上,在 type safe 的基础上,不再需要 isa 来指导对象如何构建,而这个过程会通过确定的命名类型完成。正因为这个原因,Swift 中干脆把 NSObject 的 `class` 方法都拿掉,因为 Swift 和 ObjC 在这个根本问题上的分歧,最终导致了在使用 Swift 调用 Cocoa 框架时的各种麻烦和问题。
+
+### 参照和值,Array和Dictionary背后的一些故事
+
+> 2014 年 7 月 13 日更新
+> 由于 beta 3 中 `Array` 被完全重写,这一节关于 `Array` 的一些行为和表述完全过时了。
+> 关于 `Array` 的用法现在简化了很多,请参见新加的 “真 参照和值,Array和Dictionary背后的一些故事”
+
+如果你坚持看到了这里,那么恭喜你...本文最无趣和枯燥的部分已经结束了(同时也应该吓走了不少抱着玩玩看的心态来看待 Swift 的读者吧..笑),那么开始说一些细节的东西吧。
+
+首先要明白的概念是,参照和值。在 C 系语言里摸爬滚打过的同学都知道,我们在调用一个函数的时候,往里传的参数有两种可能。一种是传递类似一个数字或者结构体这样的基本元素,这时候这个整数的值会被在内存中复制一份然后传到函数内部;另一种情况是传递一个对象,为了性能和内存上的考虑,这时候一般不会去将对象的内容复制一遍,而是会传递的一个指向同一块内存的指针。
+
+在 Swift 中一个与其他语言都不太一样的地方是,它的 Collection 类型,也就是 `Array` 和 `Dictionary`,并不是 `class` 类型,而是 `struct` 结构体。那么按照我们以往的经验,在传值或者赋值的时候应该是会复制一份。我们来试试看是不是这样的~
+
+```swift
+var dic = [0:0, 1:0, 2:0]
+var newDic = dic
+//Check dic and newDic
+dic[0] = 1
+dic //[0: 1, 1: 0, 2: 0]
+newDic //[0: 0, 1: 0, 2: 0]
+
+var arr = [0,0,0]
+var newArr = arr
+arr[0] = 1
+//Check arr and newArr
+arr //[1, 0, 0]
+newArr //[1, 0, 0]
+```
+
+`Dictionary` 的值没有问题,我们改变了 `dic` 中的值,但是 `newDic` 保持了原来的值,说明 `newDic` 确实被复制了一份。而当我们检查到 `Array` 的时候,发生了一点神奇的事情。虽然 `Array` 是 `struct`,但是当我们改变 `arr` 时,新的 `newArr` 也发生了改变,也就是说,`arr` 和 `newArr` 其实是同一个参照。这里的原因其实在 Apple 的[官方文档](https://developer.apple.com/library/prerelease/ios/documentation/swift/conceptual/swift_programming_language/ClassesAndStructures.html)中有一些说明。Swift 考虑到实际使用的情景,对 `Array` 做了特殊的处理。除非需要(比如 `Array` 的大小发生改变,或者显式地要求进行复制),否则 `Array` 在传递的时候会使用参照。
+
+在这里如果你想要只改变 `arr` 的值,而保持新赋予的 `newArr` 不变的话,你需要显式地对 `arr` 进行 `copy()`,像下面这样。
+
+```swift
+var arr = [0,0,0]
+var copiedArr = arr.copy()
+
+arr[0] = 1
+arr //[1, 0, 0]
+copiedArr //[0, 0, 0]
+```
+
+这时候 `arr` 和 `copiedArr` 将指向不同的内存地址,对原来的数组重新赋值的时候,就不会再影响新的数组了。另一种等效的做法是通过 `Array` 的初始化方法建立一个新的 `Array`:
+
+
+```swift
+var arr = [0,0,0]
+var newArr = Array(arr)
+
+arr[0] = 1
+arr //[1, 0, 0]
+newArr //[0, 0, 0]
+```
+
+值得一提的是,对于 `Array` 这个 `struct` 的这种特殊行为,Apple 还准备了另一个函数 `unshare()` 给我们使用。`unshare()` 的作用是如果对象数组不是唯一参照,则复制一份,并将作用的参照指向新的地址(这样它就变成唯一参照,不会意外改变原来的别的同样的参照了);而如果这个参照已经是唯一参照了的话,就什么都不做。
+
+```swift
+var arr = [0,0,0]
+var newArr = arr
+
+//Breakpoint 1
+arr.unshare()
+
+//Breakpoint 2
+arr[0] = 1
+arr //[1, 0, 0]
+newArr //[0, 0, 0]
+```
+
+这个设计的意图是为了更安全地使用这个优化过的行为奇怪的数组结构体。关于 `unshare()` 的行为,我们也可以通过使用 LLDB 断点来观察内存地址的变化。参见下图:
+
+
+
+另外一个要加以注意的是,`Array` 在 copy 时执行的不是深拷贝,所以 `Array` 中的参照类型在拷贝之后仍然会是参照。Array 中嵌套 Array 的情况亦是如此:对一个 Array 进行的 copy 只会将被拷贝的 `Array` 指向新的地址,而保持其中所有其他 `Array` 的引用。当然你可以为 `Array` (或者准确说是 Array)写一个递归的深拷贝扩展,但这是另外一个故事了。
+
+### 真 参照和值,Array和Dictionary背后的一些故事
+
+> 2014 年 7 月 13 日更新
+
+Apple 在 beta 3 里重写了 `Array`,它的行为简化了许多。首先 `copy` 和 `unshare` 两个方法被删掉了,而类似的行为现在以更合理的方式在幕后帮我们完成了。还是举上面的那个例子:
+
+```swift
+var dic = [0:0, 1:0, 2:0]
+var newDic = dic
+//Check dic and newDic
+dic[0] = 1
+dic //[0: 1, 1: 0, 2: 0]
+newDic //[0: 0, 1: 0, 2: 0]
+
+var arr = [0,0,0]
+var newArr = arr
+arr[0] = 1
+//Check arr and newArr
+arr //[1, 0, 0]
+newArr //before beta3:[1, 0, 0], after beta3:[0, 0, 0]
+```
+
+`Dictionary` 当然还是 OK,但是对于 `Array` 中元素的改变,在 beta 3 中发生了变化。现在不再存在作为一个值类型但是却在赋值和改变时表现为参照类型的 `Array` 的特例,而是彻头彻尾表现出了值类型的特点。这个改变避免了原来需要小心翼翼地对 `Array` 进行 `copy` 或者 `unshare` 这样的操作,而 Apple 也承诺在性能上没有问题。文档中提到其实现在的行为和之前是一贯的,只不过对于数组的复制工作现在是在背后由 Apple 只在必要的时候才去做。所以可以猜测其实在背后 `Array` 和 `Dictionary` 的行为并不是像其他 struct 那样简单的在栈上分配,而是类似参照那样,通过栈上指向堆上位置的指针来实现的。而对于它的复制操作,也是在相对空间较为宽裕的堆上来完成的。当然,现在还无法(或者说很难)拿到最后的汇编码,所以这只是一个猜测而已。最后如果能够证实对错的话,我会再进行更新。
+
+总之,beta 3 之后,原来飘忽不定难以捉摸(其实真正理解之后还是很稳定的,也很适合出笔试题)的 `Array` 现在彻底简单化了。基本只需要记住它的行为在表面上和其他的值类型完全无异,而性能方面的考量可以交给 Apple 来做。
+
+### Array vs Slice
+
+因为 `Array` 类型实在太重要了,因此不得不再多说两句。查看 `Array` 在 Swift 中的定义,我们可以发现其实 `Array` 实现了两个很重要的接口 `MutableCollection` 和 `Sliceable`。第一个接口比较简单,为 `Array` 实现了下标等特性,通过 `Collection` 通用的一些概念,可以从数据结构中获取元素,比较简单。而第二个接口 `Sliceable` 实现了通过 Range 来取出部分数组,这里稍微有点特殊。
+
+Swift 引入了在其他很多语言中很流行的用 `..` 和 `...` (beta3 中 `..` 被改成了 `..<`,虽说是为了更明确的意义,但是看起来会比较奇怪)来表示 Range 的概念。从一个数组里面取出一个子数组其实是蛮普遍的一个需求,但是如果你足够细心的话,可能会发现我们无法写这样的代码:
+
+```swift
+var arr = [0,0,0]
+var partOfArr: Array = arr[0...1]
+//Could not find an overload for 'subscript' that accepts the supplied arguments
+```
+
+你会得到一个编译错误,告诉你没有重载下标。在我们去掉我们强制加上的 `: Array` 类型设置之后,编译能通过了。这就告诉我们,我们使用 Rang 从 Array 中取出来的东西,并不是 `Array` 类型。那它到底是个什么东西?使用 REPL 可以很容易看到,在使用 Range 从 Array 里取出来的其实是一个 `Slice`,而不是一个 `Array`。
+
+```swift
+ 1> var arr = [0,0,0]
+arr: Int[] = size=3 {
+ [0] = 0
+ [1] = 0
+ [2] = 0
+}
+ 2> var slice = arr[0...1]
+slice: Slice = size=2 {
+ [0] = 0
+ [1] = 0
+}
+```
+So, what is a slice?查看 `Slice` 的定义,可以看到它几乎和 `Array` 一模一样,实现了同样的接口,拥有同样的成员,那么为什么不直接干脆给个爽快,而要新弄一个 `Slice` 呢?Apple gets crazy?当然不是..Slice的存在当然有其自己的价值和含义,而这和我们刚才提到的值和引用有一些关系。
+
+So, why is a slice?让我们先尝试 play with it。接着上面的情况,运行下面的代码试试看:
+
+```swift
+var arr : Array = [0,0,0]
+var slice = arr[0...1]
+
+arr[0] = 1
+arr //[1, 0, 0]
+slice //[1, 0]
+
+slice[1] = 2
+arr //[1, 2, 0]
+slice //[1, 2]
+```
+
+我想你已经明白一些什么了吧?这里的 `slice` 和 `arr` 当然不可能是同一个引用(它们的类型都不一样),但是很有趣的是,通过 Range 拿到的 `Slice` 中的元素,是指向原来的 `Array` 的。这个特性就非常有趣了,我们可以对感兴趣的数组片段进行观察或者操作,并且它们的值和原来的数组是对应的同步的。
+
+理所当然的,在对应着的 `Array` 或者 `Slice` 其中任意一个的内存指向发生变化时(比如添加或移除了元素,重新赋值等等),这种关系就会被打破。
+
+对于 `Slice` 和 `Array`,其实是可以比较简单地转换的。因为 `Collection` 接口是实现了 `+` 重载的,于是我们可以简单地通过相加来生成一个 `Array` (如果我们愿意的话)。不过,要是真的有需要的话,使用 `Array` 的初始化方法会是比较好的选择:
+
+```swift
+var arr : Array = [0,0,0]
+var slice = arr[0...1]
+var result1 : Array = [] + slice
+var result2 : Array = Array(slice)
+```
+
+使用 Range 下标的方式,不仅可以取到这个 Range 内的 `Slice`,还可以对原来的数组进行批量"赋值":
+
+```swift
+var arr : Array = [0,0,0]
+arr[0...1] = [1,1]
+
+arr //[1, 1, 0]
+```
+
+细心的同学可能注意到了,这里我把“赋值”打上了双引号。实际上这里做的是替换,数组的内存已经发生了变化。因为 Swift 没有强制要求替换的时候 Range 的范围要和用来替换的 `Collection` 的元素个数一致,所以其实这里一定会涉及内存的分配和新的数组生成。我们可以看看下面的例子:
+
+```swift
+var arr : Array = [0,0,0]
+var otherArr = arr
+arr[0...1] = [1,1]
+
+arr //[1, 1, 0]
+otherArr //[0, 0, 0]
+
+arr[0..1] = [1,1]
+arr //[1, 1, 1, 0]
+```
+
+给一个数组进行 Range 赋值,背后其实调用了数组的 `replaceRange` 方法,将取到的 `Slice`,替换成了赋给它的 `Array` 或者 `Slice`。而只要 Range 有效,我们就可以很灵活地写出类似这样的所谓的插入方法:
+
+```swift
+var arr : Array = [0,0,0]
+arr[1..1] = [1, 1]
+arr //[0, 1, 1, 0, 0]
+```
+这里的 `1..1` 是一个起点为 1,长度为 0 的Range,于是它取到的是原来 `[0, 0, 0]` 中 index 为 1 的位置的一个空 `Slice`,将其替换为 `[1, 1]`。清楚明白。
+
+既然都提到了这么多次 `Range`,还是需要说明一下这个 Swift 里很重要的概念(其实在 objc 里 `NSRange` 也很重要,只不过没有像 Swift 里这么普遍)。`Range` 结构体中有两个非常重要的值,`startIndex` 和 `endIndex`,它表示了这个 Range 的范围。而这个值永远是右开的,也就是说,它们会和 `x..y` 这样的表示中 `x` 和 `y` 分别相等。对于 `x < y` 的情况下的 Range,是存在数学上的表达意义的,比如 `2..1` 这样的 Range 表示从 2 开始往前数 1。但是在实际从 `Array` 或者 `Slice` 中取值时这种表达是没有意义,并且会抛出一个运行时的 EXC_BAD_INSTRUCTION 的,在使用的时候还要加以注意。
+
+### 颜文字很好,但是...
+
+有了上面的一些基础,我们可以来谈谈 `String` 了。当说到我们可以在原生的 `String` 中使用 UniCode 字符时,全场一片欢呼。没错,以后我们可以把代码写成这样了!
+
+```swift
+let π = 3.14159
+let 你好 = "你好世界"
+let 🐶🐮 = "🐶🐮”
+```
+
+Cool...虽然我不认为有多少人会去把变量名弄成中文或者猫猫狗狗,但是毕竟字符串本身还是需要支持中文日文阿拉伯文甚至 emoji 的对吧。
+
+另外一个很赞的是,Apple 把所有 `NSString` 的方法都“移植”到了 `String` 类型上,而且将 Cocoa 框架中所有涉及 `NSString` 的地方都换成了 `String`。这是一件很棒的事情,这意味着我们可以无缝在 Swift 上像原来写 objc 时候那样使用 `String`,而不必担心 `String` 和 `NSString` 之间类型转换等麻烦的问题。可能看过 session 或者细读了文档的同学会发现,新的 `String` 里,没有了原来的 `-length` 方法,取而代之,Apple 推荐我们使用 `countElements` 来获取字符串的长度。这是很 make sense 的一件事情,因为我们无法确定字符串中每个字符的字节长度,所以 Apple 为了帮助我们方便计算字符数,给了这个 O(N) 的方法。
+
+这样的字符串带来了一个挺不方便的结果,那就是我们无法直接通过 `Int` 的下标来访问 `String` 中的字符。我们查看 Swift 中 `String` 的定义,可以看到它其实是实现了 `subscript (i: String.Index) -> Character { get }` 的(其 Range 访问也相应需要一个 `Range` 版本的泛型)。如果我们能知道字符对应的 `String.Index`,我们就可以写出方便的下标访问了。举个例子,如果有下面这样的两个 `String`。
+
+```swift
+var str = "1234"
+var imageStr = "🐶🐱🐭🐰"
+```
+
+我们现在想要通过拿到上面那个 ASCII 字符串的某个数字所在的 `String.Index`,来获取下面对应位置的图标,比如 2 对应猫,应该如何做呢?一开始大概很容易想到这样的代码:
+
+```swift
+var range = str.rangeOfString("2") //记得导入 Cocoa 或者 UIKit
+imageStr[range] //EXC_BAD_INSTRUCTION
+```
+
+很不幸,EXC_BAD_INSTRUCTION,这表示 Swift 中有一个 Assertion 阻止了我们继续。其实 `String.Index` 和一般的 `Int` 之类的 index 不太一样,因为每一个 Index 代表的字节的长度是有差别的,所以它只能实现 `BidirectionalIndex`,而不能像其他的等长结构那样实现 `RandomAccessIndex` 接口(关于这两个接口分别是什么已经做了什么,留给大家自己研究下吧)。于是,在不同字符串之间的 Index 进行转换时,我们大概不得不使用一种笨办法,那就是计算步长和差值。对于我们的例子,我们会先算出在 `str` 中 2 与初始 Index 的距离,然后讲这个距离在 `imageStr` 中以 `imageStr` 的 String.Index 进行套用,算出适合其的第二个字符的 Range,然后进行 Range 的下标访问,如下:
+
+```swift
+var range = str.rangeOfString("2")
+var aDistance: Int = distance(str.startIndex, range.startIndex)
+var imageStrStartIndex = advance(imageStr.startIndex, aDistance)
+var range2 = imageStrStartIndex..imageStrStartIndex.successor()
+
+var substring: String = imageStr[range2]
+```
+
+
+大部分时候其实我们不会这么来映射字符串,不过对于 Swift 字符串的实现和与 NSString 的差异,还是值得研究一番的。
+
+### 幽灵一般的 Optional
+
+Swift 引入的最不一样的可能就是 Optional Value 了。在声明时,我们可以通过在类型后面加一个 `?` 来将变量声明为 Optional 的。如果不是 Optional 的变量,那么它就必须有值。而如果没有值的话,我们使用 Optional 并且将它设置为 `nil` 来表示没有值。
+
+```swift
+//num 不是一个 Int
+var num: Int?
+//num 没有值
+num = nil //nil
+//num 有值
+num = 3 //{Some 3}
+```
+
+Apple 在 Session 上告诉我们,Optinal Value 其实就是一个盒子,你盒子里可能装着实际的值,也可能什么都没装。
+
+我们看到 Session 里或者文档里天天说 Optional Optional,但是我们在代码里基本一个 Optional 都没有看到,这是为什么呢?而且,上面代码中给 `num` 赋值为 3 的时候的那个输出为什么看起来有点奇怪?其实,在声明类型时的这个 `?` 仅仅只是 Apple 为了简化写法而提供的一个语法糖。实际上我们是有 Optional 类型的声明,就这里的 `num` 为例,最正规的写法应该是这样的:
+
+```swift
+//真 Optional 声明和使用
+var num: Optional
+num = Optional()
+num = Optional(3)
+```
+
+没错,`num` 不是 `Int` 类型,它是一个 `Optional` 类型。到底什么是 `Optional` 呢,点进去看看:
+
+```swift
+enum Optional : LogicValue, Reflectable {
+ case None
+ case Some(T)
+ init()
+ init(_ some: T)
+
+ /// Allow use in a Boolean context.
+ func getLogicValue() -> Bool
+
+ /// Haskell's fmap, which was mis-named
+ func map(f: (T) -> U) -> U?
+ func getMirror() -> Mirror
+}
+```
+
+你也许会大吃一惊。我们每天和 Swift 打交道用的 Optional 居然是一个泛型枚举 `enum`,而其实我们在使用这个枚举时,如果没有值,我们就规定这个枚举的是 `.None`,如果有,那么它就是 `Some(value)`(带值枚举这里不展开了,有不明白的话请看文档吧)。而这个枚举又恰好实现了 `LogicValue` 接口,这也就是为什么我们能使用 `if` 来对一个 Optinal 的值进行判断并进一步进行 unwrap 的依据。
+
+```swift
+var num: Optional = 3
+if num { //因为有 LogicValue,
+ //.None 时 getLogicValue() 返回 false
+ //.Some 时返回 true
+ var realInt = num!
+ realInt //3
+}
+```
+
+既然 `var num: Int? = nil` 其实给 `num` 赋的值是一个枚举的话,那这个 `nil` 到底又是什么?它被赋值到哪里去了?一直注意的是,Swift 里的 nil 和 objc 里的 nil 完全不是一回事儿。objc 的 nil 是一个实实在在的指针,它指向一个空的对象。而这里的 nil 虽然代表空,但它只是一个语意上的概念,确是有实际的类型的,看看 Swift 的 `nil` 到底是什么吧:
+
+```swift
+/// A null sentinel value.
+var nil: NilType { get }
+```
+
+`nil` 其实只是 `NilType` 的一个变量,而且这个变量是一个 getter。Swift 给了我们一个文档注释,告诉我们 `nil` 其实只是一个 null 的标记值。实际上我们在声明或者赋值一个 Optional 的变量时,`?` 语法糖做的事情就是声明一个 `Optional`,然后查看等号右边是不是 nil 这个标记值。如果不是,则使用 `init(_ some: T)` 用等号右边的类型 T 的值生成一个 `.Some` 枚举并赋值给这个 Optional 变量;如果是 nil,将其赋为 None 枚举。
+
+所以说,Optional背后的故事,其实被这个小小的 `?` 隐藏了。
+
+我想,Optional 讨论到这里就差不多了,还有三个小问题需要说明。
+
+首先,`NilType` 这个类型非常特殊,它似乎是个 built in 的类型,我现在没有拿到关于它的任何资料。我本身逆向是个小白,现在看起来 Swift 的逆向难度也比较大,所以关于 `NilType` 的一些行为还是只能猜测。而关于 `nil` 这一 `NilType` 的类型的变量来说,猜测的话,它可能是 `Optional.None` 的一种类似多型表现,因为首先它确实是指向 0x0 的,并且与 Optional.None 的 content 的内容指向一致。但是具体细节还要等待挖掘或者公布了。
+
+> 2014 年 7 月 13 日更新
+> 从 beta3 开始 `nil` 是一个编译关键字了,`NilType` 则被从 Swift 中移除了。这个改变解决了上面提到的很多悬而未决的问题,比如对 nil 的多次封装以及如何实现自己的可 nil 的类等等。现在添加了一个叫做 `NilLiteralConvertible` 的接口来使某个类可以使用 nil 语法,而避免了原来的让人费解的隐式转换。但是现在还有一个问题,那就是 Optional 是实现了 `LogicValue` 接口的,这就是得像 `BOOL?` 这样的类型在使用的时候会一不小心就很危险。
+
+其次,Apple 推荐我们在 unwrap 的时候使用一种所谓的隐式方法,即下面这种方式来 unwrap:
+
+```swift
+var num: Int? = 3
+if let n = num {
+ //have a num
+} else {
+ //no num
+}
+```
+
+最后,这样隐式调用足够安全,性能上似乎应该也做优化(有点忘了..似乎说过),推荐在 unwrap 的时候尽可能写这样的推断,而减少直接进行 unwrap 这种行为。
+
+最后一个问题是 Optional 的变量也可以是 Optinal。因为 Optional 就相当于一个黑盒子,可以知道盒子里有没有东西 (通过 LogicValue),也可以打开这个盒子 (unwrap) 来拿到里面的东西 (你要的类型的变量或者代表没有东西的 nil)。请注意,这里没有任何规则限制一个 Optional 的量不能再次被 Optional,比如下面这种情况是完全 OK 的:
+
+```swift
+var str: String? = "Hi" //{Some "Hi"}
+var anotherStr: String?? = str //{{Some "Hi"}}
+```
+
+这其实是没有多少疑问的,很完美的两层 Optional,使用的时候也一层层解开就好。但是如果是 nil 的话,在这里就有点尴尬...
+
+```swift
+var str: String? = nil
+var anotherStr: String?? = nil
+```
+因为我们在 LLDB 里输出的时候,得到了两个 nil
+
+
+
+如果说 `str` 其实是 `Optional.None`,输出是 nil 的话还可以理解,但是我们知道 (好吧,如果你认真读了上面的 Optional 的内容的话会知道),`anotherStr` 其实是 `Optional>.Some(Optional.None)`,这是其实一个有效的非空 `Optional`,至少第一层是。而如果放在 PlayGround 里,`anotherStr` 得到的输出又是正确的 `{nil}`。What hanppened? Another Apple bug?
+
+答案是 No,这里不是 bug。为了方便观察,LLDB 会在输出的时候直接帮我们尽可能地做隐式的 unwrap,这也就导致了我们在 LLDB 中输出的值只剩了一个裸的 nil。如果想要看到 Optional 本身的值,可以在 Xcode 的 variable 观察窗口点右键,选中 `Show Raw values`,这样就能显示出 None 和 Some 了。或者我们可以直接使用 LLDB 的 `fr v -R` 命令来打印整个 raw 的值:
+
+
+
+可以清楚看到,`anotherStr` 是 `.Some` 包了一个 `.None`。
+
+(这里有个自动 unwrap 的小疑问,就是写类似 `var anotherStr: String? = str` 这样的代码也能通过,应该是 `?` 语法在这里有个隐式解包,需要进一步确认)
+
+### ? 那是什么??,! 原来如此!!
+
+问号和叹号现在的用法都是原来 objc 中没有的概念。说起来简单也简单,但是背后也还是不少玄机。原来就已经存在的用法就不说了,这里把新用法从浅入深逐个总结一下吧。
+
+首先是 `?`:
+
+* `?` 放在类型后面作为 Optional 类型的标记
+
+这个用法上面已经说过,其实就是一个 `Optional` 的语法糖,自动将等号后面的内容 wrap 成 Optional。给个用例,不再多说:
+
+```swift
+var num: Int? = nil //声明一个 Int 的 Optional,并将其设为啥都没有
+var str: String? = "Hello" //声明一个 String 的 Optional,并给它一个字符串
+```
+
+* `?` 放在某个 Optional 变量后面,表示对这个变量进行判断,并且隐式地 unwrap。比如说:
+
+```swift
+foo?.somemethod()
+```
+
+相比起一般的先判断再调用,类似这样的判断的好处是一旦判断为 `nil` 或者说是 `false`,语句便不再继续执行,而是直接返回一个 nil。上面的写法等价于
+
+```swift
+if let maybeFoo = foo {
+ maybeFoo.somemethod()
+}
+```
+
+这种写法更存在价值的地方在于可以链式调用,也就是所谓的 Optional Chaining,这样可以避免一大堆的条件分支,而使代码变得易读简洁。比如:
+
+```swift
+if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {
+ println("John's uppercase building identifier is \(upper).")
+}
+```
+注意最后 `buildingIdentifier` 后面的问号是在 `()` 之后的,这代表了这个 Optional 的判断对象是 `buildingIdentifier()` 的返回值。
+
+
+* `?` 放在某个 optional 的 protocol 方法的括号前面,以表示询问是否可以对该方法调用
+
+这中用法相当于以前 objc 中的 `-respondsToSelector:` 的判断,如果对象响应这个方法的话,则进行调用。例子:
+
+```swift
+delegate?.questionViewControllerDidGetResult?(self, result)
+```
+
+中的第二个问号。注意和上面在 `()` 后的问号不一样,这里是在 `()` 之前的,表示对方法的询问。
+
+其实在 Swift 中,默认的 potocol 类型是没有 optional 的方法的,因为基于这个前提,可以对类型安全进行确保。但是 Cocoa 框架中的 protocol 还是有很多 optional 的方法,对于这些可选的接口方法,或者你想要声明一个带有可选方法的接口时,必须要在声明 `protocol` 时再其前面加上 `@objc` 关键字,并在可选方法前面加上 `@optional`:
+
+```swift
+@objc protocol CounterDataSource {
+ @optional func optionalMethod() -> Int
+ func requiredMethod() -> Int
+ @optional var optionalGetter: Int { get }
+}
+```
+
+然后是 `!` 新用法的总结
+
+* `!` 放在 Optional 变量的后面,表示强制的 unwrap 转换:
+
+```swift
+foo!.somemethod()
+```
+
+这将会使一个 `Optional` 的量被转换为 `T`。但是需要特别注意,如果这个 Optional 的量是 nil 的话,这种转换会在运行时让程序崩溃。所以在直接写 `!` 转换的时候一定要非常注意,只有在有必死决心和十足把握时才做 `!` 强转。如果待转换量有可能是 nil 的话,我们最好使用 `if let` 的语法来做一个判断和隐式转换,保证安全。
+
+* `!` 放在类型后面,表示强制的隐式转换。
+
+这种情况下和 `?` 放在类型后面的行为比较类似,都是一个类型声明的语法糖。`?` 声明的是 `Optional`,而 `!` 其实声明的是一个 `ImplicitlyUnwrappedOptional` 类型。首先需要明确的是,这个类型是一个 `struct`,其中关键部分是一个 `Optional` 的 value,和一组从这个 value 里取值的 getter 和 方法:
+
+```swift
+struct ImplicitlyUnwrappedOptional : LogicValue, Reflectable {
+ var value: T?
+ //...
+ static var None: T! { get }
+ static func Some(value: T) -> T!
+ //...
+}
+```
+
+从外界来看,其实这和 `Optional` 的变量是类似的,有 `Some` 有 `None`。其实从本质上来说,`ImplicitlyUnwrappedOptional` 就是一个存储了 `Optional`,实现了 `Optional` 对外的方法特性的一个类型,唯一不同的是,`Optional` 需要我们手动进行进行 unwrap (不管是使用 `var!` 还是 `let if` 赋值,总要我们做点什么),而 `ImplicitlyUnwrappedOptional` 则会在使用的时候自动地去 unwrap,并对继续之后的操作调用,而不必去增加一次手动的显示/隐式操作。
+
+为什么要这么设计呢?主要是基于 objc 的 Cocoa 框架的两点考虑和妥协。
+
+首先是 objc 中是有指向空对象的指针的,就是我们所习惯的 `nil`。在 Swift 中,为了处理和 objc 的 nil 的兼容,我们需要一个可为空的量。而因为 Swift 的目的就是打造一个完全类型安全的语言,因此不仅对于 class,对于其他的类型结构我们也需要类型安全。于是很自然地,我们可以使用 Optional 的空来对 objc 做等效。因为 Cocoa 框架有大量的 API 都会返回 nil,因此我们在用 Swift 表达它们的时候,也需要换成对应的既可以表示存在,也可以表示不存在的 `Optional`。
+
+那这样的话,不是直接用 `Optional` 就好了么?为什么要弄出一个 `ImplicitlyUnwrappedOptional` 呢?因为易用性。如果全部用 `Optional` 包装的话,在调用很多 API 时我们就都需要转来转去,十分麻烦。而对于 `ImplicitlyUnwrappedOptional` 因为编译器为我们进行了很多处理,使得我们在确信返回值或者要传递的值不是空的时候,可以很方便的不需要做任何转换,直接使用。但是对于那些 Cocoa 有可能返回 nil,我们本来就需要检查的方法,我们还是应该写 if 来进行转换和检查。
+
+比如说,以下的写法就会在运行时导致一个 EXC_BAD_INSTRUCTION
+
+```swift
+let formatter = NSDateFormatter()
+let now = formatter.dateFromString("not_valid")
+let soon = now.dateByAddingTimeInterval(5.0) // EXC_BAD_INSTRUCTION
+```
+
+因为 `dateFromString` 返回的是一个 `NSDate!`,而我们的输入在原来会导致一个 `nil` 的返回,这里我们在使用 now 之前需要进行检查:
+
+```swift
+let formatter = NSDateFormatter()
+let now = formatter.dateFromString("not_valid")
+if let realNow = now {
+ realNow.dateByAddingTimeInterval(5.0)
+} else {
+ println("Bad Date")
+}
+```
+
+这和以前在 objc 时代做的事情差不多,或者,用更 Swift 的方式做
+
+```swift
+let formatter = NSDateFormatter()
+let now = formatter.dateFromString("not_valid")
+let soon = now?.dateByAddingTimeInterval(5.0)
+```
+
+### 如何写出正确的 Swift 代码
+
+现在距离 Swift 发布已经接近小一周了。很多开发者已经开始尝试用 Swift 写项目。但是不管是作为练习还是作为真正的工程,现在看来大家在写 Swift 时还是带了浓重的 objc 的影子。就如何写出带有 Swift 范儿的代码,在这里给出一点不成熟的小建议。
+
+1. 理解 Swift 的类型组织结构。Swift 的基础组织非常漂亮,主要的基础类型大部分使用了 `sturct` 来完成,然后在之上定义并且实现了各种接口,这样的设计模式其实是值得学习和借鉴的。当然,在实际操作中可能会有很大难度,因为接口比之前灵活许多,可以继承,可以放变量等等,因此在定义接口时如何保持接口的单一性和扩展性是一个不小的考验。
+2. 善用泛型。很多时候 Swift 的 Generic 并不是显式的,类型推断帮助我们做了很多的事情,因此 Generic 这个概念可能被忽视的比较多。关于泛型这个强大的工具,因为原来 objc 中是没有的,而泛型的一个代表语言 C# 虽然平时有写,但很多时候只是当作类型安全的保证在用,我自己也没有太多心得。但是在日常开发中还是多思考和总结,相信会很有进步。
+3. 尽快养成符合 Swift 的语法和习惯,比如 `if let`,比如对常量习惯性地用 `let` 而不要用 `var`,在上下文明确的时候省掉原来习惯写的 `self`,枚举只使用 `.`,合适地使用 `_` 这样的符号来增加可读性等等。既然写 Swift,就应该入乡随俗,尊重这门语言的规范,这样不管在之后和别人的讨论交流上,还是自我的长期发展上,都会很有帮助。
+4. 安心等 Apple 进一步完善。现在 Swift 还处在相对很早期的阶段,很多东西虽然已经基本定型了,但是也有不少可塑性。编译器和调试器现在感觉还不太好用(当然,因为还在 beta,也不是说责怪什么),而且对于原来基于 objc 写的 Cocoa 框架还是有很多水土不服的地方。我个人来说,现在的水平使用 Swift 写还凑合 app 这样的级别应该问题不大,在这篇文章之后我暂时不会再进一步深挖 Swift,而是打算等待正式版出来之后再看情况使用。现在 Swift 仅在 `String` 上可以和 Cocoa 框架完美对接,而对于像 `Array` 这样的类型,虽然通过一些巧妙的方式完成了桥接,但是在实际使用上可能还是需要借助大量的 `NSArray`,在转换上略显麻烦。按照现在来看,Apple 应该至少会将 Cocoa 框架另外几个重要的类迅速适配 Swift 的语言习惯,如果能找到 一种很方便地使用 Cocoa 框架的方法的话,objc 程序员转型 Swift 就应该相对容易一些了。
+
+洋洋洒洒不小心写了这么多(其实我还删了两节..因为写不动了),希望能对您学习和深入了解 Swift 有所帮助吧。因为很晚了,我没有仔细校对,文中肯定有不少错误(技术上和文字上的),欢迎您指出,我会尽快改正。
diff --git a/_posts/2014-07-15-developer-should-know-about-ios8.markdown b/_posts/2014-07-15-developer-should-know-about-ios8.markdown
new file mode 100644
index 00000000..bac4838d
--- /dev/null
+++ b/_posts/2014-07-15-developer-should-know-about-ios8.markdown
@@ -0,0 +1,100 @@
+---
+layout: post
+title: 开发者所需要知道的 iOS8 SDK 新特性
+date: 2014-07-15 22:27:05.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+WWDC 2014 已经过去一个多月。最激动人心的莫过于 Swift 这门新语言的发布,我在之前已经写了一些关于这么语言的[第一印象](http://onevcat.com/2014/06/my-opinion-about-swift/)和一些[初步的探索](http://onevcat.com/2014/06/walk-in-swift/)。在写这篇文章的时候,Swift 随着 beta 3 得到了重大的更新,而这门语言现在也还在剧烈的变化之中。对于 Swift,现在大家的探索才刚刚上路,很多背后的机制还并不是非常清楚,或者有可能发生巨大的变化,因此在这里和之后的几篇文章,直到稳定的 1.0 版本出现,我不再打算继续深入针对 Swift 写什么文章。这基本出于对未来可能的变化会容易误导读到文章的新人的考虑,而并不是说建议我们现在可以放下 Swift,而安心等待正式版本。在我的观念里,对于一个尚不稳定的版本的探索和研究,远比之后被动去接受别人的结果要来的有趣得多,理解也会深入得多。因此如果您有时间的话,建议还是能尽早接触和使用会比较好。Github 上有一个[不错的 repo](https://github.com/ksm/SwiftInFlux),记录了 Swift 一路以来的变化,并探讨了不足以及以后可能的变化,希望深研 Swift 的同学不妨关注看看。
+
+这篇总览先简要介绍下在我看来作为 iOS 开发者应该关注的开发时的变化,在之后一系列文章里我会对其中的某几个部分详细探讨一下,而其余的可能就在本文中做简介。总而言之,这次 WWDC 2014 的相关笔记(现在来说的话是暂定计划要写的内容)大概整理如下:
+
+* [开发者所需要知道的 iOS8 SDK 新特性](http://onevcat/2014/07/developer-should-know-about-ios8)
+* [iOS 界面开发的大一统](http://onevcat.com/2014/07/ios-ui-unique/)
+* [iOS 通知中心扩展制作入门](http://onevcat.com/2014/08/notification-today-widget/)
+* [可视化开发,IB 的新时代](http://onevcat.com/2014/10/ib-customize-view/)
+* iOS 和 Mac 整合开发
+* 通知中心和应用使用重心的改变
+
+---
+
+## 应用扩展 (Extension)
+
+这是一个千呼万唤始出来的特性,也是一个可以发挥无限想象力的特性。现在 Apple 允许我们在 app 中添加一个新的 target,用来提供一些扩展功能:比如在系统的通知中心中显示一个自己的 widget,在某些应用的 Action 中加入自己的操作,在分享按扭里加入自己的条目,更甚至于添加自定义的键盘等等。每一种操作对应这一个应用扩展的入口,在开发中我们只需要在工程中新建立一个对应相应入口的 target,就能从一个很好的模板开始进行一些列开发,来实现这些传统意义上可能需要越狱才能实现的功能。
+
+对于应用扩展,Apple 将其定义为 App 的功能的自然延伸,因此不是单独存在的,而是随着应用本体的包作为附属而被一同下载和安装到用户的设备中的,用户需要在之后选择将其开启。另外,由于应用扩展和应用是属于两个不同的 target 的,因此它们之间的数据和操作上的交互遵循的是另一套原则。关于应用扩展的更详细的内容,我计划在之后通过一个通知中心的 today 小框体控件的例子来详细说明。
+
+> 专题相关笔记
+>
+> [iOS 通知中心扩展制作入门](http://onevcat.com/2014/08/notification-today-widget/)
+
+## App 开发时的统一
+
+随着一代代的 iPhone 和 iPad 的出现,iOS 设备的屏幕尺寸也开始出现分裂的趋势。之前一套屏幕两个方向吃遍全世界的美好时光已然不再,现在至少已经有 3.5 寸,4寸和 10(7) 寸三种分辨率/尺寸的机型需要进行适配,再考虑到每个尺寸的横竖两种方向,以及日益呼声愈高的 4.7 寸和 5.5 寸的 iPhone,可以相见现在的布局方式已然不堪重负。虽然在 iOS 6 Apple 就推出了 Auto Layout 来辅助完成布局工作,解决了原来的相对布局的一些问题,但是在以绝对尺寸为度量的坐标系统中,难免还是有所掣肘。在 iOS 8 中,Apple 的工程师们可以说“极富想象力”地干脆把限制和表征屏幕尺寸的长宽数字给去掉了,取而代之使用 size classes 的概念,将长宽尺寸按照设备类型和方向归类为 regular 和 compact 两类。通过为不同的设备定义尺寸分类,用来定义同类型的操作特性,这使得开发者更容易利用一套 UI 来适配不同的屏幕。
+
+iOS 8 在 UIKit 中添加了一整套使用 size classes 来进行布局的 API,并且将原有的比较复杂(或者说有些冗余)的 API 作废了。结合新的 Interface Builder 和 Auto Layout,可以说对于多尺寸屏幕的适配得到了前所未有的简化。
+
+不仅如此,像是原来 iPad 专有的 SplitController 等也被以适应不同 regular 和 compact 的尺寸类型的形式 port 到了 iPhone 上,在程序设计方面两者更加统一了。另外,一直陪伴我们的 `UIAlertView` 和 `UIActionSheet` 这些老面孔也将退出舞台,取而代之全部统一以 UIViewController 来呈现。
+
+这是一个好的开始,也是一个好的变化。可以看到 Apple 在避免平台碎片化上正在努力。
+
+> 专题相关笔记
+>
+> [iOS 界面开发的大一统](http://onevcat.com/2014/07/ios-ui-unique/)
+>
+> [可视化开发,IB 的新时代](http://onevcat.com/2014/10/ib-customize-view/)
+
+## iCloud 相关
+
+作为帮主的最后一件作品,iCloud 其实非常可惜,一直没有能在 Apple 的生态圈中特别出彩。首先主要是由于 iCloud 相关的开发和 API 使用起来有一定难度,另外就是之前的 SDK 在和 iCloud 相关的各种 API 或多或少都有一些小问题。在 iOS 7 中 iCloud,特别是 iCloud 和 CoreData 结合的部分的 API 的稳定性和易用性得到了很大的改善。而在 iOS 8 中,Apple 更进一步,推出了全新的被称为 Cloud Kit 的框架。如果您熟悉或者使用过像 [Parse](https://www.parse.com) 或者 [AVOS Cloud](https://cn.avoscloud.com) 之类的 [BaaS](http://en.wikipedia.org/wiki/Backend_as_a_service) 的话,可能会对这个框架感到亲切。但是和传统的 BaaS 稍有不同的是,Cloud Kit 更多的是倾向于使用 iCloud 对数据进行集成。你可以不更改应用现有的数据模型和结构,而只是使用 Cloud Kit 来从云端获取数据或者向云端存储数据。
+
+相比与 Parse 和 AVOS 的 API,由于可以和系统深度集成,有很多在其他类似 BaaS 中没有的特性 (比如订阅某个公共对象)。但是因为是 Apple 自家产品,其缺点也是显而易见并且致命的 -- 你无法在非 Apple 的平台上使用这个框架。也就是说,如果你的应用火了,想接着出个安卓版的话,那就只能呵呵了。所以虽然 Cloud Kit 看起来很美好,而且基本等同于免费使用,但是因为平台的限制,而它所涉及的内容又是对跨平台需求很强又绕不开的数据,所以可能实际中能实用的机会并不太多。当然,如果应用是 for iOS only 的话,使用 Cloud Kit 应该是很不错的选择。
+
+关于云端存储的另一个新变化是存储源的可变化。以前我们基本别无选择,想使用沙盒外的文件的话,要么就是 iCloud 同一个 container 内的文件,要么就需要来个像 Dropbox 这样的第三方库去做一堆登陆验证什么的。不论那种方式都可以说挺麻烦的。而现在随着 [iCloud Drive](https://www.apple.com/cn/ios/ios8/icloud-drive/) 的引入,在应用间共享访问文件就变得很容易了。更甚,我们现在可以使用 [UIDocumentPickerViewController](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIDocumentPickerViewController_Class/index.html#//apple_ref/occ/cl/UIDocumentPickerViewController) 来从第三方存储 (以及第三方 app 通过应用扩展所实现的存储) 中选取文件。
+
+## Handoff 及其他 iOS 与 Mac 的协同开发
+
+虽然 PC 市场一直疲软,但是得益于 iDevice 的销售和品牌接受度的回升,Mac 的销量反而逆市上扬。这点在国内尤为明显,确实可以感觉到身边开始使用 Mac 的人在逐渐变多,这对于我们这些 iOS 开发者来说其实是一个不错的机会。iOS 8 中的 Handoff 机制(就是可以在 Mac 上继续完成在 iOS 上半途的工作)给 for both iOS and Mac 的应用带来了一个不错的契合点和卖点。而近年来在整合两个系统上的动作,也可以看得出 Apple 确实希望利用庞大的 iOS 的开发人员资源来进一步完善和丰富 Mac。iOS 开发和 Mac 开发其实同根同源,因此在转换的时候并不是很困难的事情。
+
+我们一直以来都可以写出跨两个平台的 Model 部分的代码,而只需要关心在表现上的区别。而现在 Cocoa 和 CocoaTouch 在官方支持自制 framework 后,利用 framework 来完成这一过程可以说更加简单了。
+
+> 专题相关笔记
+>
+> iOS 和 Mac 整合开发
+
+## Health Kit 和 Home Kit
+
+这是对应两个现在很热的领域 -- 可穿戴式设备和智能家电 -- 所加入的框架。基本上来说 Apple 想做的事情就是以 iOS 为基础,为其他 app 建立一个平台以及成为用户数据的管理者。
+
+Health Kit 就是一个用户体征参数的数据库,第三方应用可以向用户申请权限使用其中的数据或是向其中汇报数据。而 Home Kit 则以家庭,房间和设备的组织形式来管理和控制家中适配了 Home Kit 的智能家电。这两个超级年轻的框架的 API 相对都还比较简单,结构也很好,相信稍有经验的 iOS 开发者都能在很快掌握用法。唯一的限制在于作为普通开发者(比如我这样的只能自己业余玩的)可能手边现在不会有合适的设备来进行测试,所以很多东西其实没有办法验证。不过对于 Home Kit,Apple给我们提供了一个模拟器来模拟智能家电设备,您可以在 Xcode 6 的 Open Developer Tool 菜单中找到 Home Kit Accessory Simulator。使用模拟器可以发现,添加并且控制自定义的智能家电,用来前期开发还是蛮方便的。
+
+如果能入手一些适配于 Health Kit 或者 Home Kit 的设备的话,我可能会补充一些关于这方面的开发心得。
+
+## 游戏方面
+
+最大的改变莫过于 Scene Kit 的加入了。不过游戏天生的容易跨平台的特性 (并且也有这方面的强烈需求),与平台限制的 Sprite Kit 是冲突的,所以去年的 Sprite Kit 也还没多少人用。暂时看来这个世界现在是,并且在一段时间内还会是被 Cocos2dx/Unity 所统治的。Scene Kit 的未来估计会和 Sprite Kit 比较类似,作为对于一直进行 iOS 应用开发的开发者来说,有着不需要学习和熟悉新语言的优势,容易与系统的其他框架进行集成,所以用来转型还算不错的选择。但除此之外其他方面可能也并没有多少可以吸引人的地方了。
+
+另一个重大改变是对于 A7 和以上级别的 GPU 推出了一套全新的称为 Metal 的绘制 API,从 Keynote 的 Zen Garden 的演示来看,Metal 的性能毋庸置疑是令人折服的,Metal 的渲染方式和着色器也相当有趣。但是其实这些内容更多地是偏向底层以及面向引擎开发的,对于使用游戏引擎来制作游戏的大多数开发者来说,并不需要知道或者理解其中的东西。在 A7 的芯片下使用 Apple 自家的 Sprite Kit 或者 Scene Kit 的话,就可以直接受益于 Metal,而其他一些知名的第三方引擎,比如 Unity 和 UE 也都会在 iOS 8 推出后支持 Metal。因此,作为引擎使用者,并不需要做出除了升级开发使用的游戏引擎之外的任何改变。
+
+## 其他重要改动
+
+### Local 和 Remote 通知的变化
+
+现在需要显示 UI 或者播放声音的通知,包括 Local 通知也需要实现弹窗获得用户许可了。使用 `-registerUserNotificationSettings:` 来向用户获取许可。作为补偿,现在对于不需要打扰用户(也就是 iOS 7 加入的静默通知)的类型不再需要弹框获取用户许可。不过因为本地推送是需要许可的,所以无论怎样如果你想要依靠通知来提高用户留存率的话,现在都绕不开用户许可了。
+
+另外,通知中心加入了非常方便的 Action 特性,用户可以在收到通知后,在不打开应用的情况下完成一些操作。可以说配合通知中心的 Today 扩展,用户现在在很可能可以在不打开应用的情况下就获取到他们想要的信息,并完成互动。这对于开发者可以说是一件喜忧参半的事情,一方面我们可以给用户提供更好更快的使用体验,但是另一方面这将降低用户打开应用的意愿。不过 Apple 现在的总体思路还是 app 的体验才是最重要的,所以正确的道路应该还是优先做好 app 的体验,并且摸索一个应用和通知之间的平衡点,让大家都满意。
+
+> 专题相关笔记
+>
+> 通知中心和应用使用重心的改变
+
+### CoreLocation
+CoreLocation 室内定位。现在 CL 可以给出在建筑物中的楼层定位信息了,直接访问 `CLLocation` 实例的 `floor`,如果当前位置可用的话,会返回一个包含位置信息的非 nil 的 `CLFloor` 以标识当前楼层。这个使得定位应用的可能性大大扩展了,想象一下在复杂的地铁站或者大厦里迷路的时候,还可以依赖定位系统,幸福感涌上心头啊。
+
+### Touch ID
+Touch ID API,说是开放了 Touch ID 的验证,但是实际上能做的事情还是比较有限。因为现在提供的 API 只能验证用户是不是手机主人本人,而不能给出一个识别的标志或者唯一编码,所以想用 Touch ID 做注册登陆什么的话可能还是不太现实。不过在进行支付验证之类的已登录后的再次确认操作时就比较好用。现在看来的话这组 API 就是为了简化像 Paypal 或者支付宝这样的第三方支付和确认的流程的。希望之后能继续放开,如果能给一个唯一标识的话,也许就可以干掉整个讨厌的注册和登陆系统了。
+
+### 相机和照片
+
+新增加了 Photos.framework 框架,这个框架用于与系统内置的 Photo 应用进行交互,不仅可以替代原来的 Assets Library 作为照片和视频的选取,还能与 iCloud 照片流进行交互。除此之外,一个很重要的特性是还可以监听其他应用对于照片的改变,可以说整个框架非常灵活。
diff --git a/_posts/2014-07-29-ios-ui-unique.markdown b/_posts/2014-07-29-ios-ui-unique.markdown
new file mode 100644
index 00000000..d902a937
--- /dev/null
+++ b/_posts/2014-07-29-ios-ui-unique.markdown
@@ -0,0 +1,204 @@
+---
+layout: post
+title: WWDC 2014 Session笔记 - iOS界面开发的大一统
+date: 2014-07-29 10:54:18.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+本文是我的 [WWDC 2014 笔记](http://onevcat.com/2014/07/developer-should-know-about-ios8/) 中的一篇,涉及的 Session 有
+
+* [What's New in Cocoa Touch](http://devstreaming.apple.com/videos/wwdc/2014/202xx3ane09vxdz/202/202_hd_whats_new_in_cocoa_touch.mov?dl=1)
+* [Building Adaptive Apps with UIKit](http://devstreaming.apple.com/videos/wwdc/2014/216xxcnxc6wnkf3/216/216_hd_building_adaptive_apps_with_uikit.mov?dl=1)
+* [What's New in Interface Builder](http://devstreaming.apple.com/videos/wwdc/2014/411xx0xo98zzoor/411/411_hd_whats_new_in_interface_builder.mov?dl=1)
+* [View Controller Advancements in iOS 8](http://devstreaming.apple.com/videos/wwdc/2014/214xxq2mdbtmp23/214/214_hd_view_controller_advancements_in_ios_8.mov?dl=1)
+* [A Look Inside Presentation Controllers](http://devstreaming.apple.com/videos/wwdc/2014/228xxnfgueiskhi/228/228_hd_a_look_inside_presentation_controllers.mov?dl=1)
+
+iOS 8 和 OS X 10.10 中一个被强调了多次的主题就是大一统,Apple 希望通过 Hand-off 和各种体验的无缝切换和集成将用户黏在由 Apple 设备构成的生态圈中。而对开发者而言,今年除了 Swift 的一个大主题也是平台的统一。在 What's New in Cocoa Touch 的 Seesion 一开始,UIKit 的工程师 Luke 就指出了 iOS 8 SDK 的最重要的关键字就是自适应 (adaptivity)。这是一个很激动人心的词,首先自适应是一种设计哲学,尽量使事情保持简单,我们便可从中擢取优雅;另一方面,可能这也是 Apple 不得不做的转变。随着传说中的更大屏和超大屏的 iPhone 6 的到来,开发者在为 iOS 进行开发的时候似乎也开始面临着和安卓一样的设备尺寸的碎片化的问题。而 iOS 8 所着重希望解决的,就是这一问题。
+
+## Size Classes
+
+首先最值得一说的是,iOS 8 应用在界面设计时,迎来了一个可以说是革命性的变化 - Size Classes。
+
+### 基本概念
+
+在 iPad 和 iPhone 5 出现之前,iOS 设备就只有一种尺寸。我们在做屏幕适配时需要考虑的仅仅有设备方向而已。而很多应用并不支持转向,这样的话就完全没有屏幕适配的工作了。随着 iPad 和 iPhone 5,以及接下来的 iPhone 6 的推出,屏幕尺寸也变成了需要考虑的对象。在 iOS 7 之前,为一个应用,特别是 universal 的应用制作 UI 时,我们总会首先想我们的目标设备的长宽各是多少,方向变换以后布局又应该怎么改变,然后进行布局。iOS 6 引入了 Auto Layout 来帮助开发者使用约束进行布局,这使得在某些情况下我们不再需要考虑尺寸,而可以专注于使用约束来规定位置。
+
+既然我们有了 Auto Layout,那么其实通过约束来指定视图的位置和尺寸是没有什么问题的了,从这个方面来说,屏幕的具体的尺寸和方向已经不那么重要了。但是实战中这还不够,Auto Layout 正如其名,只是一个根据约束来进行**布局**的方案,而在对应不同设备的具体情况下的体验上还有欠缺。一个最明显的问题是它不能根据设备类型来确定不同的交互体验。很多时候你还是需要判断设备到底是 iPhone 还是 iPad,以及现在的设备方向究竟是竖直还是水平来做出判断。这样的话我们还是难以彻底摆脱对于设备的判断和依赖,而之后如果有新的尺寸和设备出现的话,这种依赖关系显然显得十分脆弱的(想想要是有 iWatch 的话..)。
+
+所以在 iOS 8 里,Apple 从最初的设计哲学上将原来的方式推翻了,并引入了一整套新的理念,来适应设备不断的发展。这就是 Size Classes。
+
+不再根据设备屏幕的具体尺寸来进行区分,而是通过它们的感官表现,将其分为**普通** (Regular) 和**紧密** (Compact) 两个种类 (class)。开发者便可以无视具体的尺寸,而是对这这两类和它们的组合进行适配。这样不论在设计时还是代码上,我们都可以不再受限于具体的尺寸,而是变成遵循尺寸的视觉感官来进行适配。
+
+
+
+简单来说,现在的 iPad 不论横屏还是竖屏,两个方向均是 Regular 的;而对于 iPhone,竖屏时竖直方向为 Regular,水平方向是 Compact,而在横屏时两个方向都是 Compact。要注意的是,这里和谈到的设备和方向,都仅仅只是为了给大家一个直观的印象。相信随着设备的变化,这个分类也会发生变动和更新。Size Classes 的设计哲学就是尺寸无关,在实际中我们也应该尽量把具体的尺寸抛开脑后,而去尽快习惯和适应新的体系。
+
+### UITraitCollection 和 UITraitEnvironment
+
+为了表征 Size Classes,Apple 在 iOS 8 中引入了一个新的类,`UITraitCollection`。这个类封装了像水平和竖直方向的 Size Class 等信息。iOS 8 的 UIKit 中大多数 UI 的基础类 (包括 `UIScreen`,`UIWindow`,`UIViewController` 和 `UIView`) 都实现了 `UITraitEnvironment` 这个接口,通过其中的 `traitCollection` 这个属性,我们可以拿到对应的 `UITraitCollection` 对象,从而得知当前的 Size Class,并进一步确定界面的布局。
+
+和 UIKit 中的响应者链正好相反,`traitCollection` 将会在 view hierarchy 中自上而下地进行传递。对于没有指定 `traitCollection` 的 UI 部件,将使用其父节点的 `traitCollection`。这在布局包含 childViewController 的界面的时候会相当有用。在 `UITraitEnvironment` 这个接口中另一个非常有用的是 `-traitCollectionDidChange:`。在 `traitCollection` 发生变化时,这个方法将被调用。在实际操作时,我们往往会在 ViewController 中重写 `-traitCollectionDidChange:` 或者 `-willTransitionToTraitCollection:withTransitionCoordinator:` 方法 (对于 ViewController 来说的话,后者也许是更好的选择,因为提供了转场上下文方便进行动画;但是对于普通的 View 来说就只有前面一个方法了),然后在其中对当前的 `traitCollection` 进行判断,并进行重新布局以及动画。代码看起来大概会是这个样子:
+
+```
+- (void)willTransitionToTraitCollection:(UITraitCollection *)newCollection
+ withTransitionCoordinator:(id )coordinator
+{
+ [super willTransitionToTraitCollection:newCollection
+ withTransitionCoordinator:coordinator];
+ [coordinator animateAlongsideTransition:^(id context) {
+ if (newCollection.verticalSizeClass == UIUserInterfaceSizeClassCompact) {
+ //To Do: modify something for compact vertical size
+ } else {
+ //To Do: modify something for other vertical size
+ }
+ [self.view setNeedsLayout];
+ } completion:nil];
+}
+```
+
+在两个 To Do 中,我们应该删除或者添加或者更改不同条件下的 Auto Layout 约束 (当然,你也可以干其他任何你想做的事情),然后调用 `-setNeedsLayout` 来在上下文中触发转移动画。如果你坚持用代码来处理的话,可能需要面临对于不同 Size Classes 来做移除旧的约束和添加新的约束这样的事情,可以说是很麻烦 (至少我觉得是麻烦的要死)。但是如果我们使用 IB 的话,这些事情和代码都可以省掉,我们可以非常方便地在 IB 中指定各种 Size Classes 的约束 (稍后会介绍如何使用 IB 来对应 Size Classes)。另外使用 IB 不仅可以节约成百上千行的布局代码,更可以从新的 Xcode 和 IB 中得到很多设计时就可以实时监视,查看并且调试的特性。可以说手写 UI 和使用 IB 设计的时间消耗和成本差距被进一步拉大,并且出现了很多手写 UI 无法实现,但是 IB 可以不假思索地完成的任务。从这个意义上来说,新的 IB 和 Size Classes 系统可以说无情地给手写代码判了个死缓。
+
+另外,新的 API 和体系的引入也同时给很多我们熟悉的 UIViewController 的有关旋转的老朋友判了死刑,比如下面这些 API 都弃用了:
+
+```
+@property(nonatomic, readonly) UIInterfaceOrientation interfaceOrientation
+
+- willRotateToInterfaceOrientation:duration:
+- willAnimateRotationToInterfaceOrientation:duration:
+- didRotateFromInterfaceOrientation:
+- shouldAutomaticallyForwardRotationMethods
+```
+
+现在全部统一到了 `viewWillTransitionToSize:withTransitionCoordinator:`,旋转的概念不再被提倡使用。其实仔细想想,所谓旋转,不过就是一种 Size 的改变而已,我们都被 Apple 骗了好多年,不是么?
+
+Farewell, I will NOT miss you at all.
+
+### 在 Interface Builder 中使用 Size Classes
+
+第一次接触 Xcode 6 和打开 IB 的时候你可能会惊呼,为什么我的画布变成正方形了。我在第一天 Keynote 结束后在 Moscone Center 的食堂里第一次打开的时候,还满以为自己找到了 iWatch 方形显示屏的确凿证据。到后来才知道,这是新的 Size Classes 对应的编辑方式。
+
+既然我们不需要关心实际的具体尺寸,那么我们也就完全没有必要在 IB 中使用像 3.5/4 寸的 iPhone 或是 10 寸的 iPad 来分开对界面进行编辑。使用一个通用的具有 "代表" 性质的尺寸在新体系中确实更不容易使人迷惑。
+
+在现在的 IB 界面的正下方,你可以看到一个 `wAny hAny` 的按钮 (因为今年 NDA 的一个明确限制是不能发相关软件截图,虽然其实可能没什么太大问题,但是还是尊重 license 比较好),这代表现在的 IB 是对应任意高度和任意宽度的。点击后便可以选择需要为哪种 Size Class 进行编辑。默认情况在 Any Any 下的修改会对任意设备和任意方向生效,而如果先进行选择后再进行编辑,就表示编辑只对选中的设定生效。这样我们就很容易在同一个 storyboard 文件里对不同的设备进行适配:按照设备需要添加或者编辑某些约束,或者是在特定尺寸下隐藏某些 view (使用 Attribute Inspector 里的 `Installed` 选框的加号添加)。这使得使用 IB 制作通用程序变简单了,我们不再需要为 iPhone 和 iPad 准备两套 storyboard 了。
+
+可以发挥的想象空间实在太大,一套界面布局通吃所有设备的画面太美好,我都不敢想。
+
+### Size Classes 和 Image Asset 及 UIAppearence
+
+Image Asset 里也加入了对 Size Classes 的支持,也就是说,我们可以对不同的 Size Class 指定不同的图片了。在 Image Asset 的编辑面板中选择某张图片,Inspector 里现在多了一个 Width 和 Height 的组合,添加我们需要对应的 Size Class, 然后把合适的图拖上去,这样在运行时 SDK 就将从中挑选对应的 Size 的图进行替换了。不仅如此,在 IB 中我们也可以选择对应的 size 来直接在编辑时查看变化(新的 Xcode 和 IB 添加了非常多编辑时的可视化特性,关于这方面我有计划单独写一篇可视化开发的文章进行说明)。
+
+这个特性一个最有用的地方在于对于不同屏幕尺寸可能我们需要的图像尺寸也有所不同。比如我们希望在 iPhone 竖屏或者 iPad 时的按钮高一些,而 iPhone 横屏时由于屏幕高度实在有限,我们希望得到一个扁一些的按钮。对于纯色按钮我们可以通过简单的约束和拉伸来实现,但是对于有图案的按钮,我们之前可能就需要在 VC 里写一些脏代码来处理了。现在,只需要指定好特定的 Image Asset,然后配置合适的 (比如不含有尺寸限制) 约束,我们就可以一行代码不写,就完成这样复杂的各个机型和方向的适配了。
+
+实际做起来实在是太简单了..但拿个 demo 说明一下吧,比如下面这个实现了竖直方向 Compact 的时候将笑脸换成哭脸 -- 当然了,一行代码都不需要。
+
+
+
+另外,在 iOS 7 中 UIImage 添加了一个 `renderingMode` 属性。我们可以使用 `imageWithRenderingMode:` 并传入一个合适的 `UIImageRenderingMode` 来指定这个 image 要不要以 [Template 的方式进行渲染](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/UIKitUICatalog/index.html#//apple_ref/doc/uid/TP40012857-UIView-SW7)。在新的 Xcode 中,我们可以直接在 Image Asset 里的 `Render As` 选项来指定是不是需要作为 template 使用。而相应的,在 `UIApperance` 中,Apple 也为我们对于 Size Classes 添加了相应的方法。使用 `+appearanceForTraitCollection:` 方法,我们就可以针对不同 trait 下的应用的 apperance 进行很简单的设定。比如在上面的例子中,我们想让笑脸是绿色,而哭脸是红色的话,不要太简单。首先在 Image Asset 里的渲染选项设置为 `Template Image`,然后直接在 `AppDelegate` 里加上这样两行:
+
+```
+UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Compact)).tintColor = UIColor.redColor()
+UIView.appearanceForTraitCollection(UITraitCollection(verticalSizeClass:.Regular)).tintColor = UIColor.greenColor()
+```
+
+
+完成,只不过拖拖鼠标,两行简单的代码,随后还能随喜换色,果然是大快所有人心的大好事。
+
+## UIViewController 的表现方式
+
+### UISplitViewController
+
+在用 Regular 和 Compact 统一了 IB 界面设计之后,Apple 的工程师可能发现了一个让人两难的历史问题,这就是 `UISplitViewController`。一直做 iPhone 而没太涉及 iPad 的童鞋可能对着这个类不是很熟悉,因为它们是 iPad Only 的。iPad 推出时为了适应突然变大的屏幕,并且远离 "放大版 iTouch" 的诟病,Apple 为 iPad 专门设计了这个主从关系的 ViewControlle容器。事实也证明了这个设计在 iPad 上确实是被广泛使用,是非常成功的。
+
+现在的问题是,如果我们只有一套 UI 画布的话,我们要怎么在这个单一的画布上处理和表现这个 iPad Only 的类呢?
+
+答案是,让它在 iPhone 上也能用就行了。没错,现在你可以直接在 iPhone 上使用 SplitViewController 了。在 Regular 的宽度时,它保持原来的特性,在 DetailViewController 中显示内容,这是毫无疑问的。而在 Compact 中,我们第一想法就是以 push 的表现形式展示。在以前,我们可能需要写不少代码来处理这些事情,比如在 AppDelegate 中就在一开始判断设备是不是 iPad,然后为应用设定两套完全不同的导航:一套基于 `UINavigationController`,另一套基于 `UISplitViewController`。而现在我们只需要一套 `UISplitViewController`,并将它的 MasterViewController 设定为一个 navgationController 就可以轻松搞定所有情况了。
+
+也许你会想,即使这样,我是不是还是需要判断设备是不是 iPad,或者现在的话是判断 Size Class 是不是 Compact,来决定是要做的到底是 navVC 的 push 还是改变 splitVC 的 viewControllers。其实不用,我们现在可以无痛地不加判断,直接用统一的方式来完成两种表现方式。这其中的奥妙在于我们不需要使用 (事实上 iOS 8 后 Apple 也不再提倡使用) `UINavigationController` 的 `pushViewController:animated:` 方法了 (又一个老朋友要和我们说再见了)。其实虽然很常用,但是这个方法是一直受到社区的议论的:因为正是这个方法的存在使得 ViewController 的耦合特性上了一个档次。在某个 ViewController 中这个方法的存在时,就意味着我们需要确保当前的 ViewController 必须处于一个导航栈的上下文中,这是完全和上下文耦合的一种方式 (虽然我们也可以很蛋疼地用判断 `navController` 是不是 `nil` 来绕开,但是毕竟真的很丑,不是么)。
+
+我们现在有了新的展示 viewController 的方法,`-showViewController:sender:` 以及 `-showDetailViewController:sender:`。调用这两个方法时,将顺着包括调用 vc 自身的响应链而上,寻找最近的实现了这个方法的 ViewController 来执行相应代码。在 iOS SDK 的默认实现中,在 `UISplitViewController` 这样的容器类中,已经有这两个方法的实现方式,而 `UINavigationController` 也实现了 `-showViewController:sender:` 的版本。对于在 navController 栈中的 vc,会调用 push 方式进行展示,而对 splitVC,`showViewController:sender:` 将在 MasterViewController 中进行 push。而 `showDetailViewController:sender:` 将根据水平方向的 Size 的情况进行选择:对于 Regular 的情况,将在 DetailViewController 中显示新的 vc,而对于 Compact 的情况,将由所在上下文情况发回给下级的 navController 或者是直接以 modal 的方式展现。关于这部分的具体内容,可以仔细看看这个[示例项目](https://developer.apple.com/devcenter/download.action?path=/wwdc_2014/wwdc_2014_sample_code/adaptivephotosanadaptiveapplication.zip)和相关的[文档 (beta版)](https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UISplitViewController_class/)。
+
+这么设计的好处是显而易见的,首先是解除了原来的耦合,使得我们的 ViewController 可以不被局限于导航控制器上下文中;另外,这几个方法都是公开的,也就是说我们的 ViewController 可以实现这两个方法,截断响应链的响应,并实现我们自己的呈现方式。这在自定义 Container Controller 的时候会非常有用。
+
+### UIPresentationController
+
+iOS 7 中加入了一套实现非常漂亮的自定义转场动画的方法 (如果你还不知道或者不记得了,可以看看我去年的[这篇笔记](http://onevcat.com/2013/10/vc-transition-in-ios7/))。Apple 在解耦和重用上的努力确实令人惊叹。而今年,顺着自适应和平台开发统一的东风,在呈现 ViewController 的方式上 Apple 也做出了从 iOS SDK 诞生以来最大的改变。iOS 8 中新加入了一个非常重要的类 `UIPresentationController`,这个 `NSObject` 的子类将用来管理所有的 ViewController 的呈现。在实现方式上,这个类和去年的自定义转场的几个类一样,是完全解耦合的。而 Apple 也在自己的各种 viewController 呈现上完全统一地使用了这个类。
+
+#### 再见 UIPopoverController
+
+和 SplitViewController 类似,`UIPopoverController` 原来也只是 iPad 使用的,现在 iPhone 上也将适用。准确地说,现在我们不再使用 `UIPopoverController` 这个类 (虽然现在文档还没有将其标为 deprecated,但是估计也是迟早的事儿了),而是改用一个新的类 `UIPopoverPresentationController`。这是 `UIPresentationController` 的子类,专门用来负责呈现以 popover 的形式呈现内容,是 iOS 8 中用来替代原有的 `UIPopoverController` 的类。
+
+比起原来的类,新的方式有什么优点呢?最大的优势是自适应,这和 `UISplitViewController` 在 iOS 8 下的表现是类似的。在 Compact 的宽度条件下,`UIPopoverPresentationController` 的呈现将会直接变成 modal 出来。这样我们基本就不再需要去判断 iPhone 还是 iPad (其实相关的判定方法也已经被标记成弃用了),就可以对应不同的设备了。以前我们可能要写类似这样的代码:
+
+```
+if UIDevice.currentDevice().userInterfaceIdiom == .Pad {
+ let popOverController = UIPopoverController(contentViewController: nextVC)
+ popOverController.presentPopoverFromRect(aRect, inView: self.view, permittedArrowDirections: .Any, animated: true)
+} else {
+ presentViewController(nextVC, animated: true, completion: nil)
+}
+```
+
+而现在需要做的是:
+
+```
+nextVC.modalPresentationStyle = .Popover
+let popover = nextVC.popoverPresentationController
+popover.sourceRect = aRect
+popover.permittedArrowDirections = .Any
+
+presentViewController(nextVC, animated: true, completion: nil)
+```
+
+没有可恶的条件判断,一切配置井井有条,可读性也非常好。
+
+除了自适应之外,新方式的另一个优点是非常容易自定义。我们可以通过继承 `UIPopoverPresentationController` 来实现我们自己想要的呈现方式。其实更准确地说,我们应该继承的是 `UIPresentationController`,主要通过实现 `-presentationTransitionWillBegin` 和 `-presentationTransitionDidEnd:` 来自定义我们的展示。像以前我们想要实现只占半个屏幕,后面原来的 view 还可见的 modal,或者是将从下到上的动画改为百叶窗或者渐隐渐现,那都是可费劲儿的事情。而在 `UIPresentationController` 的帮助下,一切变得十分自然和简单。在自己的 `UIPresentationController` 子类中:
+
+```
+override func presentationTransitionWillBegin() {
+ let transitionCoordinator = self.presentingViewController.transitionCoordinator()
+ transitionCoordinator.animateAlongsideTransition({context in
+ //Do animation here
+ }, completion: nil)
+}
+
+override func presentationTransitionDidEnd(completed: Bool) {
+ //Do clean here
+}
+```
+
+具体的用法和 iOS 7 里的自定义转场很类似,设定需要进行呈现操作的 ViewController 的 transition delegate,在 `UIViewControllerTransitioningDelegate` 的 `-presentationControllerForPresentedViewController:sourceViewController:` 方法中使用 `-initWithPresentedViewController:presentingViewController:` 生成对应的 `UIPresentationController` 子类对象返回给 SDK,然后就可以喝茶看戏了。
+
+#### 再见 UIAlertView, 再见 UIActionSheet
+
+自适应和 `UIPresentationController` 给我们带来的另一个大变化是 `UIAlertView` 和 `UIActionSheet` 这两个类的消亡 (好吧其实算不上消亡,弃用而已)。现在,Alert 和 ActionSheet 的呈现也通过 `UIPresentationController` 来实现。原来在没有 Size Class 和需要处理旋转的黑暗年代 (抱歉在这里用了这个词,但是我真的一点也不怀念那段处理设备旋转的时光) 里,把这两个 view 显示出来其实幕后是一堆恶心的事情:创建新的 window,处理新 window 的大小和方向,然后将 alert 或者 action sheet 按合适的大小和方向加到窗口上,然后还要考虑处理转向,最后显示出来。虽然 Apple 帮我们做了这些事情,但是轮到我们使用时,往往它们也只能满足最基本的需求。在适配 iPhone 和 iPad 时,`UIAlertView` 还好,但是对于 `UIActionSheet` 我们往往又得进行不同处理,来选择是不是需要 popover。
+
+另外一个要命的地方是因为这两个类是 iOS 2.0 开始就存在的爷爷级的类了,而最近一直也没什么大的更新,设计模式上还使用的是传统的 delegate 那一套东西。实际上对于这种很轻很明确的使用逻辑,block handler 才是最好的选择,君不见满 GitHub 的 block alert view 的代码,但是没辙,4.0 才出现的 block 一直由于种种原因,在这两个类中一直没有得到官方的认可和使用。
+
+而作为替代品的 `UIAlertController` 正是为了解决这些问题而出现的,值得注意的是,这是一个 `UIViewController` 的子类。可能你会问 `UIAlertController` 对应替代 `UIAlertView`,这很好,但是 `UIActionSheet` 怎么办呢?哈..答案是也用 `UIAlertController`,在 `UIAlertController` 中有一个 `preferredStyle` 的属性,暂时给我们提供了两种选择 `ActionSheet` 和 `Alert`。在实际使用时,这个类的 API 还是很简单的,使用工厂方法创建对象,进行配置后直接 present 出来:
+
+```
+let alert = UIAlertController(title: "Test", message: "Msg", preferredStyle: .Alert)
+
+let okAction = UIAlertAction(title: "OK", style: .Default) {
+ [weak alert] action in
+ print("OK Pressed")
+ alert!.dismissViewControllerAnimated(true, completion: nil)
+}
+alert.addAction(okAction)
+presentViewController(alert, animated: true, completion: nil)
+```
+
+使用上除了小心循环引用以外,并没有太多好说的。在 Alert 上加文本输入也变得非常简单了,使用 `-addTextFieldWithConfigurationHandler:` 每次向其上添加一个文本输入,然后在 handler 里拿数据就好了。
+
+要记住的是,在幕后,做呈现的还是 `UIPresentationController`。
+
+#### UISearchDisplayController -> UISearchController
+
+最后想简单提一下在做搜索栏的时候的同样类似的改变。在 iOS 8 之前做搜索栏简直是一件让人崩溃的事情,而现在我们不再需要讨厌的 `UISearchDisplayController` 了,也没有莫名其妙的在视图树中强制插入 view 了 (如果你做过搜索栏,应该知道我在说什么)。这一切在 iOS 8 中也和前面说到的 alert 和 actionSheet 一样,被一个 `UIViewController` 的子类 `UISearchController` 替代了。背后的呈现机制自然也是 `UIPresentationController`,可见新的这个类在 iOS 8 中的重要性。
+
+## 总结
+
+对于广大 iOS 开发者赖以生存的 UIKit 来说,这次最大的变化就是 Size Classes 的引入和新的 Presentation 系统了。在 Keynote 上 Craig 就告诉我们,iOS 8 SDK 将是 iOS 开发诞生以来最大的一次变革,此言不虚。虽然 iOS 8 SDK 的广泛使用估计还有要有个两年时间,但是不同设备的开发的 API 的统一这一步已然迈出,这也正是 Apple 之后的发展方向。正如两年前的 Auto Layout 正在今天大放光彩一样,之后 Size Classes 和新的 ViewController 也必将成为日常开发的主力工具。
+
+程序员嘛,果然需要每年不断学习,才能跟上时代。
diff --git a/_posts/2014-08-03-notification-today-widget.markdown b/_posts/2014-08-03-notification-today-widget.markdown
new file mode 100644
index 00000000..9df92ed7
--- /dev/null
+++ b/_posts/2014-08-03-notification-today-widget.markdown
@@ -0,0 +1,306 @@
+---
+layout: post
+title: WWDC 2014 Session笔记 - iOS 通知中心扩展制作入门
+date: 2014-08-03 11:50:39.000000000 +09:00
+tags: 能工巧匠集
+---
+本文是我的 [WWDC 2014 笔记](http://onevcat.com/2014/07/developer-should-know-about-ios8/) 中的一篇,涉及的 Session 有
+
+* [Creating Extensions for iOS and OS X, Part 1](http://devstreaming.apple.com/videos/wwdc/2014/205xxqzduadzo14/205/205_hd_creating_extensions_for_ios_and_os_x,_part_1.mov?dl=1)
+* [Creating Extensions for iOS and OS X, Part 2](http://devstreaming.apple.com/videos/wwdc/2014/217xxsvxdga3rh5/217/217_hd_creating_extensions_for_ios_and_os_x_part_2.mov?dl=1)
+
+## 总览
+
+扩展 (Extension) 是 iOS 8 和 OSX 10.10 加入的一个非常大的功能点,开发者可以通过系统提供给我们的扩展接入点 (Extension point) 来为系统特定的服务提供某些附加的功能。对于 iOS 来说,可以使用的扩展接入点有以下几个:
+
+* Today 扩展 - 在下拉的通知中心的 "今天" 的面板中添加一个 widget
+* 分享扩展 - 点击分享按钮后将网站或者照片通过应用分享
+* 动作扩展 - 点击 Action 按钮后通过判断上下文来将内容发送到应用
+* 照片编辑扩展 - 在系统的照片应用中提供照片编辑的能力
+* 文档提供扩展 - 提供和管理文件内容
+* 自定义键盘 - 提供一个可以用在所有应用的替代系统键盘的自定义键盘或输入法
+
+系统为我们提供的接入点虽然还比较有限,但是不少已经是在开发者和 iOS 的用户中呼声很高的了。而通过利用这些接入点提供相应的功能,也可以极大地丰富系统的功能和可用性。本文将先不失一般性地介绍一下各种扩展的共通特性,然后再以一个实际的例子着重介绍下通知中心的 Today 扩展的开发方法,以期为 iOS 8 的扩展的学习提供一个平滑的入口。
+
+Apple 指出,iOS 8 中开发者的中心并不应该发生改变,依然应该是围绕 app。在 app 中提供优秀交互和有用的功能,现在是,将来也会是 iOS 应用开发的核心任务。而扩展在 iOS 中是不能以单独的形式存在的,也就是说我们不能直接在 AppStore 提供一个扩展的下载,扩展一定是随着一个应用一起打包提供的。用户在安装了带有扩展的应用后,将可以在通知中心的今日界面中,或者是系统的设置中来选择开启还是关闭你的扩展。而对于开发者来说,提供扩展的方式是在 app 的项目中加入相应的扩展的 target。因为扩展一般来说是展现在系统级别的 UI 或者是其他应用中的,Apple 特别指出,扩展应该保持轻巧迅速,并且专注功能单一,在不打扰或者中断用户使用当前应用的前提下完成自己的功能点。因为用户是可以自己选择禁用扩展的,所以如果你的扩展表现欠佳的话,很可能会遭到用户弃用,甚至导致他们将你的 app 也一并卸载。
+
+### 扩展的生命周期
+
+扩展的生命周期和包含该扩展的你的容器 app (container app) 本身的生命周期是独立的,准确地说。它们是两个独立的进程,默认情况下互相不应该知道对方的存在。扩展需要对宿主 app (host app,即调用该扩展的 app) 的请求做出响应,当然,通过进行配置和一些手段,我们可以在扩展中访问和共享一些容器 app 的资源,这个我们稍后再说。
+
+因为扩展其实是依赖于调用其的宿主 app 的,因此其生命周期也是由用户在宿主 app 中的行为所决定的。一般来说,用户在宿主 app 中触发了该扩展后,扩展的生命周期就开始了:比如在分享选项中选择了你的扩展,或者向通知中心中添加了你的 widget 等等。而所有的扩展都是由 ViewController 进行定义的,在用户决定使用某个扩展时,其对应的 ViewController 就会被加载,因此你可以像在编写传统 app 的 ViewController 那样获取到诸如 `viewDidLoad` 这样的方法,并进行界面构建及做相应的逻辑。扩展应该保持功能的单一专注,并且迅速处理任务,在执行完成必要的任务,或者是在后台预约完成任务后,一般需要尽快通过回调将控制权交回给宿主 app,至此生命周期结束。
+
+按照 Apple 的说法,扩展可以使用的内存是远远低于 app 可以使用的内存的。在内存吃紧的时候,系统更倾向于优先搞掉扩展,而不会是把宿主 app 杀死。因此在开发扩展的时候,也一定需要注意内存占用的限制。另一点是比如像通知中心扩展,你的扩展可能会和其他开发人员的扩展共存,这样如果扩展阻塞了主线程的话,就会引起整个通知中心失去响应。这种情况下你的扩展和应用也就基本和用户说再见了..
+
+### 扩展和容器应用的交互
+
+扩展和容器应用本身并不共享一个进程,但是作为扩展,其实是主体应用功能的延伸,肯定不可避免地需要使用到应用本身的逻辑甚至界面。在这种情况下,我们可以使用 iOS 8 新引入的自制 framework 的方式来组织需要重用的代码,这样在链接 framework 后 app 和扩展就都能使用相同的代码了。
+
+另一个常见需求就是数据共享,即扩展和应用互相希望访问对方的数据。这可以通过开启 App Groups 和进行相应的配置来开启在两个进程间的数据共享。这包括了使用 ` NSUserDefaults` 进行小数据的共享,或者使用 `NSFileCoordinator` 和 `NSFilePresenter` 甚至是 CoreData 和 SQLite 来进行更大的文件或者是更复杂的数据交互。
+
+另外,一直以来的自定义的 url scheme 也是从扩展向应用反馈数据和交互的渠道之一。
+
+这些常见的手段和策略在接下来的 demo 中都会用到。一张图片能顶千言万语,而一个 demo 能顶千张图片。那么,我们开始吧。
+
+## Timer Demo
+
+Demo 做的应用是一个简单的计时器,即点击开始按钮后开始倒数计时,每秒根据剩余的时间来更新界面上的一个表示时间的 Label,然后在计时到 0 秒时弹出一个 alert,来告诉用户时间到,当然用户也可以使用 Stop 按钮来提前打断计时。其实这个 Demo 就是我的很早之前做的一个[番茄工作法的 app](http://pomo.onevcat.com) 的原型。
+
+为了大家方便跟随这个 demo,我把初始的时候的代码放到 GitHub 的 [start-project](https://github.com/onevcat/TimerExtensionDemo/tree/start-project) 这个 tag 上了。语言当然是要用 Swift,界面因为不是 demo 的重点,所以就非常简单能表明意思就好了。但是虽然简单,却也是利用了[上一篇文章](http://onevcat.com/2014/07/ios-ui-unique/)中所提到的 Size Classes 来完成的不同屏幕的布局,所以至少可以说在思想上是完备的 iOS 8 兼容了 =_=..
+
+初始工程运行起来的界面大概是这样的:
+
+
+
+简单说整个项目只有一个 ViewController,点击开始按钮时我们通过设定希望的计时时间来创建一个 `Timer` 实例,然后调用它的 `start` 方法。这个方法接收两个参数,分别是每次剩余时间更新,以及计时结束(不论是计时时间到的完成还是计时被用户打断)时的回调方法。另外这个方法返回一个 tuple,用来表示是否开始成功以及可能的错误。
+
+剩余时间更新的回调中刷新界面 UI,计时结束的回调里回收了 `Timer` 实例,并且显示了一个 `UIAlertController`。用户通过点击 Stop 按钮可以直接调用 `stop` 方法来打断计时。直接简单,没什么其他的 trick。
+
+我们现在计划为这个 app 做一个 Today 扩展,来在通知中心中显示并更新当前的剩余时间,并且在计时完成后显示一个按钮,点击后可以回到 app 本体,并弹出一个完成的提示。
+
+### 添加扩展 Target
+
+第一步当然是为我们的 app 添加扩展。正如在总览中所提到的,扩展是项目中的一个单独的 target。在 Xcode 6 中, Apple 为我们准备了对应各类不同扩展点的 target 模板,这使得向 app 中添加扩展非常容易。对于我们现在想做的 Today 扩展,只需点选菜单的 File > New > Target...,然后选择 iOS 中的 Application Extension 的 Today Extension 就行了。
+
+
+
+在弹出的菜单中将新的 target 命名为 `SimpleTimerTodayExtenstion`,并且让 Xcode 自动生成新的 Scheme,以方便测试使用。我们的工程中现在会多出一个和新建的 target 同名的文件夹,里面主要包含了一个 .swift 的 ViewController 程序文件,一个叫做 `MainInterface` 的 storyboard 文件和 Info.plist。其中在 plist 里 的 `NSExtension` 中定义了这个 扩展的类型和入口,而配套的 ViewController 和 StoryBoard 就是我们的扩展的具体内容和实现了。
+
+我们的主题程序在编译链接后会生成一个后缀为 `.app` 的包,里面包含主程序的二进制文件和各种资源。而扩展 target 将单独生成一个后缀名为 `.appex` 的文件包。这个文件包将随着主体程序被安装,并由用户选择激活或者添加(对于 Today widget 的话在通知中心 Today 视图中的编辑删增,对于其他的扩展的话,使用系统的设置进行管理)。我们可以看到,现在项目的 Product 中已经新增了一个扩展了。
+
+
+
+如果你有心已经打开了 `MainInterface` 文件的话,可以注意到 Apple 已经为我们准备了一个默认的 Hello World 的 label 了。我们这时候只要运行主程序,扩展就会一并安装了。将 Scheme 设为 Simple Timer 的主程序,`Cmd + R`,然后点击 Home 键将 app 切到后台,拉下通知中心。这时候你应该能在 Toady 视图中找到叫做 `SimpleTimerTodayExtenstion` 的项目,显示了一个 Hello World 的标签。如果没有的话,可以点击下面的编辑按钮看看是不是没有启用,如果在编辑菜单中也没有的话,恭喜你遇到了和 Session 视频里的演讲者同样的 bug,你可能需要删除应用,清理工程,然后再安装试试看。一般来说卸载再安装可以解决现在的 beta 版大部分的无法加载的问题,如果还是遇到问题的话,你还可以尝试重启设备(按照以往几年的 SDK 的情况来看,beta 版里这很正常,正式版中应该就没什么问题了)。
+
+如果一切正常的话,你能看到的通知中心应该类似这样:
+
+
+
+这种方式运行的扩展我们无法对其进行调试,因为我们的调试器并没有 attach 到这个扩展的 target 上。有两种方法让我们调试扩展,一种是将 Scheme 设为之前 Xcode 为我们生成的 `SimpleTimerTodayExtenstion`,然后运行时选择从 Today 视图进行运行,如图;另一种是在扩展运行时使用菜单中的 Debug > Attach to Process > By Process Identifier (PID) or name,然后输入你的扩展的名字(在我们的 demo 中是 com.onevcat.SimpleTimer.SimpleTimerTodayExtension)来把调试器挂载到进程上去。
+
+
+
+### 在应用和扩展间共享数据 - App Groups
+
+扩展既然是个 ViewController,那各种连接 `IBOutlet`,使用 `viewDidLoad` 之类的生命周期方法来设置 UI 什么的自然不在话下。我们现在的第一个难点就是,如何获取应用主体在退出时计时器的剩余时间。只要知道了还剩多久以及何时退出,我们就能在通知中心中显示出计时器正确的剩余时间了。
+
+对 iOS 开发者来说,沙盒限制了我们在设备上随意读取和写入。但是对于应用和其对应的扩展来说,Apple 在 iOS 8 中为我们提供了一种可能性,那就是 App Groups。App Groups 为同一个 vender 的应用或者扩展定义了一组域,在这个域中同一个 group 可以共享一些资源。对于我们的例子来说,我们只需要使用同一个 group 下的 `NSUserDefaults` 就能在主体应用不活跃时向其中存储数据,然后在扩展初始化时从同一处进行读取就行了。
+
+首先我们需要开启 App Groups。得益于 Xcode 5 开始引入的 Capabilities,这变得非常简单(至少不再需要去 developer portal 了)。选择主 target `SimpleTimer`,打开它的 Capabilities 选项卡,找到 App Groups 并打开开关,然后添加一个你能记得的 group 名字,比如 `group.simpleTimerSharedDefaults`。接下来你还需要为 `SimpleTimerTodayExtension` 这个 target 进行同样的配置,只不过不再需要新建 group,而是勾选刚才创建的 group 就行。
+
+
+
+然后让我们开始写代码吧!首先是在主体程序的 `ViewController.swift` 中添加一个程序失去前台的监听,在 `viewDidLoad` 中加入:
+
+```
+NSNotificationCenter.defaultCenter()
+ .addObserver(self, selector: "applicationWillResignActive",name: UIApplicationWillResignActiveNotification, object: nil)
+```
+
+然后是所调用的 `applicationWillResignActive` 方法:
+
+```
+@objc private func applicationWillResignActive() {
+ if timer == nil {
+ clearDefaults()
+ } else {
+ if timer.running {
+ saveDefaults()
+ } else {
+ clearDefaults()
+ }
+ }
+}
+
+private func saveDefaults() {
+ let userDefault = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults")
+ userDefault.setInteger(Int(timer.leftTime), forKey: "com.onevcat.simpleTimer.lefttime")
+ userDefault.setInteger(Int(NSDate().timeIntervalSince1970), forKey: "com.onevcat.simpleTimer.quitdate")
+
+ userDefault.synchronize()
+}
+
+private func clearDefaults() {
+ let userDefault = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults")
+ userDefault.removeObjectForKey("com.onevcat.simpleTimer.lefttime")
+ userDefault.removeObjectForKey("com.onevcat.simpleTimer.quitdate")
+
+ userDefault.synchronize()
+}
+```
+
+这样,在应用切到后台时,如果正在计时,我们就将当前的剩余时间和退出时的日期存到了 `NSUserDefaults` 中。这里注意,可能一般我们在使用 `NSUserDefaults` 时更多地是使用 `standardUserDefaults`,但是这里我们需要这两个数据能够被扩展访问到的话,我们必须使用在 App Groups 中定义的名字来使用 `NSUserDefaults`。
+
+接下来,我们可以到扩展的 `TodayViewController.swift` 中去获取这些数据了。在扩展 ViewController 的 `viewDidLoad` 中,添加以下代码:
+
+```
+let userDefaults = NSUserDefaults(suiteName: "group.simpleTimerSharedDefaults")
+let leftTimeWhenQuit = userDefaults.integerForKey("com.onevcat.simpleTimer.lefttime")
+let quitDate = userDefaults.integerForKey("com.onevcat.simpleTimer.quitdate")
+
+let passedTimeFromQuit = NSDate().timeIntervalSinceDate(NSDate(timeIntervalSince1970: NSTimeInterval(quitDate)))
+
+let leftTime = leftTimeWhenQuit - Int(passedTimeFromQuit)
+
+lblTImer.text = "\(leftTime)"
+```
+
+当然别忘了把 StoryBoard 的那个 label 拖出来:
+
+```
+@IBOutlet weak var lblTImer: UILabel!
+```
+
+再次运行程序,并开始一个计时,然后按 Home 键切到后台,拉出通知中心,perfect,我们的扩展能够和主程序进行数据交互了:
+
+
+
+### 在应用和扩展间共享代码 - Framework
+
+接下来的任务是在 Today 界面中进行计时,来刷新我们的界面。这部分代码其实我们已经写过(当然..确切来说是我写的,你可能只是看过),没错,就是应用中的 `Timer.swift` 文件。我们只需要在扩展的 ViewController 中用剩余时间创建一个 `Timer` 的实例,然后在更新的 callback 里设置 label 就好了嘛。但是问题是,这部分代码是在应用中的,我们要如何在扩展中也能使用它呢?
+
+一个最直接也是最简单的想法自然是把 `Timer.swift` 加入到扩展 target 的编译文件中去,这样在扩展中自然也就可以使用了。但是 iOS 8 开始 Apple 为我们提供了一个更好的选择,那就是做成 Framework。单个文件可能不会觉得有什么差别,但是随着需要共用的文件数量和种类的增加,将单个文件逐一添加到不同 target 这种管理方法很快就会将事情弄成一团乱麻。你需要考虑每一个新加或者删除的文件影响的范围,以及它们分别需要适用何处,这简直就是人间地狱。提供一个统一漂亮的 framework 会是更多人希望的选择(其实也差不多成为事实标准了)。使用 framework 进行模块化的另一个好处是可以得益于良好的访问控制,以保证你不会接触到不应该使用的东西,然后,Swift 的 namespace 是基于模块的,因此你也不再需要担心命名冲突等等一摊子 objc 时代的烦心事儿。
+
+现在让我们把 `Timer.swift` 放到 framework 里吧。首先我们新建一个 framework 的 target。File > New > Target... 中选择 Framework & Library,选中 Cocoa Touch Framework (配图中的另外几个选项可能在你的 Xcode 中是没有的,请无视它们,这是历史遗留问题),然后确定。按照 Apple 对 framework 的命名规范,也许 `SimpleTimerKit` 会是一个不错的名字。
+
+
+
+接下来,我们将 `Timer.swift` 从应用中移动到 framework 中。很简单,首先将其从应用的 target 中移除,然后加入到新建的 `SimpleTimerKit` 的 Compile Sources 中。
+
+
+
+确认在应用中 link 了新的 framwork,并且在 ViewController.swift 中加上 `import SimpleTimerKit` 后试着编译看看...好多错误,基本都是 ViewController 中说找不到 Timer 之类的。这是因为原来的实现是在同一个 module 中的,默认的 `internal` 的访问层级就可以让 ViewController 访问到关于 `Timer` 和相应方法的信息。但是现在它们处于不同的 module 中,所以我们需要对 `Timer.swift` 的访问权限进行一些修改,在需要外部访问的地方加上 `public` 关键字。关于 Swift 中的访问控制,可以参考 Apple 关于 Swift 的这篇[官方博客](https://developer.apple.com/swift/blog/?id=5),简单说就是 `private` 只允许本文件访问,不写的话默认是 `internal`,允许统一 module 访问,而要提供给别的 module 使用的话,需要声明为 public。修改后的 `Timer.swift` 文件大概是[这个样子](https://github.com/onevcat/TimerExtensionDemo/blob/master/SimpleTimer/SimpleTimer/Timer.swift)的。
+
+修改合适的访问权限后,接下来我们就可以将这个 framework 链接到扩展的 target 了。链接以后编译什么的可以通过,但是会多一个警告:
+
+
+
+这是因为作为插件,需要遵守更严格的沙盒限制,所以有一些 API 是不能使用的。为了避免这个警告,我们需要在 framework 的 target 中声明在我们使用扩展可用的 API。具体在 `SimpleTimerKit` 的 target 的 General 选项卡中,将 Deployment Info 中的 Allow app extension API only 勾选上就可以了。关于在扩展里不能使用的 API,都已经被 Apple 标上了 `NS_EXTENSION_UNAVAILABLE`,在[这里](https://gist.github.com/joeymeyer/0cb033698bfa5a0420f6)有一份简单的列表可供参考,基本来说都是 runtime 的东西以及一些会让用户迷惑或很危险的操作(当然这个标记的方法很可能会不断变动,最终一切请以 Apple 的文档和实际代码为准)。
+
+
+
+接下来,在扩展的 ViewController 中也链接 `SimpleTimerKit` 并加入 `import SimpleTimerKit`,我们就可以在扩展中使用 `Timer` 了。将刚才的直接设置 label 的代码去掉,换成下面的:
+
+```
+override func viewDidLoad() {
+ //...
+
+ if (leftTime > 0) {
+ timer = Timer(timeInteral: NSTimeInterval(leftTime))
+ timer.start(updateTick: {
+ [weak self] leftTick in self!.updateLabel()
+ }, stopHandler: nil)
+ } else {
+ //Do nothing now
+ }
+}
+
+private func updateLabel() {
+ lblTimer.text = timer.leftTimeString
+}
+```
+
+我们在扩展里也像在 app 内一样,创建 `Timer`,给定回调,坐等界面刷新。运行看看,先进入应用,开始一个计时。然后退出,打开通知中心。通知中心中现在也开始计时了,而且确实是从剩余的时间开始的,一切都很完美:
+
+
+
+### 通过扩展启动主体应用
+
+最后一个任务是,我们想要在通知中心计时完毕后,在扩展上呈现一个 "完成啦" 的按钮,并通过点击这个按钮能回到应用,并在应用内弹出结束的 alert。
+
+这其实最关键的在于我们要如何启动主体容器应用,以及向其传递数据。可能很多同学会想到 URL Scheme,没错通过 URL Scheme 我们确实可以启动特定应用并携带数据。但是一个问题是为了通过 URL 启动应用,我们一般需要调用 `UIApplication` 的 openURL 方法。如果细心的刚才看了 `NS_EXTENSION_UNAVAILABLE` 的同学可能会发现这个方法是被禁用的(这也是很 make sense 的一件事情,因为说白了扩展通过 `sharedApplication` 拿到的其实是宿主应用,宿主应用表示凭什么要让你拿到啊!)。为了完成同样的操作,Apple 为扩展提供了一个 `NSExtensionContext` 类来与宿主应用进行交互。用户在宿主应用中启动扩展后,宿主应用提供一个上下文给扩展,里面最主要的是包含了 `inputItems` 这样的待处理的数据。当然对我们现在的需求来说,我们只要用到它的 `openURL(URL:,completionHandler:)` 方法就好了。
+
+另外,我们可能还需要调整一下扩展 widget 的尺寸,以让我们有更多的空间显示按钮,这可以通过设定 `preferredContentSize` 来做到。在 `TodayViewController.swift` 中加入以下方法:
+
+```
+private func showOpenAppButton() {
+ lblTimer.text = "Finished"
+ preferredContentSize = CGSizeMake(0, 100)
+
+ let button = UIButton(frame: CGRectMake(0, 50, 50, 63))
+ button.setTitle("Open", forState: UIControlState.Normal)
+ button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
+
+ view.addSubview(button)
+}
+```
+
+在设定 `preferredContentSize` 时,指定的宽度都是无效的,系统会自动将其处理为整屏的宽度,所以扔个 0 进去就好了。在这里添加按钮时我偷了个懒,本来应该使用Auto Layout 和添加约束的,但是这并不是我们这个 demo 的重点。另一方面,为了代码清晰明了,就直接上坐标了。
+
+然后添加这个按钮的 action:
+
+```
+@objc private func buttonPressed(sender: AnyObject!) {
+ extensionContext.openURL(NSURL(string: "simpleTimer://finished"), completionHandler: nil)
+}
+```
+
+我们将传递的 URL 的 scheme 是 `simpleTimer`,以 host 的 `finished` 作为参数,就可以通知主体应用计时完成了。然后我们需要在计时完成时调用 `showOpenAppButton` 来显示按钮,更新 `viewDidLoad` 中的内容:
+
+```
+override func viewDidLoad() {
+ //...
+ if (leftTime > 0) {
+ timer = Timer(timeInteral: NSTimeInterval(leftTime))
+ timer.start(updateTick: {
+ [weak self] leftTick in self!.updateLabel()
+ }, stopHandler: {
+ [weak self] finished in self!.showOpenAppButton()
+ })
+ } else {
+ showOpenAppButton()
+ }
+}
+```
+
+最后一步是在主体应用的 target 里设置合适的 URL Scheme:
+
+
+
+然后在 `AppDelegate.swift` 中捕获这个打开事件,并检测计时是否完成,然后做出相应:
+
+```
+func application(application: UIApplication!, openURL url: NSURL!, sourceApplication: String!, annotation: AnyObject!) -> Bool {
+ if url.scheme == "simpleTimer" {
+ if url.host == "finished" {
+ NSNotificationCenter.defaultCenter()
+ .postNotificationName(taskDidFinishedInWidgetNotification, object: nil)
+ }
+ return true
+ }
+
+ return false
+}
+```
+
+在这个例子里,我们发了个通知。而在 ViewController 中我们可以一开始就监听这个通知,然后收到后停止计时并弹出提示就行了。当然我们可能需要一些小的重构,比如添加是手动打断还是计时完成的判断以弹出不一样的对话框等等,这些都很简单再次就不赘述了。
+
+
+
+至此,我们就完成了一个很基本的通知中心扩展,完整的项目可以在 [GitHub repo 的 master](https://github.com/onevcat/TimerExtensionDemo) 上找到。这个计时器现在在应用中只在前台或者通知中心显示时工作,如果你退出应用后再打开应用,其实这段时间内是没有计时的。因此这个项目之后可能的改进就是在返回应用的时候添加一下计时的判定,来更新计时器的剩余时间,或者是已经完成了的话就直接结束计时。
+
+### 其他
+
+其实在 Xcode 为我们生成的模板文件中,还有这么一段代码也很重要:
+
+```
+func widgetPerformUpdateWithCompletionHandler(completionHandler: ((NCUpdateResult) -> Void)!) {
+ // Perform any setup necessary in order to update the view.
+
+ // If an error is encoutered, use NCUpdateResult.Failed
+ // If there's no update required, use NCUpdateResult.NoData
+ // If there's an update, use NCUpdateResult.NewData
+
+ completionHandler(NCUpdateResult.NewData)
+}
+```
+
+对于通知中心扩展,即使你的扩展现在不可见 (也就是用户没有拉开通知中心),系统也会时不时地调用实现了 `NCWidgetProviding` 的扩展的这个方法,来要求扩展刷新界面。这个机制和 [iOS 7 引入的后台机制](http://onevcat.com/2013/08/ios7-background-multitask/)是很相似的。在这个方法中我们一般可以做一些像 API 请求之类的事情,在获取到了数据并更新了界面,或者是失败后都使用提供的 `completionHandler` 来向系统进行报告。
+
+值得注意的一点是 Xcode (至少现在的 beta 4) 所提供的模板文件的 ViewController 里虽然有这个方法,但是它默认并没有 conform 这个接口,所以要用的话,我们还需要在类声明时加上 `NCWidgetProviding`。
+
+## 总结
+
+这个 Demo 主要涉及了通知中心的 Toady widget 的添加和一般交互。其实扩展是一个相当大块的内容,对于其他像是分享或者是 Action 的扩展,其使用方式又会有所不同。但是核心的概念,生命周期以及与本体应用交互的方法都是相似的。Xcode 在我们创建扩展时就为我们提供了非常好的模版文件,更多的时候我们要做的只不过是在相应的方法内填上我们的逻辑,而对于配置方面基本不太需要操心,这一点还是非常方便的。
+
+就为了扩展这个功能,我已经迫不及待地想用上 iOS 8 了..不论是使用别人开发的扩展还是自己开发方便的扩展,都会让这个世界变得更美好。
diff --git a/_posts/2014-09-24-bye-kayac.markdown b/_posts/2014-09-24-bye-kayac.markdown
new file mode 100644
index 00000000..06991599
--- /dev/null
+++ b/_posts/2014-09-24-bye-kayac.markdown
@@ -0,0 +1,71 @@
+---
+layout: post
+title: 偷得浮生半月闲
+date: 2014-09-24 21:57:31.000000000 +09:00
+tags: 胡言乱语集
+---
+
+
+还有三天就正好是到日本两周年整的日子了。
+
+最近忙完了包括写书和为 iOS 8 更新 app 在内的一摊子事儿,今天又从 Kayac 办妥了离职手续,所以正式进入无业状态,也算是和这一段时期的忙碌道别了。之后会在下个月中旬入职 Line,中间有半个月时间可以用来陪陪父母到处走走,以及看书充电,也算是忙里偷闲。
+
+## 近期的爱好
+
+两年时间说长不长,说短也不短。现在回头回顾和总结这两年的生活,可以说是充实而丰满的。两年前自己在人生十字路口的选择看来还算明智,和老婆一起在这个国度里可以很专心地生活,这样就很好。自己觉得现在的精神状态也还能算及格,大抵是做到了恬静自然。因为这份心境,最近也培养了许多兴趣爱好,难得写一篇非技术文,就来小扒一下吧~
+
+### 写作
+
+因为用一段很集中的时间写了一本[关于 Swift 的书](http://swifter.tips/buy),其中感悟还是颇深,并发现写作确实是一件很有意思的事情。在写书的过程中其实遇到很不少的挫折和苦楚,也体会到了这件事情实际上并没有表面看起来那么简单。因为毕竟以前的写作经验有限,除了高考的范式作文和学术论文以外,最多的就是博客里的一些技术文章了。高考作文我是有信心的,因为至少在最后的实战里拿到了满分;学术论文我是不关心的,因为能看到的人少之又少;技术博文我是很用心的,但因为毕竟是自己的一方天地,但就算写得不对不好也就是个“有限责任博客”,所以也没什么压力。真正到了写书出版,特别是有偿售卖的时候,境况就有很大不同了。
+
+最大的是责任心。虽然说现在就算在技术出版界里狗屁文章多如牛毛,但是我自己在对待获取知识和传播知识这件事上是严肃认真的。遑论水平高低,在这个 $0.99 的 app 都得辛勤维护好几年的年代,我找不到一本动辄售价 10 美元的书却草草了事的理由 (当然在国内电子书可能想卖 10 美元还不太现实,于是我选了个低价出售)。对于这本书,因为 Swift 刚刚新生,可能还面临很多变动,所以我至少会在今后两年内维护和更新,这也是我选择电子版而不太倾向于那么快与出版社合作做纸质书的初衷。
+
+另外,其实在这个特殊的年代和国度里,出书并算不上是一件回报很高的事情。其实想想,无数次去求证一些细节,或者把所有资料清查验证,一直是很困难的。特别是在现在的比十年前那个“信息爆炸”的年代更爆炸十倍以上的时候,对信息的过滤还来不及的情况下,却因为写书要接触到更多。实在担心脑容量不够用,或者精力被彻底耗尽。说白了现在发现认真地写一本书,确实是操着卖白粉的心,却赚到的是卖白菜的钱。(好吧我承认现在白菜也不便宜就是了)
+
+虽然并不反对素食,但我也不喜欢天天只吃白菜..所以说,想靠写作或者出书来赚到生活费显然是不现实的。因此我决定继续将这件事情作为一个业余爱好,这样我就能很开心地投入其中了。
+
+嘛,博客当然会一直写下去,书的话希望还有下一次这样的开心和投入其中的机会吧。
+
+### 摄影
+
+最近入了个 70D 开始玩摄影,虽然只是个入门设备,但是和其他众多摄影爱好者一样,我觉得这会是我由富返贫的第一步。入手单反的主要原因还是 iPhone 的相机已经满足不了对照片质量的需求以及自己对摄像手法的追求了。在有些弱光条件下 iPhone 的表现实在连差强人意都说不上。另外,自己对于快门和光圈的控制欲也愈来愈强,用参数的搭配来造成的观感差异是一件非常有意思,以及值得去追求的事情。(虽然在 iOS 8 里 Apple 开放了相机的参数控制,并且也出现了像 [Manual](https://itunes.apple.com/cn/app/manual-custom-exposure-camera/id917146276?mt=8) 这样的 app,但还远远不够)。
+
+暂时现在会只注重拍摄本身,而不会去涉及太多后期的事情。作品在整理之后都会放到 500px 上[我的页面](http://500px.com/onevcat)。因为刚刚入门,所以很多片子肯定都很幼稚,希望能与大家多交流,欢迎各种指导。
+
+然后还顺便入了台打印机用来印照片,因为总是在手机平板或者电脑里看的话,总是觉着缺点什么。个人还是很喜欢小时候那样的一张张翻看实际照片的体验,可能算是一种对童年的回忆的向往吧。
+
+当然这个爱好大概会长期坚持下去,因为总感觉不把镜头或者相机照坏的话,就亏大了。另外,大概终于可以把[自我介绍](http://about.onevcat.com/#/welcome)里的 “iPhone 摄影爱好者”改成真正的“摄影爱好者”
+
+另外,还定了目标,希望有朝一日能有资格用上[类似这样的镜头](http://www.zeiss.com/camera-lenses/en_de/camera_lenses/otus/otus1485.html)!加油加油~
+
+### 编译器
+
+这其实算是主职工作的延伸。因为自己并非 CS 出身,很多基础概念是缺失的,需要补习这方面的知识而已。主要途径是跟斯坦福的[编译器课程](https://www.coursera.org/course/compilers),然后找像龙书和针对性日常会用到的 LLVM 的一些书在看。
+
+其实自己离开校园后就一直都在各种项目之间穿梭,很久没有静下心来学点东西了。之前研究 Swift 的时候觉得很多地方挺吃力的,于是就萌发了看一看计算机原理和编译相关内容的想法。折腾一番下来,好消息是其实入门没有想象中的难,比如像使用 lex 或者 bison 这样东西来作词法和语法分析器这样的活儿,基本也就是依靠经验和需求对语言进行设计;但是想要深入的话却似乎障碍重重,不论是各种情况的考虑,或者是编译器优化背后的故事,无一不是值得花费大量时间研究的内容。
+
+但是这个过程确实很有意思,不断积累、体会和玩味。虽然现在来说只是我的玩具,但也许未来某天就会有新的想法,而我也必须为此准备。
+
+### APL
+
+其实说起来 APL 是从去年就开始的兴趣爱好了。没错,就是那个 [A Programming Language](https://en.wikipedia.org/wiki/APL_(programming_language))。从前年开始决定了每年学一门新语言,2012 年的日语,去年的 APL,到今年的 Swift (众人:Swift 也算啊拖出去吊打..),算是把这个目标坚持了三年,其中最有意思的还是 APL。最早知道 APL 还是在读 [Masterminds of Programming](http://www.amazon.com/Masterminds-Programming-Conversations-Creators-Languages/dp/0596515170) 的时候,各种大师都提到从这门语言中受益良多,由此产生了兴趣。
+
+
+
+初识就觉得很 cool,连键盘都需要特制的..所以理所当然地很快就喜欢上这门语言。然后去年底大概断续花了两三个月的时间学了一下。虽然确实已经是很古老的语言,现在也基本没有使用的场景了,但是从那些“古怪”的符号中散发出的迷人的魅力还是让人心醉神怡。
+
+在[这里](http://apl.onevcat.com)架设有一个 APL 的在线解释器,有兴趣的朋友可以去感受一下。
+
+## 说说 Kayac
+
+虽然从 Kayac 离职了,但是 Kayac 的风格还是深深地烙在了我的身上。你可以说这是一种玩世不恭,也可以说是对传统的反叛,但是无论如何不可否认 Kayac 确实是很与众不同的一家企业。这并不是说 Kayac 一定能够 (或者需要) 做强做大,但是在同质化非常严重的这个年代,保持一份特立独行和与众不同的企业精神可以说非常难能可贵。
+
+另外一方面,Kayac 里有不少中国员工,而且最近也开始非常重视中国市场。最近才和 IP 的原厂合作新推出了一款完成了中文本地化的作品[口袋酪农 - 银之匙](https://itunes.apple.com/cn/app/yin-zhi-shi-silver-spoon-guan/id904828672?l=en&mt=8)。虽然有点广告嫌疑,但是有兴趣的朋友不妨可以看看,感受一下 Kayac Style 的游戏制作。
+
+也许很快会有中国事业部,也许如果我现在不离开的话这对我来说也会是职场上的一次很好的机会,但是谁又知道会如何呢?两年前我放弃了一个很好的机会来到日本,两年后我似乎又选择了同样的道路重新开始。虽然但是唯一可以肯定的是我并没有在原地打转,谁又知道下一个机会是不是就在前面的路口呢?
+
+## 之后的生活
+
+半个月的空闲时间最高!首先当然是陪父母逛日本,然后还会剩下十天左右打算做个新 app,当做对 iOS 8 的学习和用 Swift 练手。当然还有一篇 WWDC 14 笔记的更新,虽然已经拖得比较晚了,但是也总比坑掉强。
+
+然后就是在新公司继续做好工作,估计还会在日本再待上一段时间。(好吧其实是日元贬值太厉害,没钱回国了..)
diff --git a/_posts/2014-10-25-ib-customize-view.markdown b/_posts/2014-10-25-ib-customize-view.markdown
new file mode 100644
index 00000000..c5120cf4
--- /dev/null
+++ b/_posts/2014-10-25-ib-customize-view.markdown
@@ -0,0 +1,232 @@
+---
+layout: post
+title: WWDC 2014 Session笔记 - 可视化开发,IB 的新时代
+date: 2014-10-25 00:02:07.000000000 +09:00
+tags: 能工巧匠集
+---
+本文是我的 [WWDC 2014 笔记](http://onevcat.com/2014/07/developer-should-know-about-ios8/) 中的一篇,涉及的 Session 有
+
+* [What's New in Xcode 6](http://devstreaming.apple.com/videos/wwdc/2014/401xxfkzfrjyb93/401/401_whats_new_in_xcode_6.pdf?dl=1)
+* [What's New in Interface Builder](http://devstreaming.apple.com/videos/wwdc/2014/411xx0xo98zzoor/411/411_whats_new_in_interface_builder.pdf?dl=1)
+
+如果说在 WWDC 14 之前 Interface Builder (IB) 还是可选项的话,我相信在此之后 IB 已经是毫无疑问的 iOS 开发标配了,纯代码界面可以说已经渐行渐远,可以逐渐离开我们的视线了。
+
+一言蔽之,就是 Apple 在催促大家使用 IB,特别是 Storyboard 做为界面开发的唯一选择这件事上,下定了决心,也做出了实际的行动。
+
+如果是纯代码 UI 在此之前还能[有所挣扎](http://onevcat.com/2013/12/code-vs-xib-vs-storyboard/)的话,那么压死这个方案的最后一根稻草就是 Size Classes。我已经在[之前的笔记](http://onevcat.com/2014/07/ios-ui-unique/)中对这方面内容做了些简单的探索,但是还远远不够,也许在将来某一天我还会重新整理下 Size Classes 这个主题的内容,以及使用 IB 适配不同屏幕的一些实践,但是不是这次。这篇文章里想要介绍的是 Xcode 6 中为 IB 锦上添花的一个特性,那就是实时地预览自定义 view,这个特性让 IB 开发的流程更加直观可视,也可以减少很多无聊的参数配置和 UI 设置的时间。
+
+## 以前 IB 的不足
+
+作为可视化开发的工具,IB 和 Storyboard 在组织和构建 ViewController 及其导航关系时已经做得很好的。对于 ViewController 的 view 画布上的诸如 `UILabel` 或者 `UIImageView` 这样的基础的类,IB 是能够很好地支持并实时在设计的时候进行显示的。但是对于那些自定义的类,之前的 IB 就束手无策了。我们能做的仅仅是在 IB 中拖放一个 `UIView`,然后通过将 `Custom Class` 属性设置为我们自定义的 `UIView` 的子类来在 “暗示” IB 在运行时初始化一个对应的子类。这样的问题是在开发自定义的 view 时,我们不得不一遍遍地修改代码并运行,再根据运行结果进行调整和修正。而实际上,单一对某个 view 的调试这种问题只涉及到设计层面,而非运行层面,如果我们能够在设计时就有一个实时地对自定义 view 的预览该多好。
+
+没错,Apple 也是这么想的,并且在 Xcode 6 中,我们就已经可以创建这样的 `UIView` 子类了:利用新加入的 `@IBDesignable` 和 `@IBInspectable`,我们可以非常方便地完成在 IB 中实时显示自定义视图,甚至和其他一些内置 `UIView` 子类一样,直接在 IB 的 Inspector 改变某些属性,甚至我们还能通过设置断点来在 IB 中显示视图时进行调试。新的这些特性非常强大,使用起来却出乎意料的简单。下面我将通过一个实际的小例子加以说明。最终的完整例子已经放在 [GitHub](https://github.com/onevcat/ClockFaceView) 上了,现在我们从开始一步步开始吧。这些代码基于 Xcode 6.1 和 Swift 1.1。
+
+## 时钟 view 的例子
+
+### 单纯的自定义 view
+
+假设我们有一个自定义的 view,用来描画一个时钟,如果有在读 [objc.io](http://objc.io) 或者 [objc 中国](http://objccn.io) 的读者,可能会发现这段代码是[动画一章一篇文章里代码](http://objccn.io/issue-12-2/)的改造过的 Swift 版本。
+
+在这里我们有一个自定义的 `UIView` 的子类:`ClockFaceView`,其中嵌套了一个 `ClockFaceLayer` 作为 layer。如果我们不需要动画,我们也可以简单地使用 `-drawRect:` 来完成绘制。但是在这里我们还是选择使用添加 `CALayer` 的方式,这会使之后做动画简单好几个数量级 -- 因为我们可以简单地通过 CA 动画而不是每帧去计算绘制来完成动画 (在这篇帖子里不会涉及这些内容)。
+
+ // ClockFaceView.swift
+ import UIKit
+
+ class ClockFaceView : UIView {
+
+ class ClockFaceLayer : CAShapeLayer {
+
+ private let hourHand: CAShapeLayer
+ private let minuteHand: CAShapeLayer
+
+ override init() {
+ hourHand = CAShapeLayer()
+ minuteHand = CAShapeLayer()
+
+ super.init()
+ frame = CGRect(x: 0, y: 0, width: 200, height: 200)
+ path = UIBezierPath(ovalInRect: CGRectInset(frame, 5, 5)).CGPath
+ fillColor = UIColor.whiteColor().CGColor
+ strokeColor = UIColor.blackColor().CGColor
+ lineWidth = 4
+
+ hourHand.path = UIBezierPath(rect: CGRect(x: -2, y: -70, width: 4, height: 70)).CGPath
+ hourHand.fillColor = UIColor.blackColor().CGColor
+ hourHand.position = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
+ addSublayer(hourHand)
+
+ minuteHand.path = UIBezierPath(rect: CGRect(x: -1, y: -90, width: 2, height: 90)).CGPath
+ minuteHand.fillColor = UIColor.blackColor().CGColor
+ minuteHand.position = CGPoint(x: bounds.size.width / 2, y: bounds.size.height / 2)
+ addSublayer(minuteHand)
+ }
+
+ required init(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ func refreshToHour(hour: Int, minute: Int) {
+ hourHand.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(Double(hour) / 12.0 * 2.0 * M_PI)))
+ minuteHand.setAffineTransform(CGAffineTransformMakeRotation(CGFloat(Double(minute) / 60.0 * 2.0 * M_PI)))
+ }
+ }
+
+ private let clockFace: ClockFaceLayer
+
+ var time: NSDate? {
+ didSet {
+ refreshTime()
+ }
+ }
+
+ private func refreshTime() {
+ if let realTime = time {
+ if let calendar = NSCalendar(calendarIdentifier: NSGregorianCalendar) {
+ let components = calendar.components(NSCalendarUnit.CalendarUnitHour |
+ NSCalendarUnit.CalendarUnitMinute, fromDate: realTime)
+ clockFace.refreshToHour(components.hour, minute: components.minute)
+ }
+ }
+ }
+
+ override init(frame: CGRect) {
+ clockFace = ClockFaceLayer()
+ super.init(frame: frame)
+ layer.addSublayer(clockFace)
+ }
+
+ required init(coder aDecoder: NSCoder) {
+ clockFace = ClockFaceLayer()
+ super.init(coder: aDecoder)
+ layer.addSublayer(clockFace)
+ }
+ }
+
+如果你没有耐心看完的话也没有关系,简单来说就是 `ClockFaceView` 在被初始化时会向自己添加一个 `ClockFaceLayer`,用来显示分针和时针。通过设置 `time` 属性我们可以更新时钟的位置。因为提供了 `initWithCoder:`,因此我们是可以直接从 IB 里加载这个 view 的。方法就是最普通的类型指定,并让 app 在加载时初始化对应的类型:在新建的 Single View Application 的 Storyboard 中添加一个 `UIView` 控件,然后设置好约束,并且将 Class 设置为 `ClockFaceView`:
+
+
+
+运行应用,可以看到 `ClockFaceView` 被正确地初始化了,指针指向默认的 12 点整。通过为这个 view 建立 outlet 或者用其他 (比如 tag 的方式,虽然我不太喜欢这么做,但是我见过不少人这么弄) 方法找到这个 `ClockFaceView` 并设置时间的话,我们可以正确地改变其时针和分针的指向:
+
+ // ViewController.swift
+ class ViewController: UIViewController {
+
+ @IBOutlet weak var clockFaceView: ClockFaceView!
+
+ override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+
+ clockFaceView.time = NSDate()
+ }
+ }
+
+
+
+### IBDesignable,IB 中自定义 view 的渲染
+
+把大象装进冰箱有三个步骤,而让 IB 显示自定义 view 居然只有一个步骤!
+
+只要我们在 `class ClockFaceView : UIView` 这个类型定义上面加上一个 `@IBDesignable` 的标记,就完成了!
+
+在进行更改并等待编译和 IB 自动识别后,我们就可以在 IB 中原来一块白色的地方看到初始化后的时钟了:
+
+
+
+如你所想,这个标记的作用是告诉 IB 如果遇到对应的 `UIView` 子类的话,可以对其进行渲染。深入一些来说,IB 将寻找你的子类中的 `-initWithFrame:` 方法,并给入当前自定义 view 的 frame 对其进行调用。需要注意的是,在使用 IB 初始化 view 时,被调用的是 `-initWithCoder:` 而非 frame 版本,所以说在想要实现自定义 view 在 IB 中的预览的话,我们至少必须实现这两个版本的初始化方法。不过好消息是,如果我们只添加了 `@IBDesignable`,而忘了实现 `-initWithFrame:` 的话,在 IB 渲染 view 时会给我们抛出大大的错误,所以因为遗漏而花大量时间在查找哪里出了问题这种事情应该不太可能发生。
+
+
+
+### 仅设计时的配置
+
+现在在 IB 中我们显示的时钟只能默认地指向 0 点 0 分,这是因为在设计的时候,我们并没有机会去设定这个 view 的 `time` 属性,所以时针和分针都停留在了初始的位置上。在 Xcode 6 中可以在 `@IBDesignable` 标记的 `UIView` 子类中添加一个 `prepareForInterfaceBuilder` 方法。每次在 IB 即将把这个自定义的 view 渲染到画布之前会调用这个方法进行最后的配置。比如我们想在 IB 中这个时钟的 view 上显示当前时间的话,可以在 `ClockFaceView` 中加入这个方法:
+
+ class ClockFaceView : UIView {
+ //...
+
+ override func prepareForInterfaceBuilder() {
+ time = NSDate()
+ }
+
+ //...
+ }
+
+保存并切换到 IB,静待自动编译和执行,可以看到类似下面的结果:
+
+
+
+挺好的...现在我们的 IB 不仅被用来设计界面了,还兼备了看时间的功能 - 虽然这个时钟并不是实时的,只有在切换编辑器界面到 IB 或者是修改了相关文件时才会进行刷新。
+
+另外虽然这篇文章没有涉及,但是需要一提的是,如果你想要在 `prepareForInterfaceBuilder` 里加载图片的话,需要弄清楚 bundle 的概念。IB 使用的 bundle 和 app 运行时的 `mainBundle` 不是一个概念,我们需要在设计时的 IB 的 bundle 可以通过在自定义的 view 自身的 bundle 中进行查找并使用。比如想要加载一张名为 `image.png` 的图片的话:
+
+ let bundle = NSBundle(forClass: self.dynamicType)
+ if let fileName = bundle.pathForResource("image", ofType: "png") {
+ if let image = UIImage(contentsOfFile: fileName) {
+ // 在此处可以使用 image
+ }
+ }
+
+在使用 IB 中的方法读取资源时一定要注意运行环境不同这一点。
+
+### 用 IBInspectable 在 IB 中调整属性
+
+IBDesignable 的 view 的另一个很方便的地方是我们可以向 Inspector 中添加自定义的内容了。通过这样做,就可以直接在 IB 中对 view 进行一些编辑和配置。以前对于自定义 view,我们通常只能通过用类似 `IBOutlet` 的方式在代码中进行设置,或者是配置 Runtime Attribute 来进行,而现在我们有能力直接通过像给一个 `UILabel` 设定字符串或者给 `UIImageView` 设定图片这样的方式来设置自定义 view 的部分属性了,这也使得在 IB 中的自定义 view 的易用性和完整性得到了极大增强。
+
+使用方法也非常简单,只需要在某个属性前加上 `@IBInspectable` 标记即可。比如我们可以在 `ClockFaceView` 中加入以下代码:
+
+ class ClockFaceView : UIView {
+ //...
+
+ @IBInspectable
+ var color: UIColor? {
+ didSet {
+ refreshColor()
+ }
+ }
+
+ private func refreshColor() {
+ if let realColor = color {
+ clockFace.refreshColor(realColor)
+ }
+ }
+
+ //...
+ }
+
+然后在 `ClockFaceLayer` 中加入对应的 `refreshColor` 方法:
+
+ class ClockFaceLayer : CAShapeLayer {
+ //...
+
+ func refreshColor(color: UIColor) {
+ hourHand.fillColor = color.CGColor
+ minuteHand.fillColor = color.CGColor
+ strokeColor = color.CGColor
+ }
+
+ //...
+ }
+
+我们对 `ClockFaceView` 中的 `color` 属性添加了 `@IBInspectable`,在保存和编译后,这会在 IB 中对应的 view 的 Attribute Inspector 中添加一个颜色选取的属性:
+
+
+
+当我们在 IB 中设置这个属性的时候,对应的 `didSet` 将会被执行,通过 `refreshColor` 方法就可以直接改变 IB 中这个 view 的时针和分针的颜色了。
+
+注意这个改变并不像 `prepareForInterfaceBuilder` 那样仅发生在设计时,我们直接运行代码,会看到运行时的颜色也是发生了改变的。其实 `@IBInspectable` 并没有做什么太神奇的事情,我们如果查看 IB 中这个 view 的 Identity Inspector 的话会看到刚才所设定的颜色值被作为了 Runtime Attribute 被使用了。其实手动直接在 Runtime Attributes 中设定颜色也有同样的效果,因此 `@IBInspectable` 唯一做的事情就是在 IB 面板上为我们提供了一个很方便地修改属性的入口,别没有其他太多神奇之处。
+
+这个原理同时也决定了 `@IBInspectable` 是有一定限制的,即只有能够在 Runtime Attributes 中指定的类型才能够被标记后显示在 IB 中,这些类型包括 `Boolean`,`Number`,`String`,`Ponit`,`Size`,`Rect`,`Range`,`Color` 和 `Image`。像是如果想要把类似 `time` 这样的属性标记为 `@IBInspectable` 的话,在 IB 中还是无法显示的,因为 Xcode 并没有准备 `NSDate` 类型。不过其实通过 KVC 进行动态设定这种事情在原理上是没有问题的,界面的支持应该也可以通过 [Xcode 插件](http://onevcat.com/2013/02/xcode-plugin/)进行扩展,感觉上并不是一件特别困难的事情,有兴趣的同学不妨尝试,应该挺有意思 (当然也有可能会是个坑)。
+
+### 自定义渲染 view 的调试
+
+对于简单的自定义 view 来说,实时显示和属性设定什么的并不是一件很难的事情。但是对于那些比较复杂的 view,如果我们遇到某些渲染上的问题的话,如果只能靠猜的话,就未免太可怜了。幸好,Apple 为 view 在 IB 中的渲染的调试也提供了相应的方法。在 view 的源代码中设置好断点,然后切到 IB,点选中我们的自定义的 view 后,我们就可以使用菜单里的 Editor -> Debug Selected Views 来让 IB 对这个自定义 view 进行渲染。如果触发了代码中的断点,那我们的代码就会被暂停在断点处,lldb 也会就位听我们调遣。一切都感觉良好,不是么?
+
+
+
+## 总结
+
+Xcode 6 中的很多 key feature 都是基于或者重度依赖 Interface Builder 的。比如 Size Classes,比如 xib 的启动画面,再比如本篇文章中说到的自定义 view 渲染等等。在 iOS 或者 Mac 开发中,IB 现在处于一个比以往任何时候都重要的时期,使用 IB 和这些方便的特性进行开发已经从可选项变为了必须项。很难想象没有 IB 的话要怎么才能使用这些工具,更进一步地说,很难想象没有 IB 的话开发者需要浪费多少时间在本应该迅速完成的工作中。
+
+如果你还在使用代码来构建 UI 的话,现在也许是你最后的放下代码,拿起 IB 武装自己的机会了。一开始可能会有迷惑,会不习惯,会觉着被拽出了舒适区浑身无力。但是一旦适应以后,你不仅能够收获最新的技能和工具,也有机会站在一个全新的高度,来审视 app 中界面开发的种种,并从中找到乐趣。
+
+P.S. 如果你不知道要从哪里入手,推荐可以从 raywenderlich 家的这篇 [AutoLayout 教程](http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1)开始你的 IB 之旅。
diff --git a/_posts/2014-11-19-watch-kit.markdown b/_posts/2014-11-19-watch-kit.markdown
new file mode 100644
index 00000000..c36fbe86
--- /dev/null
+++ b/_posts/2014-11-19-watch-kit.markdown
@@ -0,0 +1,169 @@
+---
+layout: post
+title: Apple WatchKit 初探
+date: 2014-11-19 17:40:22.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+随着今天凌晨 Apple 发布了第一版的 Watch Kit 的 API,对于开发者来说,这款新设备的一些更详细的信息也算是逐渐浮出水面。可以说第一版的 WatchKit 开放的功能总体还是令人满意的。Apple 在承诺逐渐开放的方向上继续前进。本来在 WWDC 之后预期 Today Widget 会是各类新颖 app 的舞台以及对 iOS 功能的极大扩展,但是随着像 Launcher 和 PCalc 这些创意型的 Today Widget 接连被下架事件,让开发者也不得不下调对 WatchKit 的预期。但是至少从现在的资料来看,WatchKit 是允许进行复杂交互以及完成一些独立功能的。虽然需要依托于 iPhone app,但是至少能够发挥的舞台和空间要比我原先想象的大不少。
+
+当然,因为设备本身的无论是电量还是运算能力的限制,在进行 Watch app 开发的时候也还是有很多掣肘。现在 Watch app 仅仅只是作为视图显示和回传用户交互的存在,但是考虑到这是这款设备的第一版 SDK,另外 Apple 也有承诺之后会允许真正运行在 Watch 上的 app 的出现,Apple Watch 和 WatchKit 的未来还是很值得期待的。
+
+废话不再多,我们来简单看看 WatchKit 的一些基本信息吧。
+
+## 我们能做什么
+
+### Watch app 架构
+
+首先需要明确的是,在 iOS 系统上,app 本体是核心。所有的运行实体都是依托在本体上的:在 iOS 8 之前这是毋庸置疑的,而在 iOS 8 中添加的各种 Extension 也必须随同 app 本体捆绑,作为 app 的功能的补充。Watch app 虽然也类似于此,我们要针对 Apple Watch 进行开发,首先还是需要建立一个传统的 iOS app,然后在其中添加 Watch App 的 target。在添加之后,会发现项目中多出两个 target:其中一个是 WatchKit 的扩展,另一个是 Watch App。在项目相应的 group 下可以看到,WatchKit Extension 中含有代码 (`InterfaceController.h/m` 等),而 Watch App 里只包含了 `Interface.storyboard`。现在暂时看来 Watch App 依然是传统 iOS 设备 app 的扩展和衍生,Apple 估计会采取和 Extension 同样的态度来对待 Watch Kit。而原生可以直接运行在手表上的 app 有消息说 2015 年中后期才可能被允许。
+
+
+
+在应用安装时,负责逻辑部分的 WatchKit Extension 将随 iOS app 的主 target 被一同安装到 iPhone 中,而负责界面部分的 WatchKit App 将会在主程序安装后由 iPhone 检测有没有配对的 Apple Watch,并提示安装到 Apple Watch 中。所以在实际使用时,所有的运算、逻辑以及控制实际上都是在 iPhone 中完成的。在需要界面刷新时,由 iPhone 向 Watch 发送指令进行描画并在手表盘面上显示。反过来,用户触摸手表交互时的信息也由手表传回给 iPhone 并进行处理。而这个过程 WatchKit 会在幕后为我们完成,并不需要开发者操心。我们需要知道的就是,原则上来说,我们应该将界面相关的内容放在 Watch App 的 target 中,而将所有代码逻辑等放到 Extension 里。
+
+在手表上点击 app 图标运行 Watch App 时,手表将会负责唤醒手机上的 WatchKit Extension。而 WatchKit Extension 和 iOS app 之间的数据交互需求则由 App Groups 来完成,这和 Today Widget 以及其他一些 Extension 是一样的。如果你还没有了解过相关内容,可以参看我之前写过的一篇 [Today Extension 的教程](http://onevcat.com/2014/08/notification-today-widget/)。
+
+### 主要类
+
+#### WKInterfaceController 和生命周期
+
+`WKInterfaceController` 是 WatchKit 中的 `UIViewController` 一样的存在,也会是开发 Watch App 时花时间最多的类。每个 `WKInterfaceController` 或者其子类应该对应手表上的一个整屏内容。但是需要记住整个 WatchKit 是独立于 UIKit 而存在的,`WKInterfaceController` 是一个直接继承自 `NSObject` 的类,并没有像 `UIKit` 中 `UIResponser` 那样的对用户交互的响应功能和完备的回调。
+
+不仅在功能上相对 `UIViewController` 简单很多,在生命周期上也进行了大幅简化。每个 `WKInterfaceController` 对象必然会被调用的生命周期方法有三个,分别是该对象被初始化时的 `-initWithContext:`,将要呈现时的 `-willActivate` 以及呈现结束后的 `-didDeactivate`。同样类比 `UIViewController` 的话,可以将它们理解为分别对应 `-viewDidLoad`,`viewWillAppear:` 以及 `-viewDidDisappear:`。虽然看方法名和实际使用上可能你会认为 `-initWithContext:` 应该对应 `UIViewController` 的 `init` 或者 `initWithCoder:` 这样的方法,但是事实上在 `-initWithContext:` 时 `WKInterfaceController` 中的“视图元素” (请注意这里我加上了引号,因为它们不是真正的视图,稍后会再说明) 都已经初始化完毕可用,这其实和 `-viewDidLoad` 中的行为更加相似。
+
+我们一般在 `-initWithContext:` 和 `-willActivate` 中配置“视图元素”的属性,在 `-didDeactivate` 中停用像是 `NSTimer` 之类的会 hold 住 `self` 的对象。需要特别注意的是,在 `-didDeactivate` 中对“视图元素”属性进行设置是无效的,因为当前的 `WKInterfaceController` 已经非活跃。
+
+#### WKInterfaceObject 及其子类
+
+`WKInterfaceObject` 负责具体的界面元素设置,包括像是 `WKInterfaceButton`,`WKInterfaceLabel` 或 `WKInterfaceImage` 这类物件,也就是我们上面所提到的“视图元素”。可能一开始会产生错觉,觉得 `WKInterfaceObject` 应该对应 `UIView`,但其实上并非如此。`WKInterfaceObject` 只是 WatchKit 的实际的 View 的一个在 Watch Extension 端的代理,而非 View 本身。Watch App 中实际展现和渲染在屏幕上的 view 对于代码来说是非直接可见的,我们只能在 Extension target 中通过对应的代理对象对属性进行设置,然后在每个 run loop 需要刷新 UI 时由 WatchKit 将新的属性值从手机中传递给手表中的 Watch App 并进行界面刷新。
+
+反过来,手表中的实际的 view 想要将用户交互事件传递给 iPhone 也需要通过 `WKInterfaceObject` 代理进行。每个可交互的 `WKInterfaceObject` 子类都对应了一个 action,比如 button 对应点击事件,switch 对应开或者关的状态,slider 对应一个浮点数值表明选取值等等。关联这些事件也很简单,直接从 StoryBoard 文件中 Ctrl 拖拽到实现中就能生成对应的事件了。虽然 UI 资源文件和代码实现是在不同的 target 中的,但是在 Xcode 中的协作已然天衣无缝。
+
+Watch App 采取的布局方式和 iOS app 完全不同。你无法自由指定某个视图的具体坐标,当然也不能使用像 AutoLayout 或者 Size Classes 这样的灵活的界面布局方案。WatchKit 提供的布局可能性和灵活性相对较小,你只能在以“行”为基本单位的同时通过 group 来在行内进行“列”布局。这带来了相对简单的布局实现,当然,同时也是对界面交互的设计的一种挑战。
+
+
+
+另外值得一提的是,随着 WatchKit 的出现及其开发方式的转变,[代码写 UI 还是使用 StoryBoard](http://onevcat.com/2013/12/code-vs-xib-vs-storyboard/) 这个争论了多年的话题可以暂时落下帷幕了。针对 Watch 的开发不能使用代码的方式。首先,所有的 `WKInterfaceObject` 对象都必须要设计的时候经由 StoryBoard 进行添加,运行时我们无法再向界面上添加或者移除元素 (如果有移除需要的,可以使用隐藏);其次 `WKInterfaceObject` 与布局相关的某些属性,比如行高行数等,不能够在运行时进行变更和设定。基本来说在运行时我们只能够改变视图的内容,以及通过隐藏某些视图元素来达到有限地改变布局 (其他视图元素会试图填充被隐藏的元素)。
+
+总之,代码进行 UI 编写的传统,在 Apple 的不断努力下,于 WatchKit 发布的今天,被正式宣判了死刑。
+
+#### Table 和 Context Menu
+
+大部分 `WKInterfaceObject` 子类都很直接简单,但是有两个我想要单独说一说,那就是 `WKInterfaceTable` 和 `WKInterfaceMenu`。`UITableView` 大家都很熟悉了,在 WatchKit 中的 `WKInterfaceTable` 虽然也是用来展示一组数据,但是因为 WatchKit API 的数据传输的特点,使用上相较 `UITableView` 有很大不同和简化。首先不存在 DataSource 和 Delegate,`WKInterfaceTable` 中需要呈现的数据数量直接由其实例方法 `-setNumberOfRows:withRowType:` 进行设定。在进行设定后,使用 `-rowControllerAtIndex:` 枚举所有的 `rowController` 进行设定。这里的 `rowController` 是在 StoryBoard 中所设定的相当于 `UITableViewCell` 的东西,只不过和其他 `WKInterfaceObject` 一样,它是直接继承自 `NSObject` 的。你可以通过自定义 `rowController` 并连接 StoryBoard 的元素,并在取得 `rowController` 对其进行设定,即可完成 table 的显示。代码大概是这样的:
+
+```
+// MyRowController.swift
+import Foundation
+import WatchKit
+
+class MyRowController: NSObject {
+ @IBOutlet weak var label: WKInterfaceLabel!
+}
+
+// InterfaceController.swift
+import WatchKit
+import Foundation
+
+class InterfaceController: WKInterfaceController {
+
+ @IBOutlet weak var table: WKInterfaceTable!
+ let data = ["Index 0","Index 1","Index 2"]
+
+ override init(context: AnyObject?) {
+ // Initialize variables here.
+ super.init(context: context)
+
+ // Configure interface objects here.
+ NSLog("%@ init", self)
+
+ // 注意需要在 StoryBoard 中设置 myRowControllerType
+ // 类似 cell 的 reuse id
+ table.setNumberOfRows(data.count, withRowType: "myRowControllerType")
+ for (i, value) in enumerate(data) {
+ if let rowController = table.rowControllerAtIndex(i) as? MyRowController {
+ rowController.label.setText(value)
+ }
+ }
+ }
+}
+```
+
+
+
+对于点击事件,并没有一个实际的 delegate 存在,而是类似于其他 `WKInterfaceObject` 那样通过 action 将点击了哪个 row 作为参数发送回 `WKInterfaceController` 进行处理。
+
+另一个比较好玩的是 Context Menu,这是 WatchKit 独有的交互,在 iOS 中并不存在。在任意一个 `WKInterfaceController` 界面中,长按手表屏幕,如果当前 `WKInterfaceController` 中存在上下文菜单的话,就会尝试呼出找这个界面对应的 Context Menu。这个菜单最多可以提供四个按钮,用来针对当前环境向用户征询操作。因为手表屏幕有限,在信息显示的同时再放一些交互按钮是挺不现实的一件事情,也会很丑。而上下文菜单很好地解决了这个问题,相信长按呼出交互菜单这个操作会成为今后 Watch App 的一个很标准的交互操作。
+
+添加 Context Menu 非常简单,在 StoryBoard 里向 `WKInterfaceController` 中添加一个 Menu,并在这个 Menu 里添加对应的 MenuItem 就行了。在 `WKInterfaceController` 我们也有对应的 API 来在运行时根据上下文环境进行 MenuItem 的添加 (这是少数几个允许我们在运行时添加元素的方法之一)。
+
+```
+-addMenuItemWithItemIcon:title:action:
+-addMenuItemWithImageNamed:title:action:
+-addMenuItemWithImage:title:action:
+-clearAllMenuItems
+```
+
+
+
+但是 Menu 和 MenuItem 对应的类 `WKInterfaceMenu` 和 `WKInterfaceMenuItem` 我们是没有办法拿到的。没错,它们甚至都没有存在于文档里 :(
+
+### 基础导航
+
+`WKInterfaceController` 的内建的导航关系基本上分为三类。首先是像 `UINavigationController` 控制的类似栈的导航方式。相关的 API 有 `-pushControllerWithName:context:`,`-popController` 以及 `-popToRootController`。后两个我想不必太多解释,对于第一个方法,我们需要使用目标 controller 的 `Identifier` 字符串 (没有你只能在 StoryBoard 里进行设置) 进行创建。`context` 参数也会被传递到目标 controller 的 `-initWithContext:` 中,所以你可以以此来在 controller 中进行数据传递。
+
+另一种是我们大家熟悉的 modal 形式,对应 API 是 `-presentControllerWithName:context:` 和 `-dismissController`。对于这种导航,和 `UIKit` 中的不同之处就是在目标 controller 中会默认在左上角加上一个 Cancel 按钮,点击的话会直接关闭被 present 的 controller。我只想说 Apple 终于想通了,每个 modal 出来的 controller 都是需要关闭的这个事实...
+
+最后一种导航方式是类似 `UIPageController` 的分页式导航。在 iOS app 中,在应用第一次开始时的教学模块中这种导航方式非常常见,而在 WatchKit 里可以说得到了发扬光大。事实上我个人也认为这会是 WatchKit 里最符合使用习惯的导航方式。在 WatchKit 上的 page 导航可能会和 iOS app 的 Tab 导航所提供的功能相对应。
+
+在实现上,page 导航需要在 StoryBoard 中用 segue 的方式将不同 page 进行连接,新添加的 `next page` segue 就是干这个的:
+
+
+
+另外 modal 导航的另一个 API `-presentControllerWithNames:contexts:` 接受复数个的 `names` 和 `context`,通过这种方式 modal 呼出的复数个 Controller 也将以 page 导航方式呈现。
+
+当然,作为 StoryBoard 的经典使用方式,modal 和 push 的导航方式也是可以在 StoryBoard 中通过 segue 来实现的。同时 WatchKit 也为 segue 的方式提供了必要的 API。
+
+### 一些界面实践
+
+因为整个架构和 `UIKit` 完全不同,所以很多之前的实践是无法直接搬到 WatchKit App 中的。
+
+#### 图像处理
+
+在 `UIKit` 中我们显示图片一般使用 `UIImageView`,然后为其 `image` 属性设置一个创建好的 `UIImage` 对象。而对于 WatchKit 来说,最佳实践是将图片存放在 Watch App 的 target 中 (也就是 StoryBoard 的那个 target),在对 `WKInterfaceImage` 进行图像设置时,尽量使用它的 `-setImageNamed:` 方法。这个方法将只会把图像名字通过手机传递到手表,然后由手表在自己的 bundle 中寻找图片并加载,是最快的途径。注意我们的代码是运行在于手表的 Watch App 不同的设备上的,虽然我们也可以先通过 `UIImage` 的相关方法生成 `UIImage` 对象,然后再用 `-setImage:` 或者 `-setImageData:` 来设置手表上的图片,但是这样的话我们就需要将图片放到 Extension 的 target 中,并且需要将图片的数据通过蓝牙传到手表,一般来说这会造成不可忽视的延迟,会很影响体验。
+
+如果对于某些情况下,我们只能在 Extension 的 target 中获得图片 (比如从网络下载或者代码动态生成等),并且需要重复使用的话,最好用 `WKInterfaceDevice` 的 `-addCachedImage:name:` 方法将其缓存到手表中。这样,当我们之后再使用这张图片的时候就可以直接通过 `-setImageNamed:` 来快速地从手表上生成并使用了。每个 app 的 cache 的尺寸大约是 20M,超过的话 WatchKit 将会从最老的数据开始删除,以腾出空间存储新的数据。
+
+#### 动画
+
+因为无法拿到实际的视图元素,只有 `WKInterfaceObject` 这样的代理对象,以及布局系统的限制,所以复杂的动画,尤其是 `UIView` 系列或者是 `CALayer` 系列的动画是无法实现的。现在看来唯一可行的是帧动画,通过为 `WKInterfaceImage` 设置包含多个 image 的图像,或者是通过计时器定时替换图像的话,可以实现帧动画。虽然 Apple 自己的例子也通过这种方法实现了动画,但是对于设备的存储空间和能耗都可能会是挑战,还需要实际拿到设备以后进行实验和观察。
+
+#### 其他 Cocoa Touch 框架的使用
+
+Apple 建议最好不要使用那些需要 prompt 用户许可的特性,比如 CoreLocation 定位等。因为实际的代码是在手机上运行的,这类许可也会在手机上弹出,但是用户并不一定正好在看手机,所以很可能造成体验下降。另外大部分后台运行权限也是不建议的。
+
+对于要获取这些数据和权限,Apple 建议还是在 iOS app 中完成,并通过 App Groups 进行数据共享,从而在 Watch Extension 中拿到这些数据。
+
+#### 代码分享
+
+因为现在一个项目会有很多不同的 target,所以使用 framework 的方式封装不同 target 的公用部分的代码,而只在各 target 中实现界面相关的代码应该是必行的了。这么做的优点不仅是可以减少代码重复,也会使代码测试和品质得到提升。如果还没有进行逻辑部分的框架化和测试分离的话,在实现像各类 Extension 或者 Watch App 时可能会遇到非常多的麻烦。
+
+因为如果原有 app 有计划进行扩展推出各种 Extension 的话,将逻辑代码抽离并封装为 framework 应该是优先级最高的工作。另外新开的项目如果没有特殊原因,也强烈建议使用 framework 来组织通用代码。
+
+### Glance 和 Notification
+
+除了 Watch App 本体以外,Glance 和手表的 Notification 也是重要的使用情景。Notification 虽然概念上比较简单,但是相对于 iOS 的通知来说是天差地别。WatchKit 的通知允许开发者自行构建界面,我们可以通过 payload 设置比较复杂和带有更多信息的通知,包括图像,大段文字甚至可以交互的按钮,而不是像 iOS 上那样被限制在文字和一个对话框内。首先无论是通过 Local 还是 Remote 进行的通知发送会先到达 iPhone,然后再由 iPhone 根据内容判断是否转发到手表。WatchKit App 接收到通知后先会显示一个简短的通知,告诉用户这个 app 有一个通知。如果用户对通知的内容感兴趣的话,可以点击或者抬手观看,这样由开发者自定义的长版本的通知就会显现。
+
+Glance 是 WatchKit 的新概念,它允许 Watch App 展示一个布局固定的 `WKInterfaceController` 页面。它和 Watch App 本体相对地位相当于 iOS 上的 Today Widget 和 iOS app 本身的地位,是作为手表上的 app 的最重要的信息展示出现的。Glance 正如其名,是短时存在的提醒,不能存在可交互的元素。不过如果用户点击 Glance 页面的话,是可以启动到 Watch App 的。现在关于 Glance 本身如何启动和呈现还不是很明确,猜测是某种类似 Today Widget 的呈现方式?(比如按下两次表侧面的旋钮)
+
+## 疑问和改进方向
+
+WatchKit 总体令人满意,提供的 API 和开发环境已经足够开发者作出一些有趣的东西。但是有几个现在看来很明显的限制和希望能加强的方向。
+
+首先是从现在来看 WatchKit 并没有提供任何获取设备传感信息的 API。不论是心跳、计步或者是用户是否正在佩戴 Watch 的信息我们都是拿不到的,这限制了很多数据收集和监视的健康类 app 的制作。如果希望请求数据,还是不得不转向使用 HealthKit。但是随着 iPhone 6 和 6s 的大屏化,在运动时携带 iPhone 的人可以说是变少了。如果 Watch 不能在没有 iPhone 配对的情况下收集记录,并在之后和 iPhone 连接后将数据回传的话,那 Apple 的健康牌就失败了一大半。相信 Apple 不会放过这种把用户捆绑的机会...不过如果第三方应用能实时获取用户的佩戴状况的话,相信会有很多有意思的应用出现。
+
+另外作为在发布会上鼓吹的交互革命的旋钮和触感屏幕,现在看来并没有开放任何 API 供开发者使用,所以我们无法得知用户旋转了手表旋钮这个重要的交互事件。现在看来我们能获取的操作仅只是用户点击屏幕上的按钮或者拖动滑条这个层级,从这个角度来说,现在的 WatchKit 还远没达到可以颠覆移动应用的地步。
+
+希望之后 Apple 会给我们带来其他的好消息吧。
+
+总之,舞台已经搭好,之后唱什么戏,就要看我们的了。
diff --git a/_posts/2015-01-19-swift-pointer.markdown b/_posts/2015-01-19-swift-pointer.markdown
new file mode 100644
index 00000000..3456e263
--- /dev/null
+++ b/_posts/2015-01-19-swift-pointer.markdown
@@ -0,0 +1,144 @@
+---
+layout: post
+title: Swift 中的指针使用
+date: 2015-01-19 12:38:21.000000000 +09:00
+tags: 能工巧匠集
+---
+Apple 期望在 Swift 中指针能够尽量减少登场几率,因此在 Swift 中指针被映射为了一个泛型类型,并且还比较抽象。这在一定程度上造成了在 Swift 中指针使用的困难,特别是对那些并不熟悉指针,也没有多少指针操作经验的开发者 (包括我自己也是) 来说,在 Swift 中使用指针确实是一个挑战。在这篇文章里,我希望能从最基本的使用开始,总结一下在 Swift 中使用指针的一些常见方式和场景。这篇文章假定你至少知道指针是什么,如果对指针本身的概念不太清楚的话,可以先看看这篇[五分钟 C 指针教程](http://denniskubes.com/2012/08/16/the-5-minute-guide-to-c-pointers/) (或者它的[中文版本](http://blog.jobbole.com/25409/)),应该会很有帮助。
+
+## 初步
+
+在 Swift 中,指针都使用一个特殊的类型来表示,那就是 `UnsafePointer`。遵循了 Cocoa 的一贯不可变原则,`UnsafePointer` 也是不可变的。当然对应地,它还有一个可变变体,`UnsafeMutablePointer`。绝大部分时间里,C 中的指针都会被以这两种类型引入到 Swift 中:C 中 const 修饰的指针对应 `UnsafePointer` (最常见的应该就是 C 字符串的 `const char *` 了),而其他可变的指针则对应 `UnsafeMutablePointer`。除此之外,Swift 中存在表示一组连续数据指针的 `UnsafeBufferPointer`,表示非完整结构的不透明指针 `COpaquePointer` 等等。另外你可能已经注意到了,能够确定指向内容的指针类型都是泛型的 struct,我们可以通过这个泛型来对指针指向的类型进行约束以提供一定安全性。
+
+对于一个 `UnsafePointer` 类型,我们可以通过 `memory` 属性对其进行取值,如果这个指针是可变的 `UnsafeMutablePointer` 类型,我们还可以通过 `memory` 对它进行赋值。比如我们想要写一个利用指针直接操作内存的计数器的话,可以这么做:
+
+ func incrementor(ptr: UnsafeMutablePointer) {
+ ptr.memory += 1
+ }
+
+ var a = 10
+ incrementor(&a)
+
+ a // 11
+
+这里和 C 的指针使用类似,我们通过在变量名前面加上 `&` 符号就可以将指向这个变量的指针传递到接受指针作为参数的方法中去。在上面的 `incrementor` 中我们通过直接操作 `memory` 属性改变了指针指向的内容。
+
+与这种做法类似的是使用 Swift 的 `inout` 关键字。我们在将变量传入 `inout` 参数的函数时,同样也使用 `&` 符号表示地址。不过区别是在函数体内部我们不需要处理指针类型,而是可以对参数直接进行操作。
+
+ func incrementor1(inout num: Int) {
+ num += 1
+ }
+
+ var b = 10
+ incrementor1(&b)
+
+ b // 11
+
+虽然 `&` 在参数传递时表示的意义和 C 中一样,是某个“变量的地址”,但是在 Swift 中我们没有办法直接通过这个符号获取一个 `UnsafePointer` 的实例。需要注意这一点和 C 有所不同:
+
+ // 无法编译
+ let a = 100
+ let b = &a
+
+## 指针初始化和内存管理
+
+在 Swift 中不能直接取到现有对象的地址,我们还是可以创建新的 `UnsafeMutablePointer` 对象。与 Swift 中其他对象的自动内存管理不同,对于指针的管理,是需要我们手动进行内存的申请和释放的。一个 `UnsafeMutablePointer` 的内存有三种可能状态:
+
+* 内存没有被分配,这意味着这是一个 null 指针,或者是之前已经释放过
+* 内存进行了分配,但是值还没有被初始化
+* 内存进行了分配,并且值已经被初始化
+
+其中只有第三种状态下的指针是可以保证正常使用的。`UnsafeMutablePointer` 的初始化方法 (`init`) 完成的都是从其他类型转换到 `UnsafeMutablePointer` 的工作。我们如果想要新建一个指针,需要做的是使用 `alloc:` 这个类方法。该方法接受一个 `num: Int` 作为参数,将向系统申请 `num` 个数的对应泛型类型的内存。下面的代码申请了一个 `Int` 大小的内存,并返回指向这块内存的指针:
+
+ var intPtr = UnsafeMutablePointer.alloc(1)
+ // "UnsafeMutablePointer(0x7FD3A8E00060)"
+
+接下来应该做的是对这个指针的内容进行初始化,我们可以使用 `initialize:` 方法来完成初始化:
+
+ intPtr.initialize(10)
+ // intPtr.memory 为 10
+
+在完成初始化后,我们就可以通过 `memory` 来操作指针指向的内存值了。
+
+在使用之后,我们最好尽快释放指针指向的内容和指针本身。与 `initialize:` 配对使用的 `destroy` 用来销毁指针指向的对象,而与 `alloc:` 对应的 `dealloc:` 用来释放之前申请的内存。它们都应该被配对使用:
+
+ intPtr.destroy()
+ intPtr.dealloc(1)
+ intPtr = nil
+
+> 注意其实在这里对于 `Int` 这样的在 C 中映射为 int 的 “平凡值” 来说,`destroy` 并不是必要的,因为这些值被分配在常量段上。但是对于像类的对象或者结构体实例来说,如果不保证初始化和摧毁配对的话,是会出现内存泄露的。所以没有特殊考虑的话,不论内存中到底是什么,保证 `initialize:` 和 `destroy` 配对会是一个好习惯。
+
+## 指向数组的指针
+
+在 Swift 中将一个数组作为参数传递到 C API 时,Swift 已经帮助我们完成了转换,这在 Apple 的[官方博客](https://developer.apple.com/swift/blog/?id=6)中有个很好的例子:
+
+ import Accelerate
+
+ let a: [Float] = [1, 2, 3, 4]
+ let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
+ var result: [Float] = [0, 0, 0, 0]
+
+ vDSP_vadd(a, 1, b, 1, &result, 1, 4)
+
+ // result now contains [1.5, 2.25, 3.125, 4.0625]
+
+对于一般的接受 const 数组的 C API,其要求的类型为 `UnsafePointer`,而非 const 的数组则对应 `UnsafeMutablePointer`。使用时,对于 const 的参数,我们直接将 Swift 数组传入 (上例中的 `a` 和 `b`);而对于可变的数组,在前面加上 `&` 后传入即可 (上例中的 `result`)。
+
+对于传参,Swift 进行了简化,使用起来非常方便。但是如果我们想要使用指针来像之前用 `memory` 的方式直接操作数组的话,就需要借助一个特殊的类型:`UnsafeMutableBufferPointer`。Buffer Pointer 是一段连续的内存的指针,通常用来表达像是数组或者字典这样的集合类型。
+
+ var array = [1, 2, 3, 4, 5]
+ var arrayPtr = UnsafeMutableBufferPointer(start: &array, count: array.count)
+ // baseAddress 是第一个元素的指针
+ var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer
+
+ basePtr.memory // 1
+ basePtr.memory = 10
+ basePtr.memory // 10
+
+ //下一个元素
+ var nextPtr = basePtr.successor()
+ nextPtr.memory // 2
+
+## 指针操作和转换
+
+### withUnsafePointer
+
+上面我们说过,在 Swift 中不能像 C 里那样使用 `&` 符号直接获取地址来进行操作。如果我们想对某个变量进行指针操作,我们可以借助 `withUnsafePointer` 这个辅助方法。这个方法接受两个参数,第一个是 `inout` 的任意类型,第二个是一个闭包。Swift 会将第一个输入转换为指针,然后将这个转换后的 `Unsafe` 的指针作为参数,去调用闭包。使用起来大概是这个样子:
+
+ var test = 10
+ test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer) -> Int in
+ ptr.memory += 1
+ return ptr.memory
+ })
+
+ test // 11
+
+这里其实我们做了和文章一开始的 `incrementor` 相同的事情,区别在于不需要通过方法的调用来将值转换为指针。这么做的好处对于那些只会执行一次的指针操作来说是显而易见的,可以将“我们就是想对这个指针做点事儿”这个意图表达得更加清晰明确。
+
+### unsafeBitCast
+
+`unsafeBitCast` 是非常危险的操作,它会将一个指针指向的内存强制按位转换为目标的类型。因为这种转换是在 Swift 的类型管理之外进行的,因此编译器无法确保得到的类型是否确实正确,你必须明确地知道你在做什么。比如:
+
+ let arr = NSArray(object: "meow")
+ let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), CFString.self)
+ str // “meow”
+
+因为 `NSArray` 是可以存放任意 `NSObject` 对象的,当我们在使用 `CFArrayGetValueAtIndex` 从中取值的时候,得到的结果将是一个 `UnsafePointer`。由于我们很明白其中存放的是 `String` 对象,因此可以直接将其强制转换为 `CFString`。
+
+关于 `unsafeBitCast` 一种更常见的使用场景是不同类型的指针之间进行转换。因为指针本身所占用的的大小是一定的,所以指针的类型进行转换是不会出什么致命问题的。这在与一些 C API 协作时会很常见。比如有很多 C API 要求的输入是 `void *`,对应到 Swift 中为 `UnsafePointer`。我们可以通过下面这样的方式将任意指针转换为 UnsafePointer。
+
+ var count = 100
+ var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer) -> UnsafePointer in
+ return unsafeBitCast(a, UnsafePointer.self)
+ })
+ // voidPtr 是 UnsafePointer。相当于 C 中的 void *
+
+ // 转换回 UnsafePointer
+ var intPtr = unsafeBitCast(voidPtr, UnsafePointer.self)
+ intPtr.memory //100
+
+## 总结
+
+Swift 从设计上来说就是以安全作为重要原则的,虽然可能有些啰嗦,但是还是要重申在 Swift 中直接使用和操作指针应该作为最后的手段,它们始终是无法确保安全的。从传统的 C 代码和与之无缝配合的 Objective-C 代码迁移到 Swift 并不是一件小工程,我们的代码库肯定会时不时出现一些和 C 协作的地方。我们当然可以选择使用 Swift 重写部分陈旧代码,但是对于像是安全或者性能至关重要的部分,我们可能除了继续使用 C API 以外别无选择。如果我们想要继续使用那些 API 的话,了解一些基本的 Swift 指针操作和使用的知识会很有帮助。
+
+对于新的代码,尽量避免使用 `Unsafe` 开头的类型,意味着可以避免很多不必要的麻烦。Swift 给开发者带来的最大好处是可以让我们用更加先进的编程思想,进行更快和更专注的开发。只有在尊重这种思想的前提下,我们才能更好地享受这门新语言带来的种种优势。显然,这种思想是不包括到处使用 `UnsafePointer` 的 :)
diff --git a/_posts/2015-03-27-cross-platform.markdown b/_posts/2015-03-27-cross-platform.markdown
new file mode 100644
index 00000000..cfd9c577
--- /dev/null
+++ b/_posts/2015-03-27-cross-platform.markdown
@@ -0,0 +1,142 @@
+---
+layout: post
+title: 跨平台开发时代的 (再次) 到来?
+date: 2015-03-27 18:56:08.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+这篇文章主要想谈谈最近又刮起的移动开发跨平台之风,并着重介绍和对比一下像是 [Xamarin](https://xamarin.com),[NativeScript](https://www.nativescript.org) 和 [React Native](http://facebook.github.io/react-native/) 之类的东西。不会有特别深入的技术讨论,大家可以当作一篇科普类的文章来看。
+
+### 故事的开始
+
+“一次编码,处处运行” 永远是程序员们的理想乡。二十年前 Java 正是举着这面大旗登场,击败了众多竞争对手。但是时至今日,事实已经证明了 Java 笨重的体型和缓慢的发展显然已经很难再抓住这个时代快速跃动的脚步。在新时代的移动大潮下,一个应用想要取胜,完美的使用体验可以说必不可少。使用 native 的方式固然对提升用户体验很有帮助,但是移动的现状是必须针对不同平台 (至少是 iOS 和 Android) 进行开发。这对于开发来说妥妥的是隐患和额外的负担:我们不仅需要在不同的项目间努力用不同的语言实现同样代码的同步,还要承担由此带来的后续维护任务。如果仅只限制在 iOS 和 Android 的话还行,但是如果还要继续向 Windows Phone 等平台拓展的话,所需要付出的代价和[工数](http://en.wikipedia.org/wiki/Man-hour)将几何级增长,这显然是难以接受的。于是,一个其实一直断断续续被提及但是从没有占据过统治地位的概念又一次走进了移动开发者们的视野,那就是跨平台开发。
+
+### 本地 HTML 和 JavaScript
+
+因为每个平台都有浏览器,也都有 WebView 控件,所以我们可以使用 HTML,CSS 和 JavaScript 来将 web 的内容和体验搬到本地。通过这样做我们可以将逻辑和 UI 渲染部分都统一,以减少开发和维护成本。这种方式开发的 app 一般被称为 [Hybrid app](http://blogs.telerik.com/appbuilder/posts/12-06-14/what-is-a-hybrid-mobile-app-),像 [PhoneGap](http://phonegap.com) 或者 [Cordova](http://cordova.apache.org) 这样的解决方案就是典型的应用。除了使用前端开发的一套技巧来构建页面和交互以外,一般这类框架还会提供一些访问设备的接口,比如相机和 GPS 等。
+
+
+
+虽然使用全网页的开发策略和环境可以带来代码维护的便利,但是这种方式是有致命弱点的,那就是缓慢的渲染速度和难以驾驭的动画效果。这两者对于用户体验是致命而且难以接受的。随着三年前 Facebook 使用 native 代码重新构建 Facebook 的手机 app 这一[标志性事件](https://www.facebook.com/notes/facebook-engineering/under-the-hood-rebuilding-facebook-for-ios/10151036091753920)的发生,曾经一度占领半壁江山的网页套壳的 app 的发展也日渐式微。特别在现在对于用户体验的追求几近苛刻的现在,呆板的动画效果和生硬的交互体验已经完全无法满足人民群众对高质量 app 的心理预期了。
+
+### 跨平台之心不死的我们该怎么办
+
+想要解决用户体验的问题,基本还是需要回到 native 来进行开发,但是这种行为必然会与平台绑定。世界上总是有聪明人的,并且他们总会利用看起来更加聪明但是实际上却很笨的电脑来做那些很笨的事情 (恰得其所)。其中一件事情就是自动将某个平台的代码转换到另外的平台上去。有一家英国的小公司正在做这样的事情,[MyAppConverter](https://www.myappconverter.com) 想做的事情就是把 iOS 的代码自动转成 Java 的。但是很可惜,如果你尝试过的话,就知道他们的产品暂时还处于无法实用的状态。
+
+在这条路的另一个分叉上有一家公司走得更远,它叫做 [Apportable](http://www.apportable.com)。他们在游戏的转换上已经取得了[很大的成果](https://dashboard.apportable.com/customers),像是 Kingdom Rush 或者 Mega Run 这样的大作都使用了这家的服务将游戏从 iOS 转换到 Android,并且非常成功。可以毫不夸张地说,Apportable 是除开直接使用像 Unity 或者 Cocos2d-x 以外的另一套诱人的游戏跨平台解决方案。基本上你可以使用 Objective-C 或者 Swift 来在熟悉的平台上开发,而不必去触碰像是 C++ 这样的怪兽 (虽然其实在游戏开发中也不会碰到很难的 C++)。
+
+但是好消息终结于游戏开发了,因为游戏在不同平台上体验不会差别很大,也很少用到不同平台的不同特性,所以处理起来相对容易。当我们想开发一个非游戏的 app 时,事情就要复杂得多。虽然 Apportable [有一个计划](http://www.tengu.com)让 app 转换也能可行,但是估计还需要一段时间我们才能看到它的推出。
+
+### 新的希望
+
+#### Xamarin
+
+其实跨平台开发最大的问题还是针对不同的平台 UI 和体验的不同。如果忽视掉这个最困难的问题,只是共用逻辑部分的代码的话,问题一下子就简单不少。十多年前,当 .NET 刚刚被公布,大家对新时代的开发充满期待的同时,一群喜欢捣鼓的 Hacker 就在盘算要如何将 .NET 和 C# 搬到 Linux 上去。而这就是 [Mono](http://www.mono-project.com) 的起源。Mono 通过在其他平台上实现和 Windows 平台下功能相同的 Common Language Runtime 来运行 .NET 中间代码。现在 Mono 社区已经足够强大,并且不仅仅支持 Linux 平台,对移动设备也同样支持。Mono 背后的支撑企业 [Xamarin](http://xamarin.com) 也顺理成章并适时地推出了一整套的移动跨平台解决方案。
+
+Xamarin 的思路相对简单,那就是使用 C# 来完成所有平台共用的,和平台无关的 app 逻辑部分;然后由于各个平台的 UI 和交互不同,使用预先由 Xamarin 封装好的 C# API 来访问和操控 native 的控件,进行分别针对不同平台的 UI 开发。
+
+
+
+虽然只有逻辑部分实现了真正的跨平台,而表现层已然需要分别开发,但这确实也是一种在完整照顾用户体验的基础上的好方式 -- 至少开发语言得到了统一。因为 Xamarin 解决方案中的纯 C# 环境和有深厚的 .NET 技术背景做支撑,这个项目现在也受到了微软的支持和重视。
+
+不过存在的致命问题是针对某个特定平台你所能使用的 API 是由 Xamarin 所决定的。也就是说一旦 iOS 或者 Android 平台推出了新的 SDK,加入了新的功能,你必须要等 Xamarin 的工程师先进行封装,然后才能在自己的项目中使用。这种延迟往往可能是致命的,因为现在 AppStore 对于新功能的首页推荐往往只会有新系统上线后的一两周,错过这段时间的话,可能你的 app 就再无翻身之日。而且如果你想使用一些第三方框架的话,将不得不自己动手将它们打包成二进制,并且写 binding 为它们提供 C# 的封装,除非已经有别人帮你[做过](https://github.com/mono/monotouch-bindings)这件事情了。
+
+另外,因为 UI 部分还是各自为战,所以不同的代码库依然存在于项目之中,这对工作量的减少的帮助有限,并且之后的维护中还是存在无法同步和版本差异的隐患。但是总体来说,Xamarin 是一个很不错的解决跨平台开发的思路了。(如果抛开价格因素的话)
+
+#### NativeScript
+
+[NativeScript](https://www.nativescript.org) 是一家名叫 Telerik 的名不见经传保加利亚公司刚刚宣布的项目。虽然 Telerik 并不是很出名,但是却已经在 hybrid app 和跨平台开发这条路上走了很久。
+
+JavaScript 因为广泛的群众基础和易学易用的语言特点,已经大有一统天下的趋势。而现在主流移动平台也都有强劲的处理 JavaScript 的能力 (iOS 7 以后的 JavaScriptCore 以及 Android 自带的 V8 JavaScript Engine),因为使用 JavaScript 来跨平台水到渠成地成为了一个可选项。
+
+> 在此要吐槽一下,JavaScript 真的是一家公司,一个项目拯救回来的语言。V8 之前谁能想到 JavaScript 能有今日...
+
+NativeScript 的思路就是使用移动平台的 JavaScript 引擎来进行跨平台开发。逻辑部分自然无需多说,关键在于如何使用平台特性,JavaScript 要怎样才能调用 native 的东西呢。NativeScript 给出的答案是通过反射得到所有平台 API,预编译它们,然后将这些 API 注入到 JavaScript 运行环境,接下来在 Javascript 调用后拦截这个调用,并运行 native 代码。
+
+> 在此不打算展开说 NativeScript 详细的原理,如果你对它感兴趣,不妨去看看 Telerik 的员工的写的这篇[博客](http://developer.telerik.com/featured/nativescript-works/)以及发布时的 [Keynote](https://www.youtube.com/watch?v=8hr4E9eodS4)。
+
+
+
+这么做最大的好处是你可以任意使用最新的平台 API 以及各种第三方库。通过对元数据的反射和注入,NativeScript 的 JavaScript 运行环境总能找到它们,触发相应的调用以及最终访问到 iOS 或者 Android 的平台代码。最新版本的平台 SDK 或者第三方库的内容总是可以被获取和使用,而不需要有什么限制。
+
+举个简单的例子,比如创建一个文件,为 iOS 开发的话,可以直接在 JavaScript 里写这样的代码:
+
+```
+var fileManager = NSFileManager.defaultManager();
+fileManager.createFileAtPathContentsAttributes( path );
+```
+
+而对应的 Android 版本也许是:
+
+```
+new java.io.File( path );
+```
+
+你不需要担心 `NSFileManager` 或者 `java.io` 这类东西的存在,而是可以任意地使用它们!
+
+如果仅只是这样的话,使用上还是非常不便。NativeScript 借助类似 node 的一套包管理系统,用 modules 对这些不同平台的代码进行了统一的封装。比如上面的代码,可以统一使用下面的形式替换:
+
+
+```
+var fs = require( "file-system" );
+var file = new fs.File( path );
+```
+
+写过 node 的同学肯定对这样的形式很熟悉了,这里的 `file-system` 就是 NativeScript 进行的统一平台的封装。现在的完整的封装列表可以参见这个 [repo](https://github.com/NativeScript/cross-platform-modules)。因为写法很简单,所以开发者如果有需要的话,也可以创建自己的封装,甚至使用 npm 来发布和共享 (当然也有获取别人写的封装)。因为依赖于已有的成熟包管理系统,所以可以认为扩展性是有保证的。
+
+对于 UI 的处理,NativeScript 选择了使用类似 Android 的 XML 的方式进行布局,然后用 CSS 来控制控件的样式。这是一种很有趣的想法,虽然 UI 的布局灵活性上无法与针对不同平台的 native 布局相比,但是其实和传统的 Android 布局已经很接近。举个布局文件的例子就可见一斑:
+
+```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+熟悉 Android 或者 Window Phone 开发的读者可能会感到找到了组织。你可能已经注意到,相比于 Android 的布局方式,NativeScript 天生支持 MVVM 和 data binding,这在开发中会十分方便 (但是性能上暂时就未知了)。而像是 `Button` 或者 `ListView` 这样的控件都是由 modules 映射到对应平台的系统标准控件。这些控件的话都是使用 css 来指定样式的,这与传统的网页开发没太大区别。
+
+
+
+NativeScript 代表的思路是使用大量 web 开发的技巧来进行 app 开发。这是一个很值得期待的方向,相信也会受到很多前端开发者的欢迎 -- 因为工具链和语言都非常熟悉。但是这个方向依然面临的最大挑战还是 UI,现在看来开发者是被限制在预先定义好的 UI 控件中的,而不能像传统 Hybrid app 那样使用 HTML5 的元素。这使得如何能开发出高度自定义的 UI 和交互成为问题。另一个可能存在的问题是最终 app 的尺寸。因为我们需要将整个元数据注入到运行环境中,也存在很多在不同语言中的编译,所以不可避免地会造成较大的 app 尺寸。最后一个挑战是对于像 app 这样的工程,没有类型检查和编译器的帮助,开发起来难度会比较大。另外在调试的时候也可能会有传统 app 开发中不曾遇到的问题。
+
+总体来看,NativeScript 是很有希望的一个方案。如果它能实现自己的愿景,那必将是跨平台这块大蛋糕的有力竞争者。当然,现在 NativeScript 还太年轻,也还有[很多问题](https://www.nativescript.org/roadmap)。不妨多给这个项目一点时间,看看正式版本上线后的表现。
+
+#### React Native
+
+Facebook 几个月前[公布](https://code.facebook.com/videos/786462671439502/react-js-conf-2015-keynote-introducing-react-native-/)了 React Native,而今天这个项目终于在万众期待下[发布](http://facebook.github.io/react-native/)了。
+
+React Native 在一定程度上和 NativeScript 的概念类似:都是使用 JavaScript 和 native UI 来实现 app (所以说 JavaScript 真是有一桶浆糊的趋势..如果你现在还不会写几句 JavaScript 的话,建议尽早学一学)。但是它们的出发点略有不同,React Native 在首页上就写明了,使用这个库可以:
+
+> learn once, write anywhere
+
+而并不是 "run anywhere"。所以说 React Native 想要达成的目标其实并不是一个跨平台 app 开发方案,而是让你能够使用相似的方法和同样的语言来在不同平台进行开发的工具。另外,React Native 的主要工作是构建响应式的 View,其长处在于根据应用所处的状态来决定 View 的表现状态。而对于其他一些系统平台的 API 来说,就显得比较无力。而正是由于这些要素,使得 React Native 确实不是一个跨平台的好选择。
+
+那为什么我们还要在这篇以 “跨平台” 为主题的文章里谈及 React Native 呢?
+
+因为虽然 Facebook 不是以跨平台为出发点,但是却不可能阻止工程师想要这么来使用它。从原理上来说,React Native 继承了 React.js 的虚拟 DOM 的思想,只不过这次变成了虚拟 View。事实上这个框架提供了一组 native 实现的 view (在 iOS 平台上是 `RCT` 开头的一系列类)。我们在写 JavaScript (更准确地说,对于 React Native,我们写的是带有 XML 的 JavaScript:[JSX](http://facebook.github.io/react/docs/jsx-in-depth.html)) 时,通过将虚拟 View 添加并绑定到注册的模块中,在 native 侧用 JavaScript 运行环境 (对于 iOS 来说也就是 JavaScriptCore) 执行编译并注入好的 JavaScript 代码,获取其对 UI 的调用,将其截取并桥接到 native 代码中进行对应部件的渲染。而在布局方面,依然是通过 CSS 来实现的。
+
+这里整个过程和思路与 NativeScript 有相似之处,但是在与 native 桥接的时候采取的策略完全相反。React Native 是将 native 侧作为渲染的后端,去提供统一的 JavaScript 侧所需要的 View 的实体。NativeScript 基本算反其道行之,是在 JavaScript 里写分开的中间层来分别对应不同平台。
+
+对于非 View 的处理,对于 iOS,React Native 提供了 `RCTBridgeModule` 协议,我们可以通过在 native 侧实现这个协议来提供 JavaScript 中的访问可能。另外,回调和事件发送等也可以通过相应的 native 代码来完成。
+
+总结来说,如果想要把 React Native 作为一个跨平台方案来看的话 (实际上也并不应当如此),那么单靠 JavaScript 一侧是难以完成的,因为一款有意义的 app 不太可能完全不借助平台 API 的力量。但是毕竟这个项目背后是 Facebook,如果 Facebook 想要通过自己的影响力自立一派的话,必定会通过不断改进和工具链的完善,将 app 开发的风向引导至自己旗下。对于原来就使用 React.js 的开发者来说,这个框架降低了他们进入 app 开发的门槛。但是对于已经在做 native app 开发的人来说,是否值得和需要投入精力进行学习,还需要观察 Facebook 接下来动作。
+
+不过现在 React Native 的正式发布才过去了不到 24 小时,我想我们有的是时间来思考和检阅这样一个框架。
+
+### 总结
+
+当然还有一些其他方案,比如 [Titanium](http://www.appcelerator.com/titanium/) 等。现在使用跨平台方案开发 app 的案例并不算很多,但是无论在项目管理还是维护上,跨平台始终是一种诱惑。它们都解决了一些 Hybrid app 的遗留问题,但是它们又都有一些非 native app 的普遍面临的阴影。谁能找到一个好的方式来解决像是自定义 UI,API 扩展性以及 app 尺寸这样的问题,谁就将能在这个市场中取得领先或者胜利,从而引导之后的开发潮流。
+
+但是谁又知道最后谁能取胜呢?也有可能大家在跨平台的道路上再一次全体失败。伺机而动也许是现在开发者们很好的选择,不过我的建议是提前[学点儿 JavaScript](http://www.codecademy.com/en/tracks/javascript) 总是不会出错的。
diff --git a/_posts/2015-05-08-scheme.markdown b/_posts/2015-05-08-scheme.markdown
new file mode 100644
index 00000000..b5aa166b
--- /dev/null
+++ b/_posts/2015-05-08-scheme.markdown
@@ -0,0 +1,442 @@
+---
+layout: post
+title: Scheme 初步
+date: 2015-05-08 12:59:45.000000000 +09:00
+tags: 南箕北斗集
+---
+之前定了[每年学习一门语言](http://onevcat.com/2014/09/bye-kayac/)的目标,自然不能轻言放弃。今年目标:简单掌握 Scheme。
+
+因为自己接触这门语言也不过寥寥数天,所以更多的会以引导的方式简单介绍语法,而不会 (也没有能力) 去探讨什么深入的东西。本文很多例程和图示参考了紫藤貴文的[《もうひとつの Scheme 入門》](http://www.shido.info/lisp/idx_scm.html)这篇深入浅出的教程,这篇教程现在也有[英译版](http://www.shido.info/lisp/idx_scm_e.html)和[中译版](http://deathking.github.io/yast-cn/index.html)。我自己是参照这篇教程入门的,一方面这篇教程可以说是能找到合适初学者学习的很好的材料,另一方面也希望能挑战一下自己的日文阅读能力 (结果对比日文和英文看下来发现果然还是日文版写的比较有趣,英文翻译版本就太严肃了,而中文版感觉是照着英文译版二次翻译的,有不少内容上的缺失和翻译生硬的问题,蛮可惜的)。因为中文的 Scheme 的资料其实很少,所以顺便把自己学习的过程和一些体会整理记录下来,算是作为备忘。本文只涉及 Scheme 里最基础的一些语法部分,要是恰好能够帮助到后来的学习者入门 Scheme,那更是再好不过。
+
+## 为什么选择学 Scheme
+
+三方面的原因。
+
+首先是自己基本上对函数式语言的接触为零。平时工作和自己的开发中基本不使用函数式编程,大脑已经被指令式程序占满,有时候总显得不很灵光。而像 Swift 这样的语言其实引入了一些函数式编程的可能性。多接触一些函数式的语言,可能会对实际工作中解决某些问题有所帮助。而 Scheme 比起另一门常用 Lisp 方言 Common Lisp 来说,要简单不少。比较适合像我这样非科班出身,CS 功力不足的开发者。
+
+其次,[Structure and Interpretation of Computer Programs (SICP)](http://mitpress.mit.edu/sicp/) 里的例程都是使用 Scheme 写的。虽然不太有可能有时间补习这本经典,但是如果不会一点 Scheme 的话,那就完全没有机会去读这本书了。
+
+最后,Scheme 很酷也很好玩,虽然在实际中可能并没有鸟用,但是和别人说起来自己会一点 Scheme 的话,那种感觉还是很棒的。
+
+其实还有一点对 hacker 们很重要,如果你喜欢使用像 [Emacs](https://www.gnu.org/software/emacs/) 这样的基于 Lisp 的编辑器的话,使用 Scheme 就可以与它进行交互或者是扩展它的功能了。
+
+那让我们尽快开始吧。
+
+## 成为 Scheme Hacker 的第一步
+
+成为 Scheme Hacker 的第一步,当然是安装和配置运行环境,同时这也是最难跨过去的一步。想想有多少次你决心学习一门新语言的时候,在配置好开发环境后就再也没有碰过吧。所以我们需要尽快跨过这个步骤。
+
+最简单的开发环境点击这个[链接](http://repl.it/languages/Scheme),然后你就可以开始用 Scheme 编程了。如果你更喜欢在本地环境和终端里操作的话,可以下载 [MIT/GUN Scheme](https://www.gnu.org/software/mit-scheme/)。在 OS X 上解包后是一个 .app 文件,运行 .app 包里的 `/Contents/Resources/mit-scheme` 就可以打开一个解释器了。
+
+
+
+## Hello 1 + 1
+
+虽然大部分语言都是从 Hello World 开始的,但是对于 Scheme 来说,计算才是它的强项。所以我们从 1 + 1 开始。计算 1 + 1 程序在 Scheme 中是这样的:
+
+```scheme
+1 ]=> (+ 1 1)
+
+;Value: 2
+
+1 ]=>
+```
+`1 ]=> ` 是输入提示符,我们输入的内容是 `(+ 1 1)`,得到的结果是 2。虽然语句很简单,但是这里包含了 Scheme 的最基本的语素,有三个地方值得特别注意。
+
+1. 成对的括号。一对括号表示的是一步计算,这里 `(+ 1 1)` 表示的就是 1 + 1 这一步运算。
+2. 紧接括号的是函数名字,再然后是函数的参数。在这里,函数名字就是 "+",两个 1 是它的参数。Scheme 中大部分的运算符其实都是函数。
+3. 使用空格,tab 或是换行符来分割函数名以及参数。
+
+和别的很多语言一样,Scheme 在函数调用时也有计算优先级,会先对输入的参数进行计算,然后再进行函数调用。还是以上面的 1 + 1 为例。首先解释器看到加号,但是此时运算并没有开始。解释器会先计算第一个参数 1 的值 (其实就是 1),然后计算第二个参数 1 的值 (其实还是 1)。然后再用两个计算得到的值来进行加法运算。
+
+另外,"+" 这个函数不仅可以接受两个参数,其实它是可以接受任意多个参数的。比如 `(+)` 的结果是 0,`(+ 1 2 3 4)` 的结果是 10。
+
+学会加法以后,乘法自然也不在话下了。
+
+```scheme
+1 ]=> (* 2 3)
+
+;Value: 6
+
+1 ]=>
+```
+
+减法和除法稍微不同一些,因为它们并不满足交换律,所以可能会有疑问。但是只要记住参数是平等的,它们会顺次计算就可以了。举个例子:
+
+```scheme
+1 ]=> (- 10 5 3)
+
+;Value: 2
+
+1 ]=> (/ 20 2 2)
+
+;Value: 5
+```
+
+对于除法,有两个需要注意的地方。首先和我们熟悉的很多语言不同,Scheme 是默认有分数的概念的。比如在 C 系语言中,如果只是在整数范围的话,我们计算 `10 / 3` 的结果会是 `3`;如果是浮点型的话结果为 `3.33333`。而在 Scheme 中,结果是这样的:
+
+```scheme
+1 ]=> (/ 10 3)
+
+;Value: 10/3
+```
+
+这是一个分数,就是三分之十,绝对精确!
+
+另一个需要注意的是,如果 `/` 只有一个输入的话,它的意思是取倒数。
+
+```scheme
+1 ]=> (/ 2)
+
+;Value: 1/2
+```
+
+如果你需要一个浮点数而不是分数的话,可以使用 `exact->inexact` 方法,将精确值转为非精确值:
+
+```scheme
+1 ]=> (exact->inexact (/ 10 3))
+
+;Value: 3.3333333333333335
+```
+
+Scheme 也内建定义了一些其他的数学运算符号,如果你感兴趣,可以查看 R6RS 的[相关章节](http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_sec_11.7.4)。
+
+> R6RS (Revisedn Report on the Algorithmic Language Scheme, Version 6) 是当前的 Scheme 标准。
+
+## 定义变量和方法,Hello World
+
+通过简单的 1 + 1 运算我们可以大概知道 Scheme 中的奇怪的括号开头的意思了。有了这个作为基础,我们可以来看看如何定义变量和方法了。
+
+Scheme 中通过 `define` 来定义变量和方法:
+
+```scheme
+; s 是一个变量,值为 "Hello World"
+(define s "Hello World")
+
+; f 是一个函数,它不接受参数,调用时返回 "Hello World"
+(define f (lambda () "Hello World"))
+```
+
+上面的 `lambda` 可以生成一个闭包,它接受两个参数,第一个是一个空的列表 `()`,表示这个闭包不接受参数;第二个是 "Hello World" 这个字符串。在解释器中定义好两者之后,就可以进行调用了:
+
+```scheme
+1 ]=> (define s "Hello World")
+
+;Value: s
+
+1 ]=> (define f (lambda () "Hello World"))
+
+;Value: f
+
+1 ]=> s
+
+;Value 24: "Hello World"
+
+1 ]=> f
+
+;Value 25: #[compound-procedure 25 f]
+
+1 ]=> (f)
+
+;Value 26: "Hello World"
+```
+
+既然我们已经知道了 `lambda` 的意义和用法,那么定义一个接受参数的函数也就不是什么难事了。比如上面的 `f`,我们想要定义一个接受名字的函数的话:
+
+```scheme
+1 ]=> (define hello
+ (lambda (name)
+ (string-append "Hello " name "!")
+ )
+ )
+
+;Value: hello
+
+1 ]=> (hello "onevcat")
+
+;Value 27: "Hello onevcat!"
+```
+
+很简单,对吧?其实甚至可以更简单,define 的第一个参数可以是一个列表,其中第一个元素是函数名名字,之后的是参数列表。
+
+> 用专业一点的术语来说的话,就是 define 的第一个参数是一个 cons cell 的话,它的 car 是函数名,cdr 是参数。关于这些概念我们稍后再仔细说说。
+
+于是上面的方法可以简单地写作:
+
+```scheme
+1 ]=> (define (hello name)
+ (string-append "Hello " name "!"))
+
+;Value: hello
+
+1 ]=> (hello "onevcat")
+
+;Value 28: "Hello onevcat!"
+```
+
+光说不练假把式,所以留个小练习给大家吧,用 `define` 来定义一个函数,让其为输入的数字 +1。如果你无压力地搞定了的话,我们就可以继续看看 Scheme 里的条件语句怎么写了。
+
+## 条件分支和布尔逻辑
+
+不论是什么编程语言,条件分支或者类似的概念应该都是不可缺少的部分。在 Scheme 中,使用 `if` 可以进行条件分支的处理。和其他很多语言不一样的地方在于,函数式语言中函数才是一等公民,`if` 的行为也和一个其他的普通函数很相似,是作为一个函数来使用的。它的语法是:
+
+```
+(if condition ture_action false_action)
+```
+
+与普通函数先进行输入的取值不同,`if` 将会先对 `condition` 运算式进行取值判断。如果结果是 `true` (在 Scheme 中用 `#t` 代表 true,`#f` 代表 false),则再对 `ture_action` 进行取值,否则就执行 `false_action`。比如我们可以实现一个 `abs` 函数来返回输入的绝对值:
+
+```scheme
+1 ]=> (define (abs input)
+ (if (< input 0) (- input) input))
+
+;Value: abs
+
+1 ]=> (abs 100)
+
+;Value: 100
+
+1 ]=> (abs -100)
+
+;Value: 100
+```
+
+也许你已经猜到了,Scheme 的布尔逻辑也是遵循函数式的,最常用的就是 `and` 和 `or` 两种了。和常见 C 系语言类似的是,`and` 和 `or` 都会将参数从左到右取值,一旦遇到满足停止条件的值就会停止。但是和传统 C 系语言不同,布尔逻辑的函数返回的不一定就是 `#t` 或者 `#f`,而有可能是输入值,这和很多脚本语言的行为是比较一致的:`and` 会返回最后一个非 `#f` 的值,而 `or` 则返回第一个非 `#f` 的值:
+
+```scheme
+1 ]=> (and #f 0)
+
+;Value: #f
+
+1 ]=> (and 1 2 "Hello")
+
+;Value 13: "Hello"
+
+1 ]=> (or #f 0)
+
+;Value: 0
+
+1 ]=> (or 1 2 "Hello")
+
+;Value: 1
+
+1 ]=> (or #f #f #f)
+
+;Value: #f
+```
+
+在很多时候,Scheme 中的 `and` 和 `or` 并不全是用来做条件的组合,而是用来简化一些代码的写法,以及为了顺次执行一些代码的。比如说下面的函数在三个输入都为正数的情况下返回它们的乘积,可以想象和对比一下在指令式编程中同样功能的实现。
+
+```scheme
+(define (pro3and a b c)
+ (and (positive? a)
+ (positive? b)
+ (positive? c)
+ (* a b c)
+ )
+)
+```
+
+除了 `if` 之外,在 C 系语言里另一种常见的条件分支语句是 `switch`。Scheme 里对应的函数是 `cond`。`cond` 接受多个二元列表作为输入,从上至下依次判断列表的第一项是否满足,如果满足则返回第二项的求值结果并结束,否则一直继续到最后的 `else`:
+
+```scheme
+(cond
+ (predicate_1 clauses_1)
+ (predicate_2 clauses_2)
+ ......
+ (predicate_n clauses_n)
+ (else clauses_else))
+```
+
+在新版的 Scheme 中,标准里加入了更多的流程控制的函数,它们包括 `begin`,`when` 和 `unless` 等。
+
+`begin` 将顺次执行一系列语句:
+
+```scheme
+(define (foo)
+ (begin
+ (display "hello")
+ (newline)
+ (display "world")
+ )
+)
+```
+
+`when` 当条件满足时执行一系列代码,而 `unless` 在条件不满足时执行一系列代码。这些改动可以看出一些现代脚本语言的特色,但是新的标准据说也在 Scheme 社区造成了不小争论。虽然结合使用 `if`,`and` 和 `or` 肯定是可以写出等效的代码的,但是这些额外的分支控制语句确实增加了语言的便利性。
+
+## 循环
+
+一门完备的编程语言必须的三个要素就是赋值,分支和循环。前两个我们已经看到了,现在来看看循环吧:
+
+### do
+
+```scheme
+1 ]=> (do ((i 0 (+ i 1))) ; 初始值和 step 条件
+ ((> i 4)) ; 停止条件,取值为 #f 时停止
+ (display i) ; 循环主体 (命令)
+ )
+01234
+;Value: #t
+```
+
+唯一要解释的是这里的条件是停止条件,而不是我们习惯的进入循环主体的条件。
+
+### 递归
+
+可以看出其实 `do` 写起来还是比较繁琐的。在 Scheme 中,一种更贴合语言特点的写法是使用递归的方式来完成循环:
+
+```scheme
+1 ]=> (define (count n)
+ (and (display (- 4 n))
+ (if (= n 0) #t (count (- n 1)))
+ )
+ )
+
+;Value: count
+
+1 ]=> (count 4)
+01234
+;Value: #t
+```
+
+### 列表和递归
+
+也许你会说,用递归的方式看起来一点也不简单,甚至代码要比上面的 `do` 的版本更难理解。现在看来确实是这样的,那是因为我们还没有接触 Scheme 里一些很独特的概念,cons cell 和 list。我们在上面介绍 `define` 的时候曾经提到过,cons cell 的 `car` 和 `cdr`。结合这个数据结构,Scheme 里的递归就会变得非常好用。
+
+那么什么是 cons cell 呢?其实没有什么特别的,cons cell 就是一种数据结构,它对应了内存的两个地址,每个地址指向一个值。
+
+
+
+要初始化一个上面图示的 cons cell,可以使用 `cons` 函数:
+
+```scheme
+1 ]=> (cons 1 2)
+
+;Value 13: (1 . 2)
+```
+
+我们可以使用 `car` 和 `cdr` 来取得一个 cons cell 的两部分内容 (`car` 是 "Contents of Address part of Register" 的缩写,`cdr` 是 "Contents of Decrement part of Register"):
+
+```scheme
+1 ]=> (car (cons 1 2))
+
+;Value: 1
+
+1 ]=> (cdr (cons 1 2))
+
+;Value: 2
+```
+
+cons cell 每个节点的内容可以是任意的数据类型。一种最常见的结构是 `car` 中是数据,而 `cdr` 指向另一个 cons cell:
+
+
+
+上面这样的数据结构对应的生成代码为:
+
+```scheme
+1 ]=> (cons 3 (cons 1 2))
+
+;Value 14: (3 1 . 2)
+```
+
+有一种特殊的 cons cell 链,其最后一个 cons cell 的 `cdr` 为空列表 `'()`,这类数据结构就是 Scheme 中的列表。
+
+
+
+对于列表,我们有一种更简单的创建方式,就是类似 `'(1 2 3)` 这样。对于列表来说,它的 `cdr` 值是一个子列表:
+
+```scheme
+1 ]=> '(1 2 3)
+
+;Value 15: (1 2 3)
+
+1 ]=> (car '(1 2 3))
+
+;Value: 1
+
+1 ]=> (cdr '(1 2 3))
+
+;Value 16: (2 3)
+```
+
+而循环其实质就是对一列数据进行处理的过程,结合 Scheme 列表的特性,我们意识到如果把列表运用在递归中的话,`car` 就是遍历的当前项,而 `cdr` 就是下一次递归的输入。Scheme 和递归调用可以说能配合得天衣无缝。
+
+比如我们定义一个将列表中的所有数都加上 1 的函数的话,可以这么处理:
+
+```scheme
+(define (ins_ls ls)
+ (if (null? ls)
+ '()
+ (cons (+ (car ls) 1) (ins_ls (cdr ls)))
+ )
+)
+
+(ins_ls '(1 2 3 4 5))
+
+;=> (2 3 4 5 6)
+```
+
+### 尾递归
+
+递归存在性能上的问题,因为递归的调用需要在栈上保持,然后再层层返回,这会造成很多额外的开销。对于小型的递归来说还勉强可以接受,但是对于递归调用太深的情况来说,这显然是不可扩展的做法。于是在 Scheme 中对于大型的递归我们一般会倾向于将它写为尾递归的方式。比如上面的加 1 函数,用尾递归重写的话:
+
+```scheme
+(define (ins_ls ls)
+ (ins_ls_interal ls '()))
+
+(define (ins_ls_interal ls ls0)
+ (if (null? ls)
+ ls0
+ (ins_ls_interal (cdr ls) (cons ( + (car ls) 1) ls0))))
+
+(define (rev_ls ls)
+ (rev_ls_internal ls '()))
+
+(define (rev_ls_internal ls ls0)
+ (if (null? ls)
+ ls0
+ (rev_ls_internal (cdr ls) (cons (car ls) ls0))))
+
+(rev_ls (ins_ls '(1 2 3 4 5)))
+
+;=> (2 3 4 5 6)
+```
+
+## 函数式
+
+上面介绍了 Scheme 的最基本的赋值,分支和循环。可以说用这些东西就能够写出一些基本的程序了。一开始会比较难理解 (特别是递归),但是相信随着深入下去和习惯以后就会好很多。到现在为止,除了在定义函数时,其实我们还没有直接触碰到 Scheme 的函数式特性。在 Scheme 里函数是一等公民,我们可以将一个函数作为参数传给另外的函数并进行调用,这就是高阶函数。
+
+一个最简单的例子是排序的时候我们可以将一个返回布尔值的函数作为排序规则:
+
+```scheme
+1 ]=> (sort '(7883 9099 6729 2828 7754 4179 5340 2644 2958 2239) <)
+
+;Value 13: (2239 2644 2828 2958 4179 5340 6729 7754 7883 9099)
+```
+
+更甚于我们可以使用一个匿名函数来控制这个排序,比如按照模 100 之后的大小 (也就是数字的后两位) 进行排序:
+
+```scheme
+1 ]=> (sort '(7883 9099 6729 2828 7754 4179 5340 2644 2958 2239)
+ (lambda (x y) (< (modulo x 100) (modulo y 100))))
+
+;Value 14: (2828 6729 2239 5340 2644 7754 2958 4179 7883 9099)
+```
+
+类似这样的特性在一些 modern 的语言里并不算罕见,但是要知道 Scheme 可是有些年头的东西了。类似的还有 `map`,`filter` 等。比如上面的 list 加 1 的例子,用 `map` 函数就可以非常简单地实现:
+
+```scheme
+(map (lambda (x) (+ x 1)) '(1 2 3 4 5))
+
+;=> (2 3 4 5 6)
+```
+
+## 接下来...
+
+篇幅有限,再往长写的话估计没什么人会想看完了。到这里为止关于 Scheme 的一些基础内容也算差不多了,大概阅读最简单的 Scheme 程序应该也没有太大问题了。在进一步的学习中,如果出现不认识的函数或者语法的话,可以求助 [SRFI](http://srfi.schemers.org/final-srfis.html) 下对应的文档或是在 [MIT/GNU Scheme 文档](http://www.gnu.org/software/mit-scheme/documentation/mit-scheme-ref/index.html#Top)中寻找。
+
+本文一开始提到的[教程](http://www.shido.info/lisp/idx_scm_e.html)很适合入门,之后的话可以开始参看 [SICP](http://mitpress.mit.edu/sicp/),可以对程序设计和 Scheme 的思想有更深的了解 (虽然阅读 SICP 的目的不应该是学 Scheme,Scheme 只是帮助你进行阅读和练习的工具)。因为我自己也就是个愣头青的初学者,所以无法再给出其他建议了。如果您有什么好的资源或者建议,非常欢迎在评论中提出。
+
+另外,相比起 Scheme,如果你想要在实际的工程中使用 Lisp 家族的语言的话,[Racket](http://racket-lang.org) 也许会是更好的选择。相比于面向数学和科学计算来说,Racket 支持对象类型等概念,更注重在项目实践方面的运用。
+
+就这样吧,我要继续去和 Scheme 过周末了。
diff --git a/_posts/2015-06-11-ios9-sdk.markdown b/_posts/2015-06-11-ios9-sdk.markdown
new file mode 100644
index 00000000..7bfb4d57
--- /dev/null
+++ b/_posts/2015-06-11-ios9-sdk.markdown
@@ -0,0 +1,96 @@
+---
+layout: post
+title: 开发者所需要知道的 iOS 9 SDK 新特性
+date: 2015-06-11 10:24:16.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+本文为InfoQ中文站特供稿件,[首发地址](http://www.infoq.com/cn/news/2015/06/ios9-sdk)。如需转载,请与InfoQ中文站联系。
+
+年年岁岁花相似,岁岁年年人不同。今年的 WWDC 一如既往的热闹,但是因为要照顾家里刚出生的宝宝以及宝宝的娘,就只能在家里的“窝里蹲”家庭影院来关注这一全球 Apple 开发者的盛会了。
+
+生命不息,学习不止。一如以往几年,我会陆续写一些关于 WWDC 和新的 SDK 里我觉得有意思和我自己重点关注和学习的内容。现在回头看前几年写的东西,愈来愈感觉到以前青葱岁月的自己真是傻得可爱。不过一路以来的成长轨迹倒是很明显,也希望自己能就这样淡然地将这段旅程继续下去。
+
+矫情结束,该干活了。让我们来看看今年的 WWDC 中我认为的开发者需要关注的一些内容吧。
+
+### 总览
+
+iOS 9 时代开发者面临的最大的挑战和最急切的任务可能有两个方面,首先是如何利用和适配 iPad 的新的分屏多任务特性,其次是如何面对和利用 watchOS 2 来构建原生的手表 app。另外的新课题基本就都是现有框架的衍生和扩展,包括从单元测试扩展到 UI 测试,如何进一步占领和使用系统的通知中心及搜索页面,以及 Swift 2 的使用等。
+
+可以说,经过了 iOS 7 和 iOS 8 连续两次重量级的变革和更新,对普通的 app 开发者来说,iOS 9 SDK 略归于缓和和平静,新的 SDK 在 API 和整体设计上并没有发生什么非常巨大的改变。开发者们也正可以利用这个机会喘息一下,尽快进一步熟悉和至少过渡到使用 iOS 8 SDK 的内容来构筑自己的 app (比如尝试使用 [Size Class 和 Presentation Controller](http://onevcat.com/2014/07/ios-ui-unique/) 等),尽快提升自己的职业技能和制作的 app 的水平,并保证能跟上滚滚向前的 Apple 车轮,应该是今年 Cocoa 开发者们的主要任务。
+
+### Multitasking
+
+这可以说是 iOS 9 最大的卖点了。多任务特性,特别是分屏多任务使得 iPad 真正变得像一个堪当重任的个人电脑。虽然在很早以前就已经有越狱插件能让 iPad 同时运行多个程序,但是 Apple 还是很谨慎地到 2015 年才在自己性能最为强劲的移动设备上实装这个功能。iOS 9 中的多任务分为三种表现形式,分别是临时调出的滑动覆盖 (Slide Over),视频播放的画中画模式 (Picture in Picture) 以及真正的同时使用两个 app 的分割视图 (Split View)。现在能运行 iOS 9 的设备中只有最新的 iPad Air 2 支持分割视图方式,但是相信随着设备的更新,分割视图的使用方式很可能成为人们日常使用 iPad 的一种主流方式,因此提早进行准备是开发者们的必修功课。
+
+虽然第一眼看上去感觉要支持多任务的视图会是一件非常复杂的事情,但是实际上如果你在前一年就紧跟 Apple 步伐的话,就很简单了。滑动覆盖和分割视图的 app 会使用 iOS 8 引入的 Size Class 中的 Compact Width 和 Regular Height 的设定,配合上 AutoLayout 来进行布局。也就是说,如果你的 app 之前就是 iPhone 和 iPad 通用的,并且已经使用了 Size Class 进行布局的话,基本上你不需要再额外做什么事儿就已经能支持 iOS 9 的多任务视图了。但是如果不幸你还没有使用这些技术的话,可能你会需要尽快迁移到这套布局方式中,才能完美支持了。
+
+视频 app 的画中画模式相对简单一些,如果你使用 `AVPlayerViewController` 或者 `AVPlayerLayer` 来播放视频的话,那什么都不用做就已经支持了。但如果你之前选择的方案是 `MPMoviePlayerController` 或者 `MPMoviePlayerViewController` 的话,你可能也需要尽早迁移到 AVKit 的框架下来,因为 Media Player 将在 iOS 9 被标记为 deprecated 并不再继续维护。
+
+> 相关专题笔记
+>
+> [iOS 9 多任务分屏要点](http://onevcat.com/2015/06/multitasking/)
+
+### watchOS 2
+
+在新的 watchOS 2 中,Watch App 的架构发生了巨大改变。新系统中 Watch App 的 extension 将不像现在这样存在于 iPhone 中,而是会直接安装到手表里去,Apple Watch 从一个单纯的界面显示器进化为了可执行开发者代码的设备。得益于此,开发者们也可以在 extension 中访问到像数字表冠和 (虽然都只是很初级的访问,但是聊胜于无) 心跳计数这样的情报。虽然有所进步,但是其实 Apple 在 watchOS 2 里表现出来的态度还是十分谨慎,这可能和初代 Apple Watch 的设备限制有很大关系,所以实际上留给 app 开发者的电量和性能空间并不是十分广阔。但是相比起现在的 WatchKit 来说,可以脱离 iPhone 运行本身就是了不起的进步了。而为了和 iPhone 进行通讯,现在还添加了 WatchConnectivity 这个新框架。我们有足够的理由期待 Apple Watch 和 WatchKit 在接下来两三年里的表现。
+
+> 相关专题笔记
+>
+> [30 分钟开发一个简单的 watchOS 2 app](http://onevcat.com/2015/08/watchos2/)
+
+### UI Test
+
+在开发领域里,测试一直是保障产品质量关键。从 Xcode 4 以来,测试在 app 开发中的地位可谓是逐年上升。从 XCT 框架的引入,到测试 target 成为新建项目时的默认,再到去年加入的异步代码测试和性能测试。可以说现在 Xcode 自带的测试框架已经能满足绝大部分单元测试的需求了。
+
+但是这并不够。开发一个 iOS app 从来都是更注重 UI 和用户体验的工作,而简单地单元测试可以很容易地保证 model 层的正确,却很难在 UI 方面有所作为。如何为一个 app 编写 UI 测试一直是 Cocoa 社区的难题之一。之前的话有像是 [KIF](https://github.com/kif-framework/KIF),[Automating](https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/UsingtheAutomationInstrument/UsingtheAutomationInstrument.html),甚至是 [FBSnapshotTestCase](https://github.com/facebook/ios-snapshot-test-case) 这种脑洞大开的方案。今年 Apple 给出了一个更加诱人的选项,那就是 Xcode 自带的 XCUITest 的一系列工具。
+
+和大部分已有的 UI 测试工具类似,XCUI 使用 Accessibility 标记来确定 view,但因为是 Apple 自家的东西,它可以自动记录你的操作流程,所以你只需要书写最后的验证部分就可以了,比其他的 UI 测试工具方便很多。
+
+> 相关专题笔记
+>
+> [Xcode 7 UI 测试初窥](http://onevcat.com/2015/09/ui-testing/)
+
+### Swift 2
+
+Swift 经过了一年的改善和进步,现在已经可以很好地担任 app 开发的工作了。笔者自己也已经使用 Swift 作为日常工作的主要语言有半年多时间了,这半年里的总体感觉是越写越舒畅。Swift 2 里主要的改动是错误处理方面的变化,Apple 从 Cocoa 传统的基于 `NSError` 错误处理方式变为了 throw catch 的异常处理机制。这个转变确实可以让程序更加安全,新增的 ErrorType 也很好地将错误描述进行了统一。但是在实际接触了一两天之后,在语法上感觉要比原来的处理写的代码多一些。可能是长久以来使用 NSError 的习惯导致吧,笔者还并没有能很好地全面接受 Swift 2 中的异常机制。不过这次 Apple 做的相对激进,把 Cocoa API 中的 error 全数替换成了 throw。所以不管情不情愿,转型到异常处理是 Swift 开发者必须面对的了。
+
+另外 Apple 新加了一些像是 `guard` 和 `defer` 这样的控制流关键字,这在其他一些语言里也是很实用的特性,这让 Swift 的书写更加简化,阅读起来更流畅。为了解决在运行时的不同 SDK 的可用性的问题,Apple 还在 Swift 2 里加入了 avaliable 块,以前我们需要自己去记忆 API 的可用性,并通过检查系统版本并进行对比来做这件事情。现在有了 avaliable 检测,编译器将会检查出那些可能出现版本不匹配的 API 调用,app 开发的安全性得到了进一步的保障。为了让整个 SDK 更适合 Swift 的语法习惯,Apple 终于在 Objective-C 中引入了泛型。这看似是 Objective-C 的加强,但是实际上却实实在在地是为 Swift 一统 Apple 开发开路。有了 Objective-C 泛型以后,用 Swift 访问 Cocoa API 基本不会再得到 `AnyObject` 类型了,这使得 Swift 的安全特性又上了一层台阶。
+
+最后是 Swift 2 开源的消息。Swift 的编译器和标准库将在今年年底开源,对于一般的 app 开发者来说可能并不会带来什么巨变,但这确实意味着 Swift 将从一门 app 制作的专用语言转型为一门通用语言。最容易想到的就是基于 Swift 的后端开发,也许我们会在看到 Javascript 一统天下之前就能先感受一下 Swift 全栈的力量?
+
+> 关于 Swift 2 的新内容,我已经在我的[《Swifter - 100 个 Swift 必备 tips》](https://selfstore.io/products/171)一书的第二版中进行了详细的叙述。
+
+### App Thinning
+
+笔者在日本工作,因为这边大家流量都是包月且溢出的,所以基本不会有人对 app 的尺寸介意,无非就是下载 5 秒还是 10 秒的区别。但是在和国内同行交流的时候,发现国内 app 开发对尺寸的要求近乎苛刻。因为 iOS app 为了后向兼容,现在都同时包含了 32 bit 和 64 bit 两个 slice。另外在图片资源方面,更是 1x 2x 3x 的图像一应俱全 (好吧现在 1x 应该不太需要了)。而用户使用 app 时,因为设备是特定的,其实只需要其中的一套资源。但是现在在购买和下载的时候却是把整个 app 包都下载了。
+
+Apple 终于意识到了这件事情有多傻,iOS 9 中终于可以仅选择需要的内容 (Slicing) 下载了。这对用户来说是很大的利好,因为只需要升级到 iOS 9,就可以节省很多流量。对于开发者来说,并没有太多要做的事情,只需要使用 asset catalog 来管理素材标记 2x 3x 就可以了。
+
+给 App 瘦身的另一个手段是提交 Bitcode 给 Apple,而不是最终的二进制。Bitcode 是 LLVM 的中间码,在编译器更新时,Apple 可以用你之前提交的 Bitcode 进行优化,这样你就不必在编译器更新后再次提交你的 app,也能享受到编译器改进所带来的好处。Bitcode 支持在新项目中是默认开启的,没有特别理由的话,你也不需要将它特意关掉。
+
+最后就是按需加载的资源。这可能在游戏中应用场景会多一些。你可以用 tag 来组织像图像或者声音这样的资源,比如把它们标记为 level1,level2 这样。然后一开始只需要下载 level1 的内容,在玩的过程中再去下载 level2。或者也可以通过这个来推后下载那些需要内购才能获得的资源文件。在一些大型游戏里这是很常见的优化方法,现在在 iOS 9 里也可以方便地使用了。
+
+### 人工智能和搜索 API
+
+如果说这届 WWDC Keynote 上还有什么留给我印象深刻的内容的话,我会给更加智能的手机助理投上一票。虽然看起来还很初级,比如就是插入耳机时播放你喜欢的音乐,推荐你可能会联系的人和打开的 app 等,但是这确实是很有意义的一步。现在的 Siri 只是一个问答系统,如果上下文中断,“她”甚至不记得前面两句话说了些什么。一个不会记住 Boss 习惯的秘书一定不是一个好护士,而 Apple 正在让 iPhone 向这方面努力。好消息是我们大概暂时还不用担心会碰到故意不通过图灵测试的机器,所以在人工智能上还有很大的空间可以发挥。
+
+而[搜索 API](https://developer.apple.com/library/prerelease/ios/releasenotes/General/WhatsNewIniOS/Articles/iOS9.html#//apple_ref/doc/uid/TP40016198-DontLinkElementID_1) 实质上让 app 多了一个可能的入口。有些用户会非常频繁地使用搜索界面,这是一个绝好的展示你的 app 和提高打开率的机会。如果 app 类型合适的话,这是非常值得一做的追加特性。
+
+### 游戏相关
+
+游戏类的 app 因为在不同的移动平台上的用户体验并没有鸿沟似的差异,所以是最容易跨平台的 - 毕竟现在无论哪个开发商都无法忽视安卓的份额。这也是 Apple 自家的 SpriteKit 和 SceneKit 这样的游戏框架一直不温不火的原因。比起被局限在 Apple 平台,更多的开发商选择像是 Unity 或者 Cocos2d-x 这样的跨平台方案。但是今年 Apple 还是持续加强了游戏方面的开发工具支持,包括负责状态机维护和寻路等的 GameplayKit 框架,负责录像和回放游戏过程的 ReplayKit 框架,以及物理建模的 Model I/O 框架。
+
+这些其实都是在 Apple 的游戏开发体系中补充了一些游戏业界已经很成熟的算法和工具,为开发者节省了不少时间。对于个人开发者自制的游戏来说,Apple 的工具提供了相对低的门槛,易于上手。但是在现在大部分游戏开发都需要跨平台的年代,总感觉 Apple 体系是否能顺利走下去还需要进一步观察。
+
+### 其他
+
+HomeKit,CloudKit,HealthKit 等等杂七杂八的框架。如果是 iOS Only 的 app 的话,使用 CloudKit 做 [BaaS](http://en.wikipedia.org/wiki/Mobile_Backend_as_a_service) 也许是不错的选择,但是也要面临今后跨平台数据难以共享的风险。其他几个框架专业性相对较强,大部分需要配合硬件支援,其实一直说智能硬件是下一个爆点,但是至少现在为止还没能爆出大的声响,更多的却已经进入到廉价竞争 (手环什么的你懂的),只能说期待这些设备的后续表现吧。
+
+最后是一个对于刚入门或者打算投身到 Apple 开发中的朋友的福利。现在你可以不需要加入付费的开发者计划就能将 app 部署到自己的设备上了,而在以前这至少需要你加入 99 美金每年的开发者计划,这可以说进一步降低了进行 Apple 开发的门槛。
+
+### 总结
+
+正如上面提到的,对开发者来说,今年的 WWDC 并没有像 13 年和 14 年那样颠覆性的变化,大多是对已有特性的加强补充和对开发工具链的增强。今年可以说是一个 Cocoa 开发者们沉淀之前知识,增进自己技能的好机会。现在 WWDC 15 还在如火如荼的进行之中。如果你打算尽早拥抱新 SDK 的变化的话,请不要犹豫,直接访问 Apple 的[开发者网站](https://developer.apple.com/),去寻找和观看自己感兴趣的话题吧。
diff --git a/_posts/2015-06-15-multitasking.markdown b/_posts/2015-06-15-multitasking.markdown
new file mode 100644
index 00000000..ce7f47dd
--- /dev/null
+++ b/_posts/2015-06-15-multitasking.markdown
@@ -0,0 +1,106 @@
+---
+layout: post
+title: WWDC15 Session笔记 - iOS 9 多任务分屏要点
+date: 2015-06-15 16:25:07.000000000 +09:00
+tags: 能工巧匠集
+---
+本文是我的 [WWDC15 笔记](http://onevcat.com/2015/06/ios9-sdk/)中的一篇,涉及的 Session 有
+
+- [Getting Started with Multitasking on iPad in iOS 9](https://developer.apple.com/videos/wwdc/2015/?id=205)
+- [Multitasking Essentials for Media-Based Apps on iPad in iOS 9](https://developer.apple.com/videos/wwdc/2015/?id=211)
+- [Optimizing Your App for Multitasking on iPad in iOS 9](https://developer.apple.com/videos/wwdc/2015/?id=212)
+
+### iOS 9 多任务综述
+
+iOS 9 中最引人注目的新特性就是多任务了,在很久以前的越狱开发里就已经出现过类似的插件,而像是 Windows Surface 系列上也已经有分屏多任务的特性,可以让用户同时使用两个或多个 app。iOS 9 中也新加入类似的特性。iOS 9 中的多任务有三种表现形式,临时出现和交互的滑动覆盖 (Slide Over),真正的分屏同时操作两个 app 的分割视图 (Split View),以及在其他 app 里依然可以进行视频播放的画中画 (Picture in Picture) 模式。
+
+
+
+在关于多任务的文档中,Apple 明确指出:
+
+> 绝大部分 app 都应当适配 Slide Over 和 Split View
+
+因为这正是 iOS 9 的核心功能之一,也是你的用户所期望看到的。另一方面,支持多任务也增加了你的用户打开和使用你的 app 的可能。不过多任务有一点限制,那就是在能够安装 iOS 9 的 iPad 设备上,仅只有性能最强大的 iPad Air 2 和之后的机型支持分割视图模式,而其他像是 iPad mini 2,iPad mini 3 以及 iPad Air 只支持滑动覆盖和画中画两种模式。这在一定程度上应该还是基于移动设备资源和性能限制的考虑做出的决策,毕竟要保证良好的使用体验为前提,多任务才会有意义。
+
+对于开发者来说,虽然多种布局看起来很复杂,但是实际上如果紧跟 Apple 的技术步伐的话,将自己的 iPad app 进行多任务适配并不会是一件非常困难的事情。因为滑动覆盖模式和分割视图模式所采用的布局其实就是 Compact Width 的布局,而这一概念就是 WWDC14 上引入的基于屏幕特征的 UI 布局方式。如果你已经在使用这套布局方式了的话,那么可以说多任务视图的支持也就顺带自动完成了。不过如果你完全没有使用过甚至没有听说过这套布局方法的话,我去年的[一篇笔记](http://onevcat.com/2014/07/ios-ui-unique/)可能能帮你对此有初步了解,在下一节里我也会稍微再稍微复习一下相关概念和基本用法。
+
+### Adaptive UI 复习
+
+Adaptive UI 是 Apple 在 iOS 8 提出的概念。在此之前,我们如果想要同时为 iPhone 和 iPad 开发 app 的话,很可能会写很多设备判断的代码,比如这样:
+
+```swifts
+if UI_USER_INTERFACE_IDIOM() == .Pad {
+ // 设备是 iPad
+}
+```
+
+除此之外,如果我们想要同时适配横向和纵向的话,我们会需要类似这样的代码:
+
+```swift
+if UIInterfaceOrientationIsPortrait(orientation) {
+ // 屏幕是竖屏
+}
+```
+
+这些判断和分支不仅难写难读,也使适配开发困难重重。从 iOS 8 之后,开发者不应该再依赖这样设备向来进行 UI 适配,而应该转而使用新的 Size Class 体系。Apple 将自家的移动设备按照尺寸区别,将纵横两个方向设计了 Regular 和 Compact 的组合。比如 iPhone 在竖屏时宽度是 Compact,高度是 Regular,横屏时 iPhone 6 Plus 宽度是 Regular,高度是 Compact,而其他 iPhone 在横屏时高度和宽度都是 Compact;iPad 不论型号和方向,宽度及高度都是 Regular。现有的设备的 Size Class 如下图所示:
+
+
+
+针对 Size Class 进行开发的思想下,我们不再关心具体设备的型号或者尺寸,而是根据特定的 Size Class 的特性来展示内容。在 Regular 的宽度下,我们可以在水平方向上展示更多的内容,比如同时显示 Master 和 Detail View Controller 等。同样地,我们也不应该再关心设备旋转的问题,而是转而关心 Size Class 的变化。在开发时,如果是使用 Interface Builder 的话,在制作 UI 时就注意为不同的 Size Class 配置合适的约束和布局,在大多数情况下就已经足够了。如果使用代码的话,`UITraitCollection` 类将是使用和操作 Size Class 的关键。我们可以根据当前工作的 `UIViewController` 的 `traitCollection` 属性来设置合适的布局,并且在 `
+-willTransitionToTraitCollection:withTransitionCoordinator:` 和 `
+-viewWillTransitionToSize:withTransitionCoordinator:` 被调用时对 UI 布局做出正确的响应。
+
+虽然并不是理论上不可行,但是使用纯手写来操作 Size Class 会是一件异常痛苦的事情,我们还是应该尽可能地使用 IB 来减少这部分的工作量,加快开发效率。
+
+### iPad 中的多任务适配
+
+对于 iOS 9 中的多任务,滑动覆盖和分割视图的初始位置,新打开的 app 的尺寸都将是设备尺寸的 1/3。不过这个比例并不重要,我们需要记住的是新打开的 app 将运行在 Compact Width 和 Regular Height 的 Size Class 下。也就是说,如果你的 iPad app 使用了 Size Class 进行布局,并且是支持 iPhone 竖屏的,那么恭喜,你只需要换到 iOS 9 SDK 并且重新编译你的 app,就搞定了。
+
+因为本文的重点不是教你怎么开发一个 Adaptive UI 的 app,所以并不打算在这方面深入下去。如果你在去年缺了课,不是很了解这方面的话,[这篇教程](http://www.raywenderlich.com/83276/beginning-adaptive-layout-tutorial)可能可以帮你快速了解并掌握这些内容。如果你想要直接上手看看 iOS 9 中的 多任务是如何工作的话,可以新建一个 Master-Detail Application,并将其安装到 iPad 模拟器上。Master-Detail 的模板工程为我们搭设了一个很好的适配 Size Class 的框架,让项目可以在任何设备上都表现良好。同样你也可以观察它在 iOS 9 的 iPad 上的表现。
+
+但是其实并不是所有的 app 都应该适配多任务,比如一个需要全屏才能体验的游戏就是典型。如果你不想你的 app 可以作为多任务的副 app 被使用的话,你可以在 Info.plist 中添加 `UIRequiresFullScreen` 并将其设为 `true`。
+
+Easy enough?没错,要适配 iPad 的多任务,你需要做的就只有按照标准流程开发一个全平台通用 app,仅此而已。
+
+1. 使用 iOS 9 SDK 构建你的 app;
+2. 支持所有的方向和对应的 Size Class;
+3. 使用 launch storyboard 作为 app 启动页面。
+
+虽说没太多特别值得一提的内容,但是也还是有一些需要注意的小细节。
+
+### 一些值得注意的小细节
+
+在以前是不存在 app 在前台还要和别的 app 共享屏幕这种事情的,所以 `UIScreen.bounds` 和主窗口的 `UIWindow.bounds` 使用上来说基本是同义词。但是在多任务时代,`UIWindow` 就有可能只有 1/3 或者 1/2 屏幕大小了。如果你在之前的 app 中有使用它来定义你的视图的话,就有必要为多任务做特殊的处理了。不过虽然滑动覆盖和分割视图都是在右侧展示,但是它们的 Window 的 origin 依然是 (0, 0),这也方便了我们定义视图。
+
+第二个细节是现在 iPad UI 的 Size Class 是会发生变化的。以前不论是竖直还是水平,iPad 屏幕的 Size 总是长宽均为 Regular 的。但是在 iOS 9 中情况就不一样了,你的 app 可能被作为附加 app 通过多任务模式打开,可能会在多任务时被用户拖动从而变成全屏 app (这时 Size Class 将从 Compact 的宽度变为 Regular),甚至可能你的 app 作为主 app 被使用是会因为用户拖动而变成 Compact 宽度的 app:
+
+
+
+换句话说,你不知道你的 app 的 Size Class 会不会变化,以及何时变化,这都是用户操作的结果。因此在开发时,就必须充分考虑到这一点,力求在尺寸变化时呈现给用户良好的效果。根据屏幕大小进行合适的 UI 设计和调整自不用说,另外还应当注意在合适的时机利用 `transitionCoordinator` 的 `-animateAlongsideTransition:` 来进行布局动画,让切换更加自然。
+
+由于多任务带来了多个 app 同台运行的可能性,因此你的 app 必定会面临和别的 app 一起运行的情况。在开发移动应用时永远不能忘记的是设备平台的限制。相比于桌面设备,移动端只有有限的内存,而两个甚至三个 app 同时在前台运行,就需要我们精心设计内存的使用。对于一般开发者来说,合理地分配内存,监听 Memory Warning 来释放 cache 和不必要的 view controller,避免循环引用等等,应该成为熟练掌握的日常开发基本功。
+
+最后一个细节是对完美的苛求了。在 iOS 9 中多任务也通过 App Switcher 来进行 app 之间的切换的。所以在你的 app 被切换到后台时,系统会保存你的 app 的当前状态的截图,以供之后切换时显示。你的 app 现在有可能被作为 Regular 的全屏 app 使用,也可能使用 Compact 布局,所以在截图时系统也会依次保存两份截图。用户可能会在全屏模式下把你的 app 关闭,然后通过多任务再将你的 app 作为附加 app 打开,这时最好能保证 App Switcher 中的截图和 app 打开后用户看到的截图一致,以获取最好的体验。可能这并不是一个很大的问题,但是如果追求极致的用户体验的话,这也是必行的。对于那些含有用户敏感数据,需要将截图模糊处理的 app,现在也需要注意同时将两种布局的截图都进行处理。
+
+### 画中画模式
+
+iOS 9 中多任务的另一种表现形式就是视频的画中画模式:即使退出了,你的视频 app 也可以在用户使用别的 app 的时候保持播放,比如一边看美剧一边写日记或者发邮件。这大概会是所有的视频类 app 都必须要支持的特性了,实现起来也很容易:
+
+1. 使用 iOS 9 SDK 构建你的 app;
+2. 在 app 的 Capabilities 里,将 Background Modes 的 "Audio, AirPlay, and Picture in Picture" 勾选上 (Xcode 7 beta 中暂时为 "Audio and AirPlay");
+3. 将 AudioSession Catogory [设置为合适的选项](https://gist.github.com/onevcat/82defadf559968c6a3bc),比如 `AVAudioSessionCategoryPlayback`
+4. 使用 AVKit,AVFoundation 或者 WebKit 框架来播放视频。
+
+在 iOS 9 中,一直伴随我们的 MediaPlayer 框架中的视频播放部分正式宣布寿终正寝。也就是说,如果你在使用 `MPMoviePlayerViewController` 或者 `MPMoviePlayerController` 在播放视频的话,你就无法使用画中画的特性了,因此尽快转型到新的视频播放框架会是急迫的适配任务。因为画中画模式是基于 `AVPlayerLayer` 的。当切换到画中画时,会将正在播放视频的 layer 取出,然后进行缩小后添加到新的界面的 layer 上。这也是旧的 MediaPlayer 框架无法支持画中画的主要原因。
+
+如果你使用 `AVPlayerViewController` 的话,一旦满足这些简单的条件以后,你应该就可以在使用相应框架全屏播放视频时看到右下角的画中画按钮了。不论是点击这个按钮进入画中画模式还是直接使用 Home 键切换到后台,已经在播放的视频就将缩小到屏幕右下角成为画中画,并保持播放。在画中画模式下,系统会在视频的 AVPlayerLayer 上添加一套默认控件,用来控制暂停/继续,关闭,以及返回 app。前两个控制没什么可多说的,返回 app 的话需要我们自己处理返回后的操作。一般来说我们希望能够恢复到全屏模式并且继续播放这个视频,因为 `AVPlayerViewController` 进行播放时我们一般不会去操作 `AVPlayerLayer`,在恢复时就需要实现 `AVPlayerViewControllerDelegate` 中的 `-playerViewController:restoreUserInterfaceForPictureInPictureStopWithCompletionHandler:` 来根据传入的 ViewController 重建 UI,并将 `true` 通过 CompletionHandler 返回给系统,已告知系统恢复成功 (当然如果无法恢复的话需要传递 false)。
+
+我们也可以直接用 `AVPlayerLayer` 来构建的自定义的播放器。这时我们需要通过传入所使用的 `AVPlayerLayer` 来创建一个 `AVPictureInPictureController`。`AVPictureInPictureController` 提供了检查是否支持画中画模式的 API,以及其他一些控制画中画行为的方法。与直接使用 `AVPlayerViewController` 不太一样的是,在恢复时,系统将会把画中画时缩小的 `AVPlayerLayer` 返还到之前的 view 上。我们可以通过 `AVPictureInPictureControllerDelegate` 中的相应方法来获知画中画的执行情况,并结合自己 app 的情况来恢复 UI。
+
+### 总结
+
+通过之前几年的布局,在 AutoLayout 和 Size Class 的基础上,Apple 在 iOS 9 中放出了多任务这一杀手锏。可以说同屏执行多个 app 的需求从初代 iPad 开始就一直存在,而现在总算是姗姗来迟。在 OS X 10.11 中,Apple 也将类似的特性引入了 OSX app 的全屏模式中,可以说是统一 OSX 和 iOS 这两个平台的进一步尝试。
+
+但是 iPad 上的多任务还是有一些不足的。最大的问题是 app 依然是运行在沙盒中的,这就意味着在 iOS 上我们还是无法在两个 app 之间进行通讯:比如同时打开照片和一个笔记 app,我们无法通过拖拽方式将某张图片直接拖到笔记中去。虽然在 iOS 中也有 [XPC 服务](https://developer.apple.com/library/mac/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingXPCServices.html),但是第三方开发者现在并不能使用,这在一定程度上还是限制了多任务的可能性。
+
+不过总体来说,多任务特性使得 iPad 的实用性大大上升,这也肯定会是未来用户最常用以及最希望在 app 中看到的特性之一。花一点时间,学习 Adaptive UI 的制作方式,让 app 支持多任务运行,会是一件很合算的事情。
diff --git a/_posts/2015-08-03-watchos2.markdown b/_posts/2015-08-03-watchos2.markdown
new file mode 100644
index 00000000..c80a7e40
--- /dev/null
+++ b/_posts/2015-08-03-watchos2.markdown
@@ -0,0 +1,997 @@
+---
+layout: post
+title: WWDC15 Session笔记 - 30 分钟开发一个简单的 watchOS 2 app
+date: 2015-08-03 23:23:34.000000000 +09:00
+tags: 能工巧匠集
+---
+Apple Watch 和 watchOS 第一代产品只允许用户在 iPhone 设备上进行计算,然后将结果传输到手表上进行显示。在这个框架下,手表充当的功能在很大程度上只是手机的另一块小一些的显示器。而在 watchOS 2 中,Apple 开放了在手表端直接进行计算的能力,一些之前无法完成的 app 现在也可以进行构建了。本文将通过一个很简单的天气 app 的例子,讲解一下 watchOS 2 中新引入的一些特性的使用方法。
+
+本文是我的 [WWDC15 笔记](http://onevcat.com/2015/06/ios9-sdk/)中的一篇,在 WWDC15 中涉及到 watchOS 2 的相关内容的 session 非常多,本文所参考的有:
+
+* [Introducing WatchKit for watchOS 2](https://developer.apple.com/videos/wwdc/2015/?id=105)
+* [WatchKit In-Depth, Part 1](https://developer.apple.com/videos/wwdc/2015/?id=207)
+* [WatchKit In-Depth, Part 2](https://developer.apple.com/videos/wwdc/2015/?id=208)
+* [Introducing Watch Connectivity](https://developer.apple.com/videos/wwdc/2015/?id=713)
+* [Building Watch Apps](https://developer.apple.com/videos/wwdc/2015/?id=108)
+* [Creating Complications with ClockKit](https://developer.apple.com/videos/wwdc/2015/?id=209)
+
+## 项目简介
+
+作为一个示例项目,我们就来构建一个最简单的天气 app 吧。本文将一步步带你从零开始构建一个相对完整的 iOS + watch app。这个 app 的 iOS 端很简单,从数据源取到数据,然后解析成天气的 model 后,通过一个 PageViewController 显示出来。为了让 demo 更有说服力,我们将展示当前日期以及前后两天的天气情况,包括天气状况和气温。在手表端,我们希望构建一个类似的 app,可以展示这几天的天气情况。另外我们当然也介绍如何利用 watchOS 2 的一些新特性,比如 complications 和 Time Travel 等等。
+
+## 开始
+
+虽然本文的重点是 watchOS,但是为了完整性,我们还是从开头开始来构建这个 app 吧。因为不管是 watchOS 1 还是 2,一个手表 app 都是无法脱离手机 app 单独存在和申请的。所以我们首先来做的是一个像模像样的 iOS app 吧。
+
+### 新建项目
+
+第一步当然是使用 Xcode 7 新建一个工程,这里我们直接选择 iOS App with WatchKit App,这样 Xcode 将直接帮助我们建立一个带有 watchOS app 的 iOS 应用。
+
+
+
+在接下来的画面中,我们选中 Include Complication 选项,因为我们希望制作一个包含有 Complication 的 watch app。
+
+
+
+### UI
+
+这个 app 的 UI 部分比较简单,我将使用到的素材都放到了[这里](/assets/images/2015/WatchWeatherImage.zip)。你可以下载这些素材,并把它们解压并拖拽到项目 iOS app 的 Assets.xcassets 里去:
+
+
+
+接下来,我们来构建 UI 部分。我们想要使用 PageViewController 来作为 app 的导航,首先,在 Main.StoryBoard 中删掉原来的 ViewController,并新加一个 Page View Controller,然后在它的 Attributes Inspector 中将 Transition Style 改为 Scroll,并勾选上 Is Initial View Controller。这将使这个 view controller 成为整个 app 的入口。
+
+
+
+接下来,我们需要将这个 Page View Controller 和代码关联起来。首先将 ViewController.swift 文件中,将 ViewController 的继承关系从 `UIViewController` 改为 `UIPageViewController`。
+
+```swift
+class ViewController: UIPageViewController {
+ ...
+}
+```
+
+然后我们就可以在 StoryBoard 文件中将刚才的 Page View Controller 的 class 改为我们的 `ViewController` 了。
+
+
+
+另外我们还需要一个实际展示天气的 View Controller。创建一个继承自 `UIViewController` 的 `WeatherViewController`,然后将 WeatherViewController.swift 的内容替换为:
+
+```swift
+import UIKit
+
+class WeatherViewController: UIViewController {
+
+ enum Day: Int {
+ case DayBeforeYesterday = -2
+ case Yesterday
+ case Today
+ case Tomorrow
+ case DayAfterTomorrow
+ }
+
+ var day: Day?
+}
+```
+
+这里仅只是定义了一个 `Day` 的枚举,它将用来标记这个 `WeatherViewController` 所代表的日期 (可能你会说把 `Day` 在 ViewController 里并不是很好的选择,没错,但是放在这里有助于我们快速搭建 app,在之后我们会对此进行重构)。接下来,我们在 StoryBoard 中添加一个 ViewController,并将它的 class 改为 `WeatherViewController`。我们可以在这里构建 UI,对于这个 demo 来说,一个简单的背景,加上表示天气的图标和表示温度的标签就足够了。因为这里并不是一个关于 Auto Layout 或是 Size Class 的 demo,所以就不详细一步步地做了,我随意拖了拖 UI 和约束,最后结果如下图所示。
+
+
+
+接下来就是从 StoryBoard 中把需要的 IBOutlet 拖出来。我们需要天气图标,最高最低温度的 label。完成这些 UI 工作之后的项目可以在 GitHub 的[这个 tag](https://github.com/onevcat/WatchWeather/releases/tag/ui-setup) 下找到,如果你不想自己完成这些步骤的话,也可以直接使用这个 tag 的源文件来继续下面的 demo。当然,如果你对 AutoLayout 和 Interface Builder 还不熟悉的话,这会是一个很好的机会来从简单的布局入手,去理解对 IB 的使用。关于更多 IB 和 StoryBoard 的教程,推荐 Raywenderlich 的这两篇系列文章:[Storyboards Tutorial in Swift](http://www.raywenderlich.com/81879/storyboards-tutorial-swift-part-1) 和 [Auto Layout Tutoria](http://www.raywenderlich.com/83129/beginning-auto-layout-tutorial-swift-part-1)。
+
+然后我们可以考虑先把 Page View Controller 的框架实现出来。在 `ViewController.swift` 中,我们首先在 `ViewController` 类中加入以下方法:
+
+```swift
+func weatherViewControllerForDay(day: WeatherViewController.Day) -> UIViewController {
+
+ let vc = storyboard?.instantiateViewControllerWithIdentifier("WeatherViewController") as! WeatherViewController
+ let nav = UINavigationController(rootViewController: vc)
+ vc.day = day
+
+ return nav
+}
+```
+
+这将从当前的 StroyBoard 里寻找 id 为 "WeatherViewController" 的 ViewController,并且初始化它。我们希望能为每一天的天气显示一个 title,一个比较理想的做法就是直接将我们的 WeatherViewController 嵌套在 navigation controller 里,这样我们就可以直接使用 navigation bar 来显示标题,而不用去操心它的布局了。我们刚才并没有为 `WeatherViewController` 指定 id,在 StoryBoard 中,找到 WeatherViewController,然后在 Identity 里添加即可:
+
+
+
+接下来我们来实现 `UIPageViewControllerDataSource`。在 `ViewController.swift` 的 `viewDidLoad` 里加入:
+
+```swift
+dataSource = self
+let vc = weatherViewControllerForDay(.Today)
+setViewControllers([vc], direction: .Forward, animated: true, completion: nil)
+```
+
+首先它将 `viewController` 自己设置为 dataSource。然后设定了初始需要表示的 viewController。对于 `UIPageViewControllerDataSource` 的实现,我们在同一文件中加入一个 `ViewController` 的 extension 来搞定:
+
+```swift
+extension ViewController: UIPageViewControllerDataSource {
+ func pageViewController(pageViewController: UIPageViewController, viewControllerBeforeViewController viewController: UIViewController) -> UIViewController? {
+ guard let nav = viewController as? UINavigationController,
+ viewController = nav.viewControllers.first as? WeatherViewController,
+ day = viewController.day else {
+ return nil
+ }
+
+ if day == .DayBeforeYesterday {
+ return nil
+ }
+
+ guard let earlierDay = WeatherViewController.Day(rawValue: day.rawValue - 1) else {
+ return nil
+ }
+
+ return self.weatherViewControllerForDay(earlierDay)
+ }
+
+ func pageViewController(pageViewController: UIPageViewController, viewControllerAfterViewController viewController: UIViewController) -> UIViewController? {
+ guard let nav = viewController as? UINavigationController,
+ viewController = nav.viewControllers.first as? WeatherViewController,
+ day = viewController.day else {
+ return nil
+ }
+
+ if day == .DayAfterTomorrow {
+ return nil
+ }
+
+ guard let laterDay = WeatherViewController.Day(rawValue: day.rawValue + 1) else {
+ return nil
+ }
+
+ return self.weatherViewControllerForDay(laterDay)
+ }
+}
+```
+
+这两个方法分别根据输入的 View Controller 对象来确定前一个和后一个 View Controller,如果返回 `nil` 则说明没有之前/后的页面了。另外,我们可能还想要先将 title 显示出来,以确定现在的架构是否正确工作。在 `WeatherViewController.swift` 的 Day 枚举里添加如下属性:
+
+```swift
+var title: String {
+ let result: String
+ switch self {
+ case .DayBeforeYesterday: result = "前天"
+ case .Yesterday: result = "昨天"
+ case .Today: result = "今天"
+ case .Tomorrow: result = "明天"
+ case .DayAfterTomorrow: result = "后天"
+ }
+ return result
+ }
+```
+
+然后将 `day` 属性改为:
+
+```swift
+var day: Day? {
+ didSet {
+ title = day?.title
+ }
+}
+```
+
+运行 app,现在我们应该可以在五个页面之间进行切换了。你也可以从 GitHub 上[对应的 tag](https://github.com/onevcat/WatchWeather/releases/tag/basic-workflow) 中下载到目前为止的项目。
+
+
+
+### 重构和 Model
+
+很难有人一次性就把代码写得完美无瑕,这也是重构的意义。重构从来不是一个“等待项目完成后再开始”的活动,而是应该随着项目的展开和进行,一旦发现有可能存在问题的地方,就尽快进行改进。比如在上面我们将 `Day` 放在了 `WeatherViewController` 中,这显然不是一个很好地选择。这个枚举更接近于 Model 层的东西而非控制层,我们应该将它迁移到另外的地方。同样现在还需要实现的还有天气的 Model,即表征天气状况和高低温度的对象。我们将这些内容提取出来,放到一个 framework 中去,以便使用的维护。
+
+
+
+我们首先对现有的 `Day` 进行迁移。创建一个新的 Cocoa Touch Framework target,命名为 `WatchWeatherKit`。在这个 target 中新建 `Day.swift` 文件,其中内容为:
+
+```swift
+public enum Day: Int {
+ case DayBeforeYesterday = -2
+ case Yesterday
+ case Today
+ case Tomorrow
+ case DayAfterTomorrow
+
+ public var title: String {
+ let result: String
+ switch self {
+ case .DayBeforeYesterday: result = "前天"
+ case .Yesterday: result = "昨天"
+ case .Today: result = "今天"
+ case .Tomorrow: result = "明天"
+ case .DayAfterTomorrow: result = "后天"
+ }
+ return result
+ }
+}
+```
+
+这就是原来存在于 `WeatherViewController` 中的代码,只不过将必要的内容申明为了 `public`,这样我们才能在别的 target 中使用它们。我们现在可以将原来的 Day 整个删除掉了,接下来,我们在 `WeatherViewController.swift` 和 `ViewController.swift` 最上面加入 `import WatchWeatherKit`,并将 `WeatherViewController.Day` 改为 `Day`。现在 `Day` 枚举就被隔离出 View Controller 了。
+
+然后实现天气的 Model。在 `WatchWeatherKit` 里新建 `Weather.swift`,并书写如下代码:
+
+```swift
+import Foundation
+
+public struct Weather {
+ public enum State: Int {
+ case Sunny, Cloudy, Rain, Snow
+ }
+
+ public let state: State
+ public let highTemperature: Int
+ public let lowTemperature: Int
+ public let day: Day
+
+ public init?(json: [String: AnyObject]) {
+
+ guard let stateNumber = json["state"] as? Int,
+ state = State(rawValue: stateNumber),
+ highTemperature = json["high_temp"] as? Int,
+ lowTemperature = json["low_temp"] as? Int,
+ dayNumber = json["day"] as? Int,
+ day = Day(rawValue: dayNumber) else {
+ return nil
+ }
+
+
+ self.state = state
+ self.highTemperature = highTemperature
+ self.lowTemperature = lowTemperature
+ self.day = day
+ }
+}
+```
+
+Model 包含了天气的状态信息和最高最低温度,我们稍后会用一个 JSON 字符串中拿到字典,然后初始化它。如果字典中信息不全的话将直接返回 `nil` 表示天气对象创建失败。到此为止的项目可以在 GitHub 的 [model tag](https://github.com/onevcat/WatchWeather/releases/tag/model) 中找到。
+
+### 获取天气信息
+
+接下来的任务是获取天气的 JSON,作为一个 demo 我们完全可以用一个本地文件替代网络请求部分。不过因为之后在介绍 watch app 时会用到使用手表进行网络请求,所以这里我们还是从网络来获取天气信息。为了简单,假设我们从服务器收到的 JSON 是这个样子的:
+
+```json
+{"weathers": [
+ {"day": -2, "state": 0, "low_temp": 18, "high_temp": 25},
+ {"day": -1, "state": 2, "low_temp": 9, "high_temp": 14},
+ {"day": 0, "state": 1, "low_temp": 12, "high_temp": 16},
+ {"day": 1, "state": 3, "low_temp": 2, "high_temp": 6},
+ {"day": 2, "state": 0, "low_temp": 19, "high_temp": 28}
+ ]}
+```
+
+其中 `day` 0 表示今天,`state` 是天气状况的代码。
+
+我们已经有 `Weather` 这个 Model 类型了,现在我们需要一个 API Client 来获取这个信息。在 `WeatherWatchKit` target 中新建一个文件 `WeatherClient.swift`,并填写以下代码:
+
+```swift
+import Foundation
+
+public let WatchWeatherKitErrorDomain = "com.onevcat.WatchWeatherKit.error"
+public struct WatchWeatherKitError {
+ public static let CorruptedJSON = 1000
+}
+
+public struct WeatherClient {
+
+ public static let sharedClient = WeatherClient()
+ let session = NSURLSession.sharedSession()
+
+ public func requestWeathers(handler: ((weather: [Weather?]?, error: NSError?) -> Void)?) {
+
+ guard let url = NSURL(string: "https://raw.githubusercontent.com/onevcat/WatchWeather/master/Data/data.json") else {
+ handler?(weather: nil, error: NSError(domain: NSURLErrorDomain, code: NSURLErrorBadURL, userInfo: nil))
+ return
+ }
+
+ let task = session.dataTaskWithURL(url) { (data, response, error) -> Void in
+ if error != nil {
+ handler?(weather: nil, error: error)
+ } else {
+ do {
+ let object = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
+ if let dictionary = object as? [String: AnyObject] {
+ handler?(weather: Weather.parseWeatherResult(dictionary), error: nil)
+ }
+ } catch _ {
+ handler?(weather: nil,
+ error: NSError(domain: WatchWeatherKitErrorDomain,
+ code: WatchWeatherKitError.CorruptedJSON,
+ userInfo: nil))
+ }
+ }
+ }
+
+ task!.resume()
+ }
+}
+```
+
+其实我们的 client 现在有点过度封装和耦合,不过作为 demo 来说的话还不错。它现在只有一个方法,就是从[网络源](https://raw.githubusercontent.com/onevcat/WatchWeather/master/Data/data.json)请求一个 JSON 然后进行解析。解析的代码 `parseWeatherResult` 我们放在了 `Weather` 中,以一个 extension 的形式存在:
+
+```swift
+// MARK: - Parsing weather request
+extension Weather {
+ static func parseWeatherResult(dictionary: [String: AnyObject]) -> [Weather?]? {
+ if let weathers = dictionary["weathers"] as? [[String: AnyObject]] {
+ return weathers.map{ Weather(json: $0) }
+ } else {
+ return nil
+ }
+ }
+}
+```
+
+我们在 ViewController 中使用这个方法即可获取到天气信息,就可以构建我们的 UI 了。在 `ViewController.swift` 中,加入一个属性来存储天气数据:
+
+```swift
+var data: [Day: Weather]?
+```
+
+然后更改 `viewDidLoad` 的代码:
+
+```swift
+override func viewDidLoad() {
+ super.viewDidLoad()
+ // Do any additional setup after loading the view, typically from a nib.
+
+ dataSource = self
+
+ let vc = UIViewController()
+ vc.view.backgroundColor = UIColor.whiteColor()
+ setViewControllers([vc], direction: .Forward, animated: true, completion: nil)
+
+ UIApplication.sharedApplication().networkActivityIndicatorVisible = true
+
+ WeatherClient.sharedClient.requestWeathers { (weather, error) -> Void in
+ UIApplication.sharedApplication().networkActivityIndicatorVisible = false
+ if error == nil && weather != nil {
+ for w in weather! where w != nil {
+ self.data[w!.day] = w
+ }
+
+ let vc = self.weatherViewControllerForDay(.Today)
+ self.setViewControllers([vc], direction: .Forward, animated: false, completion: nil)
+ } else {
+ let alert = UIAlertController(title: "Error", message: error?.description ?? "Unknown Error", preferredStyle: .Alert)
+ alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
+ self.presentViewController(alert, animated: true, completion: nil)
+ }
+ }
+}
+```
+
+在这里一开始使用了一个临时的 `UIViewController` 来作为 PageViewController 在网络请求时的初始视图控制 (虽然在我们的例子中这个初始视图就是一块白屏幕)。接下来进行网络请求,并把得到的数据存储在 `data` 变量中以待使用。之后我们需要把这些数据传递给不同日期的 ViewController,在 `weatherViewControllerForDay` 方法中,换为对 weather 做设定,而非 `day`:
+
+```swift
+func weatherViewControllerForDay(day: Day) -> UIViewController {
+
+ let vc = self.storyboard?.instantiateViewControllerWithIdentifier("WeatherViewController") as! WeatherViewController
+ let nav = UINavigationController(rootViewController: vc)
+ vc.weather = data[day]
+
+ return nav
+}
+```
+
+同时我们还需要修改一下 `WeatherViewController`,将原来的:
+
+```swift
+var day: Day? {
+ didSet {
+ title = day?.title
+ }
+}
+```
+
+改为
+
+```swift
+var weather: Weather? {
+ didSet {
+ title = weather?.day.title
+ }
+}
+```
+
+另外还需要在 `UIPageViewControllerDataSource` 的两个方法中,把对应的 `viewController.day` 换为 `viewController.weather?.day`。最后我们要做的是在 `WeatherViewController` 的 `viewDidLoad` 中根据 model 更新 UI:
+
+```swift
+override func viewDidLoad() {
+ super.viewDidLoad()
+ lowTemprature.text = "\(weather!.lowTemperature)℃"
+ highTemprature.text = "\(weather!.highTemperature)℃"
+
+ let imageName: String
+ switch weather!.state {
+ case .Sunny: imageName = "sunny"
+ case .Cloudy: imageName = "cloudy"
+ case .Rain: imageName = "rain"
+ case .Snow: imageName = "snow"
+ }
+
+ weatherImage.image = UIImage(named: imageName)
+}
+```
+
+> 一个可能的改进是新建一个 `WeatherViewModel` 来将对 View 的内容和 Model 的映射关系代码从 ViewController 里分理出去,如果有兴趣的话你可以自己研究下。
+
+到此我们的 iOS 端的代码就全部完成了,运行一下看看,Perfect!到现在为止的项目可以在[这里](tag ios)找到。
+
+
+
+## Watch App
+
+### UI 构建
+
+终于进入正题了,我们可以开始设计和制作 watch app 了。
+
+首先我们把需要的图片添加到 watch app target 的 Assets.xcassets 中,这样在之后用户安装 app 时这些图片将被存放在手表中,我们可以直接快速地从手表本地读取。UI 的设计非常简单,在 Watch app 的 Interface.storyboard 中,我们先将代表天气状态的图片和温度标签拖拽到 InterfaceController 中,并将它们连接到 `InterfaceController.swift` 中的 IBOutlet 去。
+
+```swift
+@IBOutlet var weatherImage: WKInterfaceImage!
+@IBOutlet var highTempratureLabel: WKInterfaceLabel!
+@IBOutlet var lowTempratureLabel: WKInterfaceLabel!
+```
+
+接下来,我们将它复制四次,并用 next page 的 segue 串联起来,并设置它们的 title。这样,在最后的 watch app 里我们就会有五个可以左右 scorll 滑动的页面,分别表示从前天到后天的五个日子。
+
+
+
+为了标记和区分这五个 InterfaceController 实例。因为使用 next page 级联的 WKInterfaceController 会被依次创建,所以我们可以在 `awakeWithContext` 方法中用一个静态变量计数。在这里,我们想要将序号为 2 的 InterfaceController (也就是代表 “今天” 的那个) 设为当前 page。在 `InterfaceController.swift` 里添加一个静态变量:
+
+```swift
+static var index = 0
+```
+
+然后在 `awakeWithContext` 方法中加入:
+
+```swift
+InterfaceController.index = InterfaceController.index + 1
+if (InterfaceController.index == 2) {
+ becomeCurrentPage()
+}
+```
+
+### WatchKit Framework
+
+和 iOS app 类似,我们希望能够使用框架来组织代码。watch app 中的天气 model 和网络请求部分的内容其实和 iOS app 中的是完全一样的,我们没有理由重复开发。在一个 watch app 中,其实 app 本身只负责图形显示,实际的代码都是在 extension 中的。在 watchOS 2 之前,因为 extension 是在手机端,和 iOS app 处于同样的物理设备中,所以我们可以简单地将为 iOS app 中创建的框架使用在 watch extension target 中。但是在 watchOS 2 中发生了变化,因为 extension 现在直接将运行在手表上,我们无法与 iOS app 共享同一个框架了。取而代之,我们需要为手表 app 创建新的属于自己的 framewok,然后将合适的文件添加到这个 framework 中去。
+
+为项目新建一个 target,类型选择为 Watch OS 的 Watch Framework。
+
+
+
+接下来,我们把之前的 `Day.swift`,`Weather.swift` 和 `WeatherClient.swift` 三个文件添加到这个新的 target (在这里我们叫它 WatchWeatherWatchKit) 里去。我们将在新的这个 watch framework 中重用这三个文件。这样做相较于直接把这三个文件放到 watch extension target 中来说,会更易于管理组织和模块分割,也是 Apple 所推荐的使用方式。
+
+
+
+接下来我们需要手动在 watch extension 里将这个新的 framework 链接进来。在 `WatchWeather WatchKit Extension` target 的 General 页面中,将 `WatchWeatherWatchKit` 添加到 Embedded Binaries 中。Xcode 将会自动把它加到 Link Binary With Libraries 里去。这时候如果你尝试编译 watch app,可能会得到一个警告:"Linking against dylib not safe for use in application extensions"。这是因为不论是 iOS app 的 extension 还是 watchOS 的 extension,所能使用的 API 都只是完整 iOS SDK 的子集。编译器无法确定我们所动态链接的框架是否含有一些 extension 无法调用的 API。要解决这个警告,我们可以通过在 `WatchWeatherWatchKit` 的 Build Setting 中将 "Require Only App-Extension-Safe API" 设置为 `YES` 来将 target 里可用的 API 限制在 extension 中。
+
+
+
+是时候来实现我们的 app 了。首先一刻都不能再忍受的是 `InterfaceController.swift` 中的 `index`。我们既然有了 `WatchWeatherWatchKit`,就可以利用已有的模型将这里写得更清楚。在 `InterfaceController.swift` 中,首先在文件上面 `import WatchWeatherWatchKit`,然后修改 `index` 的定义,并添加一个字典来临时保存这些 Interface Controller,以便之后使用:
+
+```swift
+static var index = Day.DayBeforeYesterday.rawValue
+static var controllers = [Day: InterfaceController]()
+```
+
+将刚才我们的在 `awakeWithContext` 中添加的内容删掉,改为:
+
+```swift
+override func awakeWithContext(context: AnyObject?) {
+ super.awakeWithContext(context)
+
+ // Configure interface objects here.
+ guard let day = Day(rawValue: InterfaceController.index) else {
+ return
+ }
+
+ InterfaceController.controllers[day] = self
+ InterfaceController.index = InterfaceController.index + 1
+
+ if day == .Today {
+ becomeCurrentPage()
+ }
+}
+```
+
+现在表意就要清楚不少了。
+
+接下来就是获取天气信息了。和 iOS app 中一样,我们可以直接使用 `WeatherClient` 来获取。在 `InterfaceController.swift` 中加入以下代码:
+
+```swift
+var weather: Weather? {
+ didSet {
+ if let w = weather {
+ updateWeather(w)
+ }
+ }
+}
+
+func request() {
+ WeatherClient.sharedClient.requestWeathers({ [weak self] (weathers, error) -> Void in
+ if let weathers = weathers {
+ for weather in weathers where weather != nil {
+ guard let controller = InterfaceController.controllers[weather!.day] else {
+ continue
+ }
+ controller.weather = weather
+ }
+ } else {
+ // 2
+ let action = WKAlertAction(title: "Retry", style: .Default, handler: { () -> Void in
+ self?.request()
+ })
+ let errorMessage = (error != nil) ? error!.description : "Unknown Error"
+ self?.presentAlertControllerWithTitle("Error", message: errorMessage, preferredStyle: .Alert, actions: [action])
+ }
+ })
+}
+```
+
+如果我们获取到了天气,就设置 `weather` 属性并调用 `updateWeather` 方法依次对相应的 InterfaceController 的 UI 进行设置。如果出现了错误,我们这里简单地用一个 watchOS 2 中新加的 alert view 来进行提示并让用户重试。在这个方法的下面加上更新 UI 的方法 `updateWeather`:
+
+```swift
+func updateWeather(weather: Weather) {
+ lowTempratureLabel.setText("\(weather.lowTemperature)℃")
+ highTempratureLabel.setText("\(weather.highTemperature)℃")
+
+ let imageName: String
+ switch weather.state {
+ case .Sunny: imageName = "sunny"
+ case .Cloudy: imageName = "cloudy"
+ case .Rain: imageName = "rain"
+ case .Snow: imageName = "snow"
+ }
+
+ weatherImage.setImageNamed(imageName)
+}
+```
+
+我们只需要网络请求进行一次就可以了,所以在这里我们用一个 once_token 来限定一开始的 request 只执行一次。在 `InterfaceController.swift` 中加上一个类变量:
+
+```swift
+static var token: dispatch_once_t = 0
+```
+
+然后在 `awakeWithContext` 的最后用 `dispatch_once` 来开始请求:
+
+```swift
+dispatch_once(&InterfaceController.token) { () -> Void in
+ self.request()
+}
+```
+
+最后,在 `willActivate` 中也需要刷新 UI:
+
+```swift
+override func willActivate() {
+ super.willActivate()
+ if let w = weather {
+ updateWeather(w)
+ }
+}
+```
+
+应该就这么多了。选定手表 scheme,运行程序,除了图标的尺寸不太对以及网络请求时还显示默认的天气状况和温度以外,其他的看起来还不赖:
+
+
+
+至于显示默认值的问题,我们可以通过简单地在 StoryBoard 中将图和标签内容设为空来改善,在此就不再赘述了。
+
+值得一提的是,如果你多测试几次,比如关闭整个 app (或者模拟器),然后再运行的话,可能会有一定几率遇到下面这样的错误:
+
+
+
+如果你还记得的话,这个 1000 错误就是我们定义在 `WeatherClient.swift` 中的 `CorruptedJSON` 错误。调试一下,你就会发现在请求返回时得到的数据存在问题,会得到一个内容被完整复制了一遍的返回 (比如正确的数据 {a:1},但是我们得到的是 {a:1} {a:1})。虽然我不是太明白为什么会出现这样的状况,但这应该是 `NSURLSession` 在 watchOS SDK 上的一个缓存上的 bug。我之后会尝试向 Apple 提交一个 radar 来汇报这个问题。现在的话,我们可以通过设置不带缓存的 `NSURLSessionConfiguration` 来绕开这个问题。将 WeatherClient 中的 `session` 属性改为以下即可:
+
+```swift
+let session = NSURLSession(configuration: NSURLSessionConfiguration.ephemeralSessionConfiguration())
+```
+
+至此,我们的 watch app 本体就完成了。到这一步为止的项目可以在[这个 tag](https://github.com/onevcat/WatchWeather/releases/tag/watch-app) 找到。Notification 和 Glance 两个特性相对简单,基本只是界面的制作,为了节省篇幅 (其实这篇文章已经够长了,如果你需要休息一下的话,这里会是一个很好地机会),就不再详细说明了。你可以分别在[这里](https://developer.apple.com/library/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/ImplementingaGlance.html#//apple_ref/doc/uid/TP40014969-CH5-SW1)和[这里](https://developer.apple.com/library/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/BasicSupport.html#//apple_ref/doc/uid/TP40014969-CH18-SW1)找到开发两者所需要的一切知识。
+
+在下一节中,我们将着重于 watchOS 2 的新特性。首先是 complications。
+
+### Complications
+
+Complications 是 watchOS 2 新加入的特性,它是表盘上除了时间以外的一些功能性的小部件。比如我们的天气 app 里,将今天的天气状况显示在表盘上就是一个非常理想的应用场景,这样用户就不需要打开你的 app 就能看到今天的天气状况了 (其实今天的天气的话用户抬头望窗外就能知道。如果是一个实际的天气 app 的话,显示明天或者两小时后的天气状况会更理想,但是作为 demo 就先这样吧..)。我们在这一小节中将为刚才的天气 app 实现一个 complication。
+
+Complications 可以是不同的形状,如图所示:
+
+
+
+根据用户表盘选择的不同,表盘上对应的可用的 complications 形状也各不相同。如果你想要你的 complication 在所有表盘上都能使用的话,你需要实现所有的形状。掌管 complications 或者说是表盘相关的框架并不是我们一直使用的 WatchKit,而是一个 watchOS 2 中全新框架,ClockKit。ClockKit 会提供一些模板给我们,并在一定时间点向我们请求数据。我们依照模板使用我们的数据来实现 complication,最后 ClockKit 负责帮助我们将其渲染在表盘上。在 ClockKit 请求数据时,它会唤醒我们的 watch extension。我们需要在 extension 中实现数据源,并以一段时间线的方式把数据提供给 ClockKit。这样做有两个好处,首先 ClockKit 可以一次性获取到很多数据,这样它就能在合适的时候更新 complication 的显示,而不必再次唤醒 extension 来请求数据。其次,因为有一条时间线的数据,我们就可以使用 Time Travel 来查看 complication 已经过去的和即将到来的状况,这在某些场合下会十分方便。
+
+理论已经说了很多了,来实际操作一下吧。
+
+首先,因为我们在新建项目的时候已经选择了包含 complications,所以我们并不需要再进行额外的配置就可以开始了。如果你不小心没有选中这个选项,或者是想在已有项目中进行添加的话,你就需要手动配置,在 extension 的 target 里的 Complications Configuration 中指定数据源的 class 和支持的形状。在运行时,系统会使用在这个设置中指定的类型名字去初始化一个的实例,然后调用这个实例中实现的数据源方法。我们要做的就是在被询问这些方法时,尽快地提供需要的数据。
+
+第一步是实现数据源,这在在我们的项目中已经配置好了,就是 `ComplicationController.swift`。这是一个实现了 `CLKComplicationDataSource` 的类型,打开文件可以看到所有的方法都已经有默认空实现了,我们现在要做的就是把这些空填上。其中最关键的是 `getCurrentTimelineEntryForComplication:withHandler:`,我们需要通过这个方法来提供当前表盘所要显示的 complication。罗马不是一天建成的,项目也不是。我们先提供一个 dummy 的数据来让流程运作起来。在 ComplicationController.swift 中,将这个方法的内容换成:
+
+```swift
+func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
+ // Call the handler with the current timeline entry
+ var entry : CLKComplicationTimelineEntry?
+ let now = NSDate()
+
+ // Create the template and timeline entry.
+ if complication.family == .ModularSmall {
+
+ let imageTemplate = CLKComplicationTemplateModularSmallSimpleImage()
+ imageTemplate.imageProvider = CLKImageProvider(backgroundImage:UIImage(named: "sunny")!, backgroundColor: nil)
+
+ // Create the entry.
+ entry = CLKComplicationTimelineEntry(date: now, complicationTemplate: imageTemplate)
+ }
+ else {
+ // ...configure entries for other complication families.
+ }
+
+ // Pass the timeline entry back to ClockKit.
+ handler(entry)
+}
+```
+
+在这个方法中,系统会提供给我们所需要的 complication 的类型,我们要做的是使用合适的系统所提供的模板 (这里是 `CLKComplicationTemplateModularSmallSimpleImage`) 以及我们自己的数据,来构建一个 `CLKComplicationTimelineEntry` 对象,然后再 handler 中返回给系统。结合天气 app 的特点,我们这里选择了一个小的简单图片的模板。另外因为篇幅有限,这里只实现了 `.ModularSmall`。在实际的项目中,你应该支持尽量多的 complication 类型,这样可以保证你的用户在不同的表盘上都能使用。
+
+在提供具体的数据时,我们使用 template 的 `imageProvider` 或者 `textProvider`。在我们现在使用的这个模板中,只有一个简单的 `imageProvider`,我们从 extension 的 Assets Category 中获取并设置合适的图像就可以了 (对于 `.ModularSmall` 来说,需要图像的尺寸为 52px 和 58px 的 @2x。关于其他模板的图像尺寸要求,可以参考[文档](https://developer.apple.com/library/prerelease/watchos/documentation/ClockKit/Reference/ClockKit_framework/index.html#//apple_ref/doc/uid/TP40016082))。
+
+运行程序,选取一个带有 `ModularSmall` complication 的表盘 (如果是在模拟器的话,可以使用 Shift+Cmd+2 然后点击表盘来打开表盘选择界面),然后在 complication 中选择 WatchWeather,可以看到以下的结果:
+
+
+
+看起来不错,我们的小太阳已经在界面上熠熠生辉了,接下来就是要实现把实际的数据替换进来。对于 complication 来说,我们需要以尽可能快的速度去调用 handler 来向系统提供数据。我们并没有那么多时间去从网络上获取数据,所以需要使用之前在 watch app 或者是 iOS app 中获取到的数据来组织 complication。为了在 complication 中能直接获取数据,我们需要在用 Client 获取到数据后把它存在本地。这里我们用 UserDefaults 就已经足够了。在 `Weather.swift` 中加入以下 extension:
+
+```swift
+public extension Weather {
+ static func storeWeathersResult(dictionary: [String: AnyObject]) {
+ let userDefault = NSUserDefaults.standardUserDefaults()
+ userDefault.setObject(dictionary, forKey: kWeatherResultsKey)
+ userDefault.setObject(NSDate(), forKey: kWeatherRequestDateKey)
+
+ userDefault.synchronize()
+ }
+
+ public static func storedWeathers() -> (requestDate: NSDate?, weathers: [Weather?]?) {
+ let userDefault = NSUserDefaults.standardUserDefaults()
+ let date = userDefault.objectForKey(kWeatherRequestDateKey) as? NSDate
+
+ let weathers: [Weather?]?
+ if let dic = userDefault.objectForKey(kWeatherResultsKey) as? [String: AnyObject] {
+ weathers = parseWeatherResult(dic)
+ } else {
+ weathers = nil
+ }
+
+ return (date, weathers)
+ }
+}
+```
+
+这里我们需要知道获取到这组数据时的时间,我们以当前时间作为获取时间进行存储。一个更加合适的做法应该是在请求的返回中包含每个天气状况所对应的时间信息。但是因为我们并没有真正的服务器,也并非实际的请求,所以这里就先简单粗暴地用本地时间了。接下来,在每次请求成功后,我们调用 `storeWeathersResult` 将结果存储起来。在 `WeatherClient.swift` 中,把
+
+```swift
+dispatch_async(dispatch_get_main_queue(), { () -> Void in
+ handler?(weathers: Weather.parseWeatherResult(dictionary), error: nil)
+})
+```
+
+这段代码改为:
+
+```swift
+dispatch_async(dispatch_get_main_queue(), { () -> Void in
+ let weathers = Weather.parseWeatherResult(dictionary)
+ if weathers != nil {
+ Weather.storeWeathersResult(dictionary)
+ }
+ handler?(weathers: weathers, error: nil)
+})
+```
+
+接下来我们还需要另外一项准备工作。Complication 的时间线是以一组 `CLKComplicationTimelineEntry` 来表示的,一个 entry 中包含了 template 和对应的 `NSDate`。watchOS 将在当前时间超过这个 `NSDate` 时表示。所以如果我们需要显示当天的天气情况的话,就需要将对应的日期设定为当日的 0 点 0 分。对于其他几个日期的天气来说,这个状况也是一样的。我们需要添加一个方法来通过 Weather 的 `day` 属性和请求的当日日期来返回一个对应 entry 中需要的日期。为了运算简便,我们这里引入一个第三方框架,[SwiftDate](https://github.com/malcommac/SwiftDate)。将这个项目导入我们 app,然后在 `Weather.swift` 中添加:
+
+```swift
+public extension Weather {
+ public func dateByDayWithRequestDate(requestDate: NSDate) -> NSDate {
+ let dayOffset = day.rawValue
+ let date = requestDate.set(componentsDict: ["hour":0, "minute":0, "second":0])!
+ return date + dayOffset.day
+ }
+}
+```
+
+接下来我们就可以更新 `ComplicationController.swift` 的内容了。首先我们需要实现 `getTimelineStartDateForComplication:withHandler:` 和 `getTimelineEndDateForComplication:withHandler:` 来告诉系统我们所能提供 complication 的日期区间:
+
+```swift
+func getTimelineStartDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
+ var date: NSDate? = nil
+ let (reqestDate, weathers) = Weather.storedWeathers()
+
+ if let weathers = weathers,
+ requestDate = requestDate {
+ for w in weathers where w != nil {
+ if w!.day == .DayBeforeYesterday {
+ date = w!.dateByDayWithRequestDate(requestDate)
+ break
+ }
+ }
+ }
+
+ handler(date)
+}
+
+func getTimelineEndDateForComplication(complication: CLKComplication, withHandler handler: (NSDate?) -> Void) {
+ var date: NSDate? = nil
+ let (reqestDate, weathers) = Weather.storedWeathers()
+
+ if let weathers = weathers,
+ requestDate = requestDate {
+ for w in weathers where w != nil {
+ if w!.day == .DayAfterTomorrow {
+ date = w!.dateByDayWithRequestDate(requestDate) + 1.day - 1.second
+ break
+ }
+ }
+ }
+
+ handler(date)
+}
+```
+
+最早的时间是前天的 00:00,这是毫无疑问的。但是最晚的可显示时间并不是后天的 00:00,而是 23:59:59,这里一定需要注意。
+
+另外,为了之后创建 template 能容易一些,我们添加一个由 `Weather.State` 创建 template 的方法:
+
+```swift
+private func templateForComplication(complication: CLKComplication, weatherState: Weather.State) -> CLKComplicationTemplate? {
+ let imageTemplate: CLKComplicationTemplate?
+
+ if complication.family == .ModularSmall {
+ imageTemplate = CLKComplicationTemplateModularSmallSimpleImage()
+
+ let imageName: String
+ switch weatherState {
+ case .Sunny: imageName = "sunny"
+ case .Cloudy: imageName = "cloudy"
+ case .Rain: imageName = "rain"
+ case .Snow: imageName = "snow"
+ }
+
+ (imageTemplate as! CLKComplicationTemplateModularSmallSimpleImage).imageProvider = CLKImageProvider(backgroundImage:UIImage(named: imageName)!, backgroundColor: nil)
+ } else {
+ imageTemplate = nil
+ }
+
+ return imageTemplate
+}
+```
+
+接下来就是实现核心的三个提供时间轴的方法了,虽然很长,但是做的事情却差不多:
+
+```swift
+func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
+ // Call the handler with the current timeline entry
+ var entry : CLKComplicationTimelineEntry?
+
+ // Create the template and timeline entry.
+ let (requestDate, weathers) = Weather.storedWeathers()
+
+ if let weathers = weathers,
+ requestDate = requestDate {
+ for w in weathers where w != nil {
+
+ let weatherDate = w!.dateByDayWithRequestDate(requestDate)
+ if weatherDate == NSDate.today() {
+
+ if let template = templateForComplication(complication, weatherState: w!.state) {
+ entry = CLKComplicationTimelineEntry(date: weatherDate, complicationTemplate: template)
+ }
+ }
+ }
+ }
+
+ // Pass the timeline entry back to ClockKit.
+ handler(entry)
+}
+
+func getTimelineEntriesForComplication(complication: CLKComplication, beforeDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
+ // Call the handler with the timeline entries prior to the given date
+ var entries = [CLKComplicationTimelineEntry]()
+ let (requestDate, weathers) = Weather.storedWeathers()
+
+ if let weathers = weathers,
+ requestDate = requestDate {
+ for w in weathers where w != nil {
+ let weatherDate = w!.dateByDayWithRequestDate(requestDate)
+ if weatherDate < date {
+ if let template = templateForComplication(complication, weatherState: w!.state) {
+ let entry = CLKComplicationTimelineEntry(date: weatherDate, complicationTemplate: template)
+ entries.append(entry)
+
+ if entries.count == limit {
+ break
+ }
+ }
+ }
+ }
+ }
+
+ handler(entries)
+}
+
+func getTimelineEntriesForComplication(complication: CLKComplication, afterDate date: NSDate, limit: Int, withHandler handler: (([CLKComplicationTimelineEntry]?) -> Void)) {
+ // Call the handler with the timeline entries after to the given date
+ var entries = [CLKComplicationTimelineEntry]()
+ let (requestDate, weathers) = Weather.storedWeathers()
+
+ if let weathers = weathers,
+ requestDate = requestDate {
+ for w in weathers where w != nil {
+ let weatherDate = w!.dateByDayWithRequestDate(requestDate)
+ if weatherDate > date {
+ if let template = templateForComplication(complication, weatherState: w!.state) {
+ let entry = CLKComplicationTimelineEntry(date: weatherDate, complicationTemplate: template)
+ entries.append(entry)
+
+ if entries.count == limit {
+ break
+ }
+ }
+ }
+ }
+ }
+
+ handler(entries)
+}
+```
+
+代码来说非常简单。`getCurrentTimelineEntryForComplication` 中我们找到今天的 `Weather` 对象,然后构建合适的 entry。而对于 `beforeDate` 和 `afterDate` 两个版本的方法,按照系统提供的 `date` 我们需要组织在这个 `date` 之前或者之后的所有 entry,并将它们放到一个数组中去调用回调。这两个方法中还为我们提供了一个 `limit` 参数,我们的结果数应该不超过这个数字。在实现这三个方法后,我们的时间线就算是构建完毕了。
+
+另外,我们还可以通过实现 `getPlaceholderTemplateForComplication:withHandler:` 来提供一个在表盘定制界面是会用到的占位图像。
+
+```swift
+func getPlaceholderTemplateForComplication(complication: CLKComplication, withHandler handler: (CLKComplicationTemplate?) -> Void) {
+ // This method will be called once per supported complication, and the results will be cached
+ handler(templateForComplication(complication, weatherState: .Sunny))
+}
+```
+
+这样,在自定义表盘界面我们也可以在选择到我们的 complication 时看到表示我们的 complication 的样式了:
+
+
+
+`ComplicationController` 中最后需要实现的是 `getNextRequestedUpdateDateWithHandler`。系统会在你的 watch app 被运行时更新时间线,另外要是你的 app 一直没有被运行的话,你可以通过这个方法提供给系统一个参考时间,用来建议系统应该在什么时候为你更新时间线。这个时间应该尽可能长,以节省电池的电量。在我们的天气的例子中,每天一次更新也许会是个不错的选择:
+
+```swift
+func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
+ // Call the handler with the date when you would next like to be given the opportunity to update your complication content
+ handler(NSDate.tomorrow());
+}
+```
+
+> 你也许会注意到,因为我们这里要是不开启 watch app 的话,其实天气数据时不会更新的,这样我们设定刷新时间线似乎并没有什么意义 - 因为不开 watch app 的话数据并不会变化,而开了 watch app 的话时间线就会直接被刷新。这里我们考虑到了之后使用 Watch Connectivity 从手机端刷新 watch 数据的可能性,所以做了每天刷新一次的设置。我们在稍后会详细将这方面内容。
+
+另外,我们还需要记得在 watch app 数据更新之后,强制 reload 一下 complication 的数据。在 ComplicationController.swift 中加入:
+
+```swift
+static func reloadComplications() {
+ let server = CLKComplicationServer.sharedInstance()
+ for complication in server.activeComplications {
+ server.reloadTimelineForComplication(complication)
+ }
+}
+```
+
+然后在 `InterfaceController.swift` 的 `request` 中,在请求成功返回后调用一下这个方法就可以了。
+
+现在,我们的 watch app 已经支持 complication 了。同时,因为我们努力提供了之前和之后的数据,我们免费得到了 Time Travel 的支持。现在你不仅可以在表盘上看到今天的天气,通过旋转 Digital Crown 你还能了解到之前和之后的天气状况了:
+
+
+
+到这里为止的项目代码可以在 [complication tag](https://github.com/onevcat/WatchWeather/releases/tag/complication) 中找到。
+
+### Watch Connectivity
+
+在 watchOS 1 时代,watch 的 extension 是和 iOS app 一样,存在于手机里的。所以在 watch extension 和 iOS app 之间共享数据是比较简单的,和其他 extension 类似,使用 app group 将 app 本体和 extension 设为同一组 app,就可以在一个共享容器中共享数据了。但是这在 watchOS 2 中发生了改变。因为 watchOS 2 的手表 extension 是直接存在于手表中的,所以之前的 app group 的方法对于 watch app 来说已经失效。Watch extension 现在会使用自己的一套数据存储 (如果你之前注意到了的话,我们在请求数据后将它存到了 UserDefaults 中,但是手机和手表的 UserDefaults 是不同的,所以我们不用担心数据被不小心覆盖)。如果我们想要在 iOS 设备和手表之间共享数据的话,我们需要使用新的 Watch Connectivity 框架。
+
+`WatchConnectivity` 框架所扮演的角色就是 iOS app 和 watch extension 之间的桥梁,利用这个框架你可以在两者之间互相传递数据。在这个例子中,我们会用 `WatchConnectivity` 来改善我们的天气 app 的表现 -- 我们打算实现无论在手表还是 iOS app 中,每天最多只进行一次请求。在一个设备上请求后,我们会把数据传递到配对的另一个设备上,这样在另一个设备上打开 app 时,就可以直接显示天气状况,而不再需要请求一次了。
+
+我们在 iOS app 和 watchOS app 中都可以使用 WatchConnectivity。首先我们需要检查设备上是否能使用 session,因为在一部分设备 (比如 iPad) 上,这个框架是不能使用的。这可以通过 `WCSession.isSupported()` 来判断。在确认平台上可以使用后,我们可以设定 delegate 来监听事件,然后开始这个 session。当我们有一个已经启动的 session 后,就可以通过框架的方法来向配对的另一个设备发送数据了。
+
+大致来说数据发送分为后台发送和即时消息两类。当 iOS app 和 watch app 都在前台的时候,我们可以通过 `-sendMessage:replyHandler:errorHandler:` 来在两者之间发送消息,这在 iOS app 和 watch app 之间需要互动的时候是非常有用的。另一种是后台发送,在 iOS 或 watch app 中有一者不在前台时,我们就需要考虑使用这种方式。后台通讯有三种方式:通过 Application Context,通过 User Info,以及传送文件。文件传送简单明了就是传递一个文件,另外两个都是传递一个字典,不同之处在于 Application Context 将会使用新的数据覆盖原来的内容,而 User Info 则可以使多次内容形成队列进行传送。每种方式都会在另外一方的 session 开始运行后调用相应的 delegate 方法,于是我们就能知道有数据发送过来了。
+
+结合天气 app 的特点,我们应该选择使用 Application Context 来收发数据。这篇文章已经太长了,所以我们这里只做从 iOS 到 watchOS 的发送了。因为反过来的代码其实完全一样,我会在 repo 中完成,在这里就不再重复一遍了。
+
+首先是在 iOS app 中启动 session。在 `ViewController.swift` 中添加一个属性:`var session: WCSession?`,然后在 `viewDidLoad:` 中添加:
+
+```swift
+if WCSession.isSupported() {
+ session = WCSession.defaultSession()
+ session!.delegate = self
+ session!.activateSession()
+}
+```
+
+为了让 `self` 成为 session 的 delegate,我们需要声明 `ViewController` 实现 `WCSessionDelegate`。这里我们先在文件最后添加一个空的 extension 即可:
+
+```swift
+extension ViewController: WCSessionDelegate {
+
+}
+```
+
+注意我们一定需要设定 session 的 delegate,即使它什么都没有做。一个没有 delegate 的 session 是不能被启动或正确使用的。
+
+然后就是发送数据了。在 `requestWeathers` 的回调中,数据请求一切正常的分支最后,添加一段
+
+```swift
+if error == nil && weather != nil {
+ //...
+ if let dic = Weather.storedWeathersDictionary() {
+ do {
+ try self.session?.updateApplicationContext(dic)
+ } catch _ {
+
+ }
+ }
+} else {
+ ...
+}
+```
+
+这里的 `storedWeathersDictionary` 是个新加入的方法,它返回存储在 User Defaults 中的内容的字典表现形式 (我们在请求返回的时候就已经将结果内容存储在 User Defaults 里了,希望你还记得)。
+
+在 watchOS app 一侧,我们类似地启动一个 session。在 `InterfaceController.swift` 的 `awakeWithContext` 中的 `dispatch_once` 里,添加
+
+```swift
+if WCSession.isSupported() {
+ InterfaceController.session = WCSession.defaultSession()
+ InterfaceController.session!.delegate = self
+ InterfaceController.session!.activateSession()
+}
+```
+
+然后添加一个 extension 来接收传输过来的数据:
+
+```swift
+extension InterfaceController: WCSessionDelegate {
+ func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
+ guard let dictionary = applicationContext[kWeatherResultsKey] as? [String: AnyObject] else {
+ return
+ }
+ guard let date = applicationContext[kWeatherRequestDateKey] as? NSDate else {
+ return
+ }
+ Weather.storeWeathersResult(dictionary, requestDate: date)
+ }
+}
+```
+
+最后,在请求数据之前我们可以判断一下已经存储在 User Defaults 中的内容是否是今天请求的。如果是的话,就不再需要进行请求,而是直接使用存储的内容来刷新界面,否则的话进行请求并存储。将原来的 `self.request()` 改为:
+
+```swift
+dispatch_async(dispatch_get_main_queue()) { () -> Void in
+ if self.shouldRequest() {
+ self.request()
+ } else {
+ let (_, weathers) = Weather.storedWeathers()
+ if let weathers = weathers {
+ self.updateWeathers(weathers)
+ }
+ }
+}
+```
+
+如果你只是单纯地 copy 这些代码的话,在之前项目的基础上应该是不能编译的。这是因为在这里我并没有列举出所有的改动,而只是写出了关于 WatchConnectivity 的相关内容。这里涉及到了每次启动或者从后台切换到 app 时都需要检测并刷新界面,所以我们还需要一些额外的重构来达到这个目的。这些内容我们在此也略过了。同理,在 watchOS app 需要请求,并且请求结束的时候,我们也可以如前所述,通过几乎一样的代码和方式将请求得到的内容发回给 iOS app。这样,当我们打开 iOS app 时,也就不需要再次进行网络请求了。
+
+这部分的完整的代码可以在这个 repo 的[最终的 tag](https://github.com/onevcat/WatchWeather/releases/tag/connectivity) 上找到,您可以尝试自己实现一下,也可以直接找这里的代码进行参考。如果后续还有修正的话,我会直接在 master 上进行。
+
+## 总结
+
+本文从零开始完成了一个 iOS 和 Apple Watch 上的天气情况的 app。虽然说数据源上用的是一个 stub,但是在其他方面还算是比较完整的。本来主要的目的是探索下 watchOS 2 中的几个新 API 的用法,主要是 complication 和 WatchConnectivity。但是发现如果只是单纯地照搬文档的话一是不够直观,二是很难说明问题,所以干脆不如从头开始,和大家一起完成一个 app 来的更实在。
+
+Apple Watch 作为 Apple 的新产品线,其实所扮演的角色会非常重要。watchOS 一代由于种种限制,开发者们很难发挥出设备的优势来做出一些有意思的 app。在一代系统中,手表更多地还是只是一块 iPhone 的额外屏幕。但是在 watchOS 2 中,这一状况有望改善。更加合理和灵活的 app 组织方式以及在手表上的 native 开发,使得 Apple Watch 的可用范围提升了不止一个档次。而在经历了大半年的彷徨之后,Apple Watch 开发也逐渐趋于稳定,系统的架构和 API 也逐渐合理。其实 Apple Watch 还是一款非常有希望的产品,相信随着设备的进一步成熟和 SDK 的更加开放,我们会有机会像是直接利用 Digital Crown 或者其他一个手表特性来开发令人耳目一新的 app。个人的对于 Apple Watch 开发的建议是,现在最好能紧跟上 watch 开发的脚步,尽量进行积累,这样你才有可能在之后的爆发中取得先机和灵感。
+
+就这么多吧 (其实已经很多了),祝编程愉快~
diff --git a/_posts/2015-09-27-ui-testing.markdown b/_posts/2015-09-27-ui-testing.markdown
new file mode 100644
index 00000000..3180922c
--- /dev/null
+++ b/_posts/2015-09-27-ui-testing.markdown
@@ -0,0 +1,243 @@
+---
+layout: post
+title: WWDC15 Session笔记 - Xcode 7 UI 测试初窥
+date: 2015-09-27 20:58:47.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+Unit Test 在 iOS 开发中已经有足够多的讨论了。Objective-C 时代除了 Xcode 集成的 XCTest 以外,还有很多的测试相关的工具链可以使用,比如专注于提供 Mock 和 Stub 的 [OCMock](http://ocmock.org),使用行为驱动测试的 [Kiwi](https://github.com/kiwi-bdd/Kiwi) 或者 [Specta](https://github.com/specta/specta) 等等。在 Swift 中,我们可以继续使用 XCTest 来进行测试,而 Swift 的 mock 和 stub 的处理,我们甚至不需要再借助于第三方框架,而使用 Swift 自身可以在方法中内嵌类型的特性来完成。关于这方面的内容,可以参看下 NSHipster [这篇文章](http://nshipster.com/xctestcase/)里关于 Mocking in Swift 部分的内容。
+
+不过这些都是单元测试 (Unit Test) 的相关内容。单元测试非常适合用来做 app 的逻辑以及网络接口方面的测试,但是一个 app 所面向的最终人群还是使用的用户。对于用户来说,app 的功能和 UI 界面是否正确是判断这个 app 是否合格的更为直接标准。而传统的单元测试很难对 app 的功能或者 UI 进行测试。iOS 的开源社区有过一系列的尝试,比如 [KIF 框架](https://github.com/kif-framework/KIF),Apple 自己的 [Automating UI Testing](https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/UsingtheAutomationInstrument/UsingtheAutomationInstrument.html) 或者 Facebook 的[截图测试](https://github.com/facebook/ios-snapshot-test-case)等。关于这些 UI 测试框架的更详细的介绍,可以参看 objc 中国上的 [UI 测试](http://objccn.io/issue-15-6/)和[截图测试](http://objccn.io/issue-15-7/)两篇文章。不过这些方法有一个共同的特点,那就是配置起来十分繁琐,使用上也有诸多不便。测试的目的是保证代码的质量和发布时的信心,以加速开发和迭代的效率;但是如果测试本身太过于难写复杂的话,反而会拖累开发速度。这大概也是 UI 测试所面临的最大窘境 -- 往往开发者在一个项目里写了一两个 UI 测试用例后,就会觉得难以维护,怯于巨大的时间成本,继而放弃。
+
+Apple 在 Xcode 7 中新加入了一套 UI Testing 的工具,其目的就是解决这个问题。新的 UI Testing 比以往的解决方案要简单不少,特别是在创建测试用例的时候更集成了录制的功能,这有希望让 UI Testing 变得更为普及。这篇文章将通过一个简单的例子来说明 Xcode 7 中 UI Testing 的基本概念和使用方法。
+
+本文是我的 [WWDC15 笔记](http://onevcat.com/2015/06/ios9-sdk/)中的一篇,本文所参考的有:
+
+* [UI Testing in Xcode](https://developer.apple.com/videos/wwdc/2015/?id=406)
+
+### UI Testing 和 Accessibility
+
+在开始实际深入到 UI Testing 之前,我们可能需要补充一点关于 app 可用性 (Accessibility) 的基本知识。[UI Accessibility](https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/iPhoneAccessibility/Introduction/Introduction.html) 早在 iOS 3.0 就被引入了,用来辅助身体不便的人士使用 app。VoiceOver 是 Apple 的屏幕阅读技术,而 UI Accessibility 的基本原则就是对屏幕上的 UI 元素进行分类和标记。两者配合,通过阅读或者聆听这些元素,用户就可以在不接触屏幕的情况下通过声音来使用 app。
+
+Accessibility 的核心思想是对 UI 元素进行分类和标记 -- 将屏幕上的 UI 分类为像是按钮,文本框,cell 或者是静态文本 (也就是 label) 这样的类型,然后使用 identifier 来区分不同的 UI 元素。用户可以通过语音控制 app 的按钮点击,或是询问某个 label 的内容等等,十分方便。iOS SDK 中的控件都实现了默认的 Accessibility 支持,而我们如果使用自定义的控件的话,则需要自行使用 Accessibility 的 API 来进行添加。
+
+但是因为最初 Accessibility 和 VoiceOver 都是基于英文的,所以在国内的 iOS 应用中并不是十分受到重视。不仅如此,因为添加完备的可用性支持对于开发者来说也是不小的额外工作量,所以除非应用有特殊的使用场景,对于 Accessibility 的支持和重视程度都十分有限。但是在 UI 测试中,可用性的作用就非常大了。UI 测试的本质就是定位在屏幕上的元素,实现一些像是点击或者拖动这样的操作交互,然后获取 UI 的状态进行断言来判断是否符合我们的预期。这个过程及其需求与 Accessibility 的功能是高度吻合的。这也是为什么 iOS 中大部分的 UI 测试框架都是基于 UI Accessibility 的原因,Xcode 7 的 UI Testing 也不例外。
+
+### 集成 UI Testing
+
+在项目中加入 UI Testing 的支持非常容易。如果是新项目的话,在新建项目时 UI Testing 就已经是默认选上的了:
+
+
+
+如果你要在已有项目中添加 UI Testing 的话,可以新建一个 iOS UI Testing 的 target:
+
+
+
+无论使用那种方法,Xcode 将为你配置好你所需要的 UI 测试环境。我们这里通过一个简单的例子来说明 UI Testing 的基本使用方法。这个 app 非常简单,只有两个主要界面。首先是输入用户名密码的登陆界面,在登陆之后的带有一个 Switcher 的界面。用户可以通过点击这个开关来将下面的签到次数 +1。这个项目的代码可以在 GitHub 的[这个仓库](git@github.com:onevcat/UITestDemo.git)中找到。
+
+
+
+### UI 行为录制和第一个测试
+
+相比起其他一些 UI 测试框架,Xcode 的 UI Testing 最为诱人的优点在于可以直接录制操作。我们首先来看看 UI Testing 的基本结构吧。在新建 UI Testing 后,我们会得到一个 `{ProjectName}UITests` 文件,默认实现是:
+
+```Swift
+import XCTest
+
+class UITestDemoUITests: XCTestCase {
+
+ override func setUp() {
+ super.setUp()
+
+ // Put setup code here. This method is called before the invocation of each test method in the class.
+
+ // In UI tests it is usually best to stop immediately when a failure occurs.
+ continueAfterFailure = false
+ // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
+ XCUIApplication().launch()
+
+ // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
+ }
+
+ override func tearDown() {
+ // Put teardown code here. This method is called after the invocation of each test method in the class.
+ super.tearDown()
+ }
+
+ func testExample() {
+ // Use recording to get started writing UI tests.
+ // Use XCTAssert and related functions to verify your tests produce the correct results.
+ }
+}
+```
+
+在 `setUp` 中,我们使用 `XCUIApplication` 的 `launch` 方法来启动测试 app。和单元测试的思路类似,在每一个 UI Testing 执行之前,我们都希望从一个“干净”的 app 状态开始进行。`XCUIApplication` 是 `UIApplication` 在测试进程中的代理 (proxy),我们可以在 UI 测试中通过这个类型和应用本身进行一些交互,比如开始或者终止一个 app。我们先来测试在没有输入时直接点击 Login 按钮的运行情况。在 test 文件中加入一个方法,`testEmptyUserNameAndPassword`,在模拟器中运行程序后,将输入光标放在方法实现中,并点击工具栏上的录制按钮,就可以进行实时录制了:
+
+
+
+第一个测试非常简单,我们直接保持用户名和密码文本框为空,直接点击 login。这时 UI 录制会记录下这次点击行为:
+
+```swift
+func testEmptyUserNameAndPassword() {
+ XCUIApplication().buttons["Login"].tap()
+
+}
+```
+
+`XCUIApplication()` 我们刚才说过,是一个在 UI Testing 中代表整个 app 的对象。然后我们使用 `buttons` 来获取当前屏幕上所有的按钮的代理。使用 `buttons` 来获取一个对 app 的 query 对象,它可以用来寻找 app 内所有被标记为按钮的 UI 元素,其实上它是 `XCUIApplication().descendantsMatchingType(.Button)` 的简写形式。同样地,我们还有像是 `TextField`,`Cell` 之类的类型,完整的类型列表可以在[这里](http://masilotti.com/xctest-documentation/Constants/XCUIElementType.html)找到。类似这样的从 app 中寻找元素的方法,所得到返回是一个 `XCUIElementQuery` 对象。除了 `descendantsMatchingType` 以外,还有仅获取当前层级子元素的 `childrenMatchingType` 和所有包含的元素的 `containingType`。我们可以通过级联和结合使用这些方法获取到我们想要的层级的元素。
+
+当得到一个可用的 `XCUIElementQuery` 后,我们就可以进一步地获取代表 app 中具体 UI 元素的 `XCUIElement` 了。和 `XCUIApplication` 类似,`XCUIElement` 也只是 app 中的 UI 元素在测试框架中的代理。我们不能直接通过得到的 `XCUIElement` 来直接访问被测 app 中的元素,而只能通过 Accessibility 中的像是 `identifier` 或者 `frame` 这样的属性来获取 UI 的信息。关于具体的可用属性,可以参看 [`XCUIElementAttributes`](http://masilotti.com/xctest-documentation/Protocols/XCUIElementAttributes.html) 的文档。
+
+> 其实 `XCUIApplication` 是 `XCUIElement` 的子类,了解到这一点后,我们就不难理解 `XCUIApplication` 也是一个代理的事实了。
+
+在这里 `XCUIApplication().buttons["Login"]`,做的是在应用当前界面中找到所有的按钮,然后找到 Login 按钮。接下来我们对这个 UI 代理发送 `tap` 进行点击。在我们的 app 中,点击 Login 后我们模拟了一个网络请求,在没有填写用户名和密码的情况下,将弹出一个 alert 来提示用户需要输入必要的登陆信息:
+
+
+
+虽然 UI Testing 的交互会等待 UI 空闲后再进行之后的交互操作,但是由于登陆是在后台线程完成的,UI 其实已经空闲下来了,因此我们在测试中也需要等待一段时间,然后对这个 alert 是否弹出进行判断。将 `testEmptyUserNameAndPassword` 中的内容改写为:
+
+```swift
+func testEmptyUserNameAndPassword() {
+ let app = XCUIApplication()
+ app.buttons["Login"].tap()
+
+ let alerts = app.alerts
+ let label = app.alerts.staticTexts["Empty username/password"]
+
+ let alertCount = NSPredicate(format: "count == 1")
+ let labelExist = NSPredicate(format: "exists == 1")
+
+ expectationForPredicate(alertCount, evaluatedWithObject: alerts, handler: nil)
+ expectationForPredicate(labelExist, evaluatedWithObject: label, handler: nil)
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+}
+```
+
+注意我们这里用了一个预言期望,而不是直接采用断言。按照一般思维来说,我们可能会倾向于使用像是 dispatch_after 来让断言延迟,类似这样:
+
+```swift
+func testEmptyUserNameAndPassword() {
+ //...
+ app.buttons["Login"].tap()
+
+ let expection = expectationWithDescription("wait for login")
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(3 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
+ XCTAssertEqual(alerts.count, 1, "There should be an alert.")
+ expection.fulfill()
+ }
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+}
+```
+
+但是你会发现这段代码中 block 的部分并不会执行,这是因为在 UI Testing 中有不能 dispatch 到主线程的限制。我们可以通过把 main thread 改为其他 thread 来让代码进入 block,但是这会导致断言崩溃。因此,对于这种需要在一定时间之后再进行判断的测试例,可以使用 `expectationForPredicate` 来对未来的状态作出假设并测试在规定的超时时间内是否得到理想的结果。在 `testEmptyUserNameAndPassword` 的例子中,我们应该在点击 Login 后得到的是一个 alert 框,并且其中有一个 label,文本是 "Empty username/password"。Cmd+U,测试通过!你也可以打开模拟器查看整个过程,同时试着更改一下 Predicate 中的内容,看看运行的结果,来证明测试确实有效。
+
+### 文本输入和 ViewController 切换
+
+接下来可以试着测试下登陆成功。我们有一组可用的用户名/密码,现在要做的是用 UI Testing 的方式在用户名和密码的文本框中。最简单的方式还是直接使用 UI 动作的录制功能。对应的测试代码如下:
+
+```swift
+func testLoginSuccessfully() {
+ let app = XCUIApplication()
+ let element = app.otherElements.containingType(.NavigationBar, identifier:"Login").childrenMatchingType(.Other).element.childrenMatchingType(.Other).element.childrenMatchingType(.Other).element.childrenMatchingType(.Other).elementBoundByIndex(1)
+ let textField = element.childrenMatchingType(.Other).elementBoundByIndex(0).childrenMatchingType(.TextField).element
+ textField.tap()
+ textField.typeText("onevcat")
+ element.childrenMatchingType(.Other).elementBoundByIndex(1).childrenMatchingType(.SecureTextField).element.typeText("123")
+
+ // Other more test code
+}
+```
+
+自动录制生成的代码使用了很多 query 来查询文本框,虽然这么做是可以找到合适的文本框,但是现在的做法显然难以理解。这是因为我们没有对这两个 textfield 的 identifier 进行设置,因此无法用下标的方式进行访问。我们可以通过在 Interface Builder 或者代码中进行设置。
+
+
+
+然后就可以在测试方法中把寻找元素的 query 改为更好看的方式,并且加上测试 ViewController 切换的相关代码了:
+
+```swift
+func testLoginSuccessfully() {
+
+ let userName = "onevcat"
+ let password = "123"
+
+ let app = XCUIApplication()
+
+ let userNameTextField = app.textFields["username"]
+ userNameTextField.tap()
+ userNameTextField.typeText(userName)
+
+ let passwordTextField = app.secureTextFields["password"]
+ passwordTextField.tap()
+ passwordTextField.typeText(password)
+
+ app.buttons["Login"].tap()
+
+ let navTitle = app.navigationBars[userName].staticTexts[userName]
+ expectationForPredicate(NSPredicate(format: "exists == 1"), evaluatedWithObject: navTitle, handler: nil)
+
+ waitForExpectationsWithTimeout(5, handler: nil)
+}
+```
+
+> 注意在当前的 Xcode 版本 (7.0 7A218) 中 UI 录制在对于有 identifier 的文本框时,没有自动插入 `tap()`,这会导致测试时出现 “UI Testing Failure - Neither element nor any descendant has keyboard focus on secureTextField” 的错误。我们可以手动在输入文本 (`typeText`) 之前加入 `tap` 的调用。相信在之后的 Xcode 版本中这个问题会得到修正。
+
+对于 ViewController 切换的判断,我们可以通过判断 navigation bar 上的 title 是否正确来加以判断。
+
+### 实时的 UI 反馈测试和关于 XCUIElementQuery 的说明
+
+我们接下来测试 DetailViewController 中的 Switcher 点击。在成功登陆之后,我们可以看到一个默认为 off 状态的 switcher 按钮。点击打开这个按钮,下面的 count label 计数就会加一。首先我们需要成功登陆,在上面的测试例 (`testLoginSuccessfully`) 我们已经测试了成功登陆,我们先在新的测试方法中模拟登陆过程:
+
+```swift
+func testSwitchAndCount() {
+ let userName = "onevcat"
+ let password = "123"
+
+ let app = XCUIApplication()
+
+ let userNameTextField = app.textFields["username"]
+ userNameTextField.tap()
+ userNameTextField.typeText(userName)
+
+ let passwordTextField = app.secureTextFields["password"]
+ passwordTextField.tap()
+ passwordTextField.typeText(password)
+
+ app.buttons["Login"].tap()
+
+ // To be continued..
+}
+```
+
+接下来因为 Login 是在后台进行的,我们需要等一段时间,让新的 DetailViewController 出现。在上面两个测试例中,我们直接用 expectationForPredicate 来作为断言,这样 Xcode 只需要在超时之前观测到符合断言的变化即可以结束测试。而在这里,我们要在新的 View 里进行 UI 交互,这就需要一定时间的等待 (包括模拟的网络请求和 UI 迁移的动画等)。因为 UI 测试和 app 本身是在不同进程中运行的,我们可以简单地使用 `sleep` 来等待。接下来我们点击这个 switcher 并添加断言。在上面代码中注释的地方接着写:
+
+```swift
+func testSwitchAndCount() {
+ //...
+
+ sleep(3)
+
+ let switcher = app.switches["checkin"]
+ let l = app.staticTexts["countLabel"]
+
+ switcher.tap()
+ XCTAssertEqual(l.label, "1", "Count label should be 1 after clicking check in.")
+
+ switcher.tap()
+ XCTAssertEqual(l.label, "0", "And 0 after clicking again.")
+}
+```
+
+`checkin` 和 `countLabel` 是我们在 IB 中为 UI 设置的 identifier。默认情况下,我们可以通过 `label` 属性来获取一个 Label 的文字值。
+
+到此为止,这个简单的 demo 就书写完毕了。当然,实际的 app 中的情况会比这种 demo 复杂得多,但是基本的思路和步骤是一致的 -- 通过 query 寻找要交互的 UI 元素,进行交互,判断结果。在 UI 录制的帮助下,我们一般只需要关心如何书写断言和对结果进行判断,这大大节省了书写和维护测试的时间。
+
+对于 `XCUIElementQuery`,还有一点需要特别说明的。Query 的执行是延迟的,它和最后我们得到的 `XCUIElement` 并不是一一对应的。和 `NSURL` 与请求到的内容的关系类似,随着时间的变化,同一个 URL 有可能请求到不同的内容。我们生成 Query,然后在通过下标或者是访问方法获取的时候才真正从 app 中寻找对应的 UI 元素。这就是说,随着我们的 UI 的变化,同样的 query 也是有可能获取到不用的元素的。这在某些元素会消失或者 identifier 变化的时候是需要特别注意的。
+
+### 小结
+
+UI Testing 在易用性上比 KIF 这样的框架要有所进步,随着 UI Testing 的推出,Apple 也将原来的 UI Automation 一系列内容标记为弃用。这意味着 UI Testing 至少在今后一段时间内将会是 iOS 开发中的首选工具。但是我们也应该看到,基于 Accessibility 的测试方式有时候并不是很直接。在这个限制下,我们只能得到 UI 的代理对象,而不是 UI 元素本身,这让我们无法得到关于 UI 元素更多的信息 (比如直接获取 UI 元素中的内容,或者与 ViewController 中的相关的值),现在的 UI Testing 在很大程度上还停留在比较简易的阶段。
+
+但是相比使用 UIAutomation 在 Instruments 中用 JavaScript 与 app 交互,我们现在可以用 Swift 或者 Objective-C 直接在 Xcode 里进行 UI 测试了,这使得测试时可以方便地进行和被调试。Xcode 7.0 中的 UI Testing 作为第一个版本,还有不少限制和 bug,使用起来也有不少“小技巧”,很多时候可能并没有像单元测试那样直接。但即便如此,使用 UI Testing 来作为人工检查的替代和防止开发过程中 bug 引入还是很有意义的,相比起开发人员,也许 QA 人员从 UI 录制方面收益更多。如果 QA 职位的员工可以掌握一些基本的 UI Testing 内容的话,应该可以极大地缩短他们的工作时间和压力。而且相信 Apple 也会不断改进和迭代 UI Testing,并以此驱动 app 质量的提升,所以尽早掌握这一技术还是十分必要的。
diff --git a/_posts/2015-10-31-apple-tv.markdown b/_posts/2015-10-31-apple-tv.markdown
new file mode 100644
index 00000000..5c54be2b
--- /dev/null
+++ b/_posts/2015-10-31-apple-tv.markdown
@@ -0,0 +1,106 @@
+---
+layout: post
+title: 当 App Store 遇上电视,开发者的第四代 Apple TV 开箱体验
+date: 2015-10-31 16:49:46.000000000 +09:00
+tags: 南箕北斗集
+---
+
+### 引子
+
+2015 年 9 月,San Francisco。今年接近 100 华氏度的气温要比往年都更热,而 Apple 例行的秋季发布会也如期在这里举行。自从 iPhone 一战成名后,每年的 iPhone 旗舰机型都是移动通讯设备的业界标杆。而今年秋季发布会大家也自然地将重点放在了最新的 iPhone 6s 上。手机乏善可陈,除了硬件参数的一些常规升级外,我们并没有看到 iPhone 有多大进步。不过这也是大家预料之中,每隔两年一款的 s 系列定位就只是对之前版本的补充。另外,更大屏幕的 iPad Pro 也传闻已久。虽然 Apple 有意进军生产力市场,但是在平板电脑日渐衰颓的今天,一款并没有实质改变的设备是否能够力挽狂澜,还有待观察。
+
+要说这场发布会上能配得上三藩 9 月气温的产品,可能就只剩新款 Apple TV 了。相比起它的前辈,新款的 Apple TV 拥有一颗 A8 处理器,运行了全新和独立的操作系统 tvOS,第三方开发者可以为 tvOS 开发独立运行的 app,并且它拥有一个全新的 TV App Store。在之前一两年时间里,Apple 开放了 iOS extension 和 Apple Watch app 的开发,以求完善平台用户体验。但是不论是通知中心还是手表 app,其实都是依附在已有的 iOS app 上的,仅仅是功能的补充。Apple TV 的 App Store 可以说是自 iOS 2 时代开放 App Store 以来 iOS 开发生态中最大的一次变革。开发者们现在有了全新的交互方式,全新的使用场景,可以再一次释放创造力。
+
+这样的设备,作为一个 iOS 开发者,不论是利益所使还是兴趣所驱,结论自然都跑不离三个字,那就是“买买买”。
+
+### 开箱
+
+因为自己的开发者账号是中国区的,所以没法申请到之前的一美金的 Developer Kit,既然薅不到资本主义的羊毛,那就只能出血自己购入了。不过还好 150 美金的售价相比起其他 Apple 产品来说确实只是小菜一碟。于是顺利成章地大前天下了单,然后顺理成章地今天快递盒子就出现在了门口。
+
+
+
+包装的话和前几代产品差不错,颜色继承了一贯的黑乎乎,并没有像其他的 Apple 产品那样有别的颜色可以选择。不过考虑到电视机作为传统的黑色家电,也很少出现亮丽的色彩。也许想要等到白色或者金色的 Apple TV 的话,估计要看 Apple 自己动手做电视机了。
+
+
+
+打开包装盒以后的内容,电视盒子的主体部分和带触摸板和 Siri 的遥控器并排摆放。本体的尺寸在长宽上和前代保持一致,但是厚度增加了不少。这主要还是由于需要集成的东西变多了,毕竟本代 Apple TV 内含足够强劲的 A8 以及 2G 内存,另外,蓝牙 wifi 什么的自不用说,也是一应俱全。遥控器上半部分是玻璃的触控板,为系统的操作提供了全新的交互方式,我之后会再提及。上一张内含物全部排开的图:
+
+
+
+左右两根线分别是电源线和一根普通的 USB-Lightning 的线缆,左下的说明书的话写有开始的欢迎语,以及一些关于本体接口和遥控器按键的简单说明。和 Apple 其他很多产品类似,这份说明书对于大部分人来说应该是不需要的。
+
+值得一提的是,Apple 依然在看不到的地方做了不少功课。我们在放置 Apple TV 的时候,一般还是按照正常的方向放置的。在平时不可能看得到的底面上,Apple 也好不吝啬地做了大大的 logo。另外,设备整体摸上去触感也很不错。
+
+
+
+### 安装
+
+其实算不上安装。现在的电子产品要是不能开箱后插上电源就能傻瓜使用的话,是会受不少诟病的。当然啦,Apple TV 这样的机顶盒产品的话除了电源线,还需要一根 HDMI 来和高清电视连接。需要注意的是,HDMI 线是不包含在 Apple TV 盒子里的,需要自行准备。虽然现在客厅娱乐化家里或多或少应该都能翻出一两根 HDMI 的线,但是也不排除正好没有。所以在购买的时候可以确定一下,没有的话直接在 Apple Store 里买一根也是不错的选择。
+
+
+
+本体背面除了电源线和 HDMI 接口外,还有两个插口,分别是以太网网线接口和一个 USB-C 接口。平时使用的话直接用内置的 wifi 就可以了,没有必要插网线。这个接口的目的应该是用来在 tvOS 出问题变砖的时候强制重装系统时使用的。当然,如果你选择不用无线网络的话,平时插根网线用应该也是可以的。另一个 USB-C 的接口是为开发者准备的,还是需要连线才能够将 Apple TV 与开发的 Mac 连接。另外,在从本地恢复 tvOS 系统的时候也需要 USB-C 的连接才能在 iTunes 中找到 Apple TV 设备。
+
+
+
+开机以后是很常规的欢迎和语言选择等等,操作的话就靠新的遥控器上半部分的触控板了。tvOS 使用焦点系统进行操控,同一时间屏幕上有且仅有一个焦点 UI 元素,用户通过在遥控器触摸板上的滑动来控制焦点移动,通过按压触摸板来确认。虽说是玻璃触摸板,但是从材质上来说遥控器的下半部分非触摸的区域却更接近于日常使用的手机屏幕,而上半部分的触摸板反而金属质感强烈。我一开始的时候有些不太习惯,拿反了好几次遥控器。不过在经过一段时间适应以后就不会弄错了。
+
+初始设置的话十分方便,你可以用过使用 iOS 设备通过蓝牙来对新的 Apple TV 进行设定。这省去了输入网络密码,登陆 Apple ID 等等一系列复杂的输入过程。如果说在手机的小屏幕上进行键盘输入体验糟糕的话,在 Apple TV 上用遥控器进行输入简直就是灾难:键盘按键从 a 到 z 一字排开,你需要来回滚动焦点选择需要的字符。而且如果你像我一样在使用一些密码管理软件来管理密码,而非自己记忆的话,每次问你输入密码的时候估计都会有砸电视的冲动。还好,Apple 在第一次还是很人性化地帮我们省了这个步骤。
+
+
+
+成功登陆了自己的中国区的 Apple ID 以后,我们就进入到 tvOS 的主界面了,眼前情况只能用惨不忍睹来形容。只有照片,搜索,家庭共享和设置四个选项,和砖并没有太大区别..
+
+
+
+赶快吓得祭出了自己日本区的账号,更换账号以后,终于看到 iTunes 和 App Store 等内容了。Apple TV 现在在大陆还没有上市和销售的消息,鉴于之前音乐电影图书的 iTunes Store 在中国正式上线,可能 Apple 也正在努力想让 Apple TV 能在国内开始销售。但是之前有广电总局机顶盒只能用国产 TVOS1.0 的禁令,所以最终 Apple TV 有没有可能在中国上市,还是一个未知数。
+
+总之,你现在想要用 Apple TV 来做客厅娱乐的话,一个欧美或者日本区的 Apple ID 应该是必要的。
+
+
+
+
+
+作为开发者,最关心的自然是 App Store 了。可以看到虽然是第一天,但是商店中已经有不少 app 了,像是常看的视频 app 的 YouTube,vimeo,TED 等等一应俱全。Netflix 最近也登陆了日本,并且取得了很好的成绩,也出现在了日本区商店的推荐首页上。其他的像是 NHK,ESPN 等等频道也推出了自己的 app。
+
+### 使用
+
+使用上来说,交互设计十分简单清晰。焦点的内容会被稍微放大显示,点击触摸板选择,点击 Menu 按钮后退,这对一个普通的有其他电子产品使用经验的用户来说,几乎是没有学习成本的。观看电视节目的话要做的就是在主页面上滑动找到 app 并打开,然后选择想看的内容就可以了。
+
+由于字符输入的困难,我选择了在设置中将购买 app 需要确认 Apple ID 关掉了。Apple TV 作为一款家庭设备的话其实如果家里有小朋友的话这还是挺危险的一件事情,但是相比起每次要打开手机显示密码再花三分钟划来划去输入密码的话,我还是选择前者..
+
+在要求用户登录和输入上,值得一提的是 YouTube 的处理。YouTube 没有选择让用户直接在 Apple TV 上输入用户密码,而是采用了让用户使用其他设备进行登陆:访问特定网址进行正常登陆,然后输入特定的序列码来进行回调和授权。其实猜测这样做的很大原因是 YouTube 要求的是标准的 OAuth 2.0 登陆,需要访问 Google 账号,而在 tvOS 上是没有 WebView 支持的。也就是说,想要像在 iOS 或者 Android 上弹出 web view 的认证的话是不可能的了,除非用 TVML 专门为 Apple TV 写一套流程和界面。于此相比,现在几乎拥有 Apple TV 的用户都不太可能没有其他设备,使用更方便的设备进行登陆会是一个很不错的选择。
+
+
+
+除了电视节目,App Store 中现在另一个大类就是游戏了。在发布会上已经有开发商展示了一些游戏,比如 Crossy Road:
+
+
+
+这类游戏依然交互简单,相比起在手机上来说,你的操作不会影响到屏幕显示,你可以一直看到完整的屏幕 (而且这块屏幕比手机大得多的多得多..),手持遥控器窝在沙发里懒洋洋地划来划去的感觉相比去抬着手拿设备来说也要舒适一些。
+
+如果你认为 Apple TV 只能胜任这样的像素风小游戏的话你就错了。Gameloft 也在第一时间将《狂野飙车8》移植到了 Apple TV 上。因为 Apple TV 的 app 有 200 MB 的尺寸限制,所以对于这样的 3D 游戏,基本需要能够进行额外的资源加载。自己维护一套 asset 的规则或者使用 Apple 的 On-Demand Resources 都是可选项。Gameloft 也采用了额外的资源下载:
+
+
+
+得益于 A8 CPU 和 2GB 内存,游戏运行的帧率完全没有问题,游戏始终能跑在满帧。在操作时,游戏利用了遥控器中的重力感应和陀螺仪来识别遥控器方向,以此控制赛车。除了使用遥控器以外,游戏也支持 MFi 的各种第三方手柄或者方向盘,作为一款赛车类游戏来说,体验还是相当完整。当然,《狂野飙车8》还是属于移动端为主的视觉效果。光影和细节上相比主机游戏还是有不小差距,但是毕竟相比于 iPhone 或者 iPad,更大的屏幕意味着更接近于标准游戏主机的体验,玩起来还是挺过瘾的,可以说这次 Apple 在游戏方向上迈出了坚实的一步。
+
+
+
+### 开发
+
+回到开发的正题上,Xcode 7.1 包含了 tvOS 的 SDK,相信有兴趣的同学已经把玩过 tvOS SDK 和模拟器了。想要把程序部署到实际设备上的话,我们需要一根 USB-C 到 USB-A 的线缆将 Apple TV 与 Mac 进行连接(如果你用的是 MacBook 的话那就是两头 USB-C),然后选择正确的 provisioning file 或者直接交给 Xcode fix 以后就可以直接部署了。所以作为开发者,在购买 Apple TV 作为开发设备的时候,千万不要忘了一并买一根转接线,否则你将无法将 TV 连接到你的开发设备上。
+
+Apple TV 的 app 的话,基本来说有两种形式:一是基于网络的一套方案,使用 TVJS (一套为 Apple TV 设计的 Javascript 框架),TVML (Apple TV 专用的类似 XML 的标记语言,用来构建界面) 和 TVMLKit (胶水代码) 来构建,Apple 把这类的 app 叫做 Client-server Apps。这类 app 的开发更偏向于类似前端和网站开发:你使用 Apple 提供的常用模板,然后从自己的服务器中获取内容后,将这些内容填写到模板中进行显示。使用这种开发方法的话,基本是在和 Javascript 和 XML 相关的内容打交道。如果你已经有现成的数据 (视频) 源,那么使用这种方法能让你直接将数据填到模板里,以此来迅速地构建出符合 Apple TV 标准交互的 app。
+
+另一类 app 类似于传统 iOS app,使用一个 UIKit 的子集,一些 tvOS 的独特的特性以及 Auto layout 来进行 app 构建,Apple 将其称为 Custom Apps。如果你已经是一个 iOS 开发者的话,这种模式的 app 会让你感到如沐春风。基础控件,导航,网络等等,一切都是那么熟悉。不过相比起完整的 iOS,tvOS 有着完全不同的交互方式和体验,很多时候我们都需要注意不能将 iOS 开发中已有的经验简单粗暴地照搬。
+
+对于开发者来说,直接获取利润的方式和普通的 iOS app 基本一致,也就是付费 app,内购以及广告。现在暂时在 tvOS 上还没有成熟的广告平台 (不知道 Admob 之类的平台商有没有在这方面有什么进展),只能靠内容商自己想办法。因为是全新的平台,所以现在 App Store 上 app 数量还比较有限,可以说暂时还是一片蓝海。如果 Apple TV 能够获得成功进入大多数人的客厅的话,这绝对是一个极具潜力的消费市场。
+
+但是不得不吐槽的是,现在 App Store 的应用发现还非常不足,只有推荐,已购买和搜索三个栏目。没有分类榜单,也没有方式能打开指向 App Store 的链接 (因为 Apple 铁了心不想把网页浏览加入到 tvOS 里)。也就是说,想要发现一个 app 现在只有两种方式:被 Apple 推荐出现在首页,或者是用户在搜索中输入你的 app 名字/关键字进行搜索。搜索的方法能为 app 带来的曝光度几乎为零,而且也没有办法使用链接进行推广。用户想要下载你的 app,不得不经过痛苦的搜索过程 (还记得我们说了好几次的 Apple TV 上输入不便的问题么)。所以对广大普通开发者来说,相比起开发中的技术问题,如何让自己开发的 app 让用户看到知道,会是更大的挑战。
+
+
+
+不过输入和 App Store 的问题都不是无解。在平台化如此强大的今天,直接用 Siri 进行语音输入,使用 iCloud 密码或者 Touch ID,都是很好的输入解决方案。App Store 的话只要 Apple 愿意,添加一些分类榜单或者更完善的 app 发现机制也只是时间上的问题。对于像一个 tvOS 这样一个全新的系统和平台生态,我们还是不应该太过着急,多给它一些时间来调整。
+
+我之后可能会看时间安排,有空的话会稍微再写一点 Apple TV 开发相关的内容 :)
diff --git a/_posts/2015-12-29-2015-final.markdown b/_posts/2015-12-29-2015-final.markdown
new file mode 100644
index 00000000..f91001f4
--- /dev/null
+++ b/_posts/2015-12-29-2015-final.markdown
@@ -0,0 +1,71 @@
+---
+layout: post
+title: 写在 2015 的尾巴
+date: 2015-12-29 01:03:40.000000000 +09:00
+tags: 胡言乱语集
+---
+
+上一次写类似年终总结的东西已经是大概快十年前的事情了,那时候还刚进大学,每天也就喜欢发一些无病呻吟的东西。回望之后,发现那些蹉跎掉的岁月确实无法再重新来过,不过也让我懂得了,幸好我们还能珍惜当下。
+
+今年于我来说,注定是不平凡的一年。愈到年关,写作的冲动就愈发强烈,它驱使着我去记录下些什么,所以有了这篇写给自己的“阔别已久”的年终总结。
+
+## 无论何时,无论何地,平安就好
+
+前几天因为北京雾霾很凶,看到有人在说柴静的雾霾报告,自己之前没看过,所以就找来补了补课。《穹顶之下》确实是一部非常好的新闻调查片子,除了有关雾霾的数据和结论以外,里面有两句话让我印象深刻,一句是“我不是多怕死,我只是不想这么活”,另一句是“一切,平安就好”。
+
+算下来我明年也会满三十岁,不出意外的话,看起来我是懵懵懂懂走掉了人生的一小半旅途(左思右想,在这里还是不要立什么 flag 了)。十年前,我想过要改变这个世界,但是那时候能力不足,只有空谈和做梦的时候能改变世界,可以说是天资愚钝,不得其所;十年后,遑论多少,我确实是在以自己的力量改变着世界,哪怕仅只有那么一点点。但是现在,已经没有了当年的豪言壮志、慷慨激昂。古人说三十而立,意思是人到三十,应当会有属于自己的不可替代的位置。其实这句话只说对了一半,说对了人对于社会的那一半。但我们还有着对家庭的一半,我们每个人从出生起就已经有自己不可替代的位置了,那就是在父母的眼中。小时候出门玩耍,父母会在阳台上高呼注意安全;长大了上学,父母会在机场叮嘱小心谨慎;现在虽然远游在外,已然好几年没有回家,但是科技的进步让我们随时可以“见面”,每次他们也不忘一句提醒。以前我不懂,觉得啰嗦,无趣,直到去年同学发生的一些意外,加上今年自己也初为人父,方知这一片用心良苦。人的生命实在太脆弱了,而恰恰正是这脆弱得随时可能熄灭的一点点烛光,却牵动了太多人的心绪。
+
+平安,其实并不仅是运气或者上天的恩赐,而更多的是自我的争取。子曾经曰过:“笃信好学,守死善道。危邦不入,乱邦不居。”虽说原意更偏向仕途前景,但是这何尝不是一种生活的警醒。你可以不喜欢这其中略微消极和中庸的思想,但是避开已知和可能的危险,也正是为了更长久地“守死善道”。
+
+所以,想对自己的父母,对自己爱的人和爱自己的人们,对看这篇文章的你,说一句,无论何时,无论何地,平安就好。
+
+## 好奇的目光始终是最美的
+
+今年最重要的事情当然是女儿满夏的出生。这半年来看着小朋友一天天长大,是一件让人非常开心的事情。她除了每天精力过剩不乖乖睡觉以外,给我的生活带来了很多很美好的时光。现在满夏正在学习如何很好地爬行,这会是她主动来进行探索的重要的一步。而在此之前,她已经学会了用触摸和撕咬的方式来了解这个对她来说还尚处陌生的美好世界。
+
+真的想要感谢这个美好的世界。我们的父母在带大我们的时候,没有纸尿布,没有婴儿车,也没有如此多彩的玩具和针对婴儿的服务。但是二十多年来一切的变化,使得看护婴儿不再是一件艰难和特别劳心的事情。在满夏成长的每一天,我们都能一起开心度过,她在以她的节奏学习这个世界,而我也能从她的身上学到一些珍贵的品质 -- 对这个世界的好奇。
+
+
+
+无论是装机器人的纸盒,还是爬行垫的边角,再或者是 iPhone,只要是能塞进嘴里的东西,统统都被尝了个遍。因为这个世界对她来说整个都是新的,那些我们这些成年人已经习以为常的东西,在她的眼里都是最新鲜的存在。婴儿这种与生俱来的好奇其实我们每个人都有过,但是在活过三十年后,怎样的好奇才不会倦怠呢?我想大概没有。教主生前说过一句很有名的话,stay hungry stay foolish,而这句话有一个更美好的中文版本:
+
+> 求知若饥,虚心若愚
+
+虽然这个翻译可以说[偏离原意很多](http://www.zhihu.com/question/19557797),但是并不妨碍这句话表达出求知好奇的重要性。如果一个人对身边的事物不再敏感,不再想去探索,那么可以说这个人生命和精神实际上也就走到了尽头。无论何时,无论何地,都要保持不断的学习,这是这半年来我从满夏那里所学到的东西。
+
+所以我会同她一起看小画书,一起尝试防滑垫的味道,一起把头磕在柜子的边边角角,当然,也一起观察天上的星星和月亮。陪她玩,陪她笑,陪她哭,陪她闹。我想,如果我和她能一起成长,那这就是我们给彼此最好的礼物。
+
+## 拥抱,敬畏,感恩,回馈
+
+今年在开源社区花了不少时间,1144 个开源提交,发布了几个还算有人用的框架,也参与了几个知名项目的开发。在公司里,完成了一个 SDK 的整合维护,作为主力开发参与两个完整项目,算是为今年的开发工作交上了一份满意的答卷。
+
+
+
+[objc 中国](http://objccn.io)的连载随着原刊的完结而结束,算是把一个跨越了两年的项目有始有终地做完了。这期间结识了很多国内 iOS 开发领域乐于分享的同行。他们在开发领域的专业和对翻译工作的尽心,让 objc 中国项目可以顺利地进行和完成。在这里有机会想统一地感谢这个项目的所有[贡献者](https://github.com/objccn/articles/graphs/contributors),谢谢他们为中文 objc 社区的繁荣和发展做出的不懈努力。同时,也感谢无数开源项目给予我们的帮助和启迪。正如 objc 中国这个项目的初衷,我们爱这个世界,愿程序让这个世界变得更美好!
+
+今年是我在 LINE 工作的第一个完整的一年,入职面试的时候就针对 Swift 聊了很多,在公司这一年来也一直在使用 Swift 进行开发,并帮助公司的其他开发者逐渐过渡到 Swift。到目前为止这个进程进行得很顺利,现在 LINE 的新设的 app 已经全面转向使用 Swift,iOS 团队的其他工程师们也十分喜欢并且倾向于使用这门新语言。加上 LINE 比较自由的工作时间和宽松的企业氛围,可以说虽然比起国内 IT 业界来说,我算“收入微薄”,但这一年的工作确实十分开心。
+
+2015 年间,我也陆陆续续用 Swift 写了一些开源框架,包括图像下载和缓存的 [Kingfisher](https://github.com/onevcat/Kingfisher),APNG 的解码库 [APNGKit](https://github.com/onevcat/APNGKit),以及前两天刚完成的一个小品级工具 [Rainbow](https://github.com/onevcat/Rainbow)。侥幸,这些框架也还或多或少受到了欢迎,同时有不少开发者在使用它们。其实不能免俗地说,我最初做开源的目的还是积攒人气,提高声望。在 2013 年的时候开发了一个给代码加注释的 Xcode 插件,获得了像 [iOS Dev Weekly](https://iosdevweekly.com) 的 [Dave Verwer](https://twitter.com/daveverwer) 和 [NSHipster](http://nshipster.com) 的 [mattt](https://twitter.com/mattt) 的关注和推广,获得了不少好评。那时候发现原来除了骗一波星星以外,我写的东西还是能帮助到其他人的。2014 年咬牙自掏机票门票参加了 WWDC,也在那里见到了很多不同地方的开发者。这个时代真的是一个神奇的时代,地球两端的人只要愿意,在 12 小时内就能互相见面。我们没有理由不拥抱这个世界,去和其他人交流,去一起探索。因为 Apple 的统一平台,开发者们有更多的机会认识彼此,这确实是一件幸事。
+
+在今年,我和世界上其他 iOS 开发者的交流也在逐渐变多,这主要来源于向一些开源项目进行提交时候和这些项目开发者的交流。在这个过程中,我发现像是 [fastlane](https://github.com/fastlane/fastlane) 的 [Felix](https://github.com/KrauseFx),或者是 [Swift Package Manager](https://github.com/apple/swift-package-manager) 的 [Max Howell](https://github.com/mxcl) 这些维护者们都十分谦虚,很有责任心,并愿意接纳讨论。前几天还和老婆调侃说起知不知道那个翻转二叉树被拒的悲剧,转眼过几天我们居然有机会一起写代码,在 Twitter 上互 fo 和聊天,这种感觉真的非常奇妙。
+
+我们在工作中向开源社区索取了很多,有机会的话,感恩和回馈会是对开源社区最好的回报。以认真的态度多写一些有意义的代码,这不仅会是对个人的提升,同时也能促进社区的发展,于人于己都是一件好事。中国的开发者数目众多,中国在互联网和移动开发中得声音也越来越响亮,我们应该以怎样的姿态来面对这个越来越开放的世界。如何拥抱变化,敬畏世界,感恩开源,回馈社区,也许是新时代每个有志于软件开发的工程师所需要审视和考虑的问题。
+
+## 真 总结
+
+2015 年马上就要过去,要用一句话总结今年的话,那应该是“生活进入正轨,事业稳步发展”。一切都好,勿念。
+
+### P.S.
+
+按照国际惯例,似乎应该写一份书单。但是因为好像今年读的技术书籍以外的书就只有一本反革命的日本右翼的书,所以还是不写为妙。取而代之,列一个最近已经买过和打算去买的比较有意思的东西的清单和一句话评语吧,主要目的是帮大家长长草剁剁手为大家春节准备礼物提供参考..
+
+* [第四代 Apple TV](http://www.apple.com/tv/) - 已成为饭后闲时在 YouTube 看反动新闻的主力工具
+* [Sphero SPRK 机器球](http://www.sphero.com/sphero-sprk) - 用来逗满夏玩的球型机器人,已成为其最喜欢的食物之一
+* [Magic Trackpad 2](http://www.apple.com/cn/shop/product/MJ2R2/magic-trackpad-2) - 工作用主力输入设备,作为触摸板死忠表示很满意
+* [飞利浦 HealtyWhite 电动牙刷](http://www.philips.com.cn/c-p/HX6712_04/sonicare-healthywhite-sonic-electric-toothbrush/overview) - 每次用震得欲仙欲死,欲罢不能
+* [Dyson V6 Fluffy 吸尘器](http://shop.dyson.cn/vacuums/cordless-vacuums/v6-fluffy-209573-01) - 吸得真心干净,而且超方便,指哪儿哪儿干净
+* [飞利浦 Shaver 9000 刮胡刀](http://www.philips.com.cn/c-p/S9111_12/shaver-series-9000-wet-and-dry-electric-shaver-with-smartclick-precision-trimmer-and-aquatec-wet-dry) - 刮得还算干净,但是没有 Dyson 吸尘器吸得干净,不过清洗方便是大优点
+* [Wacom 影拓 Photo 绘图板](http://www.wacom.com/en-us/products/pen-tablets/intuos-photo) - 第一块入门级的板子,用来 PS 照片挺好
+* [Beats Solo2 Wireless 高保真耳机](http://cn.beatsbydre.com/探索/按类别浏览/rose-gold/MLLG2.html) - 美妙自由的音乐体验,专注工作值得拥有
+* [坚果文青版 蘇芳](http://www.smartisan.com/jianguo/#/overview) - 完美的 Android 4 系测试机
+* [iPhone 6s 粉色和 iPad mini 新款](http://www.apple.com/cn/) - 老婆大人的日常用机,我只有看的份
diff --git a/_posts/2016-01-19-create-framework.markdown b/_posts/2016-01-19-create-framework.markdown
new file mode 100644
index 00000000..caad4cdf
--- /dev/null
+++ b/_posts/2016-01-19-create-framework.markdown
@@ -0,0 +1,572 @@
+---
+layout: post
+title: 如何打造一个让人愉快的框架
+date: 2016-01-19 15:32:24.000000000 +09:00
+tags: 能工巧匠集
+---
+
+> 这是我在今年 1 月 10 日 [@Swift 开发者大会](http://atswift.io) 上演讲的文字稿。相关的视频还在制作中,没有到现场的朋友可以通过这个文字稿了解到这个 session 的内容。
+
+
+
+
+
+虽然我的工作是程序员,但是最近半年其实我的主要干的事儿是养了一个小孩。
+所以这半年来可以说没有积累到什么技术,反而是积累了不少养小孩的心得。
+当知道了有这么次会议可以分享这半年来的心得的时候,我毫不犹豫地选定了主题。那就是
+
+> 如何打造一个让人愉快的**小孩**
+
+但考虑到这是一次开发者会议...当我把这个想法和题目提交给大会的时候,被残酷地拒绝了。考虑到我们是一次开发者大会,所以我需要找一些更合适的主题。其实如果你对自己的代码有感情的话,我们开发和维护的项目或者框架就如同自己的孩子一般这也是我所能找到的两者的共同点。所以,我将原来拟定的主题换了两个字:
+
+> 如何打造一个让人愉快的**框架**
+
+在正式开始前,我想先给大家分享一个故事。我们那儿的 iOS 开发小组里有一个叫做武田君的人,他的代码写得不错,做事也非常严谨,可以说是楷模般的员工。但是他有一个致命的弱点 -- 喜欢自己发明轮子。他出于本能地抗拒在代码中使用第三方框架,所以接到开发任务以后他一般都要花比其他小伙伴更多的时间才能完成。
+
+武田君其实在各个方面都有建树...比如
+
+- 网络请求
+- 模型解析
+- 导航效果
+- 视图动画
+...
+
+不过虽然造了很多轮子,但是代码的重用比较糟糕,耦合严重。在新项目中使用的话,只能复制粘贴,然后针对项目修修补补。因为承担的任务总是没有办法完成,他一直是项目deadline的决定者,在日本这种社会,压力可想而知。就在我这次回国之前,武田君来向我借了一本我本科时候最喜欢的书。就是这本:
+
+
+
+我有时候就想,到底是什么让一个开发者面临如此大的精神压力,我们有什么办法来缓解这种压力。在我们有限的开发生涯中,应该如何有效利用时间来做一些更有价值的事情。
+
+显然,我们不可能一天建成罗马,也不可能一个人建成罗马。我们需要一些方法把自己和别人写的代码组织起来,高效地利用,并以此为基础构建软件。这就涉及到使用和维护框架。如何利用框架迅速构建应用,以及在开发和发布一个框架的时候应该注意一些什么,这是我今天想讲的主题。当然,为了让大家安心和专注于今天的内容,而不是挂念武田君的命运,特此声明:
+
+> 以上故事纯属虚构,如有雷同实属巧合
+
+## 使用框架
+
+在了解如何制作框架之前,先让我们看看如何使用框架。可以说,如果你想成为一个框架的提供者,首先你必须是一个优秀的使用者。
+
+在 iOS 开发的早期,使用框架其实并不是一件让人愉悦的事情。可能有几年经验的开发者都有这样的体会,那就是:
+
+> 忘不了 那些年,被手动引用和 `.a` 文件所支配的恐惧
+
+其实恐惧源于未知,回想一下,当我们刚接触软件开发的时候,懵懵懂懂地引用了一个静态库,然后面对一排排编译器报错时候手足无措的绝望。但是当我们了解了静态库的话,我们就能克服这种恐惧了。
+
+### 什么是静态库 (Static Library)
+
+所谓静态库,或者说 .a 文件,就是一系列从源码编译的目标文件的集合。它是你的源码的实现所对应的二进制。配合上公共的 .h 文件,我们可以获取到 .a 中暴露的方法或者成员等。在最后编译 app 的时候.a 将被链接到最终的可执行文件中,之后每次都随着app的可执行二进制文件一同加载,你不能控制加载的方式和时机,所以称为静态库。
+
+在 iOS 8 之前,iOS 只支持以静态库的方式来使用第三方的代码。
+
+### 什么是动态框架 (Dynamic Framework)
+
+与静态相对应的当然是动态。我们每天使用的 iOS 系统的框架是以 .framework 结尾的,它们就是动态框架。
+
+Framework 其实是一个 bundle,或者说是一个特殊的文件夹。系统的 framework 是存在于系统内部,而不会打包进 app 中。app 的启动的时候会检查所需要的动态框架是否已经加载。像 UIKit 之类的常用系统框架一般已经在内存中,就不需要再次加载,这可以保证 app 启动速度。相比静态库,framework 是自包含的,你不需要关心头文件位置等,使用起来很方便。
+
+### Universal Framework
+
+iOS 8 之前也有一些第三方库提供 .framework 文件,但是它们实质上都是静态库,只不过通过一些方法进行了包装,相比传统的 .a 要好用一些。像是原来的 Dropbox 和 Facebook 等都使用这种方法来提供 SDK。不过因为已经脱离时代,所以在此略过不说。有兴趣和需要的朋友可以参看一下[这里](https://github.com/kstenerud/iOS-Universal-Framework)和[这里](https://github.com/jverkoey/iOS-Framework)。
+
+### Library v.s. Framework
+
+对比静态库和动态框架,后者是有不少优势的。
+
+首先,静态库不能包含像 xib 文件,图片这样的资源文件,其他开发者必须将它们复制到 app 的 main bundle 中才能使用,维护和更新非常困难;而 framework 则可以将资源文件包含在自己的 bundle 中。
+其次,静态库必须打包到二进制文件中,这在以前的 iOS 开发中不是很大的问题。但是随着 iOS 扩展(比如通知中心扩展或者 Action 扩展)开发的出现,你现在可能需要将同一个 .a 包含在 app 本体以及扩展的二进制文件中,这是不必要的重复。
+
+最后,静态库只能随应用 binary 一起加载,而动态框架加载到内存后就不需要再次加载,二次启动速度加快。另外,使用时也可以控制加载时机。
+
+动态框架有非常多的优点,但是遗憾的是以前 Apple 不允许第三方框架使用动态方式,而只有系统框架可以通过动态方式加载。
+
+很多时候我们都想问,Apple,凭什么?
+
+好吧,这种事也不是一次两次了...不过好消息是:。
+
+### Cocoa Touch Framework
+
+Apple 从 iOS 8 开始允许开发者有条件地创建和使用动态框架,这种框架叫做 Cocoa Touch Framework。
+
+虽然同样是动态框架,但是和系统 framework 不同,app 中的使用的 Cocoa Touch Framework 在打包和提交 app 时会被放到 app bundle 中,运行在沙盒里,而不是系统中。也就是说,不同的 app 就算使用了同样的 framework,但还是会有多份的框架被分别签名,打包和加载。
+
+Cocoa Touch Framework 的推出主要是为了解决两个问题:首先是应对刚才提到的从 iOS 8 开始的扩展开发。其次是因为 Swift,在 Swift 开源之前,它是不支持编译为静态库的。虽然在开源后有编译为静态库的可能性,但是因为 Binary Interface 未确定,现在也还无法实用。这些问题会在 Swift 3 中将被解决,但这至少要等到今年下半年了。
+
+现在,Swift runtime 不在系统中,而是打包在各个 app 里的。所以如果要使用 Swift 静态框架,由于 ABI 不兼容,所以我们将不得不在静态包中再包含一次 runtime,可能导致同一个 app 包中包括多个版本的运行时,暂时是不可取的。
+
+### 包和依赖管理
+
+在使用框架的时候,用一些包管理和依赖管理工具可以简化使用流程。其中现在使用最广泛的应该是 [CocoaPods](http://cocoapods.org](http://cocoapods.org)。
+
+CocoaPods 是一个已经有五年历史的 ruby 程序,可以帮助获取和管理依赖框架。
+
+CocoaPods 的主要原理是框架的提供者通过编写合适的 PodSpec 文件来提供框架的基本信息,包括仓库地址,需要编译的文件,依赖等
+用户使用 Podfile 文件指定想要使用的框架,CocoaPods 会创建一个新的工程来管理这些框架和它们的依赖,并把所有这些框架编译到成一个静态的 libPod.a。然后新建一个 workspace 包含你原来的项目和这个新的框架项目,最后在原来的项目中使用这个 libPods.a
+
+这是一种“侵入式”的集成方式,它会修改你的项目配置和结构。
+
+本来 CocoaPods 已经准备在前年发布 1.0 版本,但是 Swift 和动态框架的横空出世打乱了这个计划。因为必须提供对这两者的支持。不过最近 1.0.0 的 beta 已经公布,相信这个历时五年的项目将在最近很快迎来正式发布。
+
+从 0.36.0 开始,可以通过在 Podfile 中添加 `use_frameworks!` 来编译 CocoaTouch Framework,也就是动态框架。
+
+因为现在 Swift 的代码只能被编译为动态框架,所以如果你使用的依赖中包含 Swift 代码,又想使用 CocoaPods 来管理的话,必须选择开启这个选项。
+
+`use_frameworks!` 会把项目的依赖全部改为 framework。也就是说这是一个 none or all 的更改。你无法指定某几个框架编译为动态,某几个编译为静态。我们可以这么理解:假设 Pod A 是动态框架,Pod B 是静态,Pod A 依赖 Pod B。要是 app 也依赖 Pod B:那么要么 Pod A 在 link 的时候找不到 Pod B 的符号,要么 A 和 app 都包含 B,都是无解的情况。
+
+使用 CocoaPods 很简单,用 Podfile 来描述你需要使用和依赖哪些框架,然后执行 pod install 就可以了。下面是一个典型的 Podfile 的结构。
+
+```ruby
+# Podfile
+platform :ios, '8.0'
+use_frameworks!
+
+target 'MyApp' do
+ pod 'AFNetworking', '~> 2.6'
+ pod 'ORStackView', '~> 3.0'
+ pod 'SwiftyJSON', '~> 2.3'
+end
+```
+
+```bash
+$ pod install
+```
+
+[Carthage](https://github.com/Carthage/Carthage) 是另外的一个选择,它是在 Cocoa Touch Framework 和 Swift 发布后出现的专门针对 Framework 进行的包管理工具。
+
+Carthage 相比 CocoaPods,采用的是完全不同的一条道路。Carthage 只支持动态框架,它仅负责将项目 clone 到本地并将对应的 Cocoa Framework target 进行构建。之后你需要自行将构建好的 framework 添加到项目中。和 CocoaPods 需要提交和维护框架信息不同,Carthage 是去中心化的
+它直接从 git 仓库获取项目,而不需要依靠 podspec 类似的文件来管理。
+
+使用上来说,Carthage 和 CocoaPods 类似之处在于也通过一个文件 `Cartfile` 来指定依赖关系。
+
+```ruby
+# Cartfile
+github "ReactiveCocoa/ReactiveCocoa"
+github "onevcat/Kingfisher" ~> 1.8
+github "https://enterprise.local/hello/repo.git"
+```
+
+```bash
+$ carthage update
+```
+
+在使用 Framework 的时候,我们需要将用到的框架 Embedded Binary 的方式链接到希望的 App target 中。
+
+随着上个月 Swift 开源,有了新的可能的选项,那就是 [Swift Package Manager](https://swift.org/package-manager/)。这可能是未来的包管理方式,但是现在暂时不支持 iOS 和 tvOS (也就是说 UIKit 并不支持)。
+
+Package Manager 实际上做的事情和 Carthage 相似,不过是通过 `llbuild` (low level build system)的跨平台编译工具将 Swift 编译为 .a 静态库。
+
+这个项目很新,从去年 11 月才开始。不过因为是 Apple 官方支持,所以今后很可能会集成到 Xcode 工具链中,成为项目的标配,非常值得期待。但是现在暂时还无法用于应用开发。
+
+## 创建框架
+
+作为框架的用户你可能知道这些就能够很好地使用各个框架了。但是如果你想要创建一个框架的话,还远远不够。接下来我们说一说如何创建一个框架。
+
+Xcode 为我们准备了 framework target 的模板,直接创建这个 target,就可以开始编写框架了。
+
+添加源文件,编写代码,编译,完成,就是这么简单。
+
+app 开发所得到产品直接面向最终用户;而框架开发得到的是一个中间产品,它面向的是其他开发者。对于一款 app,我们更注重使用各种手段来保证用户体验,最终目的是解决用户使用的问题。而框架的侧重点与 app 稍有不同,像是集成上的便利程度,使用上是否方便,升级的兼容等都需要考虑。虽然框架的开发和 app 的开发有不少不同,但是也有不少共通的规则和需要遵循的思维方式。
+
+### API 设计
+
+#### 最小化原则
+
+基于框架开发的特点,相较于 app 开发,需要更着重地考虑 API 的设计。你标记为 public 的内容将是框架使用者能看到的内容。提供什么样的 API 在很大程度上决定了其他的开发者会如何使用你的框架。
+
+在 API 设计的时候,从原则上来说,我们一开始可以提供尽可能少的接口来完成必要的任务,这有助于在框架初期控制框架的复杂程度。
+之后随着逐步的开发和框架使用场景的扩展,我们可以添加公共接口或者将原来的 internal 或者 private 接口标记为 public 供外界使用。
+
+```swift
+// Do this
+public func mustMethod() { ... }
+func onlyUsedInFramework() { ... }
+private func onlyUsedInFile() { ... }
+```
+
+```swift
+// Don't do this
+public func mustMethod() { ... }
+public func onlyUsedInFramework() { ... }
+public func onlyUsedInFile() { ... }
+```
+
+#### 命名考虑
+
+在决定了 public 接口以后,我们很快就会迎来编程界的最难的问题之一,命名。
+
+在 Objective-C 时代 Cocoa 开发的类型或者方法名称就以一个长字著称,Swift 时代保留了这个光荣传统。Swift 程序的命名应该尽量表意清晰,不用奇怪的缩写。在 Cocoa 的世界里,精确比简短更有吸引力。
+
+几个例子,相比于简单的 `remove`,`removeAt` 更能表达出从一个集合类型中移除元素的方式。而 `remove` 可能导致误解,是移除特定的 int 还是从某个 index 移除?
+
+```swift
+// Do this
+public mutating func removeAt(position: Index) -> Element
+```
+
+```swift
+// Don't do this
+public mutating func remove(i: Int) -> Element
+// <- index or element?
+```
+
+同样,`recursivelyFetch` 表达了递归地获取,而 `fetch` 很可能被理解为仅获取当前输入。
+
+```swift
+// Do this
+public func recursivelyFetch(urls: [(String, Range)]) throws -> [T]
+```
+
+```swift
+// Don't do this
+public func fetch(urls: [(String, Range)]) throws -> [T] // <- how?
+```
+
+另外需要注意方法名应该是动词或者动词短语开头,而属性名应该是名词。当遇到冲突时,(比如这里的 displayName,既可以是名字也可以是动词)应该特别注意属性和方法的上下文造成的理解不同。更好的方式是避免名动皆可的词语,比如把 displayName 换为 screenName,就不会产生歧义了。
+
+```swift
+public var displayName: String
+public var screenName: String // <- Better
+```
+
+```swift
+// Don't do this
+public func displayName() -> String
+// <- noun or verb? Why returning `String`?
+```
+
+在命名 API 时一个有用的诀窍是为你的 API 写文档。如果你用一句话无法将一个方法的内容表述清楚的话,这往往就意味着 API 的名字有改进的余地。好的 API 设计可以让有经验的开发者猜得八九不离十,看文档更多地只是为了确认细节。一个 API 如果能做到不需要看文档就能被使用,那么它肯定是成功的。
+
+关于 API 的命名,Apple 官方给出了一个很详细的[指南](https://swift.org/documentation/api-design-guidelines/) (Swift API Design Guidelines),相信每个开发者的必读内容。遵守这个准则,和其他开发者一道,用约定俗称的方式来进行编程和交流,这对提高框架质量非常,非常,非常重要(重要的事情要说三遍,如果你在我的演讲中只能记住一页的话,我希望是这一页。如果你还没有看过这个指南,建议去看一看,只需要花十分钟时间。)
+
+#### 优先测试,测试驱动开发
+
+你应该是你自己写的框架的第一个用户,最简单的使用你自己的框架的方式就是编写测试。据我所知,在 app 开发中,很多时候单元测试被忽视了。但是在框架开发中,这是很重要的一个环节。可能没有人会敢使用没有测试的框架。除了保证功能正确以外,通过测试,你可以发现框架中设计不合理的地方,并在第一时间进行改善。
+
+为框架编写测试的方式和为 app 测试类似,
+Swift 2 开始可以使用 @testable 来把框架引入到测试 module。这样的话可以调用 internal 方法。
+
+不过对于框架来说,理论上只测试 public 就够了。但是我个人推荐使用 testable,来对一些重要的 internal 的方法也进行测试。这可以提高开发和交付时的信心。
+
+```swift
+// In Test Target
+import XCTest
+@testable import YourFramework
+class FrameworkTypeTests: XCTestCase {
+ // ...
+}
+```
+
+---
+
+### 开发时的选择
+
+#### 命名冲突
+
+在 Objective-C 中的 static library 里一个常见问题是同样的符号在链接时会导致冲突。
+
+Swift 中我们可以通过 module 来提供类似命名空间隔离,从而避免符号冲突。但是在对系统已有的类添加 extension 的时候还是需要特别注意命名的问题。
+
+```swift
+ // F1.framework
+ extension UIImage {
+ public method() { print("F1") }
+ }
+
+ // F2.framework
+ extension UIImage {
+ public method() { print("F2") }
+ }
+```
+
+
+比如在框架 F1 和 F2 中我们都对 UIImage 定义了 method 方法,分别就输出自己来自哪个框架。
+
+如果我们需要在同一个文件里的话引入的话:
+
+```swift
+// app
+import F1
+import F2
+UIImage().method()
+// Ambiguous use of 'method()'
+```
+
+在 app 中的一个文件里同时 import F1 和 F2,就会产生编译错误,因为 F1 和 F2 都为同一个类型 UIImage 定义了 method,编译器无法确定使用哪个方法。
+
+当然因为有 import 控制,在使用的时候注意一下源文件的划分,避免同时 import F1 和 F2,似乎就能解决这个问题。
+
+```swift
+// app
+import F1
+UIImage().method()
+// 输出 F2 (结果不确定)
+```
+
+
+确实,只 import F1 的话,编译错误没有了,但是运行的时候有可能看到虽然 import 的是 F1,但是实际上调用到的是 F2 中的方法。
+
+这是因为虽然有命名空间隔离,但 NSObject 的 extension 实际上还是依赖于 Objective-C runtime 的,这两个框架都在 app 启动时候被加载,运行时究竟调用了哪个方法是和加载顺序相关的,并不确定。
+
+这种问题可以实际遇到的话,会非常难调试。
+
+所以我们开发框架时的选择,对于已存在类型的 `extension`,**必须添加前缀**,
+这和以前我们写 Objective-C 的 Category 的时候的原则是一样的。
+
+上面的例子里,在开发的时候,不应该写这样的代码,而应该加上合适的前缀,以减少冲突的可能性。
+
+```swift
+// Do this
+// F1.framework
+extension UIImage {
+ public f1_method() { print("F1") }
+}
+
+// F2.framework
+extension UIImage {
+ public f2_method() { print("F2") }
+}
+```
+
+#### 资源 bundle
+
+刚才提到过,framework 的一大优势是可以在自己的 bundle 中包含资源文件。在使用时,不需要关心框架的用户的环境,直接访问自己的类型的 bundle 就可以获取框架内的资源。
+
+```swift
+let bundle =
+ NSBundle(forClass: ClassInFramework.self)
+let path =
+ bundle.pathForResource("resource", ofType: "png")
+```
+
+
+## 发布框架
+
+最后说说发布和维护一个框架。辛苦制作的框架的最终目的其实就是让别人使用,一个没有人用的框架可以说是没有价值的。
+
+如果你想让更多的人知道你的框架,那抛开各种爱国感情和个人喜好,可以说 iOS 或者 Swift 开发者的发布目的地只有一个,那就是 GitHub。
+
+当然在像是开源中国或者 CSDN 这样的代码托管服务上发布也是很好的选择,但是不可否认的现状是只有在 GitHub 上你才能很方便地和全世界其他地方的开发者分享和交流你的代码。
+
+### 选择依赖工具
+
+关于发布,另外一个重要的问题,一般你需要选择支持一个或多个依赖管理工具。
+
+#### CocoaPods
+
+刚才也提到,CocoaPods 用 podspec 文件来描述项目信息,使用 CocoaPods 提供的命令行工具
+可以创建一个 podspec 模板,我们要做的就是按照项目的情况编辑这个文件。
+比如这里列出了一个podspec的基本结构,可以看到包含了很多项目信息。关于更详细的用法,可以参看 CocoaPods 的[文档](https://guides.cocoapods.org/making/getting-setup-with-trunk.html)。
+
+```bash
+pod spec create MyFramework
+```
+
+```swift
+Pod::Spec.new do |s|
+ s.name = "MyFramework"
+ s.version = "1.0.2"
+ s.summary = "My first framework"
+ s.description = <<-DESC
+ It's my first framework.
+ DESC
+ s.ios.deployment_target = "8.0"
+ s.source = { :git => "https://github.com/onevcat/myframework.git",
+ :tag => s.version }
+
+ s.source_files = "Class/*.{h,swift}"
+ s.public_header_files = ["MyFramework/MyFramework.h"]
+end
+```
+
+提交到 CocoaPods 也很简单,使用它们的命令行工具来检查 podspec 语法和项目是否正常编译,最后推送 podspec 到 CocoaPods 的主仓库就可以了。
+
+```bash
+# 打 tag
+git tag 1.0.2 && git push origin --tags
+
+# podspec 文法检查
+pod spec lint MyFramework.podspec
+
+# 提交到 CocoaPods 中心仓库
+pod trunk push MyFramework.podspec
+```
+
+#### Carthage
+
+另一个应该考虑尽量支持的是 Carthage,因为它的用户数量也不可小觑。
+支持 Carthage 比 CocoaPods 要简单很多,你需要做的只是保证你的框架 target 能正确编译,然后在 Manage Scheme 里把这个 target 标记为 Shared 就行了。
+
+#### Swift Package Manager
+
+Swift Package Manager 暂时还不能用于 iOS 项目的依赖管理,但是对于那些并不依赖 iOS 平台的框架来说,现在就可以开始支持 Swift Package Manager 了。
+
+Swift Package Manager 按照文件夹组织来确定模块,你需要把你的代码放到项目根目录下的 Sources 文件夹里。
+
+然后在根目录下创建 Package.swift 文件来定义 package 信息。这就是一个普通的 swift 源码文件,你需要做的是在里面定义一个 package 成员,为它指定名字和依赖关系等等。Package Manager 命令将根据这个文件和文件夹的层次来构建你的框架。
+
+```swift
+// Package.swift
+import PackageDescription
+let package = Package(
+ name: "MyKit",
+ dependencies: [
+ .Package(url: "https://github.com/onevcat/anotherPacakge.git",
+ majorVersion: 1)
+ ]
+)
+```
+
+### 版本管理
+
+在发布时另外一个需要特别注意的是版本。在 Podfile 或者 Cartfile 中指定依赖版本的时候我们可以看到类似这样的小飘箭头的符号,这代表版本兼容。比如兼容 2.6.1 表示高于 2.6.1 的 2.6.x 版本都可以使用,而 2.7 或以上不行;同理,如果兼容 2.6 的话,2.6,2.7,2.8 等等这些版本都是兼容的,而 3.0 不行。当然也可以使用 >= 或者是 = 这些符号。
+
+```ruby
+# Podfile
+pod 'AFNetworking', '~> 2.6.1'
+# 2.6.x 兼容 (2.6.1, 2.6.2, 2.6.9 等,不包含 2.7)
+
+# Podfile
+pod 'AFNetworking', '~> 2.6'
+# 2.x 兼容 (2.6.1, 2.7, 2.8 等,不包含 3.0)
+
+# Cartfile
+github "Mantle/Mantle" >= 1.1
+# 大于等于 1.1 (1.1,1.1.4, 1.3, 2.1 等)
+```
+
+#### Semantic Versioning 和版本兼容
+
+那什么叫版本兼容呢?我们看到的这套版本管理的方法叫做 [Semantic Versioning](http://semver.org)。它一般通过三个数字来定义版本。
+
+> `x(major).y(minor).z(patch)`
+
+- major - 公共 API 改动或者删减
+- minor - 新添加了公共 API
+- patch - bug 修正等
+- `0.x.y` 只遵守最后一条
+
+major 的更改表示用户必须修改自己的代码才能继续使用框架;minor 表示框架添加了新的 API,但是现有用户不需要修改代码可以保持原有行为不变;而 patch 则代表 API 没有改变,仅只是内部修正。
+
+在这个约定下,同样的 major 版本号就意味着用户不需要修改现有代码就能继续使用这个框架,所以这是使用最广的一个依赖方式,在这个兼容保证下,用户可以自由升级 minor 版本号。
+
+但是有一个例外,那就是还没有正式到达 1.0.0 版本号的框架。
+这种框架代表还在早期开发,没有正式发布,API 还在调整中,开发者只需要遵守 patch 的规则,也就是说 0.1.1 和 0.1.2 只有小的修正。但是 0.2 和 0.1 是可以完全不兼容。如果你正在使用一个未正式发布的框架的时候,需要小心这一点。
+
+框架的版本应该和 git 的 tag 对应,这可以和大多数版本管理工具兼容
+一般来说用户会默认你的框架时遵循 Semantic Versioning 和兼容规则。
+
+我们在设置版本的时候可能会注意到 Info.plist 中的 Version 和 Build 这两个值。虽然 CocoaPods 或者 Carthage 这样的包管理系统并不是使用 Info.plist 里的内容来确定依赖关系,但是我们最好还是保持这里的版本号和 git tag 的一致性。
+
+当我们编译框架项目的时候,会在头文件或者 module map 里看到这样的定义。
+框架的用户想要在运行时知道所使用的框架的版本号的话,使用这些属性会十分方便。这在做框架版本迁移的时候可能会有用。所以作为开发者,也应该维护这两个值来帮助我们确定框架版本。
+
+```c
+// MyFramework.h
+//! Project version string for MyFramework.
+FOUNDATION_EXPORT const unsigned char MyFrameworkVersionString[]; // 1.8.3
+//! Project version number for MyFramework.
+FOUNDATION_EXPORT double MyFrameworkVersionNumber; // 347
+
+// Exported module map
+//! Project version number for MyFramework.
+public var MyFrameworkVersionNumber: Double
+// 并没有导出 MyFrameworkVersionString
+```
+
+### 持续集成
+
+在框架开发中,一个优秀的持续集成环境是至关重要的。CI 可以保证潜在的贡献者在有保障的情况下对代码进行修改,减小了框架的维护压力。大部分 CI 环境对于开源项目都是免费的,得益于此,我们可以利用这个星球上最优秀的 CI 来确保我们的代码正常工作。
+
+就 iOS 或者 OSX 开发来说,Travis CI, CircleCI, Coveralls,Codecov 等都是很好的选择。
+
+开发总是有趣的,但是发布一般都很无聊。因为发布流程每次都一样,非常机械。无非就是跑测试,打 tag,上传代码,写 release log,更新 podspec 等等。虽然简单,但是费时费力,容易出错。对于这种情景,自动化流程显然是最好的选择。而相比于自己写发布脚本,在 Cocoa 社区我们有更好的工具,那就是 [fastlane](https://fastlane.tools)。
+
+fastlane 是一系列 Cocoa 开发的工具的集合,包括跑测试,打包 app,自动截图,管理 iTunes Connect 等等。
+
+不单单是 app 开发,在框架开发中,我们也可以利用到 fastlane 里很多很方便的命令。
+
+使用 fastlane 做持续发布很简单,建立自己的合适的 Fastfile 文件,然后把你想做什么写进去就好了。比如这里是一个简单的 Fastfile 的例子:
+
+```ruby
+# Fastfile
+desc "Release new version"
+lane :release do |options|
+ target_version = options[:version]
+ raise "The version is missed." if target_version.nil?
+ ensure_git_branch # 确认 master 分支
+ ensure_git_status_clean # 确认没有未提交的文件
+ scan # 运行测试
+
+ sync_build_number_to_git # 将 build 号设为 git commit 数
+ increment_version_number(version_number: target_version) # 设置版本号
+
+ version_bump_podspec(path: "Kingfisher.podspec",
+ version_number: target_version) # 更新 podspec
+ git_commit_all(message: "Bump version to #{target_version}") # 提交版本号修改
+ add_git_tag tag: target_version # 设置 tag
+ push_to_git_remote # 推送到 git 仓库
+ pod_push # 提交到 CocoaPods
+end
+
+$ fastlane release version:1.8.4
+```
+
+AFNetworking 在 3.0 版本开始加入了 fastlane 做自动集成和发布,可以说把开源项目的 CI 做到了极致。在这里强烈推荐大家有空可以看一看[这个项目](https://github.com/AFNetworking/fastlane),除了使用 fastlane 简化流程以外,这个项目里还介绍了一些发布框架时的最佳实践。
+
+我们能不能创造出像 AFNetworking 这样优秀的框架呢?一个优秀的框架包含哪些要求?
+
+### 创建一个优秀的框架
+
+一个优秀的框架必定包含这些特性:详尽的文档说明,可以指导后来开发者或者协作者迅速上手的注释,
+
+完善的测试保证功能正确以及不发生退化,简短易读可维护的代码,可以让使用者了解版本变化的更新日志,对于issue的解答等等。
+
+我们知道在科技界或者说 IT 界会有很多喜欢跑分的朋友。其实跑分这个事情可以辩证来看,它有其有意义的一面。跑分高的不一定优秀,但是优秀的跑分一般一定都会高。
+
+不止在硬件类的产品,其实在框架开发中我们其实也可以做类似的跑分来检验我们的框架质量如何。
+
+那就是 [CocoaPods Quality](https://cocoapods.org/pods/Kingfisher/quality),它是一个给开源框架打分的索引类的项目,会按照项目的受欢迎程度和完整度,并基于我们上面说的这些标准来对项目质量进行评判。
+
+对于框架使用者来说,这可以成为一个选择框架时的[重要参考](https://guides.cocoapods.org/making/quality-indexes),分数越高基本可以确定可能遇到的坑会越少。
+
+而对于框架的开发者来说,努力提高这个分数的同时,代码和框架质量肯定也得到了提高,这是一个自我完善的良好途径。在遇到功能类似的框架,我们也可以说“不服?跑个分”
+
+### 可能的问题
+
+最后想和大家探讨一下在框架开发中几个比较常见和需要特别注意的问题。
+
+首先是兼容性的保证这里的兼容性不是 API 的兼容性,而是逻辑上的兼容性。
+最可能出现问题的地方就是在不同版本中对数据持久化部分的处理是否兼容,
+包括数据库和Key-archiving。比如在新版本中添加了一个属性,如何从老版本中进行迁移如果处理不当,很可能就造成严重错误甚至 crash。
+
+另一个问题是重复的依赖。Swift 运行时还没有包含在设备中,如果对于框架,将 `EMBEDDED_CONTENT_CONTAINS_SWIFT` 设为 `YES` 的话,Swift 运行库将会被复制到框架中,这不是我们想见到的。在框架开发中这个 flag 一定是 NO,我们应该在 app 的 target 中进行设置。另外,可能你的框架会依赖其他框架,不要在项目中通过 copy file 把依赖的框架 copy 到框架 target 中,而是应该通过 Podfile 和 Cartfile 来解决依赖问题。
+
+在决定框架依赖的时候,可能遇到的最大的问题就是不同框架的依赖可能[无法兼容](https://github.com/apple/swift-package-manager/blob/master/Documentation/DependencyHells.md)。
+
+比如说一个 app 同时依赖了框架 A 和框架 B,而这两个框架又都依赖另一个框架 C。如果 A 中指定了兼容 1.1.2 而 B 中指定的是精确的 1.6.1 的话,app 的依赖关系就无法兼容了。
+
+在框架开发中,如果我们依赖了别的框架,就必须考虑和其他框架及应用的兼容。
+为了避免这种依赖无法满足的情况,我们最好尽量选择最宽松的依赖关系。
+
+一般情况下我们没有必要限定依赖的版本,如果被依赖的框架遵守我们上面提到的版本管理的规则的话,我们并没有必要去选择固定某一个版本,而应该尽可能放宽依赖限制以避免无法兼容。
+
+如果在使用框架中遇到这样的情况的话,去向依赖版本较旧的框架的维护者提 issue 或者发 pull request 会是好选择。
+
+有一些开发者表示在转向使用 Framework 以后遇到首次应用加载速度变长的问题 ([参考 1](https://github.com/artsy/eigen/issues/586),[参考 2](rdar://22948371](http://openradar.appspot.com/radar?id=4867644041723904))。
+
+社区讨论和探索结果表明可能是 Dynamic linker 在验证证书的时候的问题。
+这个时间和 app 中 dynamic framework 的数量为 n^2 时间复杂度。不过现在大家发现这可能是 Apple 在证书管理上的一个 bug,应该是只发生在开发阶段。可能现在比较安全的做法是控制使用的框架数量在合理范围之内,就我们公司的产品来说,并没有在生产环境遇到这个问题。如果你在 app 开发中遇到类似情况,这算是一个小提醒。
+
+最后,因为现在 Swift 现在 Binary Interface 还没有稳定,不论是框架还是应用项目中所有的 Swift 代码都必须用同样版本的编译器进行编译。就是说,每当 Swift 版本升级,原来 build 过的 framework 需要重新构建否则无法通过编译。对框架开发者来说,保持使用最新 release 版本的编译器来发布框架就不会有大的问题。
+
+在 Swift 3.0 以后语言的二进制接口将会稳定,届时 Swift 也将被集成到 iOS 系统中。也就是说到今年下半年的话这个问题就将不再存在。
+
+## 从今天开始开发框架
+
+做一个小的总结。现在这个时机对于中国的 Cocoa 开发者来说是非常好的时代,GitHub 中国用户很多,国内 iOS 开发圈子大家的分享精神和新东西的传播速度也非常快。可以说,我们中国开发者正在离这个世界的中心舞台越来越近,只要出现好东西的话,应该很快就能得到国内开发者的关注,继而登上 GitHub Trending 页面被世界所知。不要说五年,可能在两年之前,这都是难以想象的。
+
+> Write the code, change the world.
+
+Swift 是随着这句口号诞生的,而现在开发者改变这个世界的力度可以说是前所未有的。
+
+对于国内的开发者来说,我们真的应该希望少一些像 MingGeJS 这样的东西,而多一些能帮助这个世界的项目,以认真的态度多写一些有意义的代码,回馈开源社区,这于人于己都是一件好事。
+
+希望中国的开发者能够在 Swift 这个新时代创造出更多世界级的框架,让这些框架能帮助全球的开发者一起构建更优秀的软件。
diff --git a/_posts/2016-02-25-swift-performance.markdown b/_posts/2016-02-25-swift-performance.markdown
new file mode 100644
index 00000000..add40a30
--- /dev/null
+++ b/_posts/2016-02-25-swift-performance.markdown
@@ -0,0 +1,168 @@
+---
+layout: post
+title: Swift 性能探索和优化分析
+date: 2016-02-25 11:32:24.000000000 +09:00
+tags: 能工巧匠集
+---
+
+
+
+本文首发在 CSDN《程序员》杂志,订阅地址 [http://dingyue.programmer.com.cn/](http://dingyue.programmer.com.cn/)。
+
+Apple 在推出 Swift 时就将其冠以先进,安全和高效的新一代编程语言之名。前两点在 Swift 的语法和语言特性中已经表现得淋漓尽致:像是尾随闭包,枚举关联值,可选值和强制的类型安全等都是 Swift 显而易见的优点。但是对于高效一点,就没有那么明显了。在 2014 年 WWDC 大会上 Apple 宣称 Swift 具有超越 Objective-C 的性能,甚至某些情况下可以媲美和超过 C。但是在 Swift 正式发布后,很多开发者发现似乎 Swift 性能并没有像宣传的那样优秀。甚至在 Swift 经过了一年半的演进的今天,稍有不慎就容易掉进语言性能的陷阱中。本文将分析一些使用 Swift 进行 iOS/OS X 开发时性能上的考量和做法,同时,笔者结合自己这一年多来使用 Swift 进行开发的经验,也给出了一些对应办法。
+
+## 为什么 Swift 的性能值得期待
+
+Swift 具有一门高效语言所需要具备的绝大部分特点。与 Ruby 或者 Python 这样的解释型语言不需要再做什么对比了,相较于其前辈的 Objective-C,Swift 在编译期间就完成了方法的绑定,因此方法调用上不再是类似于 Smalltalk 的消息发送,而是直接获取方法地址并进行调用。虽然 Objective-C 对运行时查找方法的过程进行了缓存和大量的优化,但是不可否认 Swift 的调用方式会更加迅速和高效。
+
+另外,与 Objective-C 不同,Swift 是一门强类型的语言,这意味 Swift 的运行时和代码编译期间的类型是一致的,这样编译器可以得到足够的信息来在生成中间码和机器码时进行优化。虽然都使用 LLVM 工具链进行编译,但是 Swift 的编译过程相比于 Objective-C 要多一个环节 -- 生成 Swift 中间代码 (Swift Intermediate Language,SIL)。SIL 中包含有很多根据类型假定的转换,这为之后进一步在更低层级优化提供了良好的基础,分析 SIL 也是我们探索 Swift 性能的有效方法。
+
+最后,Swift 具有良好的内存使用的策略和结构。Swift 标准库中绝大部分类型都是 `struct`,对值类型的使用范围之广,在近期的编程语言中可谓首屈一指。直接在栈上进行存储以及值类型的传递按理说对性能提升作用为负,但是 Swift 巧妙地规避了不必要的值类型复制,而仅只在必要时进行内存分配。这使得 Swift 在享受不可变性带来的便利以及避免不必要的共享状态的同时,还能够保持性能上的优秀。
+
+## 对性能进行测试
+
+《计算机程序设计艺术》和 TeX 的作者[高德纳][Donald]曾经在论文中说过:
+
+> 过早的优化是万恶之源。
+
+和很多人理解的不同,这并不是说我们不应该在项目的早期就开始进行优化,而是指我们需要弄清代码中性能真正的问题和希望达到的目标后再开始进行优化。因此,我们需要知道性能问题到底出在哪儿。对程序性能的测试一定是优化的第一步。
+
+在 Cocoa 开发中,对于性能的测试有几种常见的方式。其中最简单是直接通过输出 log 来监测某一段程序运行所消耗的时间。在 Cocoa 中我们可以使用 [`CACurrentMediaTime`][cacurrentmediatime-doc] 来获取精确的时间。这个方法将会调用 mach 底层的 `mach_absolute_time()`,它的返回是一个基于 [Mach absolute time unit][mach-time] 的数字,我们通过在方法调用前后分别获取两次时刻,并计算它们的间隔,就可以了解方法的执行时间:
+
+```swift
+let start = CACurrentMediaTime()
+
+// ...
+
+let end = CACurrentMediaTime()
+
+print("测量时间:\(end - start)")
+```
+
+为了方便使用,我们还可以将这段代码封装到一个方法中,这样我们就能在项目中需要测试性能的地方方便地使用它了:
+
+```swift
+func measure(f: ()->()) {
+ let start = CACurrentMediaTime()
+ f()
+ let end = CACurrentMediaTime()
+ print("测量时间:\(end - start)")
+}
+
+measure {
+ doSomeHeavyWork()
+}
+```
+
+`CACurrentMediaTime` 和 log 的方法适合于我们对既有代码进行探索,另一种有效的方法是使用 Instruments 的 Time Profiler 来在更高层面寻找代码的性能弱点。将程序挂载到 Time Profiler 后,每一个方法调用的耗时都将被记录。
+
+当我们寻找到需要进行优化的代码路径后,为其建立一个单元测试来持续地检测代码的性能是很好的做法。在 Xcode 中默认的测试框架 XCTest 提供了检测并汇报性能的方法:`measureBlock`。通过将测试的代码块放到 `measureBlock` 中,Xcode 在测试时就会多次运行这段代码,并统计平均耗时。更方便的是,你可以设定一个基准,Xcode 会记录每次的耗时并在性能没有达到预期时进行提醒。这保证了随着项目开发,关键的代码路径不会发生性能上的退化。
+
+```swift
+func testPerformance() {
+ measureBlock() {
+ // 需要性能测试的代码
+ }
+}
+```
+
+
+
+## 优化手段,常见误用及对策
+
+### 多线程、算法及数据结构优化
+
+在确定了需要进行性能改善的代码后,一个最根本的优化方式是在程序设计层面进行改良。在移动客户端,对于影响了 UI 流畅度的代码,我们可以将其放到后台线程进行运行。Grand Central Dispatch (GCD) 或者 `NSOperation` 可以让我们方便地在不同线程中切换,而不太需要去担心线程调度的问题。一个使用 GCD 将繁重工作放到后台线程,然后在完成后回到主线程操作 UI 的典型例子是这样的:
+
+```swift
+let queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
+ dispatch_async(queue) {
+
+ // 运行时间较长的代码,放到后台线程运行
+
+ dispatch_async(dispatch_get_main_queue()) {
+ // 结束后返回主线程操作 UI
+ }
+}
+```
+
+将工作放到其他线程虽然可以避免主线程阻塞,但它并不能减少这些代码实际的执行时间。进一步地,我们可以考虑改进算法和使用的数据结构来提高效率。根据实际项目中遇到的问题的不同,我们会有不同的解决方式,在这篇文章中,我们难以覆盖和深入去分析各种情况,所以这里我们只会提及一些共通的原则。
+
+对于重复的工作,合理地利用缓存的方式可以极大提高效率,这是在优化时可以优先考虑的方式。Cocoa 开发中 `NSCache` 是专门用来管理缓存的一个类,合理地使用和配置 `NSCache` 把开发者中从管理缓存存储和失效的工作中解放出来。关于 `NSCache` 的详细使用方法,可以参看 NSHipster 关于这方面的[文章][nscache-nshipster]以及 Apple 的[相关文档][nscache-doc]。
+
+在程序开发时,数据结构使用上的选择也是重要的一环。Swift 标准库提供了一些很基本的数据结构,比如 `Array`、`Dictionary` 和 `Set` 等。这些数据结构都是配合泛型的,在保证数据类型安全的同时,一般来说也能为我们提供足够的性能。关于这些数据的容器类型方法所对应的复杂度,Apple 都在标准库的文档或者注释中进行了标记。如果标准库所提供的类型和方法无法满足性能上的要求,或者没有符合业务需求的数据结构的话,那么考虑使用自己实现的数据结构也是可选项。
+
+如果项目中有很多数学计算方面的工作导致了效率问题的话,考虑并行计算能极大改善程序性能。iOS 和 OS X 都有针对数学或者图形计算等数字信号处理方面进行了专门优化的框架:[Accelerate.framework][accelerate-doc],利用相关的 API,我们可以轻松快速地完成很多经典的数字或者图像处理问题。因为这个框架只提供一组 C API,所以在 Swift 中直接使用会有一定困难。如果你的项目中要处理的计算相对简单的话,也可以使用 [Surge][surge],它是一个基于 Accelerate 框架的 Swift 项目,让我们能在代码里从并行计算中获得难以置信的性能提升。
+
+### 编译器优化
+
+Swift 编译器十分智能,它能在编译期间帮助我们移除不需要的代码,或者将某些方法进行内联 (inline) 处理。编译器优化的强度可以在编译时通过参数进行控制,Xcode 工程默认情况下有 Debug 和 Release 两种编译配置,在 Debug 模式下,LLVM Code Generation 和 Swift Code Generation 都不开启优化,这能保证编译速度。而在 Release 模式下,LLVM 默认使用 "Fastest, Smallest [-Os]",Swift Compiler 默认使用 "Fast [-O]",作为优化级别。我们另外还有几个额外的优化级别可以选择,优化级别越高,编译器对于源码的改动幅度和开启的优化力度也就越大,同时编译期间消耗的时间也就越多。虽然绝大部分情况下没有问题,但是仍然需要当心的是,一些优化等级采用的是激进的优化策略,而禁用了一些检查。这可能在源码很复杂的情况下导致潜在的错误。如果你使用了很高的优化级别,请再三测试 Release 和 Debug 条件下程序运行的逻辑,以防止编译器优化所带来的问题。
+
+值得一提的是,Swift 编译器有一个很有用的优化等级:"Fast, Whole Module Optimization",也即 `-O -whole-module-optimization`。在这个优化等级下,Swift 编译器将会同时考虑整个 module 中所有源码的情况,并将那些没有被继承和重载的类型和方法标记为 `final`,这将尽可能地避免动态派发的调用,或者甚至将方法进行内联处理以加速运行。开启这个额外的优化将会大幅增加编译时间,所以应该只在应用要发布的时候打开这个选项。
+
+虽然现在编译器在进行优化的时候已经足够智能了,但是在面对编写得非常复杂的情况时,很多本应实施的优化可能失效。因此保持代码的整洁、干净和简单,可以让编译器优化良好工作,以得到高效的机器码。
+
+### 尽量使用 Swift 类型
+
+为了和 Objective-C 协同工作,很多 Swift 标准库类型和对应的 Cocoa 类型是可以隐式的类型转换的,比如 `Swift.Array` 与 `NSArray`,`Swift.String` 和 `NSString` 等。虽然我们不需要在语言层面做类型转换,但是这个过程却不是免费的。在转换次数很多的时候,这往往会成为性能的瓶颈。一个常见的 Swift 和 Objective-C 混用的例子是 JSON 解析。考虑以下代码:
+
+```swift
+let jsonData: NSData = //...
+let jsonObject = try? NSJSONSerialization
+ .JSONObjectWithData(jsonData, options: []) as? [String: AnyObject]
+```
+
+这是我们日常开发中很常见的代码,使用 `NSJSONSerialization` 将数据转换为 JSON 对象后,我们得到的是一个 NSObject 对象。在 Swift 中使用时,我们一般会先将其转换为 `[String: AnyObject]`,这个转换在一次性处理成千上万条 JSON 数据时会带来严重的性能退化。Swift 3 中我们可能可以基于 Swift 的 Foundation 框架来解决这个问题,但是现在,如果存在这样的情况,一种处理方式是避免使用 Swift 的字典类型,而使用 `NSDictionary`。另外,适当地使用 lazy 加载的方法,也是避免一次性进行过多的类型转换的好思路。
+
+尽可能避免混合地使用 Swift 类型和 `NSObject` 子类,会对性能的提高有所帮助。
+
+### 避免无意义的 log,保持好的编码习惯
+
+在调试程序时,很多开发者喜欢用输出 log 的方式对代码的运行进行追踪,帮助理解。Swift 编译器并不会帮我们将 `print` 或者 `debugPrint` 删去,在最终 app 中它们会把内容输出到终端,造成性能的损失。我们当然可以在发布时用查找的方式将所有这些 log 输出语句删除或者注释掉,但是更好的方法是通过添加条件编译来将这些语句排除在 Release 版本外。在 Xcode 的 Build Setting 中,在 **Other Swift flags** 的 Debug 栏中加入 `-D DEBUG` 即可加入一个编译标识。
+
+
+
+之后我们就可以通过将 `print` 或者 `debugPrint` 包装一下:
+
+```swift
+func dPrint(item: Any) {
+ #if DEBUG
+ print(item)
+ #endif
+}
+```
+
+这样,在 Release 版本中,`dPrint` 将会是一个空方法,所有对这个方法的调用都会被编译器剔除掉。需要注意的是,在这种封装下,如果你传入的 `items` 是一个表达式而不是直接的变量的话,这个表达式还是会被先执行求值的。如果这对性能也产生了可测的影响的话,我们最好用 `@autoclosure` 修饰参数来重新包装 `print`。这可以将求值运行推迟到方法内部,这样在 Release 时这个求值也会被一并去掉:
+
+```swift
+func dPrint(@autoclosure item: () -> Any) {
+ #if DEBUG
+ print(item())
+ #endif
+}
+
+dPrint(resultFromHeavyWork())
+// Release 版本中 resultFromHeavyWork() 不会被执行
+```
+
+## 小结
+
+Swift 还是一门很新的语言,并且处于高速发展中。因为现在 Swift 只用于 Cocoa 开发,因此它和 Cocoa 框架还有着千丝万缕的联系。很多时候由于这些原因,我们对于 Swift 性能的评估并不公正。这门语言本身设计就是以高性能为考量的,而随着 Swift 的开源和进一步的进化,以及配套框架的全面重写,相信在语言层面上我们能获得更好的性能和编译器的支持。
+
+最好的优化就是不用优化。在软件开发中,保证书写正确简洁的代码,在项目开始阶段就注意可能存在的性能缺陷,将可扩展性的考虑纳入软件构建中,按照实际需求进行优化,不要陷入为了优化而优化的怪圈,这些往往都可以让我们避免额外的优化时间,让我们的工作得更加愉快。
+
+### 参考
+
+- [Swift Intermediate Language][sil]
+- [NSCache - NSHipster][nscache-nshipster]
+- [NSCache 文档][nscache-doc]
+- [Surge][surge]
+
+[Donald]: https://zh.wikipedia.org/wiki/高德纳
+[cacurrentmediatime-doc]: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreAnimation_functions/index.html#//apple_ref/c/func/CACurrentMediaTime
+[mach-time]: https://developer.apple.com/library/mac/qa/qa1398/_index.html
+[sil]: http://llvm.org/devmtg/2015-10/slides/GroffLattner-SILHighLevelIR.pdf
+[nscache-nshipster]: http://nshipster.com/nscache/
+[nscache-doc]: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/NSCache_Class/
+[accelerate-doc]: https://developer.apple.com/library/tvos/documentation/Accelerate/Reference/AccelerateFWRef/index.html
+[surge]: https://github.com/mattt/Surge
diff --git a/_sass/_base.scss b/_sass/_base.scss
new file mode 100644
index 00000000..0883c3cd
--- /dev/null
+++ b/_sass/_base.scss
@@ -0,0 +1,206 @@
+/**
+ * Reset some basic elements
+ */
+body, h1, h2, h3, h4, h5, h6,
+p, blockquote, pre, hr,
+dl, dd, ol, ul, figure {
+ margin: 0;
+ padding: 0;
+}
+
+
+
+/**
+ * Basic styling
+ */
+body {
+ font: $base-font-weight #{$base-font-size}/#{$base-line-height} $base-font-family;
+ color: $text-color;
+ background-color: $background-color;
+ -webkit-text-size-adjust: 100%;
+ -webkit-font-feature-settings: "kern" 1;
+ -moz-font-feature-settings: "kern" 1;
+ -o-font-feature-settings: "kern" 1;
+ font-feature-settings: "kern" 1;
+ font-kerning: normal;
+}
+
+
+
+/**
+ * Set `margin-bottom` to maintain vertical rhythm
+ */
+h1, h2, h3, h4, h5, h6,
+p, blockquote, pre,
+ul, ol, dl, figure,
+%vertical-rhythm {
+ margin-bottom: $spacing-unit / 2;
+}
+
+
+
+/**
+ * Images
+ */
+img {
+ max-width: 100%;
+ vertical-align: middle;
+}
+
+
+
+/**
+ * Figures
+ */
+figure > img {
+ display: block;
+}
+
+figcaption {
+ font-size: $small-font-size;
+}
+
+
+
+/**
+ * Lists
+ */
+ul, ol {
+ margin-left: $spacing-unit;
+}
+
+li {
+ > ul,
+ > ol {
+ margin-bottom: 0;
+ }
+}
+
+
+
+/**
+ * Headings
+ */
+h1, h2, h3, h4, h5, h6 {
+ font-weight: $base-font-weight;
+}
+
+
+
+/**
+ * Links
+ */
+a {
+ color: $brand-color;
+ text-decoration: none;
+
+ &:visited {
+ color: darken($brand-color, 15%);
+ }
+
+ &:hover {
+ color: $text-color;
+ text-decoration: underline;
+ }
+}
+
+
+
+/**
+ * Blockquotes
+ */
+blockquote {
+ color: $grey-color;
+ border-left: 4px solid $grey-color-light;
+ padding-left: $spacing-unit / 2;
+ font-size: 18px;
+ letter-spacing: -1px;
+ font-style: italic;
+
+ > :last-child {
+ margin-bottom: 0;
+ }
+}
+
+
+
+/**
+ * Code formatting
+ */
+pre,
+code {
+ font-size: 15px;
+ border: 1px solid $grey-color-light;
+ border-radius: 3px;
+ background-color: #eef;
+}
+
+code {
+ padding: 1px 5px;
+}
+
+pre {
+ padding: 8px 12px;
+ overflow-x: auto;
+
+ > code {
+ border: 0;
+ padding-right: 0;
+ padding-left: 0;
+ }
+}
+
+
+
+/**
+ * Wrapper
+ */
+.wrapper {
+ max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit} * 2));
+ max-width: calc(#{$content-width} - (#{$spacing-unit} * 2));
+ margin-right: auto;
+ margin-left: auto;
+ padding-right: $spacing-unit;
+ padding-left: $spacing-unit;
+ @extend %clearfix;
+
+ @include media-query($on-laptop) {
+ max-width: -webkit-calc(#{$content-width} - (#{$spacing-unit}));
+ max-width: calc(#{$content-width} - (#{$spacing-unit}));
+ padding-right: $spacing-unit / 2;
+ padding-left: $spacing-unit / 2;
+ }
+}
+
+
+
+/**
+ * Clearfix
+ */
+%clearfix {
+
+ &:after {
+ content: "";
+ display: table;
+ clear: both;
+ }
+}
+
+
+
+/**
+ * Icons
+ */
+.icon {
+
+ > svg {
+ display: inline-block;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+
+ path {
+ fill: $grey-color;
+ }
+ }
+}
diff --git a/_sass/_layout.scss b/_sass/_layout.scss
new file mode 100644
index 00000000..9cbfddef
--- /dev/null
+++ b/_sass/_layout.scss
@@ -0,0 +1,242 @@
+/**
+ * Site header
+ */
+.site-header {
+ border-top: 5px solid $grey-color-dark;
+ border-bottom: 1px solid $grey-color-light;
+ min-height: 56px;
+
+ // Positioning context for the mobile navigation icon
+ position: relative;
+}
+
+.site-title {
+ font-size: 26px;
+ font-weight: 300;
+ line-height: 56px;
+ letter-spacing: -1px;
+ margin-bottom: 0;
+ float: left;
+
+ &,
+ &:visited {
+ color: $grey-color-dark;
+ }
+}
+
+.site-nav {
+ float: right;
+ line-height: 56px;
+
+ .menu-icon {
+ display: none;
+ }
+
+ .page-link {
+ color: $text-color;
+ line-height: $base-line-height;
+
+ // Gaps between nav items, but not on the last one
+ &:not(:last-child) {
+ margin-right: 20px;
+ }
+ }
+
+ @include media-query($on-palm) {
+ position: absolute;
+ top: 9px;
+ right: $spacing-unit / 2;
+ background-color: $background-color;
+ border: 1px solid $grey-color-light;
+ border-radius: 5px;
+ text-align: right;
+
+ .menu-icon {
+ display: block;
+ float: right;
+ width: 36px;
+ height: 26px;
+ line-height: 0;
+ padding-top: 10px;
+ text-align: center;
+
+ > svg {
+ width: 18px;
+ height: 15px;
+
+ path {
+ fill: $grey-color-dark;
+ }
+ }
+ }
+
+ .trigger {
+ clear: both;
+ display: none;
+ }
+
+ &:hover .trigger {
+ display: block;
+ padding-bottom: 5px;
+ }
+
+ .page-link {
+ display: block;
+ padding: 5px 10px;
+
+ &:not(:last-child) {
+ margin-right: 0;
+ }
+ margin-left: 20px;
+ }
+ }
+}
+
+
+
+/**
+ * Site footer
+ */
+.site-footer {
+ border-top: 1px solid $grey-color-light;
+ padding: $spacing-unit 0;
+}
+
+.footer-heading {
+ font-size: 18px;
+ margin-bottom: $spacing-unit / 2;
+}
+
+.contact-list,
+.social-media-list {
+ list-style: none;
+ margin-left: 0;
+}
+
+.footer-col-wrapper {
+ font-size: 15px;
+ color: $grey-color;
+ margin-left: -$spacing-unit / 2;
+ @extend %clearfix;
+}
+
+.footer-col {
+ float: left;
+ margin-bottom: $spacing-unit / 2;
+ padding-left: $spacing-unit / 2;
+}
+
+.footer-col-1 {
+ width: -webkit-calc(35% - (#{$spacing-unit} / 2));
+ width: calc(35% - (#{$spacing-unit} / 2));
+}
+
+.footer-col-2 {
+ width: -webkit-calc(20% - (#{$spacing-unit} / 2));
+ width: calc(20% - (#{$spacing-unit} / 2));
+}
+
+.footer-col-3 {
+ width: -webkit-calc(45% - (#{$spacing-unit} / 2));
+ width: calc(45% - (#{$spacing-unit} / 2));
+}
+
+@include media-query($on-laptop) {
+ .footer-col-1,
+ .footer-col-2 {
+ width: -webkit-calc(50% - (#{$spacing-unit} / 2));
+ width: calc(50% - (#{$spacing-unit} / 2));
+ }
+
+ .footer-col-3 {
+ width: -webkit-calc(100% - (#{$spacing-unit} / 2));
+ width: calc(100% - (#{$spacing-unit} / 2));
+ }
+}
+
+@include media-query($on-palm) {
+ .footer-col {
+ float: none;
+ width: -webkit-calc(100% - (#{$spacing-unit} / 2));
+ width: calc(100% - (#{$spacing-unit} / 2));
+ }
+}
+
+
+
+/**
+ * Page content
+ */
+.page-content {
+ padding: $spacing-unit 0;
+}
+
+.page-heading {
+ font-size: 20px;
+}
+
+.post-list {
+ margin-left: 0;
+ list-style: none;
+
+ > li {
+ margin-bottom: $spacing-unit;
+ }
+}
+
+.post-meta {
+ font-size: $small-font-size;
+ color: $grey-color;
+}
+
+.post-link {
+ display: block;
+ font-size: 24px;
+}
+
+
+
+/**
+ * Posts
+ */
+.post-header {
+ margin-bottom: $spacing-unit;
+}
+
+.post-title {
+ font-size: 42px;
+ letter-spacing: -1px;
+ line-height: 1;
+
+ @include media-query($on-laptop) {
+ font-size: 36px;
+ }
+}
+
+.post-content {
+ margin-bottom: $spacing-unit;
+
+ h2 {
+ font-size: 32px;
+
+ @include media-query($on-laptop) {
+ font-size: 28px;
+ }
+ }
+
+ h3 {
+ font-size: 26px;
+
+ @include media-query($on-laptop) {
+ font-size: 22px;
+ }
+ }
+
+ h4 {
+ font-size: 20px;
+
+ @include media-query($on-laptop) {
+ font-size: 18px;
+ }
+ }
+}
diff --git a/_sass/bourbon/_bourbon-deprecated-upcoming.scss b/_sass/bourbon/_bourbon-deprecated-upcoming.scss
new file mode 100644
index 00000000..f946b3b4
--- /dev/null
+++ b/_sass/bourbon/_bourbon-deprecated-upcoming.scss
@@ -0,0 +1,8 @@
+//************************************************************************//
+// These mixins/functions are deprecated
+// They will be removed in the next MAJOR version release
+//************************************************************************//
+@mixin inline-block {
+ display: inline-block;
+ @warn "inline-block mixin is deprecated and will be removed in the next major version release";
+}
diff --git a/_sass/bourbon/_bourbon.scss b/_sass/bourbon/_bourbon.scss
new file mode 100644
index 00000000..11d52d7c
--- /dev/null
+++ b/_sass/bourbon/_bourbon.scss
@@ -0,0 +1,78 @@
+// Settings
+@import "settings/prefixer";
+@import "settings/px-to-em";
+
+// Custom Helpers
+@import "helpers/convert-units";
+@import "helpers/gradient-positions-parser";
+@import "helpers/is-num";
+@import "helpers/linear-angle-parser";
+@import "helpers/linear-gradient-parser";
+@import "helpers/linear-positions-parser";
+@import "helpers/linear-side-corner-parser";
+@import "helpers/radial-arg-parser";
+@import "helpers/radial-positions-parser";
+@import "helpers/radial-gradient-parser";
+@import "helpers/render-gradients";
+@import "helpers/shape-size-stripper";
+@import "helpers/str-to-num";
+
+// Custom Functions
+@import "functions/assign";
+@import "functions/color-lightness";
+@import "functions/flex-grid";
+@import "functions/golden-ratio";
+@import "functions/grid-width";
+@import "functions/modular-scale";
+@import "functions/px-to-em";
+@import "functions/px-to-rem";
+@import "functions/strip-units";
+@import "functions/tint-shade";
+@import "functions/transition-property-name";
+@import "functions/unpack";
+
+// CSS3 Mixins
+@import "css3/animation";
+@import "css3/appearance";
+@import "css3/backface-visibility";
+@import "css3/background";
+@import "css3/background-image";
+@import "css3/border-image";
+@import "css3/border-radius";
+@import "css3/box-sizing";
+@import "css3/calc";
+@import "css3/columns";
+@import "css3/filter";
+@import "css3/flex-box";
+@import "css3/font-face";
+@import "css3/font-feature-settings";
+@import "css3/hyphens";
+@import "css3/hidpi-media-query";
+@import "css3/image-rendering";
+@import "css3/keyframes";
+@import "css3/linear-gradient";
+@import "css3/perspective";
+@import "css3/radial-gradient";
+@import "css3/transform";
+@import "css3/transition";
+@import "css3/user-select";
+@import "css3/placeholder";
+
+// Addons & other mixins
+@import "addons/button";
+@import "addons/clearfix";
+@import "addons/directional-values";
+@import "addons/ellipsis";
+@import "addons/font-family";
+@import "addons/hide-text";
+@import "addons/html5-input-types";
+@import "addons/position";
+@import "addons/prefixer";
+@import "addons/retina-image";
+@import "addons/size";
+@import "addons/timing-functions";
+@import "addons/triangle";
+@import "addons/word-wrap";
+
+// Soon to be deprecated Mixins
+@import "bourbon-deprecated-upcoming";
diff --git a/_sass/bourbon/addons/_button.scss b/_sass/bourbon/addons/_button.scss
new file mode 100644
index 00000000..14a89e48
--- /dev/null
+++ b/_sass/bourbon/addons/_button.scss
@@ -0,0 +1,374 @@
+@mixin button ($style: simple, $base-color: #4294f0, $text-size: inherit, $padding: 7px 18px) {
+
+ @if type-of($style) == string and type-of($base-color) == color {
+ @include buttonstyle($style, $base-color, $text-size, $padding);
+ }
+
+ @if type-of($style) == string and type-of($base-color) == number {
+ $padding: $text-size;
+ $text-size: $base-color;
+ $base-color: #4294f0;
+
+ @if $padding == inherit {
+ $padding: 7px 18px;
+ }
+
+ @include buttonstyle($style, $base-color, $text-size, $padding);
+ }
+
+ @if type-of($style) == color and type-of($base-color) == color {
+ $base-color: $style;
+ $style: simple;
+ @include buttonstyle($style, $base-color, $text-size, $padding);
+ }
+
+ @if type-of($style) == color and type-of($base-color) == number {
+ $padding: $text-size;
+ $text-size: $base-color;
+ $base-color: $style;
+ $style: simple;
+
+ @if $padding == inherit {
+ $padding: 7px 18px;
+ }
+
+ @include buttonstyle($style, $base-color, $text-size, $padding);
+ }
+
+ @if type-of($style) == number {
+ $padding: $base-color;
+ $text-size: $style;
+ $base-color: #4294f0;
+ $style: simple;
+
+ @if $padding == #4294f0 {
+ $padding: 7px 18px;
+ }
+
+ @include buttonstyle($style, $base-color, $text-size, $padding);
+ }
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
+}
+
+
+// Selector Style Button
+//************************************************************************//
+@mixin buttonstyle($type, $b-color, $t-size, $pad) {
+ // Grayscale button
+ @if $type == simple and $b-color == grayscale($b-color) {
+ @include simple($b-color, true, $t-size, $pad);
+ }
+
+ @if $type == shiny and $b-color == grayscale($b-color) {
+ @include shiny($b-color, true, $t-size, $pad);
+ }
+
+ @if $type == pill and $b-color == grayscale($b-color) {
+ @include pill($b-color, true, $t-size, $pad);
+ }
+
+ @if $type == flat and $b-color == grayscale($b-color) {
+ @include flat($b-color, true, $t-size, $pad);
+ }
+
+ // Colored button
+ @if $type == simple {
+ @include simple($b-color, false, $t-size, $pad);
+ }
+
+ @else if $type == shiny {
+ @include shiny($b-color, false, $t-size, $pad);
+ }
+
+ @else if $type == pill {
+ @include pill($b-color, false, $t-size, $pad);
+ }
+
+ @else if $type == flat {
+ @include flat($b-color, false, $t-size, $pad);
+ }
+}
+
+
+// Simple Button
+//************************************************************************//
+@mixin simple($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
+ $color: hsl(0, 0, 100%);
+ $border: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
+ $inset-shadow: adjust-color($base-color, $saturation: -8%, $lightness: 15%);
+ $stop-gradient: adjust-color($base-color, $saturation: 9%, $lightness: -11%);
+ $text-shadow: adjust-color($base-color, $saturation: 15%, $lightness: -18%);
+
+ @if is-light($base-color) {
+ $color: hsl(0, 0, 20%);
+ $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
+ }
+
+ @if $grayscale == true {
+ $border: grayscale($border);
+ $inset-shadow: grayscale($inset-shadow);
+ $stop-gradient: grayscale($stop-gradient);
+ $text-shadow: grayscale($text-shadow);
+ }
+
+ border: 1px solid $border;
+ border-radius: 3px;
+ box-shadow: inset 0 1px 0 0 $inset-shadow;
+ color: $color;
+ display: inline-block;
+ font-size: $textsize;
+ font-weight: bold;
+ @include linear-gradient ($base-color, $stop-gradient);
+ padding: $padding;
+ text-decoration: none;
+ text-shadow: 0 1px 0 $text-shadow;
+ background-clip: padding-box;
+
+ &:hover:not(:disabled) {
+ $base-color-hover: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
+ $inset-shadow-hover: adjust-color($base-color, $saturation: -7%, $lightness: 5%);
+ $stop-gradient-hover: adjust-color($base-color, $saturation: 8%, $lightness: -14%);
+
+ @if $grayscale == true {
+ $base-color-hover: grayscale($base-color-hover);
+ $inset-shadow-hover: grayscale($inset-shadow-hover);
+ $stop-gradient-hover: grayscale($stop-gradient-hover);
+ }
+
+ box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
+ cursor: pointer;
+ @include linear-gradient ($base-color-hover, $stop-gradient-hover);
+ }
+
+ &:active:not(:disabled),
+ &:focus:not(:disabled) {
+ $border-active: adjust-color($base-color, $saturation: 9%, $lightness: -14%);
+ $inset-shadow-active: adjust-color($base-color, $saturation: 7%, $lightness: -17%);
+
+ @if $grayscale == true {
+ $border-active: grayscale($border-active);
+ $inset-shadow-active: grayscale($inset-shadow-active);
+ }
+
+ border: 1px solid $border-active;
+ box-shadow: inset 0 0 8px 4px $inset-shadow-active, inset 0 0 8px 4px $inset-shadow-active;
+ }
+}
+
+
+// Shiny Button
+//************************************************************************//
+@mixin shiny($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
+ $color: hsl(0, 0, 100%);
+ $border: adjust-color($base-color, $red: -117, $green: -111, $blue: -81);
+ $border-bottom: adjust-color($base-color, $red: -126, $green: -127, $blue: -122);
+ $fourth-stop: adjust-color($base-color, $red: -79, $green: -70, $blue: -46);
+ $inset-shadow: adjust-color($base-color, $red: 37, $green: 29, $blue: 12);
+ $second-stop: adjust-color($base-color, $red: -56, $green: -50, $blue: -33);
+ $text-shadow: adjust-color($base-color, $red: -140, $green: -141, $blue: -114);
+ $third-stop: adjust-color($base-color, $red: -86, $green: -75, $blue: -48);
+
+ @if is-light($base-color) {
+ $color: hsl(0, 0, 20%);
+ $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
+ }
+
+ @if $grayscale == true {
+ $border: grayscale($border);
+ $border-bottom: grayscale($border-bottom);
+ $fourth-stop: grayscale($fourth-stop);
+ $inset-shadow: grayscale($inset-shadow);
+ $second-stop: grayscale($second-stop);
+ $text-shadow: grayscale($text-shadow);
+ $third-stop: grayscale($third-stop);
+ }
+
+ border: 1px solid $border;
+ border-bottom: 1px solid $border-bottom;
+ border-radius: 5px;
+ box-shadow: inset 0 1px 0 0 $inset-shadow;
+ color: $color;
+ display: inline-block;
+ font-size: $textsize;
+ font-weight: bold;
+ @include linear-gradient(top, $base-color 0%, $second-stop 50%, $third-stop 50%, $fourth-stop 100%);
+ padding: $padding;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 0 -1px 1px $text-shadow;
+
+ &:hover:not(:disabled) {
+ $first-stop-hover: adjust-color($base-color, $red: -13, $green: -15, $blue: -18);
+ $second-stop-hover: adjust-color($base-color, $red: -66, $green: -62, $blue: -51);
+ $third-stop-hover: adjust-color($base-color, $red: -93, $green: -85, $blue: -66);
+ $fourth-stop-hover: adjust-color($base-color, $red: -86, $green: -80, $blue: -63);
+
+ @if $grayscale == true {
+ $first-stop-hover: grayscale($first-stop-hover);
+ $second-stop-hover: grayscale($second-stop-hover);
+ $third-stop-hover: grayscale($third-stop-hover);
+ $fourth-stop-hover: grayscale($fourth-stop-hover);
+ }
+
+ cursor: pointer;
+ @include linear-gradient(top, $first-stop-hover 0%,
+ $second-stop-hover 50%,
+ $third-stop-hover 50%,
+ $fourth-stop-hover 100%);
+ }
+
+ &:active:not(:disabled),
+ &:focus:not(:disabled) {
+ $inset-shadow-active: adjust-color($base-color, $red: -111, $green: -116, $blue: -122);
+
+ @if $grayscale == true {
+ $inset-shadow-active: grayscale($inset-shadow-active);
+ }
+
+ box-shadow: inset 0 0 20px 0 $inset-shadow-active;
+ }
+}
+
+
+// Pill Button
+//************************************************************************//
+@mixin pill($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
+ $color: hsl(0, 0, 100%);
+ $border-bottom: adjust-color($base-color, $hue: 8, $saturation: -11%, $lightness: -26%);
+ $border-sides: adjust-color($base-color, $hue: 4, $saturation: -21%, $lightness: -21%);
+ $border-top: adjust-color($base-color, $hue: -1, $saturation: -30%, $lightness: -15%);
+ $inset-shadow: adjust-color($base-color, $hue: -1, $saturation: -1%, $lightness: 7%);
+ $stop-gradient: adjust-color($base-color, $hue: 8, $saturation: 14%, $lightness: -10%);
+ $text-shadow: adjust-color($base-color, $hue: 5, $saturation: -19%, $lightness: -15%);
+
+ @if is-light($base-color) {
+ $color: hsl(0, 0, 20%);
+ $text-shadow: adjust-color($base-color, $saturation: 10%, $lightness: 4%);
+ }
+
+ @if $grayscale == true {
+ $border-bottom: grayscale($border-bottom);
+ $border-sides: grayscale($border-sides);
+ $border-top: grayscale($border-top);
+ $inset-shadow: grayscale($inset-shadow);
+ $stop-gradient: grayscale($stop-gradient);
+ $text-shadow: grayscale($text-shadow);
+ }
+
+ border: 1px solid $border-top;
+ border-color: $border-top $border-sides $border-bottom;
+ border-radius: 16px;
+ box-shadow: inset 0 1px 0 0 $inset-shadow;
+ color: $color;
+ display: inline-block;
+ font-size: $textsize;
+ font-weight: normal;
+ line-height: 1;
+ @include linear-gradient ($base-color, $stop-gradient);
+ padding: $padding;
+ text-align: center;
+ text-decoration: none;
+ text-shadow: 0 -1px 1px $text-shadow;
+ background-clip: padding-box;
+
+ &:hover:not(:disabled) {
+ $base-color-hover: adjust-color($base-color, $lightness: -4.5%);
+ $border-bottom: adjust-color($base-color, $hue: 8, $saturation: 13.5%, $lightness: -32%);
+ $border-sides: adjust-color($base-color, $hue: 4, $saturation: -2%, $lightness: -27%);
+ $border-top: adjust-color($base-color, $hue: -1, $saturation: -17%, $lightness: -21%);
+ $inset-shadow-hover: adjust-color($base-color, $saturation: -1%, $lightness: 3%);
+ $stop-gradient-hover: adjust-color($base-color, $hue: 8, $saturation: -4%, $lightness: -15.5%);
+ $text-shadow-hover: adjust-color($base-color, $hue: 5, $saturation: -5%, $lightness: -22%);
+
+ @if $grayscale == true {
+ $base-color-hover: grayscale($base-color-hover);
+ $border-bottom: grayscale($border-bottom);
+ $border-sides: grayscale($border-sides);
+ $border-top: grayscale($border-top);
+ $inset-shadow-hover: grayscale($inset-shadow-hover);
+ $stop-gradient-hover: grayscale($stop-gradient-hover);
+ $text-shadow-hover: grayscale($text-shadow-hover);
+ }
+
+ border: 1px solid $border-top;
+ border-color: $border-top $border-sides $border-bottom;
+ box-shadow: inset 0 1px 0 0 $inset-shadow-hover;
+ cursor: pointer;
+ @include linear-gradient ($base-color-hover, $stop-gradient-hover);
+ text-shadow: 0 -1px 1px $text-shadow-hover;
+ background-clip: padding-box;
+ }
+
+ &:active:not(:disabled),
+ &:focus:not(:disabled) {
+ $active-color: adjust-color($base-color, $hue: 4, $saturation: -12%, $lightness: -10%);
+ $border-active: adjust-color($base-color, $hue: 6, $saturation: -2.5%, $lightness: -30%);
+ $border-bottom-active: adjust-color($base-color, $hue: 11, $saturation: 6%, $lightness: -31%);
+ $inset-shadow-active: adjust-color($base-color, $hue: 9, $saturation: 2%, $lightness: -21.5%);
+ $text-shadow-active: adjust-color($base-color, $hue: 5, $saturation: -12%, $lightness: -21.5%);
+
+ @if $grayscale == true {
+ $active-color: grayscale($active-color);
+ $border-active: grayscale($border-active);
+ $border-bottom-active: grayscale($border-bottom-active);
+ $inset-shadow-active: grayscale($inset-shadow-active);
+ $text-shadow-active: grayscale($text-shadow-active);
+ }
+
+ background: $active-color;
+ border: 1px solid $border-active;
+ border-bottom: 1px solid $border-bottom-active;
+ box-shadow: inset 0 0 6px 3px $inset-shadow-active;
+ text-shadow: 0 -1px 1px $text-shadow-active;
+ }
+}
+
+
+
+// Flat Button
+//************************************************************************//
+@mixin flat($base-color, $grayscale: false, $textsize: inherit, $padding: 7px 18px) {
+ $color: hsl(0, 0, 100%);
+
+ @if is-light($base-color) {
+ $color: hsl(0, 0, 20%);
+ }
+
+ background-color: $base-color;
+ border-radius: 3px;
+ border: none;
+ color: $color;
+ display: inline-block;
+ font-size: inherit;
+ font-weight: bold;
+ padding: 7px 18px;
+ text-decoration: none;
+ background-clip: padding-box;
+
+ &:hover:not(:disabled){
+ $base-color-hover: adjust-color($base-color, $saturation: 4%, $lightness: 5%);
+
+ @if $grayscale == true {
+ $base-color-hover: grayscale($base-color-hover);
+ }
+
+ background-color: $base-color-hover;
+ cursor: pointer;
+ }
+
+ &:active:not(:disabled),
+ &:focus:not(:disabled) {
+ $base-color-active: adjust-color($base-color, $saturation: -4%, $lightness: -5%);
+
+ @if $grayscale == true {
+ $base-color-active: grayscale($base-color-active);
+ }
+
+ background-color: $base-color-active;
+ cursor: pointer;
+ }
+}
diff --git a/_sass/bourbon/addons/_clearfix.scss b/_sass/bourbon/addons/_clearfix.scss
new file mode 100644
index 00000000..783cfbc7
--- /dev/null
+++ b/_sass/bourbon/addons/_clearfix.scss
@@ -0,0 +1,23 @@
+// Modern micro clearfix provides an easy way to contain floats without adding additional markup.
+//
+// Example usage:
+//
+// // Contain all floats within .wrapper
+// .wrapper {
+// @include clearfix;
+// .content,
+// .sidebar {
+// float : left;
+// }
+// }
+
+@mixin clearfix {
+ &:after {
+ content:"";
+ display:table;
+ clear:both;
+ }
+}
+
+// Acknowledgements
+// Beat *that* clearfix: [Thierry Koblentz](http://www.css-101.org/articles/clearfix/latest-new-clearfix-so-far.php)
diff --git a/_sass/bourbon/addons/_directional-values.scss b/_sass/bourbon/addons/_directional-values.scss
new file mode 100644
index 00000000..742f1031
--- /dev/null
+++ b/_sass/bourbon/addons/_directional-values.scss
@@ -0,0 +1,111 @@
+// directional-property mixins are shorthands
+// for writing properties like the following
+//
+// @include margin(null 0 10px);
+// ------
+// margin-right: 0;
+// margin-bottom: 10px;
+// margin-left: 0;
+//
+// - or -
+//
+// @include border-style(dotted null);
+// ------
+// border-top-style: dotted;
+// border-bottom-style: dotted;
+//
+// ------
+//
+// Note: You can also use false instead of null
+
+@function collapse-directionals($vals) {
+ $output: null;
+
+ $A: nth( $vals, 1 );
+ $B: if( length($vals) < 2, $A, nth($vals, 2));
+ $C: if( length($vals) < 3, $A, nth($vals, 3));
+ $D: if( length($vals) < 2, $A, nth($vals, if( length($vals) < 4, 2, 4) ));
+
+ @if $A == 0 { $A: 0 }
+ @if $B == 0 { $B: 0 }
+ @if $C == 0 { $C: 0 }
+ @if $D == 0 { $D: 0 }
+
+ @if $A == $B and $A == $C and $A == $D { $output: $A }
+ @else if $A == $C and $B == $D { $output: $A $B }
+ @else if $B == $D { $output: $A $B $C }
+ @else { $output: $A $B $C $D }
+
+ @return $output;
+}
+
+@function contains-falsy($list) {
+ @each $item in $list {
+ @if not $item {
+ @return true;
+ }
+ }
+
+ @return false;
+}
+
+@mixin directional-property($pre, $suf, $vals) {
+ // Property Names
+ $top: $pre + "-top" + if($suf, "-#{$suf}", "");
+ $bottom: $pre + "-bottom" + if($suf, "-#{$suf}", "");
+ $left: $pre + "-left" + if($suf, "-#{$suf}", "");
+ $right: $pre + "-right" + if($suf, "-#{$suf}", "");
+ $all: $pre + if($suf, "-#{$suf}", "");
+
+ $vals: collapse-directionals($vals);
+
+ @if contains-falsy($vals) {
+ @if nth($vals, 1) { #{$top}: nth($vals, 1); }
+
+ @if length($vals) == 1 {
+ @if nth($vals, 1) { #{$right}: nth($vals, 1); }
+ } @else {
+ @if nth($vals, 2) { #{$right}: nth($vals, 2); }
+ }
+
+ // prop: top/bottom right/left
+ @if length($vals) == 2 {
+ @if nth($vals, 1) { #{$bottom}: nth($vals, 1); }
+ @if nth($vals, 2) { #{$left}: nth($vals, 2); }
+
+ // prop: top right/left bottom
+ } @else if length($vals) == 3 {
+ @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
+ @if nth($vals, 2) { #{$left}: nth($vals, 2); }
+
+ // prop: top right bottom left
+ } @else if length($vals) == 4 {
+ @if nth($vals, 3) { #{$bottom}: nth($vals, 3); }
+ @if nth($vals, 4) { #{$left}: nth($vals, 4); }
+ }
+
+ // prop: top/right/bottom/left
+ } @else {
+ #{$all}: $vals;
+ }
+}
+
+@mixin margin($vals...) {
+ @include directional-property(margin, false, $vals...);
+}
+
+@mixin padding($vals...) {
+ @include directional-property(padding, false, $vals...);
+}
+
+@mixin border-style($vals...) {
+ @include directional-property(border, style, $vals...);
+}
+
+@mixin border-color($vals...) {
+ @include directional-property(border, color, $vals...);
+}
+
+@mixin border-width($vals...) {
+ @include directional-property(border, width, $vals...);
+}
diff --git a/_sass/bourbon/addons/_ellipsis.scss b/_sass/bourbon/addons/_ellipsis.scss
new file mode 100644
index 00000000..a8ea2a4a
--- /dev/null
+++ b/_sass/bourbon/addons/_ellipsis.scss
@@ -0,0 +1,7 @@
+@mixin ellipsis($width: 100%) {
+ display: inline-block;
+ max-width: $width;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/_sass/bourbon/addons/_font-family.scss b/_sass/bourbon/addons/_font-family.scss
new file mode 100644
index 00000000..31f5d9ca
--- /dev/null
+++ b/_sass/bourbon/addons/_font-family.scss
@@ -0,0 +1,5 @@
+$georgia: Georgia, Cambria, "Times New Roman", Times, serif;
+$helvetica: "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif;
+$lucida-grande: "Lucida Grande", Tahoma, Verdana, Arial, sans-serif;
+$monospace: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
+$verdana: Verdana, Geneva, sans-serif;
diff --git a/_sass/bourbon/addons/_hide-text.scss b/_sass/bourbon/addons/_hide-text.scss
new file mode 100644
index 00000000..fc794381
--- /dev/null
+++ b/_sass/bourbon/addons/_hide-text.scss
@@ -0,0 +1,10 @@
+@mixin hide-text {
+ overflow: hidden;
+
+ &:before {
+ content: "";
+ display: block;
+ width: 0;
+ height: 100%;
+ }
+}
diff --git a/_sass/bourbon/addons/_html5-input-types.scss b/_sass/bourbon/addons/_html5-input-types.scss
new file mode 100644
index 00000000..9e9324ae
--- /dev/null
+++ b/_sass/bourbon/addons/_html5-input-types.scss
@@ -0,0 +1,86 @@
+//************************************************************************//
+// Generate a variable ($all-text-inputs) with a list of all html5
+// input types that have a text-based input, excluding textarea.
+// http://diveintohtml5.org/forms.html
+//************************************************************************//
+$inputs-list: 'input[type="email"]',
+ 'input[type="number"]',
+ 'input[type="password"]',
+ 'input[type="search"]',
+ 'input[type="tel"]',
+ 'input[type="text"]',
+ 'input[type="url"]',
+
+ // Webkit & Gecko may change the display of these in the future
+ 'input[type="color"]',
+ 'input[type="date"]',
+ 'input[type="datetime"]',
+ 'input[type="datetime-local"]',
+ 'input[type="month"]',
+ 'input[type="time"]',
+ 'input[type="week"]';
+
+// Bare inputs
+//************************************************************************//
+$all-text-inputs: assign-inputs($inputs-list);
+
+// Hover Pseudo-class
+//************************************************************************//
+$all-text-inputs-hover: assign-inputs($inputs-list, hover);
+
+// Focus Pseudo-class
+//************************************************************************//
+$all-text-inputs-focus: assign-inputs($inputs-list, focus);
+
+
+
+// You must use interpolation on the variable:
+// #{$all-text-inputs}
+// #{$all-text-inputs-hover}
+// #{$all-text-inputs-focus}
+
+// Example
+//************************************************************************//
+// #{$all-text-inputs}, textarea {
+// border: 1px solid red;
+// }
+
+
+
+//************************************************************************//
+// Generate a variable ($all-button-inputs) with a list of all html5
+// input types that have a button-based input, excluding button.
+//************************************************************************//
+$inputs-button-list: 'input[type="button"]',
+ 'input[type="reset"]',
+ 'input[type="submit"]';
+
+// Bare inputs
+//************************************************************************//
+$all-button-inputs: assign-inputs($inputs-button-list);
+
+// Hover Pseudo-class
+//************************************************************************//
+$all-button-inputs-hover: assign-inputs($inputs-button-list, hover);
+
+// Focus Pseudo-class
+//************************************************************************//
+$all-button-inputs-focus: assign-inputs($inputs-button-list, focus);
+
+// Active Pseudo-class
+//************************************************************************//
+$all-button-inputs-active: assign-inputs($inputs-button-list, active);
+
+
+
+// You must use interpolation on the variable:
+// #{$all-button-inputs}
+// #{$all-button-inputs-hover}
+// #{$all-button-inputs-focus}
+// #{$all-button-inputs-active}
+
+// Example
+//************************************************************************//
+// #{$all-button-inputs}, button {
+// border: 1px solid red;
+// }
diff --git a/_sass/bourbon/addons/_position.scss b/_sass/bourbon/addons/_position.scss
new file mode 100644
index 00000000..7de75182
--- /dev/null
+++ b/_sass/bourbon/addons/_position.scss
@@ -0,0 +1,32 @@
+@mixin position ($position: relative, $coordinates: null null null null) {
+
+ @if type-of($position) == list {
+ $coordinates: $position;
+ $position: relative;
+ }
+
+ $coordinates: unpack($coordinates);
+
+ $top: nth($coordinates, 1);
+ $right: nth($coordinates, 2);
+ $bottom: nth($coordinates, 3);
+ $left: nth($coordinates, 4);
+
+ position: $position;
+
+ @if ($top and $top == auto) or (type-of($top) == number) {
+ top: $top;
+ }
+
+ @if ($right and $right == auto) or (type-of($right) == number) {
+ right: $right;
+ }
+
+ @if ($bottom and $bottom == auto) or (type-of($bottom) == number) {
+ bottom: $bottom;
+ }
+
+ @if ($left and $left == auto) or (type-of($left) == number) {
+ left: $left;
+ }
+}
diff --git a/_sass/bourbon/addons/_prefixer.scss b/_sass/bourbon/addons/_prefixer.scss
new file mode 100644
index 00000000..c32f5027
--- /dev/null
+++ b/_sass/bourbon/addons/_prefixer.scss
@@ -0,0 +1,45 @@
+//************************************************************************//
+// Example: @include prefixer(border-radius, $radii, webkit ms spec);
+//************************************************************************//
+// Variables located in /settings/_prefixer.scss
+
+@mixin prefixer ($property, $value, $prefixes) {
+ @each $prefix in $prefixes {
+ @if $prefix == webkit {
+ @if $prefix-for-webkit {
+ -webkit-#{$property}: $value;
+ }
+ }
+ @else if $prefix == moz {
+ @if $prefix-for-mozilla {
+ -moz-#{$property}: $value;
+ }
+ }
+ @else if $prefix == ms {
+ @if $prefix-for-microsoft {
+ -ms-#{$property}: $value;
+ }
+ }
+ @else if $prefix == o {
+ @if $prefix-for-opera {
+ -o-#{$property}: $value;
+ }
+ }
+ @else if $prefix == spec {
+ @if $prefix-for-spec {
+ #{$property}: $value;
+ }
+ }
+ @else {
+ @warn "Unrecognized prefix: #{$prefix}";
+ }
+ }
+}
+
+@mixin disable-prefix-for-all() {
+ $prefix-for-webkit: false !global;
+ $prefix-for-mozilla: false !global;
+ $prefix-for-microsoft: false !global;
+ $prefix-for-opera: false !global;
+ $prefix-for-spec: false !global;
+}
diff --git a/_sass/bourbon/addons/_retina-image.scss b/_sass/bourbon/addons/_retina-image.scss
new file mode 100644
index 00000000..7931bd13
--- /dev/null
+++ b/_sass/bourbon/addons/_retina-image.scss
@@ -0,0 +1,31 @@
+@mixin retina-image($filename, $background-size, $extension: png, $retina-filename: null, $retina-suffix: _2x, $asset-pipeline: false) {
+ @if $asset-pipeline {
+ background-image: image-url("#{$filename}.#{$extension}");
+ }
+ @else {
+ background-image: url("#{$filename}.#{$extension}");
+ }
+
+ @include hidpi {
+ @if $asset-pipeline {
+ @if $retina-filename {
+ background-image: image-url("#{$retina-filename}.#{$extension}");
+ }
+ @else {
+ background-image: image-url("#{$filename}#{$retina-suffix}.#{$extension}");
+ }
+ }
+
+ @else {
+ @if $retina-filename {
+ background-image: url("#{$retina-filename}.#{$extension}");
+ }
+ @else {
+ background-image: url("#{$filename}#{$retina-suffix}.#{$extension}");
+ }
+ }
+
+ background-size: $background-size;
+
+ }
+}
diff --git a/_sass/bourbon/addons/_size.scss b/_sass/bourbon/addons/_size.scss
new file mode 100644
index 00000000..ac705e26
--- /dev/null
+++ b/_sass/bourbon/addons/_size.scss
@@ -0,0 +1,16 @@
+@mixin size($size) {
+ $height: nth($size, 1);
+ $width: $height;
+
+ @if length($size) > 1 {
+ $height: nth($size, 2);
+ }
+
+ @if $height == auto or (type-of($height) == number and not unitless($height)) {
+ height: $height;
+ }
+
+ @if $width == auto or (type-of($height) == number and not unitless($width)) {
+ width: $width;
+ }
+}
diff --git a/_sass/bourbon/addons/_timing-functions.scss b/_sass/bourbon/addons/_timing-functions.scss
new file mode 100644
index 00000000..51b24109
--- /dev/null
+++ b/_sass/bourbon/addons/_timing-functions.scss
@@ -0,0 +1,32 @@
+// CSS cubic-bezier timing functions. Timing functions courtesy of jquery.easie (github.com/jaukia/easie)
+// Timing functions are the same as demo'ed here: http://jqueryui.com/demos/effect/easing.html
+
+// EASE IN
+$ease-in-quad: cubic-bezier(0.550, 0.085, 0.680, 0.530);
+$ease-in-cubic: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+$ease-in-quart: cubic-bezier(0.895, 0.030, 0.685, 0.220);
+$ease-in-quint: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+$ease-in-sine: cubic-bezier(0.470, 0.000, 0.745, 0.715);
+$ease-in-expo: cubic-bezier(0.950, 0.050, 0.795, 0.035);
+$ease-in-circ: cubic-bezier(0.600, 0.040, 0.980, 0.335);
+$ease-in-back: cubic-bezier(0.600, -0.280, 0.735, 0.045);
+
+// EASE OUT
+$ease-out-quad: cubic-bezier(0.250, 0.460, 0.450, 0.940);
+$ease-out-cubic: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+$ease-out-quart: cubic-bezier(0.165, 0.840, 0.440, 1.000);
+$ease-out-quint: cubic-bezier(0.230, 1.000, 0.320, 1.000);
+$ease-out-sine: cubic-bezier(0.390, 0.575, 0.565, 1.000);
+$ease-out-expo: cubic-bezier(0.190, 1.000, 0.220, 1.000);
+$ease-out-circ: cubic-bezier(0.075, 0.820, 0.165, 1.000);
+$ease-out-back: cubic-bezier(0.175, 0.885, 0.320, 1.275);
+
+// EASE IN OUT
+$ease-in-out-quad: cubic-bezier(0.455, 0.030, 0.515, 0.955);
+$ease-in-out-cubic: cubic-bezier(0.645, 0.045, 0.355, 1.000);
+$ease-in-out-quart: cubic-bezier(0.770, 0.000, 0.175, 1.000);
+$ease-in-out-quint: cubic-bezier(0.860, 0.000, 0.070, 1.000);
+$ease-in-out-sine: cubic-bezier(0.445, 0.050, 0.550, 0.950);
+$ease-in-out-expo: cubic-bezier(1.000, 0.000, 0.000, 1.000);
+$ease-in-out-circ: cubic-bezier(0.785, 0.135, 0.150, 0.860);
+$ease-in-out-back: cubic-bezier(0.680, -0.550, 0.265, 1.550);
diff --git a/_sass/bourbon/addons/_triangle.scss b/_sass/bourbon/addons/_triangle.scss
new file mode 100644
index 00000000..573954e4
--- /dev/null
+++ b/_sass/bourbon/addons/_triangle.scss
@@ -0,0 +1,83 @@
+@mixin triangle ($size, $color, $direction) {
+ height: 0;
+ width: 0;
+
+ $width: nth($size, 1);
+ $height: nth($size, length($size));
+
+ $foreground-color: nth($color, 1);
+ $background-color: if(length($color) == 2, nth($color, 2), transparent);
+
+ @if ($direction == up) or ($direction == down) or ($direction == right) or ($direction == left) {
+
+ $width: $width / 2;
+ $height: if(length($size) > 1, $height, $height/2);
+
+ @if $direction == up {
+ border-left: $width solid $background-color;
+ border-right: $width solid $background-color;
+ border-bottom: $height solid $foreground-color;
+
+ } @else if $direction == right {
+ border-top: $width solid $background-color;
+ border-bottom: $width solid $background-color;
+ border-left: $height solid $foreground-color;
+
+ } @else if $direction == down {
+ border-left: $width solid $background-color;
+ border-right: $width solid $background-color;
+ border-top: $height solid $foreground-color;
+
+ } @else if $direction == left {
+ border-top: $width solid $background-color;
+ border-bottom: $width solid $background-color;
+ border-right: $height solid $foreground-color;
+ }
+ }
+
+ @else if ($direction == up-right) or ($direction == up-left) {
+ border-top: $height solid $foreground-color;
+
+ @if $direction == up-right {
+ border-left: $width solid $background-color;
+
+ } @else if $direction == up-left {
+ border-right: $width solid $background-color;
+ }
+ }
+
+ @else if ($direction == down-right) or ($direction == down-left) {
+ border-bottom: $height solid $foreground-color;
+
+ @if $direction == down-right {
+ border-left: $width solid $background-color;
+
+ } @else if $direction == down-left {
+ border-right: $width solid $background-color;
+ }
+ }
+
+ @else if ($direction == inset-up) {
+ border-width: $height $width;
+ border-style: solid;
+ border-color: $background-color $background-color $foreground-color;
+ }
+
+ @else if ($direction == inset-down) {
+ border-width: $height $width;
+ border-style: solid;
+ border-color: $foreground-color $background-color $background-color;
+ }
+
+ @else if ($direction == inset-right) {
+ border-width: $width $height;
+ border-style: solid;
+ border-color: $background-color $background-color $background-color $foreground-color;
+ }
+
+ @else if ($direction == inset-left) {
+ border-width: $width $height;
+ border-style: solid;
+ border-color: $background-color $foreground-color $background-color $background-color;
+ }
+}
diff --git a/_sass/bourbon/addons/_word-wrap.scss b/_sass/bourbon/addons/_word-wrap.scss
new file mode 100644
index 00000000..9734a597
--- /dev/null
+++ b/_sass/bourbon/addons/_word-wrap.scss
@@ -0,0 +1,8 @@
+@mixin word-wrap($wrap: break-word) {
+ word-wrap: $wrap;
+
+ @if $wrap == break-word {
+ overflow-wrap: break-word;
+ word-break: break-all;
+ }
+}
diff --git a/_sass/bourbon/css3/_animation.scss b/_sass/bourbon/css3/_animation.scss
new file mode 100644
index 00000000..08c3dbf1
--- /dev/null
+++ b/_sass/bourbon/css3/_animation.scss
@@ -0,0 +1,52 @@
+// http://www.w3.org/TR/css3-animations/#the-animation-name-property-
+// Each of these mixins support comma separated lists of values, which allows different transitions for individual properties to be described in a single style rule. Each value in the list corresponds to the value at that same position in the other properties.
+
+// Official animation shorthand property.
+@mixin animation ($animations...) {
+ @include prefixer(animation, $animations, webkit moz spec);
+}
+
+// Individual Animation Properties
+@mixin animation-name ($names...) {
+ @include prefixer(animation-name, $names, webkit moz spec);
+}
+
+
+@mixin animation-duration ($times...) {
+ @include prefixer(animation-duration, $times, webkit moz spec);
+}
+
+
+@mixin animation-timing-function ($motions...) {
+// ease | linear | ease-in | ease-out | ease-in-out
+ @include prefixer(animation-timing-function, $motions, webkit moz spec);
+}
+
+
+@mixin animation-iteration-count ($values...) {
+// infinite |
+ @include prefixer(animation-iteration-count, $values, webkit moz spec);
+}
+
+
+@mixin animation-direction ($directions...) {
+// normal | alternate
+ @include prefixer(animation-direction, $directions, webkit moz spec);
+}
+
+
+@mixin animation-play-state ($states...) {
+// running | paused
+ @include prefixer(animation-play-state, $states, webkit moz spec);
+}
+
+
+@mixin animation-delay ($times...) {
+ @include prefixer(animation-delay, $times, webkit moz spec);
+}
+
+
+@mixin animation-fill-mode ($modes...) {
+// none | forwards | backwards | both
+ @include prefixer(animation-fill-mode, $modes, webkit moz spec);
+}
diff --git a/_sass/bourbon/css3/_appearance.scss b/_sass/bourbon/css3/_appearance.scss
new file mode 100644
index 00000000..3eb16e45
--- /dev/null
+++ b/_sass/bourbon/css3/_appearance.scss
@@ -0,0 +1,3 @@
+@mixin appearance ($value) {
+ @include prefixer(appearance, $value, webkit moz ms o spec);
+}
diff --git a/_sass/bourbon/css3/_backface-visibility.scss b/_sass/bourbon/css3/_backface-visibility.scss
new file mode 100644
index 00000000..1161fe60
--- /dev/null
+++ b/_sass/bourbon/css3/_backface-visibility.scss
@@ -0,0 +1,6 @@
+//************************************************************************//
+// Backface-visibility mixin
+//************************************************************************//
+@mixin backface-visibility($visibility) {
+ @include prefixer(backface-visibility, $visibility, webkit spec);
+}
diff --git a/_sass/bourbon/css3/_background-image.scss b/_sass/bourbon/css3/_background-image.scss
new file mode 100644
index 00000000..6abe88be
--- /dev/null
+++ b/_sass/bourbon/css3/_background-image.scss
@@ -0,0 +1,42 @@
+//************************************************************************//
+// Background-image property for adding multiple background images with
+// gradients, or for stringing multiple gradients together.
+//************************************************************************//
+
+@mixin background-image($images...) {
+ $webkit-images: ();
+ $spec-images: ();
+
+ @each $image in $images {
+ $webkit-image: ();
+ $spec-image: ();
+
+ @if (type-of($image) == string) {
+ $url-str: str-slice($image, 0, 3);
+ $gradient-type: str-slice($image, 0, 6);
+
+ @if $url-str == "url" {
+ $webkit-image: $image;
+ $spec-image: $image;
+ }
+
+ @else if $gradient-type == "linear" {
+ $gradients: _linear-gradient-parser($image);
+ $webkit-image: map-get($gradients, webkit-image);
+ $spec-image: map-get($gradients, spec-image);
+ }
+
+ @else if $gradient-type == "radial" {
+ $gradients: _radial-gradient-parser($image);
+ $webkit-image: map-get($gradients, webkit-image);
+ $spec-image: map-get($gradients, spec-image);
+ }
+ }
+
+ $webkit-images: append($webkit-images, $webkit-image, comma);
+ $spec-images: append($spec-images, $spec-image, comma);
+ }
+
+ background-image: $webkit-images;
+ background-image: $spec-images;
+}
diff --git a/_sass/bourbon/css3/_background.scss b/_sass/bourbon/css3/_background.scss
new file mode 100644
index 00000000..9bce9308
--- /dev/null
+++ b/_sass/bourbon/css3/_background.scss
@@ -0,0 +1,55 @@
+//************************************************************************//
+// Background property for adding multiple backgrounds using shorthand
+// notation.
+//************************************************************************//
+
+@mixin background($backgrounds...) {
+ $webkit-backgrounds: ();
+ $spec-backgrounds: ();
+
+ @each $background in $backgrounds {
+ $webkit-background: ();
+ $spec-background: ();
+ $background-type: type-of($background);
+
+ @if $background-type == string or list {
+ $background-str: if($background-type == list, nth($background, 1), $background);
+
+ $url-str: str-slice($background-str, 0, 3);
+ $gradient-type: str-slice($background-str, 0, 6);
+
+ @if $url-str == "url" {
+ $webkit-background: $background;
+ $spec-background: $background;
+ }
+
+ @else if $gradient-type == "linear" {
+ $gradients: _linear-gradient-parser("#{$background}");
+ $webkit-background: map-get($gradients, webkit-image);
+ $spec-background: map-get($gradients, spec-image);
+ }
+
+ @else if $gradient-type == "radial" {
+ $gradients: _radial-gradient-parser("#{$background}");
+ $webkit-background: map-get($gradients, webkit-image);
+ $spec-background: map-get($gradients, spec-image);
+ }
+
+ @else {
+ $webkit-background: $background;
+ $spec-background: $background;
+ }
+ }
+
+ @else {
+ $webkit-background: $background;
+ $spec-background: $background;
+ }
+
+ $webkit-backgrounds: append($webkit-backgrounds, $webkit-background, comma);
+ $spec-backgrounds: append($spec-backgrounds, $spec-background, comma);
+ }
+
+ background: $webkit-backgrounds;
+ background: $spec-backgrounds;
+}
diff --git a/_sass/bourbon/css3/_border-image.scss b/_sass/bourbon/css3/_border-image.scss
new file mode 100644
index 00000000..e338c2dc
--- /dev/null
+++ b/_sass/bourbon/css3/_border-image.scss
@@ -0,0 +1,59 @@
+@mixin border-image($borders...) {
+ $webkit-borders: ();
+ $spec-borders: ();
+
+ @each $border in $borders {
+ $webkit-border: ();
+ $spec-border: ();
+ $border-type: type-of($border);
+
+ @if $border-type == string or list {
+ $border-str: if($border-type == list, nth($border, 1), $border);
+
+ $url-str: str-slice($border-str, 0, 3);
+ $gradient-type: str-slice($border-str, 0, 6);
+
+ @if $url-str == "url" {
+ $webkit-border: $border;
+ $spec-border: $border;
+ }
+
+ @else if $gradient-type == "linear" {
+ $gradients: _linear-gradient-parser("#{$border}");
+ $webkit-border: map-get($gradients, webkit-image);
+ $spec-border: map-get($gradients, spec-image);
+ }
+
+ @else if $gradient-type == "radial" {
+ $gradients: _radial-gradient-parser("#{$border}");
+ $webkit-border: map-get($gradients, webkit-image);
+ $spec-border: map-get($gradients, spec-image);
+ }
+
+ @else {
+ $webkit-border: $border;
+ $spec-border: $border;
+ }
+ }
+
+ @else {
+ $webkit-border: $border;
+ $spec-border: $border;
+ }
+
+ $webkit-borders: append($webkit-borders, $webkit-border, comma);
+ $spec-borders: append($spec-borders, $spec-border, comma);
+ }
+
+ -webkit-border-image: $webkit-borders;
+ border-image: $spec-borders;
+ border-style: solid;
+}
+
+//Examples:
+// @include border-image(url("image.png"));
+// @include border-image(url("image.png") 20 stretch);
+// @include border-image(linear-gradient(45deg, orange, yellow));
+// @include border-image(linear-gradient(45deg, orange, yellow) stretch);
+// @include border-image(linear-gradient(45deg, orange, yellow) 20 30 40 50 stretch round);
+// @include border-image(radial-gradient(top, cover, orange, yellow, orange));
diff --git a/_sass/bourbon/css3/_border-radius.scss b/_sass/bourbon/css3/_border-radius.scss
new file mode 100644
index 00000000..7c171901
--- /dev/null
+++ b/_sass/bourbon/css3/_border-radius.scss
@@ -0,0 +1,22 @@
+//************************************************************************//
+// Shorthand Border-radius mixins
+//************************************************************************//
+@mixin border-top-radius($radii) {
+ @include prefixer(border-top-left-radius, $radii, spec);
+ @include prefixer(border-top-right-radius, $radii, spec);
+}
+
+@mixin border-bottom-radius($radii) {
+ @include prefixer(border-bottom-left-radius, $radii, spec);
+ @include prefixer(border-bottom-right-radius, $radii, spec);
+}
+
+@mixin border-left-radius($radii) {
+ @include prefixer(border-top-left-radius, $radii, spec);
+ @include prefixer(border-bottom-left-radius, $radii, spec);
+}
+
+@mixin border-right-radius($radii) {
+ @include prefixer(border-top-right-radius, $radii, spec);
+ @include prefixer(border-bottom-right-radius, $radii, spec);
+}
diff --git a/_sass/bourbon/css3/_box-sizing.scss b/_sass/bourbon/css3/_box-sizing.scss
new file mode 100644
index 00000000..f07e1d41
--- /dev/null
+++ b/_sass/bourbon/css3/_box-sizing.scss
@@ -0,0 +1,4 @@
+@mixin box-sizing ($box) {
+// content-box | border-box | inherit
+ @include prefixer(box-sizing, $box, webkit moz spec);
+}
diff --git a/_sass/bourbon/css3/_calc.scss b/_sass/bourbon/css3/_calc.scss
new file mode 100644
index 00000000..94d7e4ce
--- /dev/null
+++ b/_sass/bourbon/css3/_calc.scss
@@ -0,0 +1,4 @@
+@mixin calc($property, $value) {
+ #{$property}: -webkit-calc(#{$value});
+ #{$property}: calc(#{$value});
+}
diff --git a/_sass/bourbon/css3/_columns.scss b/_sass/bourbon/css3/_columns.scss
new file mode 100644
index 00000000..96f601c1
--- /dev/null
+++ b/_sass/bourbon/css3/_columns.scss
@@ -0,0 +1,47 @@
+@mixin columns($arg: auto) {
+// ||
+ @include prefixer(columns, $arg, webkit moz spec);
+}
+
+@mixin column-count($int: auto) {
+// auto || integer
+ @include prefixer(column-count, $int, webkit moz spec);
+}
+
+@mixin column-gap($length: normal) {
+// normal || length
+ @include prefixer(column-gap, $length, webkit moz spec);
+}
+
+@mixin column-fill($arg: auto) {
+// auto || length
+ @include prefixer(column-fill, $arg, webkit moz spec);
+}
+
+@mixin column-rule($arg) {
+// || ||
+ @include prefixer(column-rule, $arg, webkit moz spec);
+}
+
+@mixin column-rule-color($color) {
+ @include prefixer(column-rule-color, $color, webkit moz spec);
+}
+
+@mixin column-rule-style($style: none) {
+// none | hidden | dashed | dotted | double | groove | inset | inset | outset | ridge | solid
+ @include prefixer(column-rule-style, $style, webkit moz spec);
+}
+
+@mixin column-rule-width ($width: none) {
+ @include prefixer(column-rule-width, $width, webkit moz spec);
+}
+
+@mixin column-span($arg: none) {
+// none || all
+ @include prefixer(column-span, $arg, webkit moz spec);
+}
+
+@mixin column-width($length: auto) {
+// auto || length
+ @include prefixer(column-width, $length, webkit moz spec);
+}
diff --git a/_sass/bourbon/css3/_filter.scss b/_sass/bourbon/css3/_filter.scss
new file mode 100644
index 00000000..8560d776
--- /dev/null
+++ b/_sass/bourbon/css3/_filter.scss
@@ -0,0 +1,5 @@
+@mixin filter($function: none) {
+ // [
+ @include prefixer(perspective, $depth, webkit moz spec);
+}
+
+@mixin perspective-origin($value: 50% 50%) {
+ @include prefixer(perspective-origin, $value, webkit moz spec);
+}
diff --git a/_sass/bourbon/css3/_placeholder.scss b/_sass/bourbon/css3/_placeholder.scss
new file mode 100644
index 00000000..5682fd09
--- /dev/null
+++ b/_sass/bourbon/css3/_placeholder.scss
@@ -0,0 +1,8 @@
+@mixin placeholder {
+ $placeholders: ":-webkit-input" ":-moz" "-moz" "-ms-input";
+ @each $placeholder in $placeholders {
+ &:#{$placeholder}-placeholder {
+ @content;
+ }
+ }
+}
diff --git a/_sass/bourbon/css3/_radial-gradient.scss b/_sass/bourbon/css3/_radial-gradient.scss
new file mode 100644
index 00000000..7a8c3765
--- /dev/null
+++ b/_sass/bourbon/css3/_radial-gradient.scss
@@ -0,0 +1,39 @@
+// Requires Sass 3.1+
+@mixin radial-gradient($G1, $G2,
+ $G3: null, $G4: null,
+ $G5: null, $G6: null,
+ $G7: null, $G8: null,
+ $G9: null, $G10: null,
+ $pos: null,
+ $shape-size: null,
+ $fallback: null) {
+
+ $data: _radial-arg-parser($G1, $G2, $pos, $shape-size);
+ $G1: nth($data, 1);
+ $G2: nth($data, 2);
+ $pos: nth($data, 3);
+ $shape-size: nth($data, 4);
+
+ $full: $G1, $G2, $G3, $G4, $G5, $G6, $G7, $G8, $G9, $G10;
+
+ // Strip deprecated cover/contain for spec
+ $shape-size-spec: _shape-size-stripper($shape-size);
+
+ // Set $G1 as the default fallback color
+ $first-color: nth($full, 1);
+ $fallback-color: nth($first-color, 1);
+
+ @if (type-of($fallback) == color) or ($fallback == "transparent") {
+ $fallback-color: $fallback;
+ }
+
+ // Add Commas and spaces
+ $shape-size: if($shape-size, '#{$shape-size}, ', null);
+ $pos: if($pos, '#{$pos}, ', null);
+ $pos-spec: if($pos, 'at #{$pos}', null);
+ $shape-size-spec: if(($shape-size-spec != ' ') and ($pos == null), '#{$shape-size-spec}, ', '#{$shape-size-spec} ');
+
+ background-color: $fallback-color;
+ background-image: -webkit-radial-gradient(unquote(#{$pos}#{$shape-size}#{$full}));
+ background-image: unquote("radial-gradient(#{$shape-size-spec}#{$pos-spec}#{$full})");
+}
diff --git a/_sass/bourbon/css3/_transform.scss b/_sass/bourbon/css3/_transform.scss
new file mode 100644
index 00000000..8cc35963
--- /dev/null
+++ b/_sass/bourbon/css3/_transform.scss
@@ -0,0 +1,15 @@
+@mixin transform($property: none) {
+// none |
+ @include prefixer(transform, $property, webkit moz ms o spec);
+}
+
+@mixin transform-origin($axes: 50%) {
+// x-axis - left | center | right | length | %
+// y-axis - top | center | bottom | length | %
+// z-axis - length
+ @include prefixer(transform-origin, $axes, webkit moz ms o spec);
+}
+
+@mixin transform-style ($style: flat) {
+ @include prefixer(transform-style, $style, webkit moz ms o spec);
+}
diff --git a/_sass/bourbon/css3/_transition.scss b/_sass/bourbon/css3/_transition.scss
new file mode 100644
index 00000000..5ad4c0ae
--- /dev/null
+++ b/_sass/bourbon/css3/_transition.scss
@@ -0,0 +1,77 @@
+// Shorthand mixin. Supports multiple parentheses-deliminated values for each variable.
+// Example: @include transition (all 2s ease-in-out);
+// @include transition (opacity 1s ease-in 2s, width 2s ease-out);
+// @include transition-property (transform, opacity);
+
+@mixin transition ($properties...) {
+ // Fix for vendor-prefix transform property
+ $needs-prefixes: false;
+ $webkit: ();
+ $moz: ();
+ $spec: ();
+
+ // Create lists for vendor-prefixed transform
+ @each $list in $properties {
+ @if nth($list, 1) == "transform" {
+ $needs-prefixes: true;
+ $list1: -webkit-transform;
+ $list2: -moz-transform;
+ $list3: ();
+
+ @each $var in $list {
+ $list3: join($list3, $var);
+
+ @if $var != "transform" {
+ $list1: join($list1, $var);
+ $list2: join($list2, $var);
+ }
+ }
+
+ $webkit: append($webkit, $list1);
+ $moz: append($moz, $list2);
+ $spec: append($spec, $list3);
+ }
+
+ // Create lists for non-prefixed transition properties
+ @else {
+ $webkit: append($webkit, $list, comma);
+ $moz: append($moz, $list, comma);
+ $spec: append($spec, $list, comma);
+ }
+ }
+
+ @if $needs-prefixes {
+ -webkit-transition: $webkit;
+ -moz-transition: $moz;
+ transition: $spec;
+ }
+ @else {
+ @if length($properties) >= 1 {
+ @include prefixer(transition, $properties, webkit moz spec);
+ }
+
+ @else {
+ $properties: all 0.15s ease-out 0s;
+ @include prefixer(transition, $properties, webkit moz spec);
+ }
+ }
+}
+
+@mixin transition-property ($properties...) {
+ -webkit-transition-property: transition-property-names($properties, 'webkit');
+ -moz-transition-property: transition-property-names($properties, 'moz');
+ transition-property: transition-property-names($properties, false);
+}
+
+@mixin transition-duration ($times...) {
+ @include prefixer(transition-duration, $times, webkit moz spec);
+}
+
+@mixin transition-timing-function ($motions...) {
+// ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier()
+ @include prefixer(transition-timing-function, $motions, webkit moz spec);
+}
+
+@mixin transition-delay ($times...) {
+ @include prefixer(transition-delay, $times, webkit moz spec);
+}
diff --git a/_sass/bourbon/css3/_user-select.scss b/_sass/bourbon/css3/_user-select.scss
new file mode 100644
index 00000000..1380aa8b
--- /dev/null
+++ b/_sass/bourbon/css3/_user-select.scss
@@ -0,0 +1,3 @@
+@mixin user-select($arg: none) {
+ @include prefixer(user-select, $arg, webkit moz ms spec);
+}
diff --git a/_sass/bourbon/functions/_assign.scss b/_sass/bourbon/functions/_assign.scss
new file mode 100644
index 00000000..9a1db93e
--- /dev/null
+++ b/_sass/bourbon/functions/_assign.scss
@@ -0,0 +1,11 @@
+@function assign-inputs($inputs, $pseudo: null) {
+ $list : ();
+
+ @each $input in $inputs {
+ $input: unquote($input);
+ $input: if($pseudo, $input + ":" + $pseudo, $input);
+ $list: append($list, $input, comma);
+ }
+
+ @return $list;
+}
\ No newline at end of file
diff --git a/_sass/bourbon/functions/_color-lightness.scss b/_sass/bourbon/functions/_color-lightness.scss
new file mode 100644
index 00000000..8c6df4e2
--- /dev/null
+++ b/_sass/bourbon/functions/_color-lightness.scss
@@ -0,0 +1,13 @@
+// Programatically determines whether a color is light or dark
+// Returns a boolean
+// More details here http://robots.thoughtbot.com/closer-look-color-lightness
+
+@function is-light($hex-color) {
+ $-local-red: red(rgba($hex-color, 1.0));
+ $-local-green: green(rgba($hex-color, 1.0));
+ $-local-blue: blue(rgba($hex-color, 1.0));
+
+ $-local-lightness: ($-local-red * 0.2126 + $-local-green * 0.7152 + $-local-blue * 0.0722) / 255;
+
+ @return $-local-lightness > .6;
+}
diff --git a/_sass/bourbon/functions/_flex-grid.scss b/_sass/bourbon/functions/_flex-grid.scss
new file mode 100644
index 00000000..3bbd8665
--- /dev/null
+++ b/_sass/bourbon/functions/_flex-grid.scss
@@ -0,0 +1,39 @@
+// Flexible grid
+@function flex-grid($columns, $container-columns: $fg-max-columns) {
+ $width: $columns * $fg-column + ($columns - 1) * $fg-gutter;
+ $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
+ @return percentage($width / $container-width);
+}
+
+// Flexible gutter
+@function flex-gutter($container-columns: $fg-max-columns, $gutter: $fg-gutter) {
+ $container-width: $container-columns * $fg-column + ($container-columns - 1) * $fg-gutter;
+ @return percentage($gutter / $container-width);
+}
+
+// The $fg-column, $fg-gutter and $fg-max-columns variables must be defined in your base stylesheet to properly use the flex-grid function.
+// This function takes the fluid grid equation (target / context = result) and uses columns to help define each.
+//
+// The calculation presumes that your column structure will be missing the last gutter:
+//
+// -- column -- gutter -- column -- gutter -- column
+//
+// $fg-column: 60px; // Column Width
+// $fg-gutter: 25px; // Gutter Width
+// $fg-max-columns: 12; // Total Columns For Main Container
+//
+// div {
+// width: flex-grid(4); // returns (315px / 995px) = 31.65829%;
+// margin-left: flex-gutter(); // returns (25px / 995px) = 2.51256%;
+//
+// p {
+// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
+// float: left;
+// margin: flex-gutter(4); // returns (25px / 315px) = 7.936508%;
+// }
+//
+// blockquote {
+// float: left;
+// width: flex-grid(2, 4); // returns (145px / 315px) = 46.031746%;
+// }
+// }
\ No newline at end of file
diff --git a/_sass/bourbon/functions/_golden-ratio.scss b/_sass/bourbon/functions/_golden-ratio.scss
new file mode 100644
index 00000000..463d14a0
--- /dev/null
+++ b/_sass/bourbon/functions/_golden-ratio.scss
@@ -0,0 +1,3 @@
+@function golden-ratio($value, $increment) {
+ @return modular-scale($value, $increment, $golden)
+}
diff --git a/_sass/bourbon/functions/_grid-width.scss b/_sass/bourbon/functions/_grid-width.scss
new file mode 100644
index 00000000..8e63d83d
--- /dev/null
+++ b/_sass/bourbon/functions/_grid-width.scss
@@ -0,0 +1,13 @@
+@function grid-width($n) {
+ @return $n * $gw-column + ($n - 1) * $gw-gutter;
+}
+
+// The $gw-column and $gw-gutter variables must be defined in your base stylesheet to properly use the grid-width function.
+//
+// $gw-column: 100px; // Column Width
+// $gw-gutter: 40px; // Gutter Width
+//
+// div {
+// width: grid-width(4); // returns 520px;
+// margin-left: $gw-gutter; // returns 40px;
+// }
diff --git a/_sass/bourbon/functions/_modular-scale.scss b/_sass/bourbon/functions/_modular-scale.scss
new file mode 100644
index 00000000..afc59eb9
--- /dev/null
+++ b/_sass/bourbon/functions/_modular-scale.scss
@@ -0,0 +1,66 @@
+// Scaling Variables
+$golden: 1.618;
+$minor-second: 1.067;
+$major-second: 1.125;
+$minor-third: 1.2;
+$major-third: 1.25;
+$perfect-fourth: 1.333;
+$augmented-fourth: 1.414;
+$perfect-fifth: 1.5;
+$minor-sixth: 1.6;
+$major-sixth: 1.667;
+$minor-seventh: 1.778;
+$major-seventh: 1.875;
+$octave: 2;
+$major-tenth: 2.5;
+$major-eleventh: 2.667;
+$major-twelfth: 3;
+$double-octave: 4;
+
+@function modular-scale($value, $increment, $ratio) {
+ $v1: nth($value, 1);
+ $v2: nth($value, length($value));
+ $value: $v1;
+
+ // scale $v2 to just above $v1
+ @while $v2 > $v1 {
+ $v2: ($v2 / $ratio); // will be off-by-1
+ }
+ @while $v2 < $v1 {
+ $v2: ($v2 * $ratio); // will fix off-by-1
+ }
+
+ // check AFTER scaling $v2 to prevent double-counting corner-case
+ $double-stranded: $v2 > $v1;
+
+ @if $increment > 0 {
+ @for $i from 1 through $increment {
+ @if $double-stranded and ($v1 * $ratio) > $v2 {
+ $value: $v2;
+ $v2: ($v2 * $ratio);
+ } @else {
+ $v1: ($v1 * $ratio);
+ $value: $v1;
+ }
+ }
+ }
+
+ @if $increment < 0 {
+ // adjust $v2 to just below $v1
+ @if $double-stranded {
+ $v2: ($v2 / $ratio);
+ }
+
+ @for $i from $increment through -1 {
+ @if $double-stranded and ($v1 / $ratio) < $v2 {
+ $value: $v2;
+ $v2: ($v2 / $ratio);
+ } @else {
+ $v1: ($v1 / $ratio);
+ $value: $v1;
+ }
+ }
+ }
+
+ @return $value;
+}
diff --git a/_sass/bourbon/functions/_px-to-em.scss b/_sass/bourbon/functions/_px-to-em.scss
new file mode 100644
index 00000000..4832245e
--- /dev/null
+++ b/_sass/bourbon/functions/_px-to-em.scss
@@ -0,0 +1,13 @@
+// Convert pixels to ems
+// eg. for a relational value of 12px write em(12) when the parent is 16px
+// if the parent is another value say 24px write em(12, 24)
+
+@function em($pxval, $base: $em-base) {
+ @if not unitless($pxval) {
+ $pxval: strip-units($pxval);
+ }
+ @if not unitless($base) {
+ $base: strip-units($base);
+ }
+ @return ($pxval / $base) * 1em;
+}
diff --git a/_sass/bourbon/functions/_px-to-rem.scss b/_sass/bourbon/functions/_px-to-rem.scss
new file mode 100644
index 00000000..96b244e4
--- /dev/null
+++ b/_sass/bourbon/functions/_px-to-rem.scss
@@ -0,0 +1,15 @@
+// Convert pixels to rems
+// eg. for a relational value of 12px write rem(12)
+// Assumes $em-base is the font-size of
+
+@function rem($pxval) {
+ @if not unitless($pxval) {
+ $pxval: strip-units($pxval);
+ }
+
+ $base: $em-base;
+ @if not unitless($base) {
+ $base: strip-units($base);
+ }
+ @return ($pxval / $base) * 1rem;
+}
diff --git a/_sass/bourbon/functions/_strip-units.scss b/_sass/bourbon/functions/_strip-units.scss
new file mode 100644
index 00000000..6afc6e60
--- /dev/null
+++ b/_sass/bourbon/functions/_strip-units.scss
@@ -0,0 +1,5 @@
+// Srtips the units from a value. e.g. 12px -> 12
+
+@function strip-units($val) {
+ @return ($val / ($val * 0 + 1));
+}
diff --git a/_sass/bourbon/functions/_tint-shade.scss b/_sass/bourbon/functions/_tint-shade.scss
new file mode 100644
index 00000000..f7172004
--- /dev/null
+++ b/_sass/bourbon/functions/_tint-shade.scss
@@ -0,0 +1,9 @@
+// Add percentage of white to a color
+@function tint($color, $percent){
+ @return mix(white, $color, $percent);
+}
+
+// Add percentage of black to a color
+@function shade($color, $percent){
+ @return mix(black, $color, $percent);
+}
diff --git a/_sass/bourbon/functions/_transition-property-name.scss b/_sass/bourbon/functions/_transition-property-name.scss
new file mode 100644
index 00000000..54cd4228
--- /dev/null
+++ b/_sass/bourbon/functions/_transition-property-name.scss
@@ -0,0 +1,22 @@
+// Return vendor-prefixed property names if appropriate
+// Example: transition-property-names((transform, color, background), moz) -> -moz-transform, color, background
+//************************************************************************//
+@function transition-property-names($props, $vendor: false) {
+ $new-props: ();
+
+ @each $prop in $props {
+ $new-props: append($new-props, transition-property-name($prop, $vendor), comma);
+ }
+
+ @return $new-props;
+}
+
+@function transition-property-name($prop, $vendor: false) {
+ // put other properties that need to be prefixed here aswell
+ @if $vendor and $prop == transform {
+ @return unquote('-'+$vendor+'-'+$prop);
+ }
+ @else {
+ @return $prop;
+ }
+}
\ No newline at end of file
diff --git a/_sass/bourbon/functions/_unpack.scss b/_sass/bourbon/functions/_unpack.scss
new file mode 100644
index 00000000..37759636
--- /dev/null
+++ b/_sass/bourbon/functions/_unpack.scss
@@ -0,0 +1,17 @@
+// Convert shorthand to the 4-value syntax
+
+@function unpack($shorthand) {
+ @if length($shorthand) == 1 {
+ @return nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1) nth($shorthand, 1);
+ }
+ @else if length($shorthand) == 2 {
+ @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 1) nth($shorthand, 2);
+ }
+ @else if length($shorthand) == 3 {
+ @return nth($shorthand, 1) nth($shorthand, 2) nth($shorthand, 3) nth($shorthand, 2);
+ }
+ @else {
+ @return $shorthand;
+ }
+}
+
diff --git a/_sass/bourbon/helpers/_convert-units.scss b/_sass/bourbon/helpers/_convert-units.scss
new file mode 100644
index 00000000..3443db39
--- /dev/null
+++ b/_sass/bourbon/helpers/_convert-units.scss
@@ -0,0 +1,15 @@
+//************************************************************************//
+// Helper function for str-to-num fn.
+// Source: http://sassmeister.com/gist/9647408
+//************************************************************************//
+@function _convert-units($number, $unit) {
+ $strings: 'px' 'cm' 'mm' '%' 'ch' 'pica' 'in' 'em' 'rem' 'pt' 'pc' 'ex' 'vw' 'vh' 'vmin' 'vmax', 'deg', 'rad', 'grad', 'turn';
+ $units: 1px 1cm 1mm 1% 1ch 1pica 1in 1em 1rem 1pt 1pc 1ex 1vw 1vh 1vmin 1vmax, 1deg, 1rad, 1grad, 1turn;
+ $index: index($strings, $unit);
+
+ @if not $index {
+ @warn "Unknown unit `#{$unit}`.";
+ @return false;
+ }
+ @return $number * nth($units, $index);
+}
diff --git a/_sass/bourbon/helpers/_gradient-positions-parser.scss b/_sass/bourbon/helpers/_gradient-positions-parser.scss
new file mode 100644
index 00000000..07d30b6c
--- /dev/null
+++ b/_sass/bourbon/helpers/_gradient-positions-parser.scss
@@ -0,0 +1,13 @@
+@function _gradient-positions-parser($gradient-type, $gradient-positions) {
+ @if $gradient-positions
+ and ($gradient-type == linear)
+ and (type-of($gradient-positions) != color) {
+ $gradient-positions: _linear-positions-parser($gradient-positions);
+ }
+ @else if $gradient-positions
+ and ($gradient-type == radial)
+ and (type-of($gradient-positions) != color) {
+ $gradient-positions: _radial-positions-parser($gradient-positions);
+ }
+ @return $gradient-positions;
+}
diff --git a/_sass/bourbon/helpers/_is-num.scss b/_sass/bourbon/helpers/_is-num.scss
new file mode 100644
index 00000000..71459e14
--- /dev/null
+++ b/_sass/bourbon/helpers/_is-num.scss
@@ -0,0 +1,8 @@
+//************************************************************************//
+// Helper for linear-gradient-parser
+//************************************************************************//
+@function _is-num($char) {
+ $values: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 0 1 2 3 4 5 6 7 8 9;
+ $index: index($values, $char);
+ @return if($index, true, false);
+}
diff --git a/_sass/bourbon/helpers/_linear-angle-parser.scss b/_sass/bourbon/helpers/_linear-angle-parser.scss
new file mode 100644
index 00000000..e0401ed8
--- /dev/null
+++ b/_sass/bourbon/helpers/_linear-angle-parser.scss
@@ -0,0 +1,25 @@
+// Private function for linear-gradient-parser
+@function _linear-angle-parser($image, $first-val, $prefix, $suffix) {
+ $offset: null;
+ $unit-short: str-slice($first-val, str-length($first-val) - 2, str-length($first-val));
+ $unit-long: str-slice($first-val, str-length($first-val) - 3, str-length($first-val));
+
+ @if ($unit-long == "grad") or
+ ($unit-long == "turn") {
+ $offset: if($unit-long == "grad", -100grad * 3, -0.75turn);
+ }
+
+ @else if ($unit-short == "deg") or
+ ($unit-short == "rad") {
+ $offset: if($unit-short == "deg", -90 * 3, 1.6rad);
+ }
+
+ @if $offset {
+ $num: _str-to-num($first-val);
+
+ @return (
+ webkit-image: -webkit- + $prefix + ($offset - $num) + $suffix,
+ spec-image: $image
+ );
+ }
+}
diff --git a/_sass/bourbon/helpers/_linear-gradient-parser.scss b/_sass/bourbon/helpers/_linear-gradient-parser.scss
new file mode 100644
index 00000000..12bcdcda
--- /dev/null
+++ b/_sass/bourbon/helpers/_linear-gradient-parser.scss
@@ -0,0 +1,41 @@
+@function _linear-gradient-parser($image) {
+ $image: unquote($image);
+ $gradients: ();
+ $start: str-index($image, "(");
+ $end: str-index($image, ",");
+ $first-val: str-slice($image, $start + 1, $end - 1);
+
+ $prefix: str-slice($image, 0, $start);
+ $suffix: str-slice($image, $end, str-length($image));
+
+ $has-multiple-vals: str-index($first-val, " ");
+ $has-single-position: unquote(_position-flipper($first-val) + "");
+ $has-angle: _is-num(str-slice($first-val, 0, 0));
+
+ @if $has-multiple-vals {
+ $gradients: _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals);
+ }
+
+ @else if $has-single-position != "" {
+ $pos: unquote($has-single-position + "");
+
+ $gradients: (
+ webkit-image: -webkit- + $image,
+ spec-image: $prefix + "to " + $pos + $suffix
+ );
+ }
+
+ @else if $has-angle {
+ // Rotate degree for webkit
+ $gradients: _linear-angle-parser($image, $first-val, $prefix, $suffix);
+ }
+
+ @else {
+ $gradients: (
+ webkit-image: -webkit- + $image,
+ spec-image: $image
+ );
+ }
+
+ @return $gradients;
+}
diff --git a/_sass/bourbon/helpers/_linear-positions-parser.scss b/_sass/bourbon/helpers/_linear-positions-parser.scss
new file mode 100644
index 00000000..d26383ed
--- /dev/null
+++ b/_sass/bourbon/helpers/_linear-positions-parser.scss
@@ -0,0 +1,61 @@
+@function _linear-positions-parser($pos) {
+ $type: type-of(nth($pos, 1));
+ $spec: null;
+ $degree: null;
+ $side: null;
+ $corner: null;
+ $length: length($pos);
+ // Parse Side and corner positions
+ @if ($length > 1) {
+ @if nth($pos, 1) == "to" { // Newer syntax
+ $side: nth($pos, 2);
+
+ @if $length == 2 { // eg. to top
+ // Swap for backwards compatability
+ $degree: _position-flipper(nth($pos, 2));
+ }
+ @else if $length == 3 { // eg. to top left
+ $corner: nth($pos, 3);
+ }
+ }
+ @else if $length == 2 { // Older syntax ("top left")
+ $side: _position-flipper(nth($pos, 1));
+ $corner: _position-flipper(nth($pos, 2));
+ }
+
+ @if ("#{$side} #{$corner}" == "left top") or ("#{$side} #{$corner}" == "top left") {
+ $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
+ }
+ @else if ("#{$side} #{$corner}" == "right top") or ("#{$side} #{$corner}" == "top right") {
+ $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
+ }
+ @else if ("#{$side} #{$corner}" == "right bottom") or ("#{$side} #{$corner}" == "bottom right") {
+ $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
+ }
+ @else if ("#{$side} #{$corner}" == "left bottom") or ("#{$side} #{$corner}" == "bottom left") {
+ $degree: _position-flipper(#{$side}) _position-flipper(#{$corner});
+ }
+ $spec: to $side $corner;
+ }
+ @else if $length == 1 {
+ // Swap for backwards compatability
+ @if $type == string {
+ $degree: $pos;
+ $spec: to _position-flipper($pos);
+ }
+ @else {
+ $degree: -270 - $pos; //rotate the gradient opposite from spec
+ $spec: $pos;
+ }
+ }
+ $degree: unquote($degree + ",");
+ $spec: unquote($spec + ",");
+ @return $degree $spec;
+}
+
+@function _position-flipper($pos) {
+ @return if($pos == left, right, null)
+ if($pos == right, left, null)
+ if($pos == top, bottom, null)
+ if($pos == bottom, top, null);
+}
diff --git a/_sass/bourbon/helpers/_linear-side-corner-parser.scss b/_sass/bourbon/helpers/_linear-side-corner-parser.scss
new file mode 100644
index 00000000..86ad88fb
--- /dev/null
+++ b/_sass/bourbon/helpers/_linear-side-corner-parser.scss
@@ -0,0 +1,31 @@
+// Private function for linear-gradient-parser
+@function _linear-side-corner-parser($image, $first-val, $prefix, $suffix, $has-multiple-vals) {
+ $val-1: str-slice($first-val, 0, $has-multiple-vals - 1 );
+ $val-2: str-slice($first-val, $has-multiple-vals + 1, str-length($first-val));
+ $val-3: null;
+ $has-val-3: str-index($val-2, " ");
+
+ @if $has-val-3 {
+ $val-3: str-slice($val-2, $has-val-3 + 1, str-length($val-2));
+ $val-2: str-slice($val-2, 0, $has-val-3 - 1);
+ }
+
+ $pos: _position-flipper($val-1) _position-flipper($val-2) _position-flipper($val-3);
+ $pos: unquote($pos + "");
+
+ // Use old spec for webkit
+ @if $val-1 == "to" {
+ @return (
+ webkit-image: -webkit- + $prefix + $pos + $suffix,
+ spec-image: $image
+ );
+ }
+
+ // Bring the code up to spec
+ @else {
+ @return (
+ webkit-image: -webkit- + $image,
+ spec-image: $prefix + "to " + $pos + $suffix
+ );
+ }
+}
diff --git a/_sass/bourbon/helpers/_radial-arg-parser.scss b/_sass/bourbon/helpers/_radial-arg-parser.scss
new file mode 100644
index 00000000..a3a3704a
--- /dev/null
+++ b/_sass/bourbon/helpers/_radial-arg-parser.scss
@@ -0,0 +1,69 @@
+@function _radial-arg-parser($G1, $G2, $pos, $shape-size) {
+ @each $value in $G1, $G2 {
+ $first-val: nth($value, 1);
+ $pos-type: type-of($first-val);
+ $spec-at-index: null;
+
+ // Determine if spec was passed to mixin
+ @if type-of($value) == list {
+ $spec-at-index: if(index($value, at), index($value, at), false);
+ }
+ @if $spec-at-index {
+ @if $spec-at-index > 1 {
+ @for $i from 1 through ($spec-at-index - 1) {
+ $shape-size: $shape-size nth($value, $i);
+ }
+ @for $i from ($spec-at-index + 1) through length($value) {
+ $pos: $pos nth($value, $i);
+ }
+ }
+ @else if $spec-at-index == 1 {
+ @for $i from ($spec-at-index + 1) through length($value) {
+ $pos: $pos nth($value, $i);
+ }
+ }
+ $G1: null;
+ }
+
+ // If not spec calculate correct values
+ @else {
+ @if ($pos-type != color) or ($first-val != "transparent") {
+ @if ($pos-type == number)
+ or ($first-val == "center")
+ or ($first-val == "top")
+ or ($first-val == "right")
+ or ($first-val == "bottom")
+ or ($first-val == "left") {
+
+ $pos: $value;
+
+ @if $pos == $G1 {
+ $G1: null;
+ }
+ }
+
+ @else if
+ ($first-val == "ellipse")
+ or ($first-val == "circle")
+ or ($first-val == "closest-side")
+ or ($first-val == "closest-corner")
+ or ($first-val == "farthest-side")
+ or ($first-val == "farthest-corner")
+ or ($first-val == "contain")
+ or ($first-val == "cover") {
+
+ $shape-size: $value;
+
+ @if $value == $G1 {
+ $G1: null;
+ }
+
+ @else if $value == $G2 {
+ $G2: null;
+ }
+ }
+ }
+ }
+ }
+ @return $G1, $G2, $pos, $shape-size;
+}
diff --git a/_sass/bourbon/helpers/_radial-gradient-parser.scss b/_sass/bourbon/helpers/_radial-gradient-parser.scss
new file mode 100644
index 00000000..6dde50f0
--- /dev/null
+++ b/_sass/bourbon/helpers/_radial-gradient-parser.scss
@@ -0,0 +1,50 @@
+@function _radial-gradient-parser($image) {
+ $image: unquote($image);
+ $gradients: ();
+ $start: str-index($image, "(");
+ $end: str-index($image, ",");
+ $first-val: str-slice($image, $start + 1, $end - 1);
+
+ $prefix: str-slice($image, 0, $start);
+ $suffix: str-slice($image, $end, str-length($image));
+
+ $is-spec-syntax: str-index($first-val, "at");
+
+ @if $is-spec-syntax and $is-spec-syntax > 1 {
+ $keyword: str-slice($first-val, 1, $is-spec-syntax - 2);
+ $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));
+ $pos: append($pos, $keyword, comma);
+
+ $gradients: (
+ webkit-image: -webkit- + $prefix + $pos + $suffix,
+ spec-image: $image
+ )
+ }
+
+ @else if $is-spec-syntax == 1 {
+ $pos: str-slice($first-val, $is-spec-syntax + 3, str-length($first-val));
+
+ $gradients: (
+ webkit-image: -webkit- + $prefix + $pos + $suffix,
+ spec-image: $image
+ )
+ }
+
+ @else if str-index($image, "cover") or str-index($image, "contain") {
+ @warn "Radial-gradient needs to be updated to conform to latest spec.";
+
+ $gradients: (
+ webkit-image: null,
+ spec-image: $image
+ )
+ }
+
+ @else {
+ $gradients: (
+ webkit-image: -webkit- + $image,
+ spec-image: $image
+ )
+ }
+
+ @return $gradients;
+}
diff --git a/_sass/bourbon/helpers/_radial-positions-parser.scss b/_sass/bourbon/helpers/_radial-positions-parser.scss
new file mode 100644
index 00000000..6a5b4777
--- /dev/null
+++ b/_sass/bourbon/helpers/_radial-positions-parser.scss
@@ -0,0 +1,18 @@
+@function _radial-positions-parser($gradient-pos) {
+ $shape-size: nth($gradient-pos, 1);
+ $pos: nth($gradient-pos, 2);
+ $shape-size-spec: _shape-size-stripper($shape-size);
+
+ $pre-spec: unquote(if($pos, "#{$pos}, ", null))
+ unquote(if($shape-size, "#{$shape-size},", null));
+ $pos-spec: if($pos, "at #{$pos}", null);
+
+ $spec: "#{$shape-size-spec} #{$pos-spec}";
+
+ // Add comma
+ @if ($spec != ' ') {
+ $spec: "#{$spec},"
+ }
+
+ @return $pre-spec $spec;
+}
diff --git a/_sass/bourbon/helpers/_render-gradients.scss b/_sass/bourbon/helpers/_render-gradients.scss
new file mode 100644
index 00000000..57656768
--- /dev/null
+++ b/_sass/bourbon/helpers/_render-gradients.scss
@@ -0,0 +1,26 @@
+// User for linear and radial gradients within background-image or border-image properties
+
+@function _render-gradients($gradient-positions, $gradients, $gradient-type, $vendor: false) {
+ $pre-spec: null;
+ $spec: null;
+ $vendor-gradients: null;
+ @if $gradient-type == linear {
+ @if $gradient-positions {
+ $pre-spec: nth($gradient-positions, 1);
+ $spec: nth($gradient-positions, 2);
+ }
+ }
+ @else if $gradient-type == radial {
+ $pre-spec: nth($gradient-positions, 1);
+ $spec: nth($gradient-positions, 2);
+ }
+
+ @if $vendor {
+ $vendor-gradients: -#{$vendor}-#{$gradient-type}-gradient(#{$pre-spec} $gradients);
+ }
+ @else if $vendor == false {
+ $vendor-gradients: "#{$gradient-type}-gradient(#{$spec} #{$gradients})";
+ $vendor-gradients: unquote($vendor-gradients);
+ }
+ @return $vendor-gradients;
+}
diff --git a/_sass/bourbon/helpers/_shape-size-stripper.scss b/_sass/bourbon/helpers/_shape-size-stripper.scss
new file mode 100644
index 00000000..ee5eda42
--- /dev/null
+++ b/_sass/bourbon/helpers/_shape-size-stripper.scss
@@ -0,0 +1,10 @@
+@function _shape-size-stripper($shape-size) {
+ $shape-size-spec: null;
+ @each $value in $shape-size {
+ @if ($value == "cover") or ($value == "contain") {
+ $value: null;
+ }
+ $shape-size-spec: "#{$shape-size-spec} #{$value}";
+ }
+ @return $shape-size-spec;
+}
diff --git a/_sass/bourbon/helpers/_str-to-num.scss b/_sass/bourbon/helpers/_str-to-num.scss
new file mode 100644
index 00000000..b3d61682
--- /dev/null
+++ b/_sass/bourbon/helpers/_str-to-num.scss
@@ -0,0 +1,50 @@
+//************************************************************************//
+// Helper function for linear/radial-gradient-parsers.
+// Source: http://sassmeister.com/gist/9647408
+//************************************************************************//
+@function _str-to-num($string) {
+ // Matrices
+ $strings: '0' '1' '2' '3' '4' '5' '6' '7' '8' '9';
+ $numbers: 0 1 2 3 4 5 6 7 8 9;
+
+ // Result
+ $result: 0;
+ $divider: 0;
+ $minus: false;
+
+ // Looping through all characters
+ @for $i from 1 through str-length($string) {
+ $character: str-slice($string, $i, $i);
+ $index: index($strings, $character);
+
+ @if $character == '-' {
+ $minus: true;
+ }
+
+ @else if $character == '.' {
+ $divider: 1;
+ }
+
+ @else {
+ @if not $index {
+ $result: if($minus, $result * -1, $result);
+ @return _convert-units($result, str-slice($string, $i));
+ }
+
+ $number: nth($numbers, $index);
+
+ @if $divider == 0 {
+ $result: $result * 10;
+ }
+
+ @else {
+ // Move the decimal dot to the left
+ $divider: $divider * 10;
+ $number: $number / $divider;
+ }
+
+ $result: $result + $number;
+ }
+ }
+ @return if($minus, $result * -1, $result);
+}
diff --git a/_sass/bourbon/settings/_prefixer.scss b/_sass/bourbon/settings/_prefixer.scss
new file mode 100644
index 00000000..ecab49fb
--- /dev/null
+++ b/_sass/bourbon/settings/_prefixer.scss
@@ -0,0 +1,6 @@
+// Variable settings for /addons/prefixer.scss
+$prefix-for-webkit: true !default;
+$prefix-for-mozilla: true !default;
+$prefix-for-microsoft: true !default;
+$prefix-for-opera: true !default;
+$prefix-for-spec: true !default; // required for keyframe mixin
diff --git a/_sass/bourbon/settings/_px-to-em.scss b/_sass/bourbon/settings/_px-to-em.scss
new file mode 100644
index 00000000..f2f9a3e8
--- /dev/null
+++ b/_sass/bourbon/settings/_px-to-em.scss
@@ -0,0 +1 @@
+$em-base: 16px !default;
diff --git a/_sass/components/_buttons.scss b/_sass/components/_buttons.scss
new file mode 100644
index 00000000..08a56a17
--- /dev/null
+++ b/_sass/components/_buttons.scss
@@ -0,0 +1,86 @@
+// ------------------------------
+// BUTTONS
+// ------------------------------
+
+.btn {
+ padding: 10px 20px;
+ border: 1px solid $primary-color;
+ border-radius: 20px;
+ font-size: .9em;
+ font-weight: bold;
+ letter-spacing: 1px;
+ text-shadow: none;
+ color: $primary-color;
+ -webkit-font-smoothing: antialiased;
+
+ &:hover {
+ color: darken($primary-color, 15%);
+ border-color: darken($primary-color, 15%);
+ }
+
+}
+
+.btn-border-small {
+ border: 1px solid $primary-color;
+ border-radius: 20px;
+ padding: 6px 8px;
+ font-size: .8em;
+ margin-left: 10px;
+}
+
+.btn-secondary {
+ border-color: $secondary-color;
+ color: $secondary-color;
+
+ &:hover {
+ color: darken($secondary-color, 15%);
+ border-color: darken($secondary-color, 15%);
+ }
+
+}
+
+.btn-tertiary {
+ border-color: $gray-dark;
+ color: $gray-dark;
+
+ &:hover {
+ color: darken($gray-dark, 15%);
+ border-color: darken($gray-dark, 15%);
+ }
+
+}
+
+.btn-large {
+ padding: 10px 24px;
+ font-size: 1.1em;
+}
+
+.btn-small {
+ padding: 8px 12px;
+ font-size: .7em;
+}
+
+// ------------------------------
+// MOBILE MENU BUTTON
+// ------------------------------
+
+.btn-mobile-menu {
+ display: none;
+ position: fixed;
+ z-index: 9999;
+ top: 0;
+ right: 0;
+ left: 0;
+ width: 100%;
+ height: 35px;
+ background: rgba($gray-darkest,.98);
+ border-bottom: 1px solid rgba(255,255,255,.1);
+ text-align: center;
+}
+
+.btn-mobile-menu__icon,
+.btn-mobile-close__icon {
+ position: relative;
+ top: 10px;
+ color: #FFF;
+}
\ No newline at end of file
diff --git a/_sass/components/_forms.scss b/_sass/components/_forms.scss
new file mode 100644
index 00000000..927dcbaa
--- /dev/null
+++ b/_sass/components/_forms.scss
@@ -0,0 +1,34 @@
+*:focus {
+ outline: none;
+}
+
+input[type="text"],
+input[type="password"],
+input[type="datetime"],
+input[type="datetime-local"],
+input[type="date"],
+input[type="month"],
+input[type="time"],
+input[type="week"],
+input[type="number"],
+input[type="email"],
+input[type="url"],
+input[type="search"],
+input[type="tel"] {
+ width: 240px;
+ padding: 1em 1em;
+ background: #FFF;
+ border: 1px solid $gray-light;
+ border-radius: $border-radius;
+ font-size: .9em;
+ color: $gray-darker;
+
+ &:focus {
+ border-color: $secondary-color;
+ }
+
+ @include placeholder {
+ color: $gray;
+ }
+
+}
\ No newline at end of file
diff --git a/_sass/components/_icons.scss b/_sass/components/_icons.scss
new file mode 100644
index 00000000..5b3b32f0
--- /dev/null
+++ b/_sass/components/_icons.scss
@@ -0,0 +1,17 @@
+i {
+ font-family: 'entypo';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 18px;
+}
+
+.social {
+ font-size: 22px;
+}
+
+.icon-social {
+ font-family: 'entypo-social';
+ font-size: 22px;
+ display: block;
+ position: relative;
+}
\ No newline at end of file
diff --git a/_sass/components/_navigation.scss b/_sass/components/_navigation.scss
new file mode 100644
index 00000000..f7fdab29
--- /dev/null
+++ b/_sass/components/_navigation.scss
@@ -0,0 +1,53 @@
+nav {
+ display: inline-block;
+ position: relative;
+}
+
+.navigation {
+ display: inline-block;
+ float: left;
+ position: relative;
+ margin: 0;
+ list-style-type: none;
+}
+
+ .navigation__item {
+ display: inline-block;
+ margin: 5px 1px 0 0;
+ line-height: 1em;
+
+ a {
+ display: block;
+ position: relative;
+ @extend .btn;
+ border-color: #FFF;
+ color: #FFF;
+ opacity: .8;
+
+ &:hover {
+ color: #FFF;
+ border-color: #FFF;
+ opacity: 1;
+ }
+
+ }
+
+ }
+
+ .navigation--social {
+ a {
+ border: 0px;
+ padding: 6px 8px 6px 9px;
+
+ .label {
+ display: none;
+ }
+
+ .icon {
+ display: block;
+ font-size: 1.7em;
+ }
+
+ }
+
+ }
\ No newline at end of file
diff --git a/_sass/components/_pagination.scss b/_sass/components/_pagination.scss
new file mode 100644
index 00000000..0b1053b3
--- /dev/null
+++ b/_sass/components/_pagination.scss
@@ -0,0 +1,22 @@
+// ------------------------------
+// PAGINATION
+// ------------------------------
+
+.pagination {
+ display: block;
+ margin: 0 0 4em 0;
+}
+
+ .pagination__page-number {
+ margin: 0;
+ font-size: .8em;
+ color: $gray-dark;
+ }
+
+ .pagination__newer {
+ margin-right: 1em;
+ }
+
+ .pagination__older {
+ margin-left: 1em;
+ }
\ No newline at end of file
diff --git a/_sass/components/_panels.scss b/_sass/components/_panels.scss
new file mode 100644
index 00000000..1c3cd025
--- /dev/null
+++ b/_sass/components/_panels.scss
@@ -0,0 +1,164 @@
+// ------------------------------
+// PANEL
+// ------------------------------
+
+.panel {
+ display: table;
+ width: 100%;
+ height: 100%;
+}
+
+ .panel__vertical {
+ display: table-cell;
+ vertical-align: middle;
+ }
+
+.panel-title {
+ margin: 0 0 5px 0;
+ font-size: 2.5em;
+ letter-spacing: 4px;
+ color: #FFF;
+}
+
+.panel-subtitle {
+ font-family: $serif-font;
+ font-size: 1.2em;
+ font-weight: lighter;
+ letter-spacing: 3px;
+ color: $gray;
+ -webkit-font-smoothing: antialiased;
+}
+
+// ------------------------------
+// COVER PANEL
+// ------------------------------
+
+.panel-cover {
+ display: block;
+ position: fixed;
+ z-index: 900;
+ width: 100%;
+ max-width: none;
+ height: 100%;
+ background: url(../images/background-cover.jpg) top left no-repeat $gray-darker;
+ background-size: cover;
+}
+
+ .panel-cover--collapsed {
+ width: 30%;
+ max-width: 700px;
+ }
+
+ .panel-cover--overlay {
+ display: block;
+ position: absolute;
+ z-index: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ @include linear-gradient(140deg, rgba(#444444,.6) 20%, rgba(#000000,.9));
+ }
+
+ .panel-cover__logo {
+ margin-bottom: .2em;
+ }
+
+ .panel-cover__description {
+ margin: 0 30px;
+ }
+
+ .panel-cover__divider {
+ width: 50%;
+ margin: 20px auto;
+ border-top: 1px solid rgba(255,255,255,.14);
+ }
+
+ .panel-cover__divider--secondary {
+ width: 15%;
+ }
+
+ // ------------------------------
+ // MAIN PANEL
+ // ------------------------------
+
+ .panel-main {
+ display: table;
+ width: 100%;
+ height: 100%;
+ }
+
+ .panel-main__inner {
+ display: table-cell;
+ vertical-align: middle;
+ position: relative;
+ z-index: 800;
+ padding: 0 60px;
+ }
+
+ .panel-main__content {
+ max-width: 620px;
+ margin: 0 auto;
+ }
+
+ .panel-main__content--fixed {
+ width: 480px;
+ transition: width 1s;
+ -webkit-transition: width 1s; /* Safari */
+ }
+
+ .panel-inverted {
+ font-weight: 100;
+ text-align: center;
+ color: #FFF;
+ text-shadow: 0 1px 1px rgba(000,000,000,.4);
+
+ a {
+ color: #FFF;
+ }
+
+ }
+
+// ------------------------------
+// COVER NAVIGATION
+// ------------------------------
+
+.cover-navigation {
+ margin-top: 42px;
+}
+
+ .cover-navigation--social {
+ margin-left: 30px;
+ }
+
+// ------------------------------
+// ADDITIONAL COVER COLOURS
+// ------------------------------
+
+.cover-blue {
+ @include linear-gradient(140deg, rgba($blue,.6) 20%, rgba(darken($blue, 20%),.8));
+}
+
+.cover-green {
+ @include linear-gradient(140deg, rgba($green,.6) 20%, rgba(darken($green, 20%),.8));
+}
+
+.cover-purple {
+ @include linear-gradient(140deg, rgba($purple,.6) 20%, rgba(darken($purple, 20%),.8));
+}
+
+.cover-red {
+ @include linear-gradient(140deg, rgba(darken($red, 30%),.6) 20%, rgba(darken($red, 50%),.8));
+}
+
+.cover-orange {
+ @include linear-gradient(140deg, rgba(darken($orange, 30%),.6) 20%, rgba(darken($orange, 50%),.8));
+}
+
+.cover-slate {
+ @include linear-gradient(140deg, rgba($slate,.6) 20%, rgba(darken($slate, 20%),.8));
+}
+
+.cover-disabled {
+ background: none;
+}
diff --git a/_sass/components/_post-list.scss b/_sass/components/_post-list.scss
new file mode 100644
index 00000000..ce5235ef
--- /dev/null
+++ b/_sass/components/_post-list.scss
@@ -0,0 +1,63 @@
+// ------------------------------
+// POST LIST
+// ------------------------------
+
+.post-list {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+ text-align: left;
+
+ li {
+ margin: 0 0 2.2em 0;
+
+ &:last-child {
+
+ hr {
+ display: none;
+ }
+
+ }
+
+ }
+
+}
+
+ .post-list__post-title {
+ margin-top: 0;
+ margin-bottom: .2em;
+ font-size: 1.5em;
+ line-height: 1.3em;
+
+ a {
+ color: $gray-darkest;
+
+ &:hover {
+ color: $hover-color;
+ }
+
+ }
+
+ }
+
+ .post-list__meta {
+ display: block;
+ margin: .7em 0 0 0;
+ font-size: .9em;
+ color: darken($gray, 2%);
+ }
+
+ .post-list__meta--date {
+ margin-right: .5em;
+ color: darken($gray, 2%);
+ }
+
+ .post-list__meta--tags {
+ margin-left: .5em;
+ }
+
+ .post-list__divider {
+ width: 30%;
+ margin: 2.2em 0 2.1em 0;
+ border-top: 1px solid $gray-light;
+ }
\ No newline at end of file
diff --git a/_sass/components/_read-more.scss b/_sass/components/_read-more.scss
new file mode 100644
index 00000000..1cba1ec0
--- /dev/null
+++ b/_sass/components/_read-more.scss
@@ -0,0 +1,21 @@
+// ------------------------------
+// READ MORE
+// ------------------------------
+
+.read-more {
+ margin-bottom: 1em;
+ padding-top: 1.2em;
+ padding-bottom: 1em;
+ border-top: 1px solid #DDDDDD;
+}
+
+.read-more-item {
+ display: inline-block;;
+ vertical-align: top;
+ width: 48%;
+}
+
+.read-more-item-dim {
+ color: $hover-color;
+ font-size: .8em;
+}
diff --git a/_sass/global.scss b/_sass/global.scss
new file mode 100644
index 00000000..0865d864
--- /dev/null
+++ b/_sass/global.scss
@@ -0,0 +1,340 @@
+// ------------------------------
+// BASICS
+// ------------------------------
+
+html, body {
+ height: 100%;
+}
+
+html {
+ height: 100%;
+ max-height: 100%;
+}
+
+body {
+ font-family: $sans-font;
+ font-size: 1em;
+ color: $gray-darker;
+ -webkit-font-smoothing: antialiased;
+}
+
+::selection {
+ background: lighten($primary-color, 36%);
+}
+
+::-moz-selection {
+ background: lighten($primary-color, 36%);
+}
+
+// ------------------------------
+// TYPOGRAPHY
+// ------------------------------
+
+a {
+ text-decoration: none;
+ color: $link-color;
+
+ &:hover {
+ color: $hover-color;
+ -o-transition:.5s;
+ -ms-transition:.5s;
+ -moz-transition:.5s;
+ -webkit-transition:.5s;
+ }
+
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h5 {
+ margin-top: 1.0em;
+ margin-bottom: .5em;
+ font-family: $serif-font;
+ font-weight: lighter;
+ color: $gray-darkest;
+ -webkit-font-smoothing: antialiased;
+}
+
+h1 {
+ margin-top: 0;
+ font-size: 2.5em;
+ line-height: 1.2em;
+ letter-spacing: .05em;
+}
+
+h2 {
+ font-size: 2.0em;
+}
+
+h3 {
+ font-size: 1.6em;
+}
+
+h4 {
+ font-size: 1.2em;
+}
+
+h4 {
+ font-size: 1.1em;
+}
+
+h5 {
+ font-size: 1em;
+}
+
+p {
+ margin-bottom: 1.3em;
+ line-height: 1.7em;
+}
+
+strong {
+ font-weight: bold;
+}
+
+em {
+ font-style: italic;
+}
+
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ font-family: $serif-font;
+ font-weight: lighter;
+ font-size: 1em;
+ border-left: 3px solid $primary-color;
+
+ p {
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ }
+
+}
+
+// ------------------------------
+// BASIC STYLING
+// ------------------------------
+
+ol, ul {
+ margin: 0 0 1.3em 2.5em;
+
+ li {
+ margin: 0 0 .2em 0;
+ line-height: 1.6em;
+ }
+
+ ol, ul {
+ margin: .1em 0 .2em 2em;
+ }
+
+}
+
+ol {
+ list-style-type: decimal;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+code {
+ padding: .1em .4em;
+ background: lighten($secondary-color, 32%);
+ border: 1px solid lighten($secondary-color, 25%);
+ border-radius: $border-radius;
+ font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
+ font-size: .9em;
+ vertical-align: bottom;
+ word-wrap: break-word;
+}
+
+pre {
+ margin-bottom: 1.3em;
+ padding: 1em 2.5%;
+ background: lighten($secondary-color, 32%);
+ border: 1px solid lighten($secondary-color, 25%);
+ border-radius: $border-radius;
+ font-family: Consolas, Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace, serif;
+ font-size: .9em;
+ font-weight: normal;
+ line-height: 1.3em;
+ overflow:scroll;
+
+ code {
+ padding: 0;
+ background: none;
+ border: none;
+ word-wrap: normal;
+ }
+
+}
+
+table {
+ color: $gray-darkest;
+ font-size: .9em;
+ text-align: center;
+ line-height: 40px;
+ border-spacing: 0;
+ border: 2px solid darken($primary-color, 10%);
+ width: 90%;
+ margin: 50px auto;
+}
+
+thead tr:first-child {
+ background-color: darken($primary-color, 10%);
+ color: $gray-lightest;
+ border: none;
+}
+
+th {font-weight: bold;}
+th:first-child, td:first-child {padding: 0 15px 0 20px;}
+
+thead tr:last-child th {
+ border-bottom: 1px solid $gray-light;
+}
+
+tbody tr:last-child td {border: none;}
+tbody td {
+ border-bottom: 1px solid $gray-light;
+ font-size: .85em;
+}
+
+td:last-child {
+ padding-right: 10px;
+}
+
+.date,
+.time,
+.author,
+.tags {
+ font-size: .8em;
+ color: darken($gray, 2%);
+
+ a {
+ color: $gray-darker;
+
+ &:hover {
+ color: $hover-color;
+ }
+
+ }
+
+}
+
+.excerpt {
+ margin: 0;
+ font-size: .9em;
+ color: $gray-dark;
+}
+
+.intro {
+ font-family: $serif-font;
+ font-size: 1.2em;
+ font-weight: lighter;
+ color: $gray-dark;
+}
+
+.block-heading {
+ @include column(12);
+ position: relative;
+ bottom: -15px;
+ font-size: .8em;
+ font-weight: bold;
+ text-align: center;
+ text-transform: uppercase;
+ letter-spacing: 1px;
+}
+
+.label {
+ position: relative;
+ display: inline-block;
+ padding: 8px 18px 9px 18px;
+ background: $primary-color;
+ border-radius: $border-radius;
+ text-align: center;
+ color: #FFF;
+}
+
+// ------------------------------
+// MAIN LAYOUT
+// ------------------------------
+
+.container {
+ position: relative;
+ z-index: 500;
+ width: $total-width - $gutter-width;
+ margin: 0 auto;
+}
+
+.content-wrapper {
+ z-index: 800;
+ width: 70%;
+ margin-left: 30%;
+}
+
+ .content-wrapper__inner {
+ margin: 0 10%;
+ padding: 50px 0;
+ }
+
+.footer {
+ display: block;
+ padding: 2em 0 0 0;
+ border-top: 2px solid $gray-light;
+ font-size: .7em;
+ color: lighten($gray-dark, 10%);
+}
+
+ .footer__copyright {
+ display: block;
+ margin-bottom: .7em;
+
+ a {
+ color: lighten($gray-dark, 5%);
+ text-decoration: underline;
+
+ &:hover {
+ color: $hover-color;
+ }
+
+ }
+
+ }
+
+// ------------------------------
+// AVATAR & LOGO
+// ------------------------------
+
+.avatar,
+.logo {
+ border-radius: 50%;
+ border: 3px solid #FFF;
+ box-shadow:0 0 1px 1px rgba(000,000,000,.3);
+}
+
+// ------------------------------
+// DIVIDERS
+// ------------------------------
+
+hr {
+ border: none;
+}
+
+.section-title__divider {
+ width: 30%;
+ margin: 2.2em 0 2.1em 0;
+ border-top: 1px solid $gray-light;
+}
+
+// ------------------------------
+// MISC STYLES / HELPERS
+// ------------------------------
+
+.hidden {
+ display: none !important;
+}
diff --git a/_sass/grid.scss b/_sass/grid.scss
new file mode 100644
index 00000000..39e74209
--- /dev/null
+++ b/_sass/grid.scss
@@ -0,0 +1,67 @@
+/////////////////
+// Semantic.gs // for SCSS: http://sass-lang.com/
+/////////////////
+
+// Defaults which you can freely override
+$column-width: 60px;
+$gutter-width: 20px;
+$columns: 12;
+
+// Utility function — you should never need to modify this
+@function gridsystem-width($columns:$columns) {
+ @return ($column-width * $columns) + ($gutter-width * $columns);
+}
+
+// Set $total-width to 100% for a fluid layout
+$total-width: gridsystem-width($columns);
+
+// Uncomment these two lines and the star-hack width/margin lines below to enable sub-pixel fix for IE6 & 7. See http://tylertate.com/blog/2012/01/05/subpixel-rounding.html
+// $min-width: 999999;
+// $correction: 0.5 / $min-width * 100;
+
+// The micro clearfix http://nicolasgallagher.com/micro-clearfix-hack/
+@mixin clearfix() {
+ *zoom:1;
+
+ &:before,
+ &:after {
+ content:"";
+ display:table;
+ }
+ &:after {
+ clear:both;
+ }
+}
+
+
+//////////
+// GRID //
+//////////
+
+body {
+ width: 100%;
+ @include clearfix();
+}
+
+@mixin row($columns:$columns) {
+ display: block;
+ width: $total-width*(($gutter-width + gridsystem-width($columns))/gridsystem-width($columns));
+ margin: 0 $total-width*((($gutter-width*.5)/gridsystem-width($columns))*-1);
+ // *width: $total-width*(($gutter-width + gridsystem-width($columns))/gridsystem-width($columns))-$correction;
+ // *margin: 0 $total-width*((($gutter-width*.5)/gridsystem-width($columns))*-1)-$correction;
+ @include clearfix();
+}
+@mixin column($x,$columns:$columns) {
+ display: inline;
+ float: left;
+ width: $total-width*(((($gutter-width+$column-width)*$x)-$gutter-width) / gridsystem-width($columns));
+ margin: 0 $total-width*(($gutter-width*.5)/gridsystem-width($columns));
+ // *width: $total-width*(((($gutter-width+$column-width)*$x)-$gutter-width) / gridsystem-width($columns))-$correction;
+ // *margin: 0 $total-width*(($gutter-width*.5)/gridsystem-width($columns))-$correction;
+}
+@mixin push($offset:1) {
+ margin-left: $total-width*((($gutter-width+$column-width)*$offset) / gridsystem-width($columns)) + $total-width*(($gutter-width*.5)/gridsystem-width($columns));
+}
+@mixin pull($offset:1) {
+ margin-right: $total-width*((($gutter-width+$column-width)*$offset) / gridsystem-width($columns)) + $total-width*(($gutter-width*.5)/gridsystem-width($columns));
+}
\ No newline at end of file
diff --git a/_sass/media-queries.scss b/_sass/media-queries.scss
new file mode 100644
index 00000000..880b10c4
--- /dev/null
+++ b/_sass/media-queries.scss
@@ -0,0 +1,201 @@
+// ------------------------------
+// MEDIA QUERIES
+// ------------------------------
+
+@media all and (min-width: 1300px) {
+
+}
+
+// ------------------------------
+// < 1100px
+// ------------------------------
+
+@media all and (max-width: 1100px) {
+
+ .panel-cover__logo {
+ width: 70px;
+ }
+
+ .panel-title {
+ font-size: 2em;
+ }
+
+ .panel-subtitle {
+ font-size: 1em;
+ }
+
+ .panel-cover__description {
+ margin: 0 10px;
+ font-size: .9em;
+ }
+
+ .navigation--social {
+ margin-top: 5px;
+ margin-left: 0;
+ }
+
+}
+
+// ------------------------------
+// < 960px
+// ------------------------------
+
+@media all and (max-width: 960px) {
+
+ .btn-mobile-menu {
+ display: block;
+ }
+
+ .panel-main {
+ display: table;
+ position: relative;
+ }
+
+ .panel-cover--collapsed {
+ width: 100%;
+ max-width: none;
+ }
+
+ .panel-main__inner {
+ display: table-cell;
+ padding: 60px 10%;
+ }
+
+ .panel-cover__description {
+ display: block;
+ max-width: 600px;
+ margin: 0 auto;
+ }
+
+ .panel-cover__divider--secondary {
+ display: none;
+ }
+
+ .panel-cover {
+ width: 100%;
+ height: 100%;
+ background-position: center center;
+
+ &.panel-cover--collapsed {
+ display: block;
+ position: relative;
+ height: auto;
+ padding: 0;
+ background-position: center center;
+
+ .panel-main__inner {
+ display: block;
+ padding: 70px 0 30px 0;
+ }
+
+ .panel-cover__logo {
+ width: 60px;
+ border-width: 2px;
+ }
+
+ .panel-cover__description {
+ display: none;
+ }
+
+ .panel-cover__divider {
+ display: none;
+ margin: 1em auto;
+ }
+
+ }
+
+ }
+
+ .navigation-wrapper {
+ display: none;
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ width: 100%;
+ padding: 20px 0;
+ background: rgba($gray-darkest,.98);
+ border-bottom: 1px solid rgba(255,255,255,.15);
+
+ &.visible {
+ display: block;
+ }
+ }
+
+ .cover-navigation {
+ display: block;
+ position: relative;
+ float: left;
+ clear: left;
+ width: 100%;
+
+ .navigation {
+ display: block;
+ width: 100%;
+
+ li {
+ width: 80%;
+ margin-bottom: .4em;
+ }
+
+ }
+
+ &.navigation--social {
+ padding-top: 5px;
+
+ .navigation {
+
+ li {
+ display: inline-block;
+ width: 25.8%;
+ }
+
+ }
+
+ }
+
+ }
+
+ .content-wrapper {
+ width: 80%;
+ max-width: none;
+ margin: 0 auto;
+ }
+
+ .content-wrapper__inner {
+ margin-right: 0;
+ margin-left: 0;
+ }
+
+ .navigation__item {
+ width: 100%;
+ margin: 0 0 .4em 0;
+ }
+
+}
+
+// ------------------------------
+// < 340px
+// ------------------------------
+
+@media all and (max-width: 340px) {
+
+ .panel-main__inner {
+ padding: 0 5%;
+ }
+
+ .panel-title {
+ margin-bottom: .1em;
+ font-size: 1.5em;
+ }
+
+ .panel-subtitle {
+ font-size: .9em;
+ }
+
+ .btn {
+ display: block;
+ margin-bottom: .4em;
+ }
+
+}
diff --git a/_sass/reset.scss b/_sass/reset.scss
new file mode 100644
index 00000000..fbecaa09
--- /dev/null
+++ b/_sass/reset.scss
@@ -0,0 +1,40 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline; }
+
+/* HTML5 display-role reset for older browsers */
+
+article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
+ display: block; }
+
+body {
+ line-height: 1; }
+
+ol, ul {
+ list-style: none; }
+
+blockquote, q {
+ quotes: none; }
+
+blockquote {
+ &:before, &:after {
+ content: '';
+ content: none; } }
+
+q {
+ &:before, &:after {
+ content: '';
+ content: none; } }
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0; }
\ No newline at end of file
diff --git a/_sass/sections/_post.scss b/_sass/sections/_post.scss
new file mode 100644
index 00000000..8aec8f83
--- /dev/null
+++ b/_sass/sections/_post.scss
@@ -0,0 +1,66 @@
+// ------------------------------
+// SINGLE POST LAYOUT
+// ------------------------------
+
+.post-comments {
+ border-top: 1px solid $gray-light;
+ padding: 60px 0;
+}
+
+// ------------------------------
+// SINGLE POST METAß
+// ------------------------------
+
+.post-meta {
+ margin: 0 0 .4em 0;
+ color: darken($gray, 2%);
+}
+
+ .post-meta__date {
+ margin-right: .5em;
+ }
+
+ .post-meta__tags {
+ margin-left: .4em;
+ }
+
+ .post-meta__author {
+ margin-left: 1.5em;
+ }
+
+ .post-meta__avatar {
+ display: inline-block;
+ width: 22px;
+ height: 22px;
+ margin: 0 .3em -.4em 0;
+ border: none;
+ box-shadow:none;
+ }
+
+// ------------------------------
+// SINGLE POST STYLES
+// ------------------------------
+
+.post {
+
+ img {
+ max-width: 100%;
+ margin: 0 auto;
+ border-radius: $border-radius;
+ text-align: center;
+ display: block;
+ }
+
+ pre {
+ width: 95%;
+ overflow: auto;
+ }
+
+ hr {
+ display: block;
+ width: 30%;
+ margin: 2em 0;
+ border-top: 1px solid $gray-light;
+ }
+
+}
diff --git a/_sass/variables.scss b/_sass/variables.scss
new file mode 100644
index 00000000..ef0a3bf5
--- /dev/null
+++ b/_sass/variables.scss
@@ -0,0 +1,27 @@
+
+$gray-lightest: #F8F8F8;
+$gray-lighter: #EEEEEE;
+$gray-light: #DDDDDD;
+$gray: #CCCCCC;
+$gray-dark: #999999;
+$gray-darker: #666666;
+$gray-darkest: #333333;
+
+$green: #156F78;
+$purple: #493252;
+$red: #E25440;
+$orange: #FB9C50;
+$slate: #3D4260;
+$blue: #2568A3;
+$catred: #BF1827;
+
+$primary-color: $catred;
+$secondary-color: #5BA4E5;
+
+$link-color: $primary-color;
+$hover-color: darken($link-color, 15%);
+
+$sans-font: "ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;
+$serif-font: "ff-tisa-web-pro-1","ff-tisa-web-pro-2","Lucida Grande","Hiragino Sans GB","Hiragino Sans GB W3","Microsoft YaHei","WenQuanYi Micro Hei",sans-serif;
+
+$border-radius: 3px;
\ No newline at end of file
diff --git a/_sass/vno.scss b/_sass/vno.scss
new file mode 100644
index 00000000..8348646d
--- /dev/null
+++ b/_sass/vno.scss
@@ -0,0 +1,47 @@
+@import 'bourbon/bourbon';
+
+// ------------------------------
+// THE BASICS
+// ------------------------------
+
+@import 'reset.scss';
+@import 'grid.scss';
+@import 'variables.scss';
+
+// ------------------------------
+// GLOBAL STYLES
+// ------------------------------
+
+@import 'global.scss';
+
+// ------------------------------
+// SECTIONS
+// ------------------------------
+
+@import 'sections/post.scss';
+
+// ------------------------------
+// COMPONENTS
+// ------------------------------
+
+@import 'components/panels.scss';
+@import 'components/buttons.scss';
+@import 'components/navigation.scss';
+@import 'components/pagination.scss';
+@import 'components/icons.scss';
+@import 'components/post-list.scss';
+@import 'components/forms.scss';
+@import 'components/read-more.scss';
+
+// ------------------------------
+// MEDIA QUERIES
+// ------------------------------
+
+@import 'media-queries.scss';
+
+// ------------------------------
+// EXTRAS
+// ------------------------------
+
+@import 'animate.css';
+@import 'tomorrow.css';
diff --git a/about.md b/about.md
new file mode 100644
index 00000000..d60b73fa
--- /dev/null
+++ b/about.md
@@ -0,0 +1,15 @@
+---
+layout: page
+title: About
+permalink: /about/
+---
+
+This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at [jekyllrb.com](http://jekyllrb.com/)
+
+You can find the source code for the Jekyll new theme at:
+
+[jekyll-new](https://github.com/jglovier/jekyll-new)
+
+You can find the source code for Jekyll at
+
+[jekyll](https://github.com/jekyll/jekyll)
diff --git a/css/animate.css b/css/animate.css
new file mode 100755
index 00000000..492bbbd1
--- /dev/null
+++ b/css/animate.css
@@ -0,0 +1,3432 @@
+.animated{-webkit-animation-fill-mode:both;-moz-animation-fill-mode:both;-ms-animation-fill-mode:both;-o-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:1s;-moz-animation-duration:1s;-ms-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s;}.animated.hinge{-webkit-animation-duration:1s;-moz-animation-duration:1s;-ms-animation-duration:1s;-o-animation-duration:1s;animation-duration:1s;}@-webkit-keyframes flash {
+ 0%, 50%, 100% {opacity: 1;} 25%, 75% {opacity: 0;}
+}
+
+@-moz-keyframes flash {
+ 0%, 50%, 100% {opacity: 1;}
+ 25%, 75% {opacity: 0;}
+}
+
+@-o-keyframes flash {
+ 0%, 50%, 100% {opacity: 1;}
+ 25%, 75% {opacity: 0;}
+}
+
+@keyframes flash {
+ 0%, 50%, 100% {opacity: 1;}
+ 25%, 75% {opacity: 0;}
+}
+
+.flash {
+ -webkit-animation-name: flash;
+ -moz-animation-name: flash;
+ -o-animation-name: flash;
+ animation-name: flash;
+}
+@-webkit-keyframes shake {
+ 0%, 100% {-webkit-transform: translateX(0);}
+ 10%, 30%, 50%, 70%, 90% {-webkit-transform: translateX(-10px);}
+ 20%, 40%, 60%, 80% {-webkit-transform: translateX(10px);}
+}
+
+@-moz-keyframes shake {
+ 0%, 100% {-moz-transform: translateX(0);}
+ 10%, 30%, 50%, 70%, 90% {-moz-transform: translateX(-10px);}
+ 20%, 40%, 60%, 80% {-moz-transform: translateX(10px);}
+}
+
+@-o-keyframes shake {
+ 0%, 100% {-o-transform: translateX(0);}
+ 10%, 30%, 50%, 70%, 90% {-o-transform: translateX(-10px);}
+ 20%, 40%, 60%, 80% {-o-transform: translateX(10px);}
+}
+
+@keyframes shake {
+ 0%, 100% {transform: translateX(0);}
+ 10%, 30%, 50%, 70%, 90% {transform: translateX(-10px);}
+ 20%, 40%, 60%, 80% {transform: translateX(10px);}
+}
+
+.shake {
+ -webkit-animation-name: shake;
+ -moz-animation-name: shake;
+ -o-animation-name: shake;
+ animation-name: shake;
+}
+@-webkit-keyframes bounce {
+ 0%, 20%, 50%, 80%, 100% {-webkit-transform: translateY(0);}
+ 40% {-webkit-transform: translateY(-30px);}
+ 60% {-webkit-transform: translateY(-15px);}
+}
+
+@-moz-keyframes bounce {
+ 0%, 20%, 50%, 80%, 100% {-moz-transform: translateY(0);}
+ 40% {-moz-transform: translateY(-30px);}
+ 60% {-moz-transform: translateY(-15px);}
+}
+
+@-o-keyframes bounce {
+ 0%, 20%, 50%, 80%, 100% {-o-transform: translateY(0);}
+ 40% {-o-transform: translateY(-30px);}
+ 60% {-o-transform: translateY(-15px);}
+}
+@keyframes bounce {
+ 0%, 20%, 50%, 80%, 100% {transform: translateY(0);}
+ 40% {transform: translateY(-30px);}
+ 60% {transform: translateY(-15px);}
+}
+
+.bounce {
+ -webkit-animation-name: bounce;
+ -moz-animation-name: bounce;
+ -o-animation-name: bounce;
+ animation-name: bounce;
+}
+@-webkit-keyframes tada {
+ 0% {-webkit-transform: scale(1);}
+ 10%, 20% {-webkit-transform: scale(0.9) rotate(-3deg);}
+ 30%, 50%, 70%, 90% {-webkit-transform: scale(1.1) rotate(3deg);}
+ 40%, 60%, 80% {-webkit-transform: scale(1.1) rotate(-3deg);}
+ 100% {-webkit-transform: scale(1) rotate(0);}
+}
+
+@-moz-keyframes tada {
+ 0% {-moz-transform: scale(1);}
+ 10%, 20% {-moz-transform: scale(0.9) rotate(-3deg);}
+ 30%, 50%, 70%, 90% {-moz-transform: scale(1.1) rotate(3deg);}
+ 40%, 60%, 80% {-moz-transform: scale(1.1) rotate(-3deg);}
+ 100% {-moz-transform: scale(1) rotate(0);}
+}
+
+@-o-keyframes tada {
+ 0% {-o-transform: scale(1);}
+ 10%, 20% {-o-transform: scale(0.9) rotate(-3deg);}
+ 30%, 50%, 70%, 90% {-o-transform: scale(1.1) rotate(3deg);}
+ 40%, 60%, 80% {-o-transform: scale(1.1) rotate(-3deg);}
+ 100% {-o-transform: scale(1) rotate(0);}
+}
+
+@keyframes tada {
+ 0% {transform: scale(1);}
+ 10%, 20% {transform: scale(0.9) rotate(-3deg);}
+ 30%, 50%, 70%, 90% {transform: scale(1.1) rotate(3deg);}
+ 40%, 60%, 80% {transform: scale(1.1) rotate(-3deg);}
+ 100% {transform: scale(1) rotate(0);}
+}
+
+.tada {
+ -webkit-animation-name: tada;
+ -moz-animation-name: tada;
+ -o-animation-name: tada;
+ animation-name: tada;
+}
+@-webkit-keyframes swing {
+ 20%, 40%, 60%, 80%, 100% { -webkit-transform-origin: top center; }
+ 20% { -webkit-transform: rotate(15deg); }
+ 40% { -webkit-transform: rotate(-10deg); }
+ 60% { -webkit-transform: rotate(5deg); }
+ 80% { -webkit-transform: rotate(-5deg); }
+ 100% { -webkit-transform: rotate(0deg); }
+}
+
+@-moz-keyframes swing {
+ 20% { -moz-transform: rotate(15deg); }
+ 40% { -moz-transform: rotate(-10deg); }
+ 60% { -moz-transform: rotate(5deg); }
+ 80% { -moz-transform: rotate(-5deg); }
+ 100% { -moz-transform: rotate(0deg); }
+}
+
+@-o-keyframes swing {
+ 20% { -o-transform: rotate(15deg); }
+ 40% { -o-transform: rotate(-10deg); }
+ 60% { -o-transform: rotate(5deg); }
+ 80% { -o-transform: rotate(-5deg); }
+ 100% { -o-transform: rotate(0deg); }
+}
+
+@keyframes swing {
+ 20% { transform: rotate(15deg); }
+ 40% { transform: rotate(-10deg); }
+ 60% { transform: rotate(5deg); }
+ 80% { transform: rotate(-5deg); }
+ 100% { transform: rotate(0deg); }
+}
+
+.swing {
+ -webkit-transform-origin: top center;
+ -moz-transform-origin: top center;
+ -o-transform-origin: top center;
+ transform-origin: top center;
+ -webkit-animation-name: swing;
+ -moz-animation-name: swing;
+ -o-animation-name: swing;
+ animation-name: swing;
+}
+/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
+
+@-webkit-keyframes wobble {
+ 0% { -webkit-transform: translateX(0%); }
+ 15% { -webkit-transform: translateX(-25%) rotate(-5deg); }
+ 30% { -webkit-transform: translateX(20%) rotate(3deg); }
+ 45% { -webkit-transform: translateX(-15%) rotate(-3deg); }
+ 60% { -webkit-transform: translateX(10%) rotate(2deg); }
+ 75% { -webkit-transform: translateX(-5%) rotate(-1deg); }
+ 100% { -webkit-transform: translateX(0%); }
+}
+
+@-moz-keyframes wobble {
+ 0% { -moz-transform: translateX(0%); }
+ 15% { -moz-transform: translateX(-25%) rotate(-5deg); }
+ 30% { -moz-transform: translateX(20%) rotate(3deg); }
+ 45% { -moz-transform: translateX(-15%) rotate(-3deg); }
+ 60% { -moz-transform: translateX(10%) rotate(2deg); }
+ 75% { -moz-transform: translateX(-5%) rotate(-1deg); }
+ 100% { -moz-transform: translateX(0%); }
+}
+
+@-o-keyframes wobble {
+ 0% { -o-transform: translateX(0%); }
+ 15% { -o-transform: translateX(-25%) rotate(-5deg); }
+ 30% { -o-transform: translateX(20%) rotate(3deg); }
+ 45% { -o-transform: translateX(-15%) rotate(-3deg); }
+ 60% { -o-transform: translateX(10%) rotate(2deg); }
+ 75% { -o-transform: translateX(-5%) rotate(-1deg); }
+ 100% { -o-transform: translateX(0%); }
+}
+
+@keyframes wobble {
+ 0% { transform: translateX(0%); }
+ 15% { transform: translateX(-25%) rotate(-5deg); }
+ 30% { transform: translateX(20%) rotate(3deg); }
+ 45% { transform: translateX(-15%) rotate(-3deg); }
+ 60% { transform: translateX(10%) rotate(2deg); }
+ 75% { transform: translateX(-5%) rotate(-1deg); }
+ 100% { transform: translateX(0%); }
+}
+
+.wobble {
+ -webkit-animation-name: wobble;
+ -moz-animation-name: wobble;
+ -o-animation-name: wobble;
+ animation-name: wobble;
+}
+/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
+
+@-webkit-keyframes pulse {
+ 0% { -webkit-transform: scale(1); }
+ 50% { -webkit-transform: scale(1.1); }
+ 100% { -webkit-transform: scale(1); }
+}
+@-moz-keyframes pulse {
+ 0% { -moz-transform: scale(1); }
+ 50% { -moz-transform: scale(1.1); }
+ 100% { -moz-transform: scale(1); }
+}
+@-o-keyframes pulse {
+ 0% { -o-transform: scale(1); }
+ 50% { -o-transform: scale(1.1); }
+ 100% { -o-transform: scale(1); }
+}
+@keyframes pulse {
+ 0% { transform: scale(1); }
+ 50% { transform: scale(1.1); }
+ 100% { transform: scale(1); }
+}
+
+.pulse {
+ -webkit-animation-name: pulse;
+ -moz-animation-name: pulse;
+ -o-animation-name: pulse;
+ animation-name: pulse;
+}
+@-webkit-keyframes flip {
+ 0% {
+ -webkit-transform: perspective(400px) translateZ(0) rotateY(0) scale(1);
+ -webkit-animation-timing-function: ease-out;
+ }
+ 40% {
+ -webkit-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1);
+ -webkit-animation-timing-function: ease-out;
+ }
+ 50% {
+ -webkit-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1);
+ -webkit-animation-timing-function: ease-in;
+ }
+ 80% {
+ -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95);
+ -webkit-animation-timing-function: ease-in;
+ }
+ 100% {
+ -webkit-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1);
+ -webkit-animation-timing-function: ease-in;
+ }
+}
+@-moz-keyframes flip {
+ 0% {
+ -moz-transform: perspective(400px) translateZ(0) rotateY(0) scale(1);
+ -moz-animation-timing-function: ease-out;
+ }
+ 40% {
+ -moz-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1);
+ -moz-animation-timing-function: ease-out;
+ }
+ 50% {
+ -moz-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1);
+ -moz-animation-timing-function: ease-in;
+ }
+ 80% {
+ -moz-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95);
+ -moz-animation-timing-function: ease-in;
+ }
+ 100% {
+ -moz-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1);
+ -moz-animation-timing-function: ease-in;
+ }
+}
+@-o-keyframes flip {
+ 0% {
+ -o-transform: perspective(400px) translateZ(0) rotateY(0) scale(1);
+ -o-animation-timing-function: ease-out;
+ }
+ 40% {
+ -o-transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1);
+ -o-animation-timing-function: ease-out;
+ }
+ 50% {
+ -o-transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1);
+ -o-animation-timing-function: ease-in;
+ }
+ 80% {
+ -o-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95);
+ -o-animation-timing-function: ease-in;
+ }
+ 100% {
+ -o-transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1);
+ -o-animation-timing-function: ease-in;
+ }
+}
+@keyframes flip {
+ 0% {
+ transform: perspective(400px) translateZ(0) rotateY(0) scale(1);
+ animation-timing-function: ease-out;
+ }
+ 40% {
+ transform: perspective(400px) translateZ(150px) rotateY(170deg) scale(1);
+ animation-timing-function: ease-out;
+ }
+ 50% {
+ transform: perspective(400px) translateZ(150px) rotateY(190deg) scale(1);
+ animation-timing-function: ease-in;
+ }
+ 80% {
+ transform: perspective(400px) translateZ(0) rotateY(360deg) scale(.95);
+ animation-timing-function: ease-in;
+ }
+ 100% {
+ transform: perspective(400px) translateZ(0) rotateY(360deg) scale(1);
+ animation-timing-function: ease-in;
+ }
+}
+
+.animated.flip {
+ -webkit-backface-visibility: visible !important;
+ -webkit-animation-name: flip;
+ -moz-backface-visibility: visible !important;
+ -moz-animation-name: flip;
+ -o-backface-visibility: visible !important;
+ -o-animation-name: flip;
+ backface-visibility: visible !important;
+ animation-name: flip;
+}
+
+@-webkit-keyframes flipInX {
+ 0% {
+ -webkit-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -webkit-transform: perspective(400px) rotateX(-10deg);
+ }
+
+ 70% {
+ -webkit-transform: perspective(400px) rotateX(10deg);
+ }
+
+ 100% {
+ -webkit-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+}
+@-moz-keyframes flipInX {
+ 0% {
+ -moz-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -moz-transform: perspective(400px) rotateX(-10deg);
+ }
+
+ 70% {
+ -moz-transform: perspective(400px) rotateX(10deg);
+ }
+
+ 100% {
+ -moz-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+}
+@-o-keyframes flipInX {
+ 0% {
+ -o-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -o-transform: perspective(400px) rotateX(-10deg);
+ }
+
+ 70% {
+ -o-transform: perspective(400px) rotateX(10deg);
+ }
+
+ 100% {
+ -o-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+}
+@keyframes flipInX {
+ 0% {
+ transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ transform: perspective(400px) rotateX(-10deg);
+ }
+
+ 70% {
+ transform: perspective(400px) rotateX(10deg);
+ }
+
+ 100% {
+ transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+}
+
+.flipInX {
+ -webkit-backface-visibility: visible !important;
+ -webkit-animation-name: flipInX;
+ -moz-backface-visibility: visible !important;
+ -moz-animation-name: flipInX;
+ -o-backface-visibility: visible !important;
+ -o-animation-name: flipInX;
+ backface-visibility: visible !important;
+ animation-name: flipInX;
+}
+@-webkit-keyframes flipOutX {
+ 0% {
+ -webkit-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -webkit-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes flipOutX {
+ 0% {
+ -moz-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -moz-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes flipOutX {
+ 0% {
+ -o-transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -o-transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+}
+
+@keyframes flipOutX {
+ 0% {
+ transform: perspective(400px) rotateX(0deg);
+ opacity: 1;
+ }
+ 100% {
+ transform: perspective(400px) rotateX(90deg);
+ opacity: 0;
+ }
+}
+
+.flipOutX {
+ -webkit-animation-name: flipOutX;
+ -webkit-backface-visibility: visible !important;
+ -moz-animation-name: flipOutX;
+ -moz-backface-visibility: visible !important;
+ -o-animation-name: flipOutX;
+ -o-backface-visibility: visible !important;
+ animation-name: flipOutX;
+ backface-visibility: visible !important;
+}
+@-webkit-keyframes flipInY {
+ 0% {
+ -webkit-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -webkit-transform: perspective(400px) rotateY(-10deg);
+ }
+
+ 70% {
+ -webkit-transform: perspective(400px) rotateY(10deg);
+ }
+
+ 100% {
+ -webkit-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+}
+@-moz-keyframes flipInY {
+ 0% {
+ -moz-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -moz-transform: perspective(400px) rotateY(-10deg);
+ }
+
+ 70% {
+ -moz-transform: perspective(400px) rotateY(10deg);
+ }
+
+ 100% {
+ -moz-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+}
+@-o-keyframes flipInY {
+ 0% {
+ -o-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ -o-transform: perspective(400px) rotateY(-10deg);
+ }
+
+ 70% {
+ -o-transform: perspective(400px) rotateY(10deg);
+ }
+
+ 100% {
+ -o-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+}
+@keyframes flipInY {
+ 0% {
+ transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+
+ 40% {
+ transform: perspective(400px) rotateY(-10deg);
+ }
+
+ 70% {
+ transform: perspective(400px) rotateY(10deg);
+ }
+
+ 100% {
+ transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+}
+
+.flipInY {
+ -webkit-backface-visibility: visible !important;
+ -webkit-animation-name: flipInY;
+ -moz-backface-visibility: visible !important;
+ -moz-animation-name: flipInY;
+ -o-backface-visibility: visible !important;
+ -o-animation-name: flipInY;
+ backface-visibility: visible !important;
+ animation-name: flipInY;
+}
+@-webkit-keyframes flipOutY {
+ 0% {
+ -webkit-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -webkit-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+}
+@-moz-keyframes flipOutY {
+ 0% {
+ -moz-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -moz-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+}
+@-o-keyframes flipOutY {
+ 0% {
+ -o-transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+ 100% {
+ -o-transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+}
+@keyframes flipOutY {
+ 0% {
+ transform: perspective(400px) rotateY(0deg);
+ opacity: 1;
+ }
+ 100% {
+ transform: perspective(400px) rotateY(90deg);
+ opacity: 0;
+ }
+}
+
+.flipOutY {
+ -webkit-backface-visibility: visible !important;
+ -webkit-animation-name: flipOutY;
+ -moz-backface-visibility: visible !important;
+ -moz-animation-name: flipOutY;
+ -o-backface-visibility: visible !important;
+ -o-animation-name: flipOutY;
+ backface-visibility: visible !important;
+ animation-name: flipOutY;
+}
+@-webkit-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@-moz-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@-o-keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+@keyframes fadeIn {
+ 0% {opacity: 0;}
+ 100% {opacity: 1;}
+}
+
+.fadeIn {
+ -webkit-animation-name: fadeIn;
+ -moz-animation-name: fadeIn;
+ -o-animation-name: fadeIn;
+ animation-name: fadeIn;
+}
+@-webkit-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInUp {
+ 0% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInUp {
+ -webkit-animation-name: fadeInUp;
+ -moz-animation-name: fadeInUp;
+ -o-animation-name: fadeInUp;
+ animation-name: fadeInUp;
+}
+@-webkit-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInDown {
+ 0% {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInDown {
+ -webkit-animation-name: fadeInDown;
+ -moz-animation-name: fadeInDown;
+ -o-animation-name: fadeInDown;
+ animation-name: fadeInDown;
+}
+@-webkit-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes fadeInLeft {
+ 0% {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInLeft {
+ -webkit-animation-name: fadeInLeft;
+ -moz-animation-name: fadeInLeft;
+ -o-animation-name: fadeInLeft;
+ animation-name: fadeInLeft;
+}
+@-webkit-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes fadeInRight {
+ 0% {
+ opacity: 0;
+ transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInRight {
+ -webkit-animation-name: fadeInRight;
+ -moz-animation-name: fadeInRight;
+ -o-animation-name: fadeInRight;
+ animation-name: fadeInRight;
+}
+@-webkit-keyframes fadeInUpBig {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInUpBig {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInUpBig {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInUpBig {
+ 0% {
+ opacity: 0;
+ transform: translateY(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInUpBig {
+ -webkit-animation-name: fadeInUpBig;
+ -moz-animation-name: fadeInUpBig;
+ -o-animation-name: fadeInUpBig;
+ animation-name: fadeInUpBig;
+}
+@-webkit-keyframes fadeInDownBig {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes fadeInDownBig {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes fadeInDownBig {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes fadeInDownBig {
+ 0% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.fadeInDownBig {
+ -webkit-animation-name: fadeInDownBig;
+ -moz-animation-name: fadeInDownBig;
+ -o-animation-name: fadeInDownBig;
+ animation-name: fadeInDownBig;
+}
+@-webkit-keyframes fadeInLeftBig {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+@-moz-keyframes fadeInLeftBig {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+@-o-keyframes fadeInLeftBig {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+@keyframes fadeInLeftBig {
+ 0% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInLeftBig {
+ -webkit-animation-name: fadeInLeftBig;
+ -moz-animation-name: fadeInLeftBig;
+ -o-animation-name: fadeInLeftBig;
+ animation-name: fadeInLeftBig;
+}
+@-webkit-keyframes fadeInRightBig {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes fadeInRightBig {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes fadeInRightBig {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes fadeInRightBig {
+ 0% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+
+ 100% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+}
+
+.fadeInRightBig {
+ -webkit-animation-name: fadeInRightBig;
+ -moz-animation-name: fadeInRightBig;
+ -o-animation-name: fadeInRightBig;
+ animation-name: fadeInRightBig;
+}
+@-webkit-keyframes fadeOut {
+ 0% {opacity: 1;}
+ 100% {opacity: 0;}
+}
+
+@-moz-keyframes fadeOut {
+ 0% {opacity: 1;}
+ 100% {opacity: 0;}
+}
+
+@-o-keyframes fadeOut {
+ 0% {opacity: 1;}
+ 100% {opacity: 0;}
+}
+
+@keyframes fadeOut {
+ 0% {opacity: 1;}
+ 100% {opacity: 0;}
+}
+
+.fadeOut {
+ -webkit-animation-name: fadeOut;
+ -moz-animation-name: fadeOut;
+ -o-animation-name: fadeOut;
+ animation-name: fadeOut;
+}
+@-webkit-keyframes fadeOutUp {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(-20px);
+ }
+}
+@-moz-keyframes fadeOutUp {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(-20px);
+ }
+}
+@-o-keyframes fadeOutUp {
+ 0% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(-20px);
+ }
+}
+@keyframes fadeOutUp {
+ 0% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-20px);
+ }
+}
+
+.fadeOutUp {
+ -webkit-animation-name: fadeOutUp;
+ -moz-animation-name: fadeOutUp;
+ -o-animation-name: fadeOutUp;
+ animation-name: fadeOutUp;
+}
+@-webkit-keyframes fadeOutDown {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(20px);
+ }
+}
+
+@-moz-keyframes fadeOutDown {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(20px);
+ }
+}
+
+@-o-keyframes fadeOutDown {
+ 0% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(20px);
+ }
+}
+
+@keyframes fadeOutDown {
+ 0% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(20px);
+ }
+}
+
+.fadeOutDown {
+ -webkit-animation-name: fadeOutDown;
+ -moz-animation-name: fadeOutDown;
+ -o-animation-name: fadeOutDown;
+ animation-name: fadeOutDown;
+}
+@-webkit-keyframes fadeOutLeft {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(-20px);
+ }
+}
+
+@-moz-keyframes fadeOutLeft {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(-20px);
+ }
+}
+
+@-o-keyframes fadeOutLeft {
+ 0% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(-20px);
+ }
+}
+
+@keyframes fadeOutLeft {
+ 0% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(-20px);
+ }
+}
+
+.fadeOutLeft {
+ -webkit-animation-name: fadeOutLeft;
+ -moz-animation-name: fadeOutLeft;
+ -o-animation-name: fadeOutLeft;
+ animation-name: fadeOutLeft;
+}
+@-webkit-keyframes fadeOutRight {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(20px);
+ }
+}
+
+@-moz-keyframes fadeOutRight {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(20px);
+ }
+}
+
+@-o-keyframes fadeOutRight {
+ 0% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(20px);
+ }
+}
+
+@keyframes fadeOutRight {
+ 0% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(20px);
+ }
+}
+
+.fadeOutRight {
+ -webkit-animation-name: fadeOutRight;
+ -moz-animation-name: fadeOutRight;
+ -o-animation-name: fadeOutRight;
+ animation-name: fadeOutRight;
+}
+@-webkit-keyframes fadeOutUpBig {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+}
+
+@-moz-keyframes fadeOutUpBig {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+}
+
+@-o-keyframes fadeOutUpBig {
+ 0% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+}
+
+@keyframes fadeOutUpBig {
+ 0% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+}
+
+.fadeOutUpBig {
+ -webkit-animation-name: fadeOutUpBig;
+ -moz-animation-name: fadeOutUpBig;
+ -o-animation-name: fadeOutUpBig;
+ animation-name: fadeOutUpBig;
+}
+@-webkit-keyframes fadeOutDownBig {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(2000px);
+ }
+}
+
+@-moz-keyframes fadeOutDownBig {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(2000px);
+ }
+}
+
+@-o-keyframes fadeOutDownBig {
+ 0% {
+ opacity: 1;
+ -o-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(2000px);
+ }
+}
+
+@keyframes fadeOutDownBig {
+ 0% {
+ opacity: 1;
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(2000px);
+ }
+}
+
+.fadeOutDownBig {
+ -webkit-animation-name: fadeOutDownBig;
+ -moz-animation-name: fadeOutDownBig;
+ -o-animation-name: fadeOutDownBig;
+ animation-name: fadeOutDownBig;
+}
+@-webkit-keyframes fadeOutLeftBig {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+}
+
+@-moz-keyframes fadeOutLeftBig {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+}
+
+@-o-keyframes fadeOutLeftBig {
+ 0% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+}
+
+@keyframes fadeOutLeftBig {
+ 0% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+}
+
+.fadeOutLeftBig {
+ -webkit-animation-name: fadeOutLeftBig;
+ -moz-animation-name: fadeOutLeftBig;
+ -o-animation-name: fadeOutLeftBig;
+ animation-name: fadeOutLeftBig;
+}
+@-webkit-keyframes fadeOutRightBig {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+}
+@-moz-keyframes fadeOutRightBig {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+}
+@-o-keyframes fadeOutRightBig {
+ 0% {
+ opacity: 1;
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+}
+@keyframes fadeOutRightBig {
+ 0% {
+ opacity: 1;
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+}
+
+.fadeOutRightBig {
+ -webkit-animation-name: fadeOutRightBig;
+ -moz-animation-name: fadeOutRightBig;
+ -o-animation-name: fadeOutRightBig;
+ animation-name: fadeOutRightBig;
+}
+@-webkit-keyframes slideInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+
+ 100% {
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes slideInDown {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+
+ 100% {
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes slideInDown {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+
+ 100% {
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes slideInDown {
+ 0% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+
+ 100% {
+ transform: translateY(0);
+ }
+}
+
+.slideInDown {
+ -webkit-animation-name: slideInDown;
+ -moz-animation-name: slideInDown;
+ -o-animation-name: slideInDown;
+ animation-name: slideInDown;
+}
+@-webkit-keyframes slideInLeft {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+
+ 100% {
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes slideInLeft {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+
+ 100% {
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes slideInLeft {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+
+ 100% {
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes slideInLeft {
+ 0% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+.slideInLeft {
+ -webkit-animation-name: slideInLeft;
+ -moz-animation-name: slideInLeft;
+ -o-animation-name: slideInLeft;
+ animation-name: slideInLeft;
+}
+@-webkit-keyframes slideInRight {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+
+ 100% {
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes slideInRight {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+
+ 100% {
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes slideInRight {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+
+ 100% {
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes slideInRight {
+ 0% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+.slideInRight {
+ -webkit-animation-name: slideInRight;
+ -moz-animation-name: slideInRight;
+ -o-animation-name: slideInRight;
+ animation-name: slideInRight;
+}
+@-webkit-keyframes slideOutUp {
+ 0% {
+ -webkit-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+}
+
+@-moz-keyframes slideOutUp {
+ 0% {
+ -moz-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+}
+
+@-o-keyframes slideOutUp {
+ 0% {
+ -o-transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+}
+
+@keyframes slideOutUp {
+ 0% {
+ transform: translateY(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+}
+
+.slideOutUp {
+ -webkit-animation-name: slideOutUp;
+ -moz-animation-name: slideOutUp;
+ -o-animation-name: slideOutUp;
+ animation-name: slideOutUp;
+}
+@-webkit-keyframes slideOutLeft {
+ 0% {
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+}
+
+@-moz-keyframes slideOutLeft {
+ 0% {
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+}
+
+@-o-keyframes slideOutLeft {
+ 0% {
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+}
+
+@keyframes slideOutLeft {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+}
+
+.slideOutLeft {
+ -webkit-animation-name: slideOutLeft;
+ -moz-animation-name: slideOutLeft;
+ -o-animation-name: slideOutLeft;
+ animation-name: slideOutLeft;
+}
+@-webkit-keyframes slideOutRight {
+ 0% {
+ -webkit-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+}
+
+@-moz-keyframes slideOutRight {
+ 0% {
+ -moz-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+}
+
+@-o-keyframes slideOutRight {
+ 0% {
+ -o-transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+}
+
+@keyframes slideOutRight {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+}
+
+.slideOutRight {
+ -webkit-animation-name: slideOutRight;
+ -moz-animation-name: slideOutRight;
+ -o-animation-name: slideOutRight;
+ animation-name: slideOutRight;
+}
+@-webkit-keyframes bounceIn {
+ 0% {
+ opacity: 0;
+ -webkit-transform: scale(.3);
+ }
+
+ 50% {
+ opacity: 1;
+ -webkit-transform: scale(1.05);
+ }
+
+ 70% {
+ -webkit-transform: scale(.9);
+ }
+
+ 100% {
+ -webkit-transform: scale(1);
+ }
+}
+
+@-moz-keyframes bounceIn {
+ 0% {
+ opacity: 0;
+ -moz-transform: scale(.3);
+ }
+
+ 50% {
+ opacity: 1;
+ -moz-transform: scale(1.05);
+ }
+
+ 70% {
+ -moz-transform: scale(.9);
+ }
+
+ 100% {
+ -moz-transform: scale(1);
+ }
+}
+
+@-o-keyframes bounceIn {
+ 0% {
+ opacity: 0;
+ -o-transform: scale(.3);
+ }
+
+ 50% {
+ opacity: 1;
+ -o-transform: scale(1.05);
+ }
+
+ 70% {
+ -o-transform: scale(.9);
+ }
+
+ 100% {
+ -o-transform: scale(1);
+ }
+}
+
+@keyframes bounceIn {
+ 0% {
+ opacity: 0;
+ transform: scale(.3);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: scale(1.05);
+ }
+
+ 70% {
+ transform: scale(.9);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+.bounceIn {
+ -webkit-animation-name: bounceIn;
+ -moz-animation-name: bounceIn;
+ -o-animation-name: bounceIn;
+ animation-name: bounceIn;
+}
+@-webkit-keyframes bounceInUp {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -webkit-transform: translateY(-30px);
+ }
+
+ 80% {
+ -webkit-transform: translateY(10px);
+ }
+
+ 100% {
+ -webkit-transform: translateY(0);
+ }
+}
+@-moz-keyframes bounceInUp {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -moz-transform: translateY(-30px);
+ }
+
+ 80% {
+ -moz-transform: translateY(10px);
+ }
+
+ 100% {
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes bounceInUp {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -o-transform: translateY(-30px);
+ }
+
+ 80% {
+ -o-transform: translateY(10px);
+ }
+
+ 100% {
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes bounceInUp {
+ 0% {
+ opacity: 0;
+ transform: translateY(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translateY(-30px);
+ }
+
+ 80% {
+ transform: translateY(10px);
+ }
+
+ 100% {
+ transform: translateY(0);
+ }
+}
+
+.bounceInUp {
+ -webkit-animation-name: bounceInUp;
+ -moz-animation-name: bounceInUp;
+ -o-animation-name: bounceInUp;
+ animation-name: bounceInUp;
+}
+@-webkit-keyframes bounceInDown {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -webkit-transform: translateY(30px);
+ }
+
+ 80% {
+ -webkit-transform: translateY(-10px);
+ }
+
+ 100% {
+ -webkit-transform: translateY(0);
+ }
+}
+
+@-moz-keyframes bounceInDown {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -moz-transform: translateY(30px);
+ }
+
+ 80% {
+ -moz-transform: translateY(-10px);
+ }
+
+ 100% {
+ -moz-transform: translateY(0);
+ }
+}
+
+@-o-keyframes bounceInDown {
+ 0% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -o-transform: translateY(30px);
+ }
+
+ 80% {
+ -o-transform: translateY(-10px);
+ }
+
+ 100% {
+ -o-transform: translateY(0);
+ }
+}
+
+@keyframes bounceInDown {
+ 0% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translateY(30px);
+ }
+
+ 80% {
+ transform: translateY(-10px);
+ }
+
+ 100% {
+ transform: translateY(0);
+ }
+}
+
+.bounceInDown {
+ -webkit-animation-name: bounceInDown;
+ -moz-animation-name: bounceInDown;
+ -o-animation-name: bounceInDown;
+ animation-name: bounceInDown;
+}
+@-webkit-keyframes bounceInLeft {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -webkit-transform: translateX(30px);
+ }
+
+ 80% {
+ -webkit-transform: translateX(-10px);
+ }
+
+ 100% {
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes bounceInLeft {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -moz-transform: translateX(30px);
+ }
+
+ 80% {
+ -moz-transform: translateX(-10px);
+ }
+
+ 100% {
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes bounceInLeft {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -o-transform: translateX(30px);
+ }
+
+ 80% {
+ -o-transform: translateX(-10px);
+ }
+
+ 100% {
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes bounceInLeft {
+ 0% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translateX(30px);
+ }
+
+ 80% {
+ transform: translateX(-10px);
+ }
+
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+.bounceInLeft {
+ -webkit-animation-name: bounceInLeft;
+ -moz-animation-name: bounceInLeft;
+ -o-animation-name: bounceInLeft;
+ animation-name: bounceInLeft;
+}
+@-webkit-keyframes bounceInRight {
+ 0% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -webkit-transform: translateX(-30px);
+ }
+
+ 80% {
+ -webkit-transform: translateX(10px);
+ }
+
+ 100% {
+ -webkit-transform: translateX(0);
+ }
+}
+
+@-moz-keyframes bounceInRight {
+ 0% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -moz-transform: translateX(-30px);
+ }
+
+ 80% {
+ -moz-transform: translateX(10px);
+ }
+
+ 100% {
+ -moz-transform: translateX(0);
+ }
+}
+
+@-o-keyframes bounceInRight {
+ 0% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ -o-transform: translateX(-30px);
+ }
+
+ 80% {
+ -o-transform: translateX(10px);
+ }
+
+ 100% {
+ -o-transform: translateX(0);
+ }
+}
+
+@keyframes bounceInRight {
+ 0% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+
+ 60% {
+ opacity: 1;
+ transform: translateX(-30px);
+ }
+
+ 80% {
+ transform: translateX(10px);
+ }
+
+ 100% {
+ transform: translateX(0);
+ }
+}
+
+.bounceInRight {
+ -webkit-animation-name: bounceInRight;
+ -moz-animation-name: bounceInRight;
+ -o-animation-name: bounceInRight;
+ animation-name: bounceInRight;
+}
+@-webkit-keyframes bounceOut {
+ 0% {
+ -webkit-transform: scale(1);
+ }
+
+ 25% {
+ -webkit-transform: scale(.95);
+ }
+
+ 50% {
+ opacity: 1;
+ -webkit-transform: scale(1.1);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: scale(.3);
+ }
+}
+
+@-moz-keyframes bounceOut {
+ 0% {
+ -moz-transform: scale(1);
+ }
+
+ 25% {
+ -moz-transform: scale(.95);
+ }
+
+ 50% {
+ opacity: 1;
+ -moz-transform: scale(1.1);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: scale(.3);
+ }
+}
+
+@-o-keyframes bounceOut {
+ 0% {
+ -o-transform: scale(1);
+ }
+
+ 25% {
+ -o-transform: scale(.95);
+ }
+
+ 50% {
+ opacity: 1;
+ -o-transform: scale(1.1);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: scale(.3);
+ }
+}
+
+@keyframes bounceOut {
+ 0% {
+ transform: scale(1);
+ }
+
+ 25% {
+ transform: scale(.95);
+ }
+
+ 50% {
+ opacity: 1;
+ transform: scale(1.1);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: scale(.3);
+ }
+}
+
+.bounceOut {
+ -webkit-animation-name: bounceOut;
+ -moz-animation-name: bounceOut;
+ -o-animation-name: bounceOut;
+ animation-name: bounceOut;
+}
+@-webkit-keyframes bounceOutUp {
+ 0% {
+ -webkit-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -webkit-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(-2000px);
+ }
+}
+
+@-moz-keyframes bounceOutUp {
+ 0% {
+ -moz-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -moz-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(-2000px);
+ }
+}
+
+@-o-keyframes bounceOutUp {
+ 0% {
+ -o-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -o-transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(-2000px);
+ }
+}
+
+@keyframes bounceOutUp {
+ 0% {
+ transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ transform: translateY(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(-2000px);
+ }
+}
+
+.bounceOutUp {
+ -webkit-animation-name: bounceOutUp;
+ -moz-animation-name: bounceOutUp;
+ -o-animation-name: bounceOutUp;
+ animation-name: bounceOutUp;
+}
+@-webkit-keyframes bounceOutDown {
+ 0% {
+ -webkit-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -webkit-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateY(2000px);
+ }
+}
+
+@-moz-keyframes bounceOutDown {
+ 0% {
+ -moz-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -moz-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateY(2000px);
+ }
+}
+
+@-o-keyframes bounceOutDown {
+ 0% {
+ -o-transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -o-transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateY(2000px);
+ }
+}
+
+@keyframes bounceOutDown {
+ 0% {
+ transform: translateY(0);
+ }
+
+ 20% {
+ opacity: 1;
+ transform: translateY(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateY(2000px);
+ }
+}
+
+.bounceOutDown {
+ -webkit-animation-name: bounceOutDown;
+ -moz-animation-name: bounceOutDown;
+ -o-animation-name: bounceOutDown;
+ animation-name: bounceOutDown;
+}
+@-webkit-keyframes bounceOutLeft {
+ 0% {
+ -webkit-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -webkit-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(-2000px);
+ }
+}
+
+@-moz-keyframes bounceOutLeft {
+ 0% {
+ -moz-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -moz-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(-2000px);
+ }
+}
+
+@-o-keyframes bounceOutLeft {
+ 0% {
+ -o-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -o-transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(-2000px);
+ }
+}
+
+@keyframes bounceOutLeft {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ transform: translateX(20px);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(-2000px);
+ }
+}
+
+.bounceOutLeft {
+ -webkit-animation-name: bounceOutLeft;
+ -moz-animation-name: bounceOutLeft;
+ -o-animation-name: bounceOutLeft;
+ animation-name: bounceOutLeft;
+}
+@-webkit-keyframes bounceOutRight {
+ 0% {
+ -webkit-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -webkit-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(2000px);
+ }
+}
+
+@-moz-keyframes bounceOutRight {
+ 0% {
+ -moz-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -moz-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(2000px);
+ }
+}
+
+@-o-keyframes bounceOutRight {
+ 0% {
+ -o-transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ -o-transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(2000px);
+ }
+}
+
+@keyframes bounceOutRight {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 20% {
+ opacity: 1;
+ transform: translateX(-20px);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(2000px);
+ }
+}
+
+.bounceOutRight {
+ -webkit-animation-name: bounceOutRight;
+ -moz-animation-name: bounceOutRight;
+ -o-animation-name: bounceOutRight;
+ animation-name: bounceOutRight;
+}
+@-webkit-keyframes rotateIn {
+ 0% {
+ -webkit-transform-origin: center center;
+ -webkit-transform: rotate(-200deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -webkit-transform-origin: center center;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+}
+@-moz-keyframes rotateIn {
+ 0% {
+ -moz-transform-origin: center center;
+ -moz-transform: rotate(-200deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -moz-transform-origin: center center;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+}
+@-o-keyframes rotateIn {
+ 0% {
+ -o-transform-origin: center center;
+ -o-transform: rotate(-200deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -o-transform-origin: center center;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+}
+@keyframes rotateIn {
+ 0% {
+ transform-origin: center center;
+ transform: rotate(-200deg);
+ opacity: 0;
+ }
+
+ 100% {
+ transform-origin: center center;
+ transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+.rotateIn {
+ -webkit-animation-name: rotateIn;
+ -moz-animation-name: rotateIn;
+ -o-animation-name: rotateIn;
+ animation-name: rotateIn;
+}
+@-webkit-keyframes rotateInUpLeft {
+ 0% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-moz-keyframes rotateInUpLeft {
+ 0% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-o-keyframes rotateInUpLeft {
+ 0% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@keyframes rotateInUpLeft {
+ 0% {
+ transform-origin: left bottom;
+ transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ transform-origin: left bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+.rotateInUpLeft {
+ -webkit-animation-name: rotateInUpLeft;
+ -moz-animation-name: rotateInUpLeft;
+ -o-animation-name: rotateInUpLeft;
+ animation-name: rotateInUpLeft;
+}
+@-webkit-keyframes rotateInDownLeft {
+ 0% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-moz-keyframes rotateInDownLeft {
+ 0% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-o-keyframes rotateInDownLeft {
+ 0% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@keyframes rotateInDownLeft {
+ 0% {
+ transform-origin: left bottom;
+ transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ transform-origin: left bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+.rotateInDownLeft {
+ -webkit-animation-name: rotateInDownLeft;
+ -moz-animation-name: rotateInDownLeft;
+ -o-animation-name: rotateInDownLeft;
+ animation-name: rotateInDownLeft;
+}
+@-webkit-keyframes rotateInUpRight {
+ 0% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-moz-keyframes rotateInUpRight {
+ 0% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-o-keyframes rotateInUpRight {
+ 0% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@keyframes rotateInUpRight {
+ 0% {
+ transform-origin: right bottom;
+ transform: rotate(-90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ transform-origin: right bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+.rotateInUpRight {
+ -webkit-animation-name: rotateInUpRight;
+ -moz-animation-name: rotateInUpRight;
+ -o-animation-name: rotateInUpRight;
+ animation-name: rotateInUpRight;
+}
+@-webkit-keyframes rotateInDownRight {
+ 0% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-moz-keyframes rotateInDownRight {
+ 0% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@-o-keyframes rotateInDownRight {
+ 0% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+@keyframes rotateInDownRight {
+ 0% {
+ transform-origin: right bottom;
+ transform: rotate(90deg);
+ opacity: 0;
+ }
+
+ 100% {
+ transform-origin: right bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+}
+
+.rotateInDownRight {
+ -webkit-animation-name: rotateInDownRight;
+ -moz-animation-name: rotateInDownRight;
+ -o-animation-name: rotateInDownRight;
+ animation-name: rotateInDownRight;
+}
+@-webkit-keyframes rotateOut {
+ 0% {
+ -webkit-transform-origin: center center;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -webkit-transform-origin: center center;
+ -webkit-transform: rotate(200deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes rotateOut {
+ 0% {
+ -moz-transform-origin: center center;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -moz-transform-origin: center center;
+ -moz-transform: rotate(200deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes rotateOut {
+ 0% {
+ -o-transform-origin: center center;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -o-transform-origin: center center;
+ -o-transform: rotate(200deg);
+ opacity: 0;
+ }
+}
+
+@keyframes rotateOut {
+ 0% {
+ transform-origin: center center;
+ transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ transform-origin: center center;
+ transform: rotate(200deg);
+ opacity: 0;
+ }
+}
+
+.rotateOut {
+ -webkit-animation-name: rotateOut;
+ -moz-animation-name: rotateOut;
+ -o-animation-name: rotateOut;
+ animation-name: rotateOut;
+}
+@-webkit-keyframes rotateOutUpLeft {
+ 0% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes rotateOutUpLeft {
+ 0% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes rotateOutUpLeft {
+ 0% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@keyframes rotateOutUpLeft {
+ 0% {
+ transform-origin: left bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -transform-origin: left bottom;
+ -transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+.rotateOutUpLeft {
+ -webkit-animation-name: rotateOutUpLeft;
+ -moz-animation-name: rotateOutUpLeft;
+ -o-animation-name: rotateOutUpLeft;
+ animation-name: rotateOutUpLeft;
+}
+@-webkit-keyframes rotateOutDownLeft {
+ 0% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -webkit-transform-origin: left bottom;
+ -webkit-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes rotateOutDownLeft {
+ 0% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -moz-transform-origin: left bottom;
+ -moz-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes rotateOutDownLeft {
+ 0% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -o-transform-origin: left bottom;
+ -o-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@keyframes rotateOutDownLeft {
+ 0% {
+ transform-origin: left bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ transform-origin: left bottom;
+ transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+.rotateOutDownLeft {
+ -webkit-animation-name: rotateOutDownLeft;
+ -moz-animation-name: rotateOutDownLeft;
+ -o-animation-name: rotateOutDownLeft;
+ animation-name: rotateOutDownLeft;
+}
+@-webkit-keyframes rotateOutUpRight {
+ 0% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes rotateOutUpRight {
+ 0% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes rotateOutUpRight {
+ 0% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+@keyframes rotateOutUpRight {
+ 0% {
+ transform-origin: right bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ transform-origin: right bottom;
+ transform: rotate(90deg);
+ opacity: 0;
+ }
+}
+
+.rotateOutUpRight {
+ -webkit-animation-name: rotateOutUpRight;
+ -moz-animation-name: rotateOutUpRight;
+ -o-animation-name: rotateOutUpRight;
+ animation-name: rotateOutUpRight;
+}
+@-webkit-keyframes rotateOutDownRight {
+ 0% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -webkit-transform-origin: right bottom;
+ -webkit-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@-moz-keyframes rotateOutDownRight {
+ 0% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -moz-transform-origin: right bottom;
+ -moz-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@-o-keyframes rotateOutDownRight {
+ 0% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ -o-transform-origin: right bottom;
+ -o-transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+@keyframes rotateOutDownRight {
+ 0% {
+ transform-origin: right bottom;
+ transform: rotate(0);
+ opacity: 1;
+ }
+
+ 100% {
+ transform-origin: right bottom;
+ transform: rotate(-90deg);
+ opacity: 0;
+ }
+}
+
+.rotateOutDownRight {
+ -webkit-animation-name: rotateOutDownRight;
+ -moz-animation-name: rotateOutDownRight;
+ -o-animation-name: rotateOutDownRight;
+ animation-name: rotateOutDownRight;
+}
+@-webkit-keyframes lightSpeedIn {
+ 0% { -webkit-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+ 60% { -webkit-transform: translateX(-20%) skewX(30deg); opacity: 1; }
+ 80% { -webkit-transform: translateX(0%) skewX(-15deg); opacity: 1; }
+ 100% { -webkit-transform: translateX(0%) skewX(0deg); opacity: 1; }
+}
+
+@-moz-keyframes lightSpeedIn {
+ 0% { -moz-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+ 60% { -moz-transform: translateX(-20%) skewX(30deg); opacity: 1; }
+ 80% { -moz-transform: translateX(0%) skewX(-15deg); opacity: 1; }
+ 100% { -moz-transform: translateX(0%) skewX(0deg); opacity: 1; }
+}
+
+@-o-keyframes lightSpeedIn {
+ 0% { -o-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+ 60% { -o-transform: translateX(-20%) skewX(30deg); opacity: 1; }
+ 80% { -o-transform: translateX(0%) skewX(-15deg); opacity: 1; }
+ 100% { -o-transform: translateX(0%) skewX(0deg); opacity: 1; }
+}
+
+@keyframes lightSpeedIn {
+ 0% { transform: translateX(100%) skewX(-30deg); opacity: 0; }
+ 60% { transform: translateX(-20%) skewX(30deg); opacity: 1; }
+ 80% { transform: translateX(0%) skewX(-15deg); opacity: 1; }
+ 100% { transform: translateX(0%) skewX(0deg); opacity: 1; }
+}
+
+.lightSpeedIn {
+ -webkit-animation-name: lightSpeedIn;
+ -moz-animation-name: lightSpeedIn;
+ -o-animation-name: lightSpeedIn;
+ animation-name: lightSpeedIn;
+
+ -webkit-animation-timing-function: ease-out;
+ -moz-animation-timing-function: ease-out;
+ -o-animation-timing-function: ease-out;
+ animation-timing-function: ease-out;
+}
+@-webkit-keyframes lightSpeedOut {
+ 0% { -webkit-transform: translateX(0%) skewX(0deg); opacity: 1; }
+ 100% { -webkit-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+}
+
+@-moz-keyframes lightSpeedOut {
+ 0% { -moz-transform: translateX(0%) skewX(0deg); opacity: 1; }
+ 100% { -moz-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+}
+
+@-o-keyframes lightSpeedOut {
+ 0% { -o-transform: translateX(0%) skewX(0deg); opacity: 1; }
+ 100% { -o-transform: translateX(100%) skewX(-30deg); opacity: 0; }
+}
+
+@keyframes lightSpeedOut {
+ 0% { transform: translateX(0%) skewX(0deg); opacity: 1; }
+ 100% { transform: translateX(100%) skewX(-30deg); opacity: 0; }
+}
+
+.lightSpeedOut {
+ -webkit-animation-name: lightSpeedOut;
+ -moz-animation-name: lightSpeedOut;
+ -o-animation-name: lightSpeedOut;
+ animation-name: lightSpeedOut;
+
+ -webkit-animation-timing-function: ease-in;
+ -moz-animation-timing-function: ease-in;
+ -o-animation-timing-function: ease-in;
+ animation-timing-function: ease-in;
+}
+@-webkit-keyframes hinge {
+ 0% { -webkit-transform: rotate(0); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; }
+ 20%, 60% { -webkit-transform: rotate(80deg); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; }
+ 40% { -webkit-transform: rotate(60deg); -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; }
+ 80% { -webkit-transform: rotate(60deg) translateY(0); opacity: 1; -webkit-transform-origin: top left; -webkit-animation-timing-function: ease-in-out; }
+ 100% { -webkit-transform: translateY(700px); opacity: 0; }
+}
+
+@-moz-keyframes hinge {
+ 0% { -moz-transform: rotate(0); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; }
+ 20%, 60% { -moz-transform: rotate(80deg); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; }
+ 40% { -moz-transform: rotate(60deg); -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; }
+ 80% { -moz-transform: rotate(60deg) translateY(0); opacity: 1; -moz-transform-origin: top left; -moz-animation-timing-function: ease-in-out; }
+ 100% { -moz-transform: translateY(700px); opacity: 0; }
+}
+
+@-o-keyframes hinge {
+ 0% { -o-transform: rotate(0); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; }
+ 20%, 60% { -o-transform: rotate(80deg); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; }
+ 40% { -o-transform: rotate(60deg); -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; }
+ 80% { -o-transform: rotate(60deg) translateY(0); opacity: 1; -o-transform-origin: top left; -o-animation-timing-function: ease-in-out; }
+ 100% { -o-transform: translateY(700px); opacity: 0; }
+}
+
+@keyframes hinge {
+ 0% { transform: rotate(0); transform-origin: top left; animation-timing-function: ease-in-out; }
+ 20%, 60% { transform: rotate(80deg); transform-origin: top left; animation-timing-function: ease-in-out; }
+ 40% { transform: rotate(60deg); transform-origin: top left; animation-timing-function: ease-in-out; }
+ 80% { transform: rotate(60deg) translateY(0); opacity: 1; transform-origin: top left; animation-timing-function: ease-in-out; }
+ 100% { transform: translateY(700px); opacity: 0; }
+}
+
+.hinge {
+ -webkit-animation-name: hinge;
+ -moz-animation-name: hinge;
+ -o-animation-name: hinge;
+ animation-name: hinge;
+}
+/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
+
+@-webkit-keyframes rollIn {
+ 0% { opacity: 0; -webkit-transform: translateX(-100%) rotate(-120deg); }
+ 100% { opacity: 1; -webkit-transform: translateX(0px) rotate(0deg); }
+}
+
+@-moz-keyframes rollIn {
+ 0% { opacity: 0; -moz-transform: translateX(-100%) rotate(-120deg); }
+ 100% { opacity: 1; -moz-transform: translateX(0px) rotate(0deg); }
+}
+
+@-o-keyframes rollIn {
+ 0% { opacity: 0; -o-transform: translateX(-100%) rotate(-120deg); }
+ 100% { opacity: 1; -o-transform: translateX(0px) rotate(0deg); }
+}
+
+@keyframes rollIn {
+ 0% { opacity: 0; transform: translateX(-100%) rotate(-120deg); }
+ 100% { opacity: 1; transform: translateX(0px) rotate(0deg); }
+}
+
+.rollIn {
+ -webkit-animation-name: rollIn;
+ -moz-animation-name: rollIn;
+ -o-animation-name: rollIn;
+ animation-name: rollIn;
+}
+/* originally authored by Nick Pettit - https://github.com/nickpettit/glide */
+
+@-webkit-keyframes rollOut {
+ 0% {
+ opacity: 1;
+ -webkit-transform: translateX(0px) rotate(0deg);
+ }
+
+ 100% {
+ opacity: 0;
+ -webkit-transform: translateX(100%) rotate(120deg);
+ }
+}
+
+@-moz-keyframes rollOut {
+ 0% {
+ opacity: 1;
+ -moz-transform: translateX(0px) rotate(0deg);
+ }
+
+ 100% {
+ opacity: 0;
+ -moz-transform: translateX(100%) rotate(120deg);
+ }
+}
+
+@-o-keyframes rollOut {
+ 0% {
+ opacity: 1;
+ -o-transform: translateX(0px) rotate(0deg);
+ }
+
+ 100% {
+ opacity: 0;
+ -o-transform: translateX(100%) rotate(120deg);
+ }
+}
+
+@keyframes rollOut {
+ 0% {
+ opacity: 1;
+ transform: translateX(0px) rotate(0deg);
+ }
+
+ 100% {
+ opacity: 0;
+ transform: translateX(100%) rotate(120deg);
+ }
+}
+
+.rollOut {
+ -webkit-animation-name: rollOut;
+ -moz-animation-name: rollOut;
+ -o-animation-name: rollOut;
+ animation-name: rollOut;
+}
diff --git a/css/main.scss b/css/main.scss
new file mode 100644
index 00000000..41ce4459
--- /dev/null
+++ b/css/main.scss
@@ -0,0 +1,9 @@
+---
+# Only the main Sass file needs front matter (the dashes are enough)
+---
+@charset "utf-8";
+
+// Import partials from `sass_dir` (defaults to `_sass`)
+@import
+ "vno"
+;
diff --git a/css/tomorrow.css b/css/tomorrow.css
new file mode 100644
index 00000000..59db8f52
--- /dev/null
+++ b/css/tomorrow.css
@@ -0,0 +1,92 @@
+/* http://jmblog.github.com/color-themes-for-google-code-highlightjs */
+
+/* Tomorrow Comment */
+.hljs-comment {
+ color: #8e908c;
+}
+
+/* Tomorrow Red */
+.hljs-variable,
+.hljs-attribute,
+.hljs-tag,
+.hljs-regexp,
+.ruby .hljs-constant,
+.xml .hljs-tag .hljs-title,
+.xml .hljs-pi,
+.xml .hljs-doctype,
+.html .hljs-doctype,
+.css .hljs-id,
+.css .hljs-class,
+.css .hljs-pseudo {
+ color: #c82829;
+}
+
+/* Tomorrow Orange */
+.hljs-number,
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-built_in,
+.hljs-literal,
+.hljs-params,
+.hljs-constant {
+ color: #f5871f;
+}
+
+/* Tomorrow Yellow */
+.ruby .hljs-class .hljs-title,
+.css .hljs-rules .hljs-attribute {
+ color: #eab700;
+}
+
+/* Tomorrow Green */
+.hljs-string,
+.hljs-value,
+.hljs-inheritance,
+.hljs-header,
+.ruby .hljs-symbol,
+.xml .hljs-cdata {
+ color: #718c00;
+}
+
+/* Tomorrow Aqua */
+.hljs-title,
+.css .hljs-hexcolor {
+ color: #3e999f;
+}
+
+/* Tomorrow Blue */
+.hljs-function,
+.python .hljs-decorator,
+.python .hljs-title,
+.ruby .hljs-function .hljs-title,
+.ruby .hljs-title .hljs-keyword,
+.perl .hljs-sub,
+.javascript .hljs-title,
+.coffeescript .hljs-title {
+ color: #4271ae;
+}
+
+/* Tomorrow Purple */
+.hljs-keyword,
+.javascript .hljs-function {
+ color: #8959a8;
+}
+
+.hljs {
+ display: block;
+ overflow-x: auto;
+ /*background: white;*/
+ color: #4d4d4c;
+ padding: 0.5em;
+ -webkit-text-size-adjust: none;
+}
+
+.coffeescript .javascript,
+.javascript .xml,
+.tex .hljs-formula,
+.xml .javascript,
+.xml .vbscript,
+.xml .css,
+.xml .hljs-cdata {
+ opacity: 0.5;
+}
diff --git a/feed.xml b/feed.xml
new file mode 100644
index 00000000..a6628bd8
--- /dev/null
+++ b/feed.xml
@@ -0,0 +1,30 @@
+---
+layout: null
+---
+
+
+
+ {{ site.title | xml_escape }}
+ {{ site.description | xml_escape }}
+ {{ site.url }}{{ site.baseurl }}/
+
+ {{ site.time | date_to_rfc822 }}
+ {{ site.time | date_to_rfc822 }}
+ Jekyll v{{ jekyll.version }}
+ {% for post in site.posts limit:10 %}
+ -
+ {{ post.title | xml_escape }}
+ {{ post.content | xml_escape }}
+ {{ post.date | date_to_rfc822 }}
+ {{ post.url | prepend: site.baseurl | prepend: site.url }}
+ {{ post.url | prepend: site.baseurl | prepend: site.url }}
+ {% for tag in post.tags %}
+ {{ tag | xml_escape }}
+ {% endfor %}
+ {% for cat in post.categories %}
+ {{ cat | xml_escape }}
+ {% endfor %}
+
+ {% endfor %}
+
+
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..3cf1d57b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,20 @@
+---
+layout: default
+---
+
+
+
+
+ {% for post in paginator.posts %}
+ -
+
+
{{ post.content | strip_html | strip_newlines | truncate: 250 }}…
+ •
{{ post.tags }}继续阅读
+
+
+ {% endfor %}
+
+
+ {% include pagination.html %}
+
+
diff --git a/js/highlight.pack.js b/js/highlight.pack.js
new file mode 100755
index 00000000..0071dd82
--- /dev/null
+++ b/js/highlight.pack.js
@@ -0,0 +1 @@
+!function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){var n=(e.className+" "+(e.parentNode?e.parentNode.className:"")).split(/\s+/);return n=n.map(function(e){return e.replace(/^lang(uage)?-/,"")}),n.filter(function(e){return N(e)||/no(-?)highlight|plain|text/.test(e)})[0]}function i(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function o(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function u(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){l+=""+t(e)+">"}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,l="",f=[];e.length||r.length;){var g=i();if(l+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);f.reverse().forEach(o)}else"start"==g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return l+n(a.substr(s))}function c(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,o){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),o&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&o.tE&&(a.tE+=(a.e?"|":"")+o.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(i(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,o);var l=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=l.length?t(l.join("|"),!0):{exec:function(){return null}}}}r(e)}function s(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function d(){if(!L.k)return n(y);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(y);r;){e+=n(y.substr(t,r.index-t));var a=g(L,r);a?(B+=a[1],e+=p(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(y)}return e+n(y.substr(t))}function h(){if(L.sL&&!w[L.sL])return n(y);var e=L.sL?s(L.sL,y,!0,M[L.sL]):l(y);return L.r>0&&(B+=e.r),"continuous"==L.subLanguageMode&&(M[L.sL]=e.top),p(e.language,e.value,!1,!0)}function b(){return void 0!==L.sL?h():d()}function v(e,t){var r=e.cN?p(e.cN,"",!0):"";e.rB?(k+=r,y=""):e.eB?(k+=n(t)+r,y=""):(k+=r,y=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(y+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(y+=t),k+=b();do L.cN&&(k+=""),B+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),y="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(f(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return y+=t,t.length||1}var E=N(e);if(!E)throw new Error('Unknown language: "'+e+'"');c(E);var R,L=i||E,M={},k="";for(R=L;R!=E;R=R.parent)R.cN&&(k=p(R.cN,"",!0)+k);var y="",B=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:B,value:k,language:e,top:L}}catch(S){if(-1!=S.message.indexOf("Illegal"))return{r:0,value:n(t)};throw S}}function l(e,t){t=t||x.languages||Object.keys(w);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(N(n)){var t=s(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function f(e){return x.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,x.tabReplace)})),x.useBR&&(e=e.replace(/\n/g,"
")),e}function g(e,n,t){var r=n?E[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=a(e);if(!/no(-?)highlight|plain|text/.test(n)){var t;x.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/
/g,"\n")):t=e;var r=t.textContent,i=n?s(n,r,!0):l(r),c=o(t);if(c.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=i.value,i.value=u(c,o(p),r)}i.value=f(i.value),e.innerHTML=i.value,e.className=g(e.className,n,i.language),e.result={language:i.language,re:i.r},i.second_best&&(e.second_best={language:i.second_best.language,re:i.second_best.r})}}function d(e){x=i(x,e)}function h(){if(!h.called){h.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function b(){addEventListener("DOMContentLoaded",h,!1),addEventListener("load",h,!1)}function v(n,t){var r=w[n]=t(e);r.aliases&&r.aliases.forEach(function(e){E[e]=n})}function m(){return Object.keys(w)}function N(e){return w[e]||w[E[e]]}var x={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},w={},E={};return e.highlight=s,e.highlightAuto=l,e.fixMarkup=f,e.highlightBlock=p,e.configure=d,e.initHighlighting=h,e.initHighlightingOnLoad=b,e.registerLanguage=v,e.listLanguages=m,e.getLanguage=N,e.inherit=i,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("xml",function(t){var e="[A-Za-z0-9\\._:-]+",s={b:/<\?(php)?(?!\w)/,e:/\?>/,sL:"php",subLanguageMode:"continuous"},c={eW:!0,i:/,r:0,c:[s,{cN:"attribute",b:e,r:0},{b:"=",r:0,c:[{cN:"value",c:[s],v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s\/>]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:!0,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},t.C("",{r:10}),{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"",rE:!0,sL:"css"}},{cN:"tag",b:"",rE:!0,sL:""}},s,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"?",e:"/?>",c:[{cN:"title",b:/[^ \/><\n\t]+/,r:0},c]}]}});hljs.registerLanguage("cpp",function(t){var i={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary intmax_t uintmax_t int8_t uint8_t int16_t uint16_t int32_t uint32_t int64_t uint64_t int_least8_t uint_least8_t int_least16_t uint_least16_t int_least32_t uint_least32_t int_least64_t uint_least64_t int_fast8_t uint_fast8_t int_fast16_t uint_fast16_t int_fast32_t uint_fast32_t int_fast64_t uint_fast64_t intptr_t uintptr_t atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong atomic_wchar_t atomic_char16_t atomic_char32_t atomic_intmax_t atomic_uintmax_t atomic_intptr_t atomic_uintptr_t atomic_size_t atomic_ptrdiff_t atomic_int_least8_t atomic_int_least16_t atomic_int_least32_t atomic_int_least64_t atomic_uint_least8_t atomic_uint_least16_t atomic_uint_least32_t atomic_uint_least64_t atomic_int_fast8_t atomic_int_fast16_t atomic_int_fast32_t atomic_int_fast64_t atomic_uint_fast8_t atomic_uint_fast16_t atomic_uint_fast32_t atomic_uint_fast64_t",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","cc","h","c++","h++","hpp"],k:i,i:"",c:[t.CLCM,t.CBCM,t.QSM,{cN:"string",b:"'\\\\?.",e:"'",i:"."},{cN:"number",b:"\\b(\\d+(\\.\\d*)?|\\.\\d+)(u|U|l|L|ul|UL|f|F)"},t.CNM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line pragma",c:[{b:/\\\n/,r:0},{b:'include\\s*[<"]',e:'[>"]',k:"include",i:"\\n"},t.CLCM]},{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:i,c:["self"]},{b:t.IR+"::",k:i},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"\\s+)+"+t.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:t.IR+"\\s*\\(",rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:i,r:0,c:[t.CBCM]},t.CLCM,t.CBCM]}]}});hljs.registerLanguage("javascript",function(e){return{aliases:["js"],k:{keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as await",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},c:[{cN:"pi",r:10,v:[{b:/^\s*('|")use strict('|")/},{b:/^\s*('|")use asm('|")/}]},e.ASM,e.QSM,{cN:"string",b:"`",e:"`",c:[e.BE,{cN:"subst",b:"\\$\\{",e:"\\}"}]},e.CLCM,e.CBCM,{cN:"number",b:"\\b(0[xXbBoO][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",r:0},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{b:/,e:/>\s*[);\]]/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[e.CLCM,e.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+e.IR,r:0},{bK:"import",e:"[;$]",k:"import from as",c:[e.ASM,e.QSM]},{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:"^\\[.+\\]:",rB:!0,c:[{cN:"link_reference",b:"\\[",e:"\\]:",eB:!0,eE:!0,starts:{cN:"link_url",e:"$"}}]}]}});hljs.registerLanguage("swift",function(e){var i={keyword:"class deinit enum extension func import init let protocol static struct subscript typealias var break case continue default do else fallthrough if in for return switch where while as dynamicType is new super self Self Type __COLUMN__ __FILE__ __FUNCTION__ __LINE__ associativity didSet get infix inout left mutating none nonmutating operator override postfix precedence prefix right set unowned unowned safe unsafe weak willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue assert bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal false filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced join lexicographicalCompare map max maxElement min minElement nil numericCast partition posix print println quickSort reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith strideof strideofValue swap swift toString transcode true underestimateCount unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafePointers withVaList"},t={cN:"type",b:"\\b[A-Z][\\w']*",r:0},n=e.C("/\\*","\\*/",{c:["self"]}),r={cN:"subst",b:/\\\(/,e:"\\)",k:i,c:[]},s={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0},o=e.inherit(e.QSM,{c:[r,e.BE]});return r.c=[s],{k:i,c:[o,e.CLCM,n,t,s,{cN:"func",bK:"func",e:"{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b:/,e:/>/,i:/>/},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:i,c:["self",s,o,e.CBCM,{b:":"}],i:/["']/}],i:/\[|%/},{cN:"class",bK:"struct protocol class extension enum",k:i,e:"\\{",eE:!0,c:[e.inherit(e.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@assignment|@class_protocol|@exported|@final|@lazy|@noreturn|@NSCopying|@NSManaged|@objc|@optional|@required|@auto_closure|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix)"}]}});hljs.registerLanguage("scheme",function(e){var t="[^\\(\\)\\[\\]\\{\\}\",'`;#|\\\\\\s]+",r="(\\-|\\+)?\\d+([./]\\d+)?",i=r+"[+\\-]"+r+"i",a={built_in:"case-lambda call/cc class define-class exit-handler field import inherit init-field interface let*-values let-values let/ec mixin opt-lambda override protect provide public rename require require-for-syntax syntax syntax-case syntax-error unit/sig unless when with-syntax and begin call-with-current-continuation call-with-input-file call-with-output-file case cond define define-syntax delay do dynamic-wind else for-each if lambda let let* let-syntax letrec letrec-syntax map or syntax-rules ' * + , ,@ - ... / ; < <= = => > >= ` abs acos angle append apply asin assoc assq assv atan boolean? caar cadr call-with-input-file call-with-output-file call-with-values car cdddar cddddr cdr ceiling char->integer char-alphabetic? char-ci<=? char-ci char-ci=? char-ci>=? char-ci>? char-downcase char-lower-case? char-numeric? char-ready? char-upcase char-upper-case? char-whitespace? char<=? char char=? char>=? char>? char? close-input-port close-output-port complex? cons cos current-input-port current-output-port denominator display eof-object? eq? equal? eqv? eval even? exact->inexact exact? exp expt floor force gcd imag-part inexact->exact inexact? input-port? integer->char integer? interaction-environment lcm length list list->string list->vector list-ref list-tail list? load log magnitude make-polar make-rectangular make-string make-vector max member memq memv min modulo negative? newline not null-environment null? number->string number? numerator odd? open-input-file open-output-file output-port? pair? peek-char port? positive? procedure? quasiquote quote quotient rational? rationalize read read-char real-part real? remainder reverse round scheme-report-environment set! set-car! set-cdr! sin sqrt string string->list string->number string->symbol string-append string-ci<=? string-ci string-ci=? string-ci>=? string-ci>? string-copy string-fill! string-length string-ref string-set! string<=? string string=? string>=? string>? string? substring symbol->string symbol? tan transcript-off transcript-on truncate values vector vector->list vector-fill! vector-length vector-ref vector-set! with-input-from-file with-output-to-file write write-char zero?"},n={cN:"shebang",b:"^#!",e:"$"},c={cN:"literal",b:"(#t|#f|#\\\\"+t+"|#\\\\.)"},l={cN:"number",v:[{b:r,r:0},{b:i,r:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"}]},s=e.QSM,o=[e.C(";","$",{r:0}),e.C("#\\|","\\|#")],u={b:t,r:0},p={cN:"variable",b:"'"+t},d={eW:!0,r:0},g={cN:"list",v:[{b:"\\(",e:"\\)"},{b:"\\[",e:"\\]"}],c:[{cN:"keyword",b:t,l:t,k:a},d]};return d.c=[c,l,s,u,p,g].concat(o),{i:/\S/,c:[n,l,s,p,g].concat(o)}});hljs.registerLanguage("ruby",function(e){var c="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",b={cN:"yardoctag",b:"@[A-Za-z]+"},a={cN:"value",b:"#<",e:">"},n=[e.C("#","$",{c:[b]}),e.C("^\\=begin","^\\=end",{c:[b],r:10}),e.C("^__END__","\\n$")],s={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,s],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]},i={cN:"params",b:"\\(",e:"\\)",k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+e.IR+"::)?"+e.IR}]}].concat(n)},{cN:"function",bK:"def",e:" |$|;",r:0,c:[e.inherit(e.TM,{b:c}),i].concat(n)},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":",c:[t,{b:c}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+e.RSR+")\\s*",c:[a,{cN:"regexp",c:[e.BE,s],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(n),r:0}].concat(n);s.c=d,i.c=d;var o="[>?]>",l="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",N=[{b:/^\s*=>/,cN:"status",starts:{e:"$",c:d}},{cN:"prompt",b:"^("+o+"|"+l+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,c:n.concat(N).concat(d)}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["m","mm","objc","obj-c"],k:i,l:o,i:"",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"preprocessor",b:"#",e:"$",c:[{cN:"title",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});
\ No newline at end of file
diff --git a/js/main.js b/js/main.js
new file mode 100755
index 00000000..e005af76
--- /dev/null
+++ b/js/main.js
@@ -0,0 +1,52 @@
+$(document).ready(function() {
+
+ $('a.blog-button').click(function() {
+ // If already in blog, return early without animate overlay panel again.
+ if (location.hash && location.hash == "#blog") return;
+ if ($('.panel-cover').hasClass('panel-cover--collapsed')) return;
+ $('.main-post-list').removeClass('hidden');
+ currentWidth = $('.panel-cover').width();
+ if (currentWidth < 960) {
+ $('.panel-cover').addClass('panel-cover--collapsed');
+ } else {
+ $('.panel-cover').css('max-width',currentWidth);
+ $('.panel-cover').animate({'max-width': '700px', 'width': '30%'}, 400, swing = 'swing', function() {} );
+ }
+ });
+
+ if (window.location.hash && window.location.hash == "#blog") {
+ $('.panel-cover').addClass('panel-cover--collapsed');
+ $('.main-post-list').removeClass('hidden');
+ }
+
+ if (window.location.pathname.substring(0, 5) == "/tag/") {
+ $('.panel-cover').addClass('panel-cover--collapsed');
+ }
+
+ $('.btn-mobile-menu__icon').click(function() {
+ if ($('.navigation-wrapper').css('display') == "block") {
+ $('.navigation-wrapper').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
+ $('.navigation-wrapper').toggleClass('visible animated bounceOutUp');
+ $('.navigation-wrapper').off('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend');
+ });
+ $('.navigation-wrapper').toggleClass('animated bounceInDown animated bounceOutUp');
+
+ } else {
+ $('.navigation-wrapper').toggleClass('visible animated bounceInDown');
+ }
+ $('.btn-mobile-menu__icon').toggleClass('fa fa-list fa fa-angle-up animated fadeIn');
+ });
+
+ $('.navigation-wrapper .blog-button').click(function() {
+ if ($('.navigation-wrapper').css('display') == "block") {
+ $('.navigation-wrapper').on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function() {
+ $('.navigation-wrapper').toggleClass('visible animated bounceOutUp');
+ $('.navigation-wrapper').off('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend');
+ });
+
+ $('.navigation-wrapper').toggleClass('animated bounceInDown animated bounceOutUp');
+ }
+
+ $('.btn-mobile-menu__icon').toggleClass('fa fa-list fa fa-angle-up animated fadeIn');
+ });
+});