记江苏之行(二)

April 23, 2012

从五点半一直捡到七点半,这时天色已经完完全全黑了下来,我还有一段路没有捡完,但是实在是没法继续下来,我提着桶开始往回走。然后在农家乐住了一晚,第二天又赶去了兴化市区,做大巴去了泰州。

去泰州,是因为我想在泰州逛逛之后直接做下午三点半从泰州到上海的大巴回去

是的,我逛了逛泰州老街,太水了,天气热的很,感觉没啥好看,商业化太浓了。
IMG_0936
于是我去了坡子街,买了双鞋子,因为之前的鞋子因为前一天在菜花里走来走去走得已经不成样了,确实也穿了好久了,平时也没什么时间去买什么鞋子,上海人太多了,压马路这种事太嘈杂了。结果逛来逛去买了一双2B的红黑配的鞋子,不过看起来还蛮有质感,红黑配这种色调我一直很喜欢,依稀记得大二时的实习我做了一个以红黑配色为主题的网站,结果被人说成了黄网,人家说黄网都是这种色调搭配的,其实我内心还是很纯洁的,根本没想那去。

玩的是蛮high。

但是。。。。。。

我在去泰州的路上上微博,突然看到新闻说上午十一点有一辆从扬州到上海的旅游团的大巴在沿江高速上因为爆胎穿过了防护墙跟对面的大货车迎面相撞,结果造成了几人死亡和几人重伤的事故。看到微博上得配图,非常惨,我顿时感觉后背一股凉气。刚才几分钟前还是活生生的高高兴兴的人们,下一秒却是阴阳相隔,而这个事故离我在的地方(我在泰州)也不是很远,那么近距离虽然不是几米而是上百公里,依然觉得非常震撼。 我也是旅游出来周末放松,或许他们也应该是的,而且应该是看完牡丹花蛮开心的返回上海,我也是刚刚看完油菜花也是蛮开心的正在返回途中,以前新闻上看到也不会特别留意没太多触动,但是这件事情跟我的情况是那么相似,我几乎能感受和他们出事前一样的感觉,生命何其珍贵,每个人的生命都只有一次,这些深深触动了我。想起之前我还对周一可能有可能要处理棘手的技术问题而有点烦,但是现在我觉得这都不是问题。

我决定改道,从泰州去了无锡。在路上,我还在想着拿起事故,也想起了乔布斯之前在斯坦福的毕业典礼上得演讲,生命留给我们的时间是那么宝贵,如果我们不听从内心的选择,做自己想做的事情,或许当你突然离世,你也许真的什么也没有给这个世界留下。

”活着就是为了改变世界“。

我还很年轻,也许我的经历还是很嫩的,但是我觉得是的,人是应该在适当时候停下来,仔细想想到底自己喜欢这份工作吗,还有当初的那份激情吗? 如果当你觉得回答不了自己,也许就是应该做出选择的时候了。我之前做的是互联网领域,技术做的还不错,但是到了一定时间,我觉得我已经找不到当初进公司的那份激情了,激情对于我有多重要? 我想起了,当初刚进大一时第一次上思想政治课,老师要同学上去讲人生观,我当时上去直接说”生活就是要有激情啊“,看起来当时还满拽的。我认为这个激情就是做自己喜欢做的事情。

You’ve got to find what you love. –steve jobs

更重要的是:只要你想好了,年轻没有什么是不可以损失的,不要思前顾后太久太多。

我一哥们最近打算换工作,咨询我的意见,我说你如果自己现在不喜欢自己的工作,只是把他当成养活自己的工作,我觉得你看准了自己想干什么,直接去就好了,不管它大或小。你现在想最坏的情形是什么,即使三个月后你失业了,但是这个三个月的经历,有了激情的浇灌,再重新找工作或许还有更好更合适你的工作了。没什么可损失的嘛,看好了,就直接去试。
IMG_0945

话说回来,去了一趟无锡,去了一趟了惠山老街。很安静,无锡给我的感觉是就是绿色,旁边好多树啊,感觉非常不错,:-( , 可伶的DJN童鞋的合肥啊….
IMG_0941

做自己想做的事情,顾及别人的感受(不自私),不去理会别人的眼光和评论,坚持自己内心的选择。

0

记江苏之行(一)

April 23, 2012

上个周末去了江苏一趟,这一趟确实是非常值得的。

距离这一次出行,我有大概接近一个月没有出去转转了,一直忙于很多事情,也确实让我感觉有点疲惫,于是趁着这个五一来之前,赶到非常热而且人多的五一之前,我决定出去一趟(后来证明我的决策是非常英明的,兴化撑船的大妈说上周几万人,连车都开不进来,这周明显人少了好多,到了下一周或者五一了,最佳看花的时间也就过了)。

本来我的想法是去嘉兴西塘的,然后准备在那住一晚上,但是我定的太晚了,所以没有任何的客栈有床铺了。刚刚好,也是看到了林逸欣的微博,想起来她原来当过旅游的外景主持,又看到了她在江苏的片段里面刚刚好讲到兴化剁田的万亩油菜花,而且油菜花在四月份看是最合适的,到了五月份花就开始了凋落了,而且五一人超多超热。所以我下定决心去兴化。

周五晚上坐动车去了镇江,等我下车时已经是晚上快10点了,而且下面下着瓢泼大雨,我打车来到了镇江的西津渡历史古街。整个古街都在淅淅沥沥的雨中显得非常朦胧,而且整个街上空无一人,这可是梦寐以求的画面,没有喧嚣,我一个人撑着伞瞎逛着,其实也蛮慎得慌的。刚好看到一家酒吧,于是我赶紧进去,要了瓶啤酒,喝着啤酒,听着风雨声,感觉卸下了所有的包袱和疲惫。
IMG_0783
第二天一大早,根据我的计划,是一大早又逛了逛西津渡古街,因为晚上太黑了,没怎么看清楚。早上很安静,雨也停了,我走在街上,慢慢悠悠的很快就逛完了,因为街实在是很短。

看了一个什么镇江英使馆的建筑,还有点意思,刚好旁边有一个镇江博物馆,顺手过去看了看。但是进去之后发现太令我失望了,整个博物馆喧嚣声一片,很多孩子在跑来跑去大声喧哗,家长则在旁边自己聊自己的,感觉已经把这当成了小学了。我跟保安说了一下,让他找老师说一下让他们安静一些,基本的公德也该是有教的吧,结果根本连老师也找不着。我看了看,实在是太吵了,没法看,也就走了去汽车站赶从镇江到兴化的班车。
IMG_0792
镇江给我的感觉是非常安静节奏缓慢的一座城市,不错。

做从镇江到兴化的班车有了将近三个小时,终于在下午两点到了兴化市区,一路上给我的感觉就是水多树多花多,江苏真是好地方啊。到了兴化市区,急急忙忙赶着从兴化去缸顾乡的班车,因为兴化剁田其实不在市区,而且在离市区比较远的村子里。

到了下午四点,终于了到了缸顾乡的千岛菜田。一下车,放眼望去一片黄片的情景,真是蔚为壮观。
IMG_0928
其实油菜花,还是很常见的,不管南北哪都有,我家里的镇子上也有,只不过是零零散散的种着,很少有大片大片连起来的。小时候,比较喜欢去油菜花田里捉蜜蜂,两只手各拿一张纸,然后看到油菜花上的蜜蜂,悄悄的靠上去,突然一下子把两个手掌合起来,就抓住了,屡试不爽。油菜花吃起来也有讲究,一般会把花去掉,但是油菜花茎秆顶部部分,吃起来比较苦,这个我是深有感受的。我跟我妈说我去看油菜花了,我妈说不就油菜花嘛,这有啥好看的,家里一大片不? 其实我或因读书或因工作的缘故,几乎没有在四月份是在家的,油菜花的近距离接触已经好久没有过了,也是一种回味童年吧。

IMG_0878
下了车,我就迫不及待的检了票,然后去看菜花落。站上高台,放眼望去,整个充斥在眼中的全部都是黄油油的菜花,这是万亩连延不绝的菜花啊,感觉真是被震撼到了,非常漂亮的景色。
IMG_0926
整个黄色的世界,被青色的河水分成了一个个小方块,看起来好像“井/田”字。当然坐上小船,欣赏着两岸的菜花必然是必不可少的项目啦。
IMG_0853
黄油油的菜花中有一条小道供游人行走参观,顺着这条小道,仔细品味着两旁近在咫尺的菜花的香味,感觉非常奇妙。

其实这条小道不算很长,花了没多长时间,我就已经逛完了。
IMG_0875
但是走在菜花包围的小道上,虽然两旁景色迷人,但是还是有很多很刹风景的地方:垃圾。道旁边两旁,到处到游人随意丢弃的冰淇淋盒子塑料袋子卫生纸矿泉水瓶等等,非常非常的刺眼和让人不舒服。当第一次走时,我还悄悄的捡起了一两个瓶子,然后给扔到了垃圾桶了。一边走,一边内心在想:“或许应该做些什么,也许我可以就这么走了,但是这一片风景在以后也会很快忘记;因为你没有真正融入它,你没有参与感,你得到的感觉或许不是最纯真的最原始的‘。一个奇怪的想法一直在心里盘旋着,我一直内心在激烈斗争着。
当我走回第一次路线的起点时,我看到旁边的警务台旁边有一个红色的塑料桶,我想到这可以是我可以用到的。这个时候天色开始变黑了,而且人慢慢变少了,我内心的斗争也稍微平复了。内心的斗争源自于我是一个人,我还是很怕周遭人的看法,我表示我虽然我有时候比较特立独行,但是无可否认我还是比较留意别人的看法的,任何人都是这样的。这个时候人少了一点点,我想到了如果别人问起我,我可以假意说我是挂名在景区做义工的;如果别人拿很怪异的眼神看我,我就故作镇定一定的装的很像一个真的。然后我迈出了第一步,我把卫衣的帽子翻了下来,盖在头上,在两旁弯着腰一路上捡着垃圾,真是什么垃圾都有,而且有些扔的非常隐蔽,我都不知道他们如何扔进去的,那得是练到了一定功力的“修养”极高才行,轻轻一抛,确是恰当好处,别人还真是难以看到。捡了一阵子,碰到游人,有得会说你是志愿者吗,我木有看他/她,我虚了,我说“是”;其实我在想“沃勒个去,我还能咋回答,我说我是冒牌的志愿者?”。一开始有些拘谨,看到别人怪异的眼神总觉得别人眼神好像在说“装B男”,是的,换做我,第一感觉必然是我也会这么看自己。但是有时候在想,我们看到一些人有些独特的地方或者干了一些别人没有勇气干出来的事情时,我们习惯打击他/她这种“独特”,因为我们无法承认我们自己是没有“特色”的,所以我们想把他拉下来到我们这个等级来,好维持我们仅有的自尊和自信。而缺少特点的人,缺少勇气干自己想干事情的人,缺少勇气听从自己内心深处呼呼的人,在现在的社会中太多了。而一个理想的社会是鼓励每个人的特殊,应为这是他在这个社会无法被替代的唯一性,他得价值。
IMG_0912
慢慢的,我在垃圾中忙碌开来,已经完全投入了,看到对面来的游人之类的,我都根本没有任何当初的拘谨和面子感,如果我带了一个红色的有义工标志的什么袖子,会让我感觉很体面点,更多是因为我觉得是冒牌的。哪有穿成这样,背着一个书包,当义工检垃圾的,我觉得我给他们丢脸了。捡了好一阵子,大概四分之一吧,累的不行,腰的提不起来,想我打篮球也算是腰力不错的了,各种滞空,哈哈,但是那个时候也是感觉不行了,于是我停下来,握住一把油菜花,把脸贴过去,狠狠吸了一把它的香味,感觉立马又有活力的感觉,而且这个时候路上几乎没有游人,天色也黑了不少,但是还是有点残晖,这个田野感觉顿时安静了下来,天地万物,万籁俱静,整个黄油油的一片包围着着我,放佛要将我吞噬,我顿时感觉这才是我真的追寻的那种似曾相识的感觉,那种安静和那种感觉真像我当年初中时下大雨我把雨衣给住的比我远多的童鞋后我一个人骑着单车在瓢泼大雨中享受着淋透逛哮的感觉,一种透彻心扉美好和原始的纯真。相比起第二天能有看花时可能能有更好感觉的游人们和我的初中童鞋,我觉得我收获的快乐和幸福比他们都要多得多。我开始明白消防战士的感觉,当你看到你抢救出来的财产或者人们,因为你看到因为你的存在,别人的生命能继续或者生活状态能有改变,你的内心会告诉你做了正确的事情,任何你经历的痛苦都会被巨大的幸福感吞噬,你内心的幸福感已经远远大于任何物质诉求,虽然这份工作很危险工资也不高。
IMG_0916
这却是一份意外的与心灵的对话。

IMG_0874

0

合肥之行

April 14, 2012

       距离上次去合肥也有两三周了,今天突然想起来可以记一笔流水账。
想去合肥,是因为我之前读过梁启超的《李鸿章传》,里面详细描述他的生平主要是加入湘军曾国藩手下开始一步一步做到开创淮军,打败太平天国,直至真正入阁参与了洋务运动以及各种谈判。
更重要的是张辽是我在三国中非常喜欢的人物,当年在逍遥津八百人打败孙权十万人,至今想想都是热血沸腾。
在此之前,我对李鸿章的印象依然提留在初高中的课本上面,总觉得他是一个“卖国贼”非常懦弱的人。
于是,做为一个2B青年,有时候为了美化自己也时常文艺一下。
去过杭州,主要目的除了见同学之外,是想去拜访于谦,也顺便看了看龚自珍的故居。
所以,去某个城市逛逛之前,我都会特别留意一些有人文特色的地方。

               

合肥之行 March 2012

        非常不错的是,一路上都有美女相伴,因为我大学童鞋的高中女童鞋刚刚好在安徽大学读研,于是我就麻烦她当我的导游了。
安徽大学:
IMG_0647

               

第一站:三河古镇

        周六上午去了这个三河古镇,其实谈不上古镇,因为咋们都清楚哪有古镇啊,只不过近几年翻新而已;而我非不太欢喜大城市的喧嚣,我比较喜欢安静的地方,至少人少点的地方。
三河镇离合肥有点远吧,坐车也做了将近一个多小时才到。
IMG_0649
看起来还蛮安静的,整个镇子其实也就是一条河穿过,非常短。
IMG_0651
尝试了一下这边的米酒,还不错。
IMG_0657
然后就是来到了一个清代风格的小楼:
IMG_0685
手贱的不断想拍出七彩:
IMG_0689
然后就是玩啦,围着小镇转,非常不错的阳光,春天。
照片
DJN童鞋你太高了。。。。。。。

               

第二站:李府

        下午匆匆从三河镇坐车回来,然后去了李鸿章府,是的, 我想不通的是李府居然在合肥最繁华的步行街,意味这我也加入了压马路的大军,
然后破解一切障碍,终于在一个不起眼的而且在一群现代化建筑中显得非常炸眼的古门牌地方找到了。
IMG_0708
话说其实也没啥好看的,只不过来缅怀一下。
IMG_0714

               

逍遥公园

       这本是我这次来合肥最大的一个目的之一,我想见见张辽当年大战的古战场,结果一进去公园,一开始就是什么各种儿童乐园,各种嘈杂,我这是吐槽了。
然后好不容易来到张辽的衣冠冢(谁知道了),结果牌子也没看到,就是光秃秃的一个坟头,旁边写着“请不用登上坟头各种践踏”,我当时就崩溃了。
好吧,来公园真是失败。
IMG_0724

               

庐州太太

        DJN童鞋说她作为东道主,然后有童鞋来合肥,吃的了,一定得去庐州太太,比较有合肥的特色。悲情的是,当我们如同《功夫足球》中周星驰在风沙漫天的足球场匍匐着像战士一样似的穿过合肥一条灰尘漫天的路,来到了庐州太太面前。
结果人爆满,我俩来了一个号,居然是90几,然后我两在旁边坐着玩手机,大概半个小时过去,依旧还是四十几号,吐槽了。然后我们就去旁边的一家XXX餐馆,吃了个牛蛙(这牛蛙真可伶,类似韩寒写的差不多,我们都是牛蛙,上面在闹的都是特权阶级的事情)。
IMG_0727
然后吃完差不多就是送DJN童鞋回安大了,怎么说这点绅士风度还是应该有的,特别是晚上。

               

包公祠

       如果这次中最坑爹的就是这个包公祠,简直就是坑爹爹爹。。。。。。。
IMG_0734
我不想说了。,。。。。
IMG_0736
真是看不到多少真的古迹了,完完全全就是一个仿造。吐槽!!!! 伤不起,文艺青年!

               

庐州太太

是的,从包公祠回来,又去了昨晚的泸州太太,终于人品爆发,等上了。
这鱼真是超赞。
IMG_0743

               

晚秋

       吃晚饭,去电影院看了晚秋,太坑爹了,有木有。 我这种文艺青年表示看不懂,整片有点抑郁。

               

回程

       整个过程,非常感谢DJN的陪同和导游,非常有亲和力,也是有点文艺范。。。。。
       赞,你!

2

problem with adding custom font in Xcode 4.3.2

April 13, 2012

    Well, I guess John Muchow’s blog Load and Access Custom Fonts” pretty much said everything about adding custom font before xcode 4.3.2. Before Xcode 4.3.2, when you add custom font, you need to make sure name of font you use in following code snippet:

[label2 setFont: [UIFont fontWithName: @"BlackBoard" size:18]];

        

Where is my font ?

    the “BlarkBoard” is the real name when you install it on mac. So the file name you just add to Resource and plist file , like “BlackBoard.ttf” is always the same the name of font that installed on mac.So you need to open that font file in FontBook, and write the name showing up down, like “Black BoardSb”.
    Then when you want to refer to font in the code:

[label2 setFont: [UIFont fontWithName: @"Black BoardSb" size:18]];

    That is so important and it is always easy to forget about. Actually there is one way to debug it when the fonts doesn’t show up as you expected by using:

NSLog(@"Available fonts: %@", [UIFont familyNames]);

    So far, all is good.
        

But if you use Xcode 4.3.2……

   But if you use Xcode 4.3.2, although you are very sure that every steps above are done properly, you still can’t find the font you expected in
the log message of [UIFont familyNames].
Here is the reason:
          You have to go to “Copy Bundle Resources” in “Build Phases” of your project, and add font file to that list.

Screen Shot 2012-04-13 at 6.24.59 PM

    Then run the project. Bang! It works now.
    I’ve no freaking idea what’s going on.It seems there is a bug relating to custom font in Xcode 4.3.2.

0

IOS使用CAReplicatorLayer重建动态的倒影

March 31, 2012

最近在看ios中关于core animation的一些东西,其中就有一个是任何创建倒影。 创建倒影应该是蛮常见的吧,比如你打开iphone中的音乐,这个时候如果你将你的手机横过来,就可以看到这个cover flow的效果了。仔细看的话,你会发现这个每张专辑图片下都有一个倒影。是的,这便是这篇文章的主题。我们会先讲一个普通且常见的创建倒影的方式,然后讲一讲它的缺点,最后讲讲新重建倒影的方式。
coverflow
常见创建倒影的方式:

  1.    创建一个origin layer,然后将图片设置为它的contents
  2.    重建一个跟origin layer大小一致的reflection layer,将origin layer赋值给reflection layer,并且将其位置定位到origin layer的下面
  3.    如果可以,你可以设置一个什么半透明效果layer(optional) 让倒影看起来有点朦胧的感觉
  4.    创建一个origin layer的mask,并且是一个带有gradient渐变效果的层,而且它的高度只有图片高度的一半
  5.   将reflection layer加入到origin layer的sublayers中

直接上code snippet:

- (void)createReflection {
 
[self.view setBackgroundColor:[UIColor whiteColor]];
CALayer *contentLayer = [CALayer layer];
UIImage *flag = [UIImage imageNamed:@"chuck-norris.png"];
contentLayer.bounds = CGRectMake(0, 0, flag.size.width, flag.size.height);
contentLayer.contents = (id)flag.CGImage;
contentLayer.position = CGPointMake(self.view.center.x, 10);
contentLayer.anchorPoint = CGPointMake(0.5, 0);
contentLayer.backgroundColor = [UIColor blueColor].CGColor;
contentLayer.cornerRadius = 4.0;
 
CALayer *reflection = [CALayer layer];
reflection.bounds = contentLayer.bounds;
reflection.position = CGPointMake(contentLayer.bounds.size.width/2, contentLayer.bounds.size.height * 1.5);
reflection.contents = contentLayer.contents;
reflection.transform = CATransform3DMakeRotation(M_PI, 1, 0, 0);
 
CALayer *blackLayer = [CALayer layer];
blackLayer.backgroundColor = [UIColor blackColor].CGColor;
blackLayer.bounds = reflection.bounds;
blackLayer.position = CGPointMake(blackLayer.bounds.size.width/2, blackLayer.bounds.size.height/2);
blackLayer.opacity = 0.6;
[reflection addSublayer:blackLayer];
 
CAGradientLayer *mask = [CAGradientLayer layer];
mask.bounds = CGRectMake(0, 0, reflection.bounds.size.width, reflection.bounds.size.height /2 );
mask.position = CGPointMake(mask.bounds.size.width/2, mask.bounds.size.height);
mask.anchorPoint = CGPointMake(0.5, 0);
mask.colors = [NSArray arrayWithObjects:
(id)[UIColor clearColor].CGColor,
(id)[UIColor whiteColor].CGColor, nil
];
 
reflection.mask = mask;
 
[contentLayer addSublayer:reflection];
 
[self.view.layer addSublayer:contentLayer];
}

代码看起来还是蛮直观的, 但是这里有几个重要的基本概念得弄清楚。

第一个坐标系(Geometry):

CALayer *contentLayer = [CALayer layer];
UIImage *flag = [UIImage imageNamed:@"chuck-norris.png"];
contentLayer.bounds = CGRectMake(0, 0, flag.size.width, flag.size.height);
contentLayer.contents = (id)flag.CGImage;
contentLayer.position = CGPointMake(self.view.center.x, 10);
contentLayer.anchorPoint = CGPointMake(0.5, 0);
contentLayer.backgroundColor = [UIColor blueColor].CGColor;
contentLayer.cornerRadius = 4.0;

这一段代码很简单就是将contentLayer定位到屏幕水平中间,竖直方向离顶端10个point的地方,然后设置了它的背景颜色和圆角效果(如果要对image做复杂的圆角处理,参考WWDC 2011 Practical Drawing中Bezier Path Drawing). 但是这里却体现core animation中看似最简单实际比较复杂的部分: Geometry. 它有几个常常出现的关键字: position, bounds, anchorpoint, 基本上它们是结对出现的。

如果你看过apple的developer documentation中关于”Layer Geometry and Transforms“ 以为自己懂了,然后一看到实际代码却晕了的人,可以看看接下来这段解释。 ios的坐标系是这样: x轴往右是正,y轴往下是正。这根一般的坐标系不太一样。

bounds 很简单,它有origin 和size两个部分组成。 由于bounds是针对自己所在的layer,所以它的origin没有啥可以参考的。 所以设置bounds基本上主要目的是设置它的高度和宽度。 而position不一样,它是针对super layer而言的, 这一点跟frame一样。 而AnchorPoint却跟bounds和position又不一样,它是相对于bounds和position的比例而言,所以它是小数的方式表示。如果没有显示指明anchorpoint的值,默认是(0.5,0.5);如果没有显示指明position的值,它的值是(0,0)。
Screen Shot 2012-03-31 at 8.16.06 PM
这一段描述还是过于抽象,这样从上面的例子来说吧。我们先设置bounds,那就是contentLayer的长和高。 然后设置它的position,它是在水平的中间和竖直方向的顶部。假图片的长高是200. 那么这里如果不设置anchorPoint的值,那么它默认是(0.5,0,5) ,那么你会看到图片前(200 – 100 – 10 = 90) 90像素超出了整个屏幕。因为它(0.5,0.5)定义了postion将是在bounds横轴宽的中间位置和数轴的中间位置。 如果我们想让图片在10像素时才开始显示,其实也是想让数轴上的position刚刚也是bounds的origin y轴起点,由于y轴往下是正,所以我们就是将anchorpoint设置为(0.5,0) .

 

那你再看如果设置mask层时,可能就相对容易了。我们先设置它的长高,长度是一样的,但是高度就只有一半了。然后接下来需要注意了:

mask.position = CGPointMake(mask.bounds.size.width/2, mask.bounds.size.height);

你需要注意到position并不是CGPointMake(mask.bounds.size.width/2, 0),  因为我们需要做倒影,所以在

reflection.transform = CATransform3DMakeRotation(M_PI, 1, 0, 0);

调用了CATrasnform3D来对空间做出调整。这里我们绕着x轴,顺时针旋转了180度,所以整个reflection层都倒过来了。但是它是按照哪个点进行旋转了,虽然我们知道它是按照x轴旋转的? 所以这里的anchorpoint就是重要的,它作为任何改变层的geometry的轴心点。我们知道如果我们不指定anchorpoint,它默认是(0.5,0,5)就是在层的正中间。 所以图片才会正确的翻过来。但是因为这种transform赋值是会应用到所有的sublayer 包括自己的。 所以此时reflection包括所以它的子层的坐标系发了改变,y轴已经倒了过来,现在是指向了上面。试想一下,x轴不懂,顺时针转180度,此时是不是y轴就刚好倒了它的反面。所这正是mask的position.y 是mask.bounds.size.height而非0的原因。
Screen Shot 2012-03-31 at 7.42.00 PM
所以这个代码还行,但是它有些缺点,其中最重要的是它是静态的。

 

假设我们此时我们现在在contentLayer上了加了一个文字层, 我现在想我点击这个文字增加一个animation动画,在contentlayer上上下活动,然后我想这个倒影层始终保持更新同步(文字也在倒影层中移动)?  这个该怎么做了?

所以这是当前做法的局限性,本质上,它是静态的,而且相比接下来的做法,它的性能也大大不如。

 

具有动态更新能力的CARepliatorLayer

如果有看过wwdc 2011中Session 421 Core Animation Essentials,你可能记得作者简要的描述了一下CAReplicatorLayer的原理,但是可能没有太实际的感受。所以私底下,我找了找,找到了一位大牛Brian M. CoynerCore Animation Fun House ,这种就有一个关于如何使用CAReplicatorLayer作倒影的例子。虽然看起来很简单,但还是涉及到一些关于core animation很基础很重要的概念。有感兴趣的可以checkout他的项目,然后仔细看看.
Screen Shot 2012-03-31 at 8.42.53 PM

首先神马是CAReplicatorLayer:

The CAReplicatorLayer class creates a specified number of copies of its sublayers (the source layer), each copy potentially having geometric, temporal and color transformations applied to it.

简要的说,它自己能够重建包括自己在内的n个copies,这些copies是原layer中的所有sublayers,并且任何对原layer的sublayers设置的transform是可以积累的(accumulative).

基本上这样的一个关系:我们首先会重建一个CAReplicatorLayer实例,作为我们的sourceLayer, 这个sourceLayer我们需要一份copy,那包括自己在内就是2; 所以我们设置了它的instantCount = 2;这个是包括自己在内总共为2. 然后我们将SourceLayer的宽度设置为image的宽度,但是将其高度设置为image.size.height * 1.5; 并且在sourcelayer上加上masksToBounds为true的属性,这样一来我们可以保证超出的倒影部分会cut调一半,加上sourceLayer上正常的image,刚刚好组成了我们的完整的倒影。我们sourceLayer的sublayer就是_imageLayer,。但是我们只是单纯设置instantCount = 2的话, 那个_imageReplicatorLayer(这个就指代的是copy过来的第一个变量)是会继承sourceLayers中_imagelayer的Geometry,所以它两是重合的。那么我们必须做出transform, CARepliatorLayer有一个属性叫做instantTransform,这个属性指定了除了原来copy之外所有replication layer的trasnform规则,重要的是它是递增的。比如我们这里需要将imageReplicatorLayer应该往下移动image的高度,这一样来可以保证它是刚刚好在原来imagelayer的正下方,就跟倒影一样。 但是不一样的地方是:这个复制的imageReplicatorLayer它不是正常的,它是需要倒过来的,所以我们在transform上使用了

transform = CATransform3DScale(transform, 1.0, -1.0, 1.0);

这一个的意思是大小不变,但是y轴倒过来,这个应用到imageReplicatorLayer的坐标系是y轴朝上。

这样以来你就不能单纯是向原来一样移动image.height了, 因为y轴反了过来,所以你应该是 -2 * image.size.height 这样以来就搞定了。

最后我们给它加了一个渐变层,让它看起来更接近倒影的感觉。

[self.view setBackgroundColor:[UIColor whiteColor]];
[[self view] layer].borderColor = [UIColor blueColor].CGColor;
[[self view] layer].borderWidth = 2;
 
CAReplicatorLayer *layer = [CAReplicatorLayer layer];
[layer setContentsScale:[[UIScreen mainScreen] scale] ];
 
[layer setBounds:CGRectMake(0, 0, image.size.width, image.size.height * 1.5)];
layer.masksToBounds =  YES;
layer.anchorPoint = CGPointMake(0.5, 0.0);
layer.position = CGPointMake(self.view.frame.size.width/2, 10.0);
layer.borderColor = [UIColor redColor].CGColor;
layer.borderWidth = 0;
 
layer.instanceCount = 2;
 
CATransform3D transform = CATransform3DIdentity;
 
transform = CATransform3DScale(transform, 1.0, -1.0, 1.0);
transform = CATransform3DTranslate(transform, 0, -[image size].height * 2, 1.0);
 
layer.instanceTransform = transform;
 
_imageLayer = [CALayer layer];
[_imageLayer setContentsScale:[[UIScreen mainScreen] scale]];
[_imageLayer setContents:(__bridge id)image.CGImage];
[_imageLayer setBounds:CGRectMake(0.0, 0.0, [image size].width, [image size].height)];
[_imageLayer setAnchorPoint:CGPointMake(0, 0)];
 
[layer addSublayer:_imageLayer];
 
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
[gradientLayer setColors:[NSArray arrayWithObjects:(__bridge id)[[UIColor whiteColor] colorWithAlphaComponent:0.25].CGColor, [UIColor whiteColor].CGColor, nil]];
 
[gradientLayer setBounds:CGRectMake(0, 0, layer.frame.size.width, [image size].height * 0.5 + 1.0)];
[gradientLayer setAnchorPoint:CGPointMake(0.5, 0)];
[gradientLayer setPosition:CGPointMake(self.view.frame.size.width/2, image.size.height + 10.0)];
[gradientLayer setZPosition:1];
 
[gradientLayer setContentsScale:[[UIScreen mainScreen] scale]];
 
[[[self view] layer] addSublayer:layer];
[[[self view] layer] addSublayer:gradientLayer];

所以了,如果现在你想在图片层上加一个文字,而且想让文字也出现在倒影中,就是非常简单了。因为所有的replicator layer会监视source layer中sublayers的动作变化,一旦它发生变动,所有的replicator layers会对应的进行重画,而且性能看起来还不错。

//Final step to show it dynamic nature
CATextLayer *textLayer = [CATextLayer layer];
[textLayer setContentsScale:[[UIScreen mainScreen] scale] ];
[textLayer setString:@"Chuck Norris"];
[textLayer setFontSize:18];
[textLayer setAlignmentMode:kCAAlignmentCenter];
[textLayer setShadowColor:[UIColor blackColor].CGColor];
[textLayer setShadowOpacity:1.0];
[textLayer setShadowOffset:CGSizeMake(-4, -4)];
[textLayer setBounds:CGRectMake(0, 0, _imageLayer.frame.size.width, 30)];
[textLayer setPosition:CGPointMake(_imageLayer.frame.size.width/2, _imageLayer.frame.size.height - 20)];
//[textLayer setAnchorPoint:CGPointMake(0.5, 0.5)];
[_imageLayer addSublayer:textLayer];
[self.view setUserInteractionEnabled:YES];
[self.view setMultipleTouchEnabled:YES];
[self.view addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(animateTextLayer:)]];
 
- (void)animateTextLayer:(id)animateTextLayer {
CALayer *textLayer = [[_imageLayer sublayers] objectAtIndex:0];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.y"];
CGFloat endPoint = [textLayer frame].size.height /2;
 
//textLayer.position.y
[animation setFromValue:[NSNumber numberWithFloat:textLayer.frame.origin.y + endPoint]];
[animation setToValue:[NSNumber numberWithFloat:endPoint]];
[animation setDuration:3.0];
[animation setRepeatCount:MAXFLOAT];
[animation setAutoreverses:YES];
 
[textLayer addAnimation:animation forKey:nil];
 
}

非常酷,而且直观。 事实上CAReplicatorLayer可以做出很多经验的效果。

joericioppo 有一个结合CAReplicatorLayer和CATransform3D起来的demo,甚是强大,就是这3D中各种轴旋转,直接把我转晕了,但是透过它,你可以更好的理解CAReplicatorLayer以及如何更好的使用CAReplicatorLayer: https://github.com/joericioppo/CAReplicatorLayer_Animation

0

简结

December 29, 2011

2011年马上就过去了.
    感激,丰富,新奇,不舍
2012展望
  练熟吉他
  读完刚刚买的一些书籍
   多多转转
……

0

墨尔本生活锁记

December 4, 2011

这次来墨尔本,已然是2011年中的第三次了, 显然已经是对墨尔本熟门熟路了, 刚刚接机的时候司机师傅还跟我介绍墨尔本.这次墨尔本之行,除了在工作上完成来之前定下来的目的之外, 在生活上这一次比前两次可要爽多了. 大概因为前两次来这边, 客户他们觉得并没有很好照顾好我们, 而他们觉得在西安时他们被照顾的很好, 所以这一次他们还是非常热情的. 刚刚好,也是时机碰对了,因为墨尔本正在举行19th Australian Corporate Games 比赛, 而客户所在的公司刚刚也报名参加了其中的很多项目, 当然就包括篮球. 因为之前在西安的时候, 我跟他们打过篮球, 他们觉得我这个技术不错, 于是非常强烈的要求我参加, Sam想了一个办法,让我顶替之前不能上场的球员.要是找我要照片地址等等信息,终于成功的注册上了. 我个人觉得也是蛮搞笑的, 我是TW的然后代表他们上场比赛, 有点外援的意思.
是的,这是我很2的入场证明,哈哈
IMG_20111126_151330
于是我跟Sam约好十二点半,他开车来我住的hotel来接我, 然后周六来到了比赛的场馆, 一进去还是相当不错的, 这场地.
IMG_20111126_164729
不错, 下午我们将会有三场比赛, 每场比赛分上下两半场, 每半场12分钟. 悲剧的是我们等到快比赛时,然后只有四个人来了, 更悲剧的是这个球服上没有任何的号码和名字, Sam带了一根黑笔, 因为他是搞UI的嘛,所以随手一画就帮我们画了个数字,但跟人家其他队伍比起来还真是寒酸阿. 好在在比赛即将开始时,有一哥们赶到了, 凑齐了五个人比赛了. 第一场比赛, 把眼镜一取下来,发现模模糊糊,我才发现我也有一阵子没打球, 不过也没办法, 硬着上, 结果我老被吹走步, 传球都走步, 然后我带球上篮直接人家打我手了,裁判却不吹, 我觉得这裁判很业余. 比较搞笑的是,即使是知道参加比赛, 大家之前都没在一起练习过, 比赛时都是自己搞自己的.囧~
毫无疑问,我们连输三场, 对方这个头都是1.9几的, 我们这边缺少一个中锋去枪篮板.
game in melbourne
不过大家都玩的挺high的,比完之后大家互相在对方球衣上签名.

上个周三Sam提议去吃”泡馍”, 我以为这边真有泡馍了,结果大家走之前一看, 我发现他说的泡馍不是我说的, 他说的是”parma“. 囧了, 然后大家想这去哪吃, 整来整去的,大家决定去吃意大利的doc pizza. 去了之后,发现那家店就是在一个意大利街区里,里面各种意大利的吃的, 这家批萨店非常火爆. 于是Mike,Simon,Sam骑自行车, 我跟Kalops和heather就做车去的. 店里的菜单各种意大利文字和地图, 英文的菜名也是各种意大利字, 搞的我晕头转向, 然后我就问了下Simon帮忙定了一个. 六个人,六分批萨, 每个人都轮换这分享吃, 味道确实非常不错.
IMG_20111201_200039
而且这个啤酒很给力,一开始以为没有问题,喝起来很甜, 结果后劲十足, 晚上回去之后感觉就头痛了.吃完正餐,大家点了一些什么点心,都是非常甜的东西. 吃得饱到不行
IMG_20111201_204839
上周四,参加了Agile Melbourne, 来了几位大牛比如写的Joshua Kerievsky和rebeccawb,主要讲了讲关于产品策略和如何在公司中推行敏捷等等.
IMG_20111129_191731
这周六, Simon提议去这边的一个地质公园: Werribee Gorge State Park. 我其实也是很好奇这边的地址公园会是什么样子, 跟华山有什么区别了.经过将近一个半小时的驾驶,终于来到了这个公园.

werribee gorge state park
进去之后,发现两边的树木很奇怪,他们的皮都剥落了, 非常瘦直, 而且路上人非常少,整个过程中就遇到了六个人. 路上的一个牌子上写着有可能出现鸭嘴兽,我顿时觉得这个很有意思, 因为之前只有在教科书上看到. 但是Simon这个基本上看不到,因为它们生活在非常难以看到的地方.
IMG_20111203_141119
很奇怪的是这边的基本上没有什么花, 我以为这个时节应该是漫山遍野都是花,结果不是. 而且两旁很多枯死的树木.
IMG_20111203_134839
走到中间还没有一段是需要沿着绳索攀岩过去的地方, 我觉得很有意思, 但是就是太短了.
IMG_20111203_142954
快到路程终点时, 看到了Wallaby, 很小很可爱的动物,非常敏觉,听到我们的声音就藏了起来, 但是藏起来的方式就有点像鸵鸟把头埋在沙粒.但是它的伪装技术明显要高超的多,不仔细看,还看不出来.
IMG_20111203_161101
走下来大概有六公里的山路, 花了将近三个小时, 路上非常崎岖,但是风景非常不错, 这么长的路, 一路上跟simon也聊了很多, 关于这边的地理环境, 甚至讲到了无政府主义跟敏捷的联系等等, 还是非常拓展了我的知识的.
IMG_20111203_134316

0

跨域ajax请求

October 30, 2011

    之前写的jenkins-dashboard在几个团队中都在用,效果不错, 但是就是搭建起来有点麻烦. 因为它目前工作原理是起一个ruby进程然后每次去轮训jenkins ci上的每个build的状态(通过jenkins暴露的api),然后写到本地的一个html中.接着在写一个主文件比如dashboard.html,它每隔几秒钟去装载刚才ruby生成的html文件,这样就可以在dashboard上及时显示和更新ci的状态.但是问题是这个太重量级了,你还得装ruby环境和几个gem包甚至可能需要rvm,WTF.于是同事准备只用一个html就搞定,就是说每个几秒钟去发ajax请求到jenkins上拿到最新的构建状态,并更新dom结构或者样式, 这样一来用户就可以主需要下个html文件,并配置下ci的地址和想监控的构建就好了.几天后,我问同事怎么样了,他说ajax请求其他域名出错了. 回头我想了想我是忘记这个事情了, 因为浏览器有Same Origin Policy的安全限制. 好比现在:

localhost —-ajax request —> http://ci.jruby.org/view/Ruboto/api/json

不行, 因为虽然ci.jruby.org这个服务器是暴露了它的json/xml的api,但是这个请求不是在localhost下面,所以无法成功.
        

怎么实现跨域的ajax?

    最简单的办法是将本地的服务器作为一个代理,将第三方的ajax请求都转发出去, 这样一来我在客户端,我基本无需改动.但是它的弊处是它扩展性和伸缩性差.
    第二种: 之前说过因为同源策略的限制,所以ajax请求无法跨域,但是还是hack的方法. 在浏览器中,对于script这个tag是一个例外,浏览器可以请求其他域的javascript数据源.那这么一来, 我可以在有一个script tag中定义好callback函数:

 <script type="text/javascript">
    function doSth(data){
      alert(data.currency + ', ' + data.account);
    }
 </script>
 <script type="text/javascript" src="http://another.domain.com/money?format=xml&amp;callback=doSth"></script>

那怎么让服务器知道它需要组装数据然后调用你在本地服务器定义的callback函数了?(e.g.doSth()). 所以你看到下面的script tag中src属性指定了callback是doSth这个函数引用. 下面是服务器端的大概逻辑:

data=....
request.getParameter('callback');
callback(data)

这个解决方案被称为jsonp(JSON-with-padding),通常被请求的数据都是以json方式来交换.
        

JSONP-jQuery支持

    jQuery自从1.2开始在getJSON()中支持jsonp模式.如果你想请求远程的一个ajax请求,你需要做两件事情:

  • 在url中加上format=[json|xml]和callback=?
  • 定义你的callback函数

这里的callback=? 这个?占位符会在jQuery执行时,替换成匿名函数的引用. 它会首先将匿名函数转换为一个全局函数同时挂到
window对象上. 然后当请求完成,那么它会自动将其删除. 如果你的请求不是跨域的请求,它会将其转换为一个正常的ajax请求.
    比如我想请求

 jQuery.getJSON("http://another.domain.com/money?format=json&amp;callback=?", function(data) {
    alert(data.currency + ', ' + data.account);
 });

        

但是……

    到目前为止,jsonp看起来很不错, jquery对它的支持更是不错, 那么…….问题是?
它需要你在第三方的服务器暴露的服务进行修改,以便使其支持正确响应jsonp模式的请求.可以想象,对于another.domain.com/money?format=json这个功能而言,它不能只返回json对象,而是callback(json_data).我们知道
很多时候我们没有对第三方服务进行修改的权限.所以这个时候便是YQL来拯救.
        

YQL

    YQL是Yahoo提供的一个第三方query平台和api, 用来抓取第三方的数据信息,而不需要关心第三方的具体协议(REST)和数据格式(RSS,ATOM,XML,JSON, etc).
引用YQL的一个示意图:

    有兴趣的可以去YQL Console玩玩. 但是这里我们关注的是YQL它提供JSONP服务支持,也就是说你可以发送一个请求JSON数据的请求同时可以指定你的回调函数,相当于它给所以第三方的服务提供了自动化JSONP服务支持而不需要你对第三方服务做任何的修改,这就使得跨域的ajax修改不依赖于具体的第三方服务端的设置,完全只需要修改前端的javascript代码. yep~
    YQL语法的大概格式是:

http://query.yahooapis.com/v1/public?q=[command]&amp;format=json&amp;callback=?

    结合jQuery,比如你想拿到http://ci.jruby.org/view/Ruboto/api/json的结果:

SELECT * FROM json WHERE url ="http://ci.jruby.org/view/Ruboto/api/json"

但是你需要它对应的REST QUERY的地址(web service):

jQuery.getJSON("http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20json%20where%20url%20%3D%22http%3A%2F%2Fci.jruby.org%2Fview%2FRuboto%2Fapi%2Fjson%22&amp;format=json&amp;callback=?",function(data) {
     alert("view name : "+ data.query.results.json.name);
});

实际上你还可以简化一下jQuery和YQL的使用, 比如构造url的过程, 这里james padolsey就写了一个很方便的封装.
        

Other Cross Domain Strageties

    ok,我们讲过跨域ajax请求的两种办法: 设置代理服务器转发, 使用YQL. 但是这两种并没有本质上解决问题,二者都是通过一些独特的方法绕过浏览器对于跨域请求的限制.实际上,在如今mashup急剧增加, 跨域之间的资源共享确实是非常重要, W3C推出了 CORS(Cross-Origin Resource Sharing), 简要的说它是标准XMLHttpRequest(aka.XHR)的扩展,允许浏览器做出不同域之间的请求. 相比以往的普通的XMLHttpRequest对象的工作方式, CORS不同的是它会先向服务器端发出请求,询问是否其他域的请求是否被允许,如果不是,那么就会被拒绝;如果服务端根据预定规则(有点类似于ACL)去检查其权限为允许放行的话,那么它就会亮绿灯允许这个请求继而拿到第三方的数据.
    所以你可以看到在服务器端可能有定义如下的权限规则:

   Access-Control-Allow-Origin: http://www.another.domain.com

Nicholas Zakas写了一篇关于CORS for cross-domain Ajax的文章.
但是CORS的未来还不清楚,浏览器的支持也不是非常好,而且要求权限验证逻辑写在服务端,对于OPS的人维护起来也确实是麻烦,特别是你有多个平台需要维护(qa/staging/production).
相比JSONP的方式,二者各有利弊, 还可以参考easyXDM.
        

引用

JSONP introduction

Yahoo! Query Language

Screencast: Introducing YQL  from YQL Site

YQL: An Introduction on Youtube

Loading external content with Ajax using jQuery and YQL

Cross-domain communications with JSONP, Part 1: Combine JSONP and jQuery to quickly build powerful mashups

Cross-domain communications with JSONP, Part 2: Building mashups with JSONP, jQuery, and Yahoo! Query Language

        

TODO List:  Rewrite Jenkins-dashboard using purely html/javascript, no heavy-lifting ruby.   
2

Jasmine测试

October 22, 2011

Jasmine作为一个javascript的测试框架, 借鉴了很多其他测试框架比如RSpec,JSpec的有点,包含了很多优秀的设计思想.结合最近的使用心得,它的主要优点有:

  1.  专心做assertion框架, 独立模块化的设计使其非常好的能和其他库进行集成.比如它可以和像提供非常强大健壮的持续集成支持的jsTestDriver(非常专业的non-headless test/spec runner)互相配合相得益彰; 根据jquery相应扩展出的下面会讲到的jasmine-jquery等.
  2. idomatic的语法. Jasmine借鉴了RSpec的语法设计, 如果你是接触过并使用过rspec(当然除了像DHH这种rspec-offender),你发会发现jasmine的语法跟rspec会有惊人的类似,学习使用起来很快就会得心应手,似曾相识.

接下来Jasmine是如何简化javascript的测试了?

 describe("Calculator", function() {
  it("adding algorithm", function() {
     expect(Calculator.add(1, 2)).toEqual(3);
  });
 });

上面是一个简单的测试案例, 简单的测试Calculator的加法逻辑, 但是你看到Jasmine测试的语法确实非常简洁.jasmine这个对于纯javascript的测试,那是得心应手.
但是通常说javascript测试中两个难点:第一个是DOM的交互;第二个是ajax.
与DOM交互测试难处在于: 你需要准备测试对应的fixture也就是html代码片段, 然后让你想测试的javascript在html上执行, 最后在验证DOM上面元素的变化.另外一个方面这些与dom打交道的可能是第三方的库,比如最常见的jquery. 第一个问题其实也可以解决,那便是我们可以使用$(‘body’).append(“some test fixtures”), 但它的可用性不好而且最重要的这个字符串型的html片段非常难看,同时你每次都得在测试最后手动删掉这些fixtures.当然你可以将这些fixture写到一些文件之中, 但是你不得不使用ajax去载入文件然后手动的将其append到body后面,代码非常繁琐而且你每次都得记得去DOM中清除掉这些fixtures. 总结处理html fixtures的两个要点是:

  1. 如何更加简洁来处理html fixtures的读取加载. 避免需要硬编码的在<body>上面去添加字符串型的fixtures,; 也需要避免使用非常长的使用ajax来加载html fixtures文件的尴尬
  2. 在每个测试案例运行之后在DOM文件中清除掉fixtures,这个应该是默认的行为,而不是需要自己手动添加tear down的代码.

另外一个问题便是在assert的时候, 你要判断某一个元素的有没有class? 你可能需要写成:

    expect($("#market-summary").attr("class")).toEqual("no-margin-wrapper");

因为jasmine内置的matcher并没有提供这些, 这也看出来它这些match并不是给断言DOM来设计的. 不过虽然jasmine提供了类似于rspec的个性化matcher的功能,但是这个没有理由让我们重新发明轮子.

这个时候便是Jasmine-jQuery大展伸手的时候了. 它给jasmine提供了两大支持:

  • a set of custom matchers for jQuery framework
  • an API for handling HTML fixtures in your specs
第一个它提供了非常强大的个性化matcher, 简化对于DOM条件的断言. 比如上面的判断元素class name, 在集成jasmine-jquery之后, 我可以写成:
   expect($("#market-summary")).toHaveClass("no-margin-wrapper");

这样一来,代码的可读性也跟着大大提供了. 更多的matcher可以参考jasmine-jquery的github主页.
第二是关于怎么简化处理html fixtures的.

 loadFixtures('myfixture.html');

如果你没有个性化fixture加载的地址, jasmine-jquery会默认去找到spec/javascript/fixtures目录下的myfixute.hml文件,本质上还是使用$.ajax去请求这个文件,然后在append到DOM中. 同时它还提供了一套非常完备的API来处理fixture相关的操作. 另外一个提到便是它每次会在测试案例跑完之后清除掉DOM中掉之前加载的fixture, 所以你不需要每次在之后手动加 $(“xxxx).remove().
另外一个便是测试ajax.这里测试分为两种单元测试和集成测试.集成测试也就是真正会touch服务器端,请使用其他工具比如capybara等. 对于javascript单元测试,在测试时我们都必须注意不能真的让它touch到服务器端,也就是不能真正让它发出ajax请求, 如果发出来了,我们也要阻拦住然后替换它(有兴趣的可以看看sinonjs).  你可以使用jasmine有自己一套mock框架spy, 它可以用来mock, stub和fake你想测试协作者的功能.  假设你有一个ajax请求, 在success的回调函数中做很多事情,比如修改DOM阿等等.

var Validation = function(){
   var init = function(){
      $.ajax({
         url: url,
          // ....
         success: function(data) {
           $("#validation-result").text(data);
         }
     });
  }
  return init : init;
});
var Util = function(){
   var sendAjax = function(url, successCallback, errorCallback,...){
      $.ajax({
         url: url,
          // ....
         success: successCallback
     });
  }
  return sendAjax : sendAjax;
});

在对应的测试中,我们就可以使用spy来mock这个ajax请求,使其并不会真得发出ajax.

describe("Validation", function () {
	it("when ajax request succeeded and callback get called", function () {
		loadFixtures('validation.html');
		expect($("#validation-result")).toBeEmpty();
		spyOn($, "ajax");
		Valiation.init();
		expect($.ajax).toHaveBeenCalled();
		expect($.ajax.mostRecentCall.args[0].url).toEqual('some url');
		$.ajax.mostRecentCall.args[0].success("failed");
		expect($("#validation-result")).toHaveText("failed");
	 })
});
相比之下如果我们将send ajax这段boilerplate放在一个单独的模块,而其他引用它的文件的测试就只需要spy这个模块就好了.
var Validation = function(){
   var successCallback = function (data) {
     $("#validation-result").text(data);
   }
   var init = function(){
     Util.sendAjax("some url", "GET", successCallback);
   }
	 return init : init;
 });

以及测试:

describe("Validation", function () {
	it("when ajax request succeeded and callback get called", function () {
		loadFixtures('validation.html');
		expect($("#validation-result")).toBeEmpty();
		spyOn(Util, "sendAjax");
		Valiation.init();
		expect(Util.sendAjax).toHaveBeenCalled();
		expect(Util.sendAjax.mostRecentCall.args[0]).toEqual('some url');
		Util.sendAjax.mostRecentCall.args[1]("failed");
		expect($("#validation-result")).toHaveText("failed");
	 })
});

而接着对于sendAjax的测试就非常简单了, 我们同样可以选择使用spyOn, 也可以使用sinonjs来mock ajax请求.

Jasmine的问题与注意事项:

  1. 基于jasmine的机制,它会将所有的引用包括报表包含在一个静态HMTL文件中(SpecRunner.html). 如果你使用jasmine-jquery,在浏览器中执行时如果你使用firebug进行inspect时,你会发现一个tag: <div class=’jasmine-fixtures’><div>, 所有的fixtures会默认加载到这个div下,然后测试案例跑完之后,它会去清空这个div的内容.但是问题是如果你有测试案例中代码会对比如<body>等在<div class=’jasmine-fixtures’></div>之外的元素进行操作时,它的痕迹是不会在测试案例跑完之后自己清空, 这个时候需要你自己去手动写代码清空, 否则会影响到下面的测试案例.
  2. 因为所有的测试在浏览器中都跑一个窗口下, 所以代码跟浏览器相关的操作都需要仔细考虑. 比如你代码中有重新转向另一个url,
    window.location.href= "www.xxx.com"

    然后你去assert它当前的url是不是”www.xxx.com”.你会发现这个你的jasmine runner的窗口也跟着跳转了, 然后后面测试也无法跑了.如果你理解了jasmine的工作方式之后,你就知道它为什么会这样了. 这个时候要求你需要划分模块了, 将这个转向的逻辑抽出来放在另一个模块中比如叫做NavigationHelper, 然后测试时,你不需要直接是去

     expect(window.location.href).toEqual("www.xxx.com");

    而是可以spy这个NavigationHelper. 这也许是为什么javascript测试覆盖率很难达到100%的一个原因之一.

  3. jasmine在测试全局变量时非常不给力.但是问题不在于jasmine不给力,问题在于为什么引入全局变量. 就像Douglas Crockford说的: global variables are evils. 但是问题是有一些是第三方库强迫的,而且因为商业上原因,你还不能把它去掉或者替换.这个时候你只能采用ad-hoc的方式了.
  4. jasmine有一个有意思的事情便是如果你的spec测试中, 有语法错误. 比如在calculator-spec.js中
      expect(1 + 1).toEqual(2);

    你给粗心写成了

      expect(1 + 1).toEqual(2))));

    然后你去跑jasmine测试, 你会发现spec runner依然是绿色. 但是如果你仔细看, 你会发现这个测试案例的数量却是减少了.在定睛一看,你会发现这个calculator-spec.js文件就没有被执行, 也就是它会忽视整个测试文件,但是它却可以心不惊肉不跳的执行下面的测试.
    解决办法: 保持良好的观察力, 养成记住上次测试跑了多少个测试案例的数量, 这当然是开玩笑了. 这种情况下两种办法: 第一种可以使用jslint来进行语法格式检查. 比如这个它会提示:

      Lint at line 17 character 20: Expected ')' to have an indentation at 7 instead at 20.
              expect(1 + 1)).toEqual(2);

    当然一个良好的项目有javascript测试,但是却没有jslint检查,这个就不太专业了. 另外一个办法是:你可以跑javascript coverage,
    一个好的项目的话,对于测试覆盖率是有要求的,如果没有达到一定的阈值,就会报错. 比如这里,一个语法问题导致整个测试文件都没跑,通常情况下,测试覆盖虑应该都会降,你这个时候跑CI应该都会错误.但是这个也是不一定的, 它没有jslint检查那么彻底.

引申:

javascript单元测试是非常重要的,它写起来的容易与否跟我们是否合理的划分模块和依赖, 有很大的关系.

就是之前提出的全局变量的事情, 你可以将参数的计算逻辑从第三方库中抽离出来, 这样可以独立测试并覆盖到, 最大限度地做到能测就测到. 另外一个case便是因为javascript测试需要的fixtures并不会随着产品代码的修改而及时更新,但是这个时候你跑jasmine测试它并不会失败.这个事情上次跟gigix聊了一会, 他建议是尽量将javascript划分成比较合理的模块,这样各个模块都可以独立测试,然后通过真正的集成测试比如jasmine来验证行为.比如有一个段逻辑是计算弹出盒子的位置,这个计算位置的逻辑可以抽离出来,然后在写一个cucumber测试,这个测试只要验证盒子出来就好,而不需要关心这个盒子在哪个位置,因为那部分逻辑已经在单元测试中覆盖到. 我后来想到包括你想前面提到的window.href.location这一段逻辑,它抽离出来的好处在于在集成测试上面,哪怕有非常多的地方掉了这个逻辑, 但是我只需要写一个覆盖到重定位的cucumber场景就好了. 其实我想了想这个问题是: 究竟得写多少cucumber测试才能确保产品html的改动同时及时反应在javascript的单元测试中fixtures上面,使得二者保持同步,确保javascript代码时刻能正确工作.之前说到jasmine的设计非常巧妙, 其中之一便是体现在跟CI的集成上面. 简要的来说现在的spec runner的host分为两种: headless>和non-headless. 第一种就是不起显示的浏览器,也就是没有浏览器, 你可以使用java实现的js引擎rhino以及在模拟浏览器中DOM实现的Envjs,优势是速度上非常快.第二种使用浏览器的比如jsTestDriver, jasmine有跟其集成的jasmine-jstd-adapter .
参考:
想简单体验一下:  http://try-jasmine.heroku.com/
railscast上面的介绍:  http://railscasts.com/episodes/261-testing-javascript-with-jasmine
jasmine-jquery作者的博客:  http://testdrivenwebsites.com/2010/07/29/html-fixtures-in-jasmine-using-jasmine-jquery/
SinonJS: Planning, Cheating and Faking Your Way Through JavaScript Tests

1

古今笑

September 24, 2011

最近读了节选部分的<古今笑>,清李渔给古今笑做的序有提到这本书”雅俗并嗜,购之唯恨不早”.<古今谭概>是冯梦龙收集的一些稗官野史中的记录,然后加入自己的一些品评,跟苏轼的有点类似.这类的笔记有意思的在于它提供了很多侧面的方式帮助我们了解古人的形象和生活,拉近了距离.

异服
翟耆年好奇,巾服一如唐人,自名唐装。一日往见许彦周。彦周髽髻,著犊鼻裤,蹑高屐出迎。翟愕然。彦周徐曰:“吾晋装也,公何怪?”
梦龙注: 只容得你唐装!

ps:有点像司马相如跟卓文君私奔时”自着犊鼻裤,与保庸杂作,涤器于市中”的风范.  不知道这个”犊鼻裤”是啥样?看到了这篇”[二十年二十题之]犊鼻裤 ” ,难道就是日本相扑中穿得那个丁字裤? oh no.

浴酒

石裕造酒数斛,忽解衣入其中,恣沐浴而出,告子弟曰:“吾平生饮酒,恨毛发未识其味。今日聊以设之,庶无厚薄。”

ps:喝酒能喝道这个地步,叫我等只能喝两瓶啤酒的人情何以堪?

痴趣
陶渊明日用铜钵煮粥为食,遇发火,则再拜曰:“非有是火,何以充腹?”
贾岛常以岁除,取一年所得诗,祭以酒,曰:“劳吾精神,以是补之。”
韩退之尝登华山巅,穷极幽险,心悸目眩,不能下,发狂号哭,投书与家人别。华阴令百计取之,方能下。
张旭大醉,以头濡墨而书。
ps:每当看到最后一句, "狂草"张旭这个举动,令人感叹,这个是痴迷到了何种地址,都叫人羡慕.
我们应该效仿贾岛,过年时,取一年所写代码,祭以酒,曰:“劳吾精神,以是补之。”
拙对
《谐史》:河南一士夫延师教子,其子不慧。出对曰:“门前绿水流将去。”
子对云:“屋里青山跳出来。”士夫甚怒。
一日士夫偕馆宾诣一道观拜客。道士有号彭青山者,脚跛,闻士夫至,跳出相迎。
馆宾谓士夫曰:“昨令公子所谓‘屋里青山跳出来’,信有之矣。”士夫乃大笑。

ps:这学问可比还珠格格好多了
不知杜少陵
宋乾道间,林谦之为司业,与正字彭仲举游天竺小饮。
论诗,谈到少陵妙处,仲举微醉,忽大呼曰;“杜少陵可杀!”
有俗子在邻壁,闻之,遍告人曰;“有一怪事,林司业与彭正字在天竺谋杀人。”
或问:“所谋杀为谁?”曰:“杜少陵也,但不知何处人。”闻者绝倒。

雪诗
陆诗伯《雪》诗云:“大雪洋洋下,柴米都长价。板凳当柴烧,吓得床儿怕。”又云:“玉皇大帝卖私盐,一个苏州拖面煎。”
又云:“不闻天上打罗橱,满地纷纷都是面。”又云:“昨夜玉皇哀诏到,万里江山都带孝。”

ps:很好的描述当前的物价水平下人民大众的生活
点金成铁
梁王籍诗云:“蝉噪林愈静,鸟鸣山更幽。”王荆公改用其句曰:“一鸟不鸣山更幽。”
山谷笑曰:“此‘点金成铁’手也!”

ps:王安石悲剧了,这一改可是没有"绿"字那么传神
马上食饼
张衡由令史至三品,已团甲,退朝,于路傍见蒸饼新熟,遂买得,于马上食之。为御史弹奏,竟落甲。
梦龙注:向闻二卵弃将,今见一饼失官,若在晋人,反为任诞。

 ps:难得真性情 可以在唐代,而非晋朝 
鸣鹅
会稽有姥,养一鹅,善鸣。右军求市不得,遂携亲友就观。姥闻羲之至,烹鹅以待。右军叹惜弥日。
ps:王羲之果然爱鹅,难道鹅跟书法之间有联系?

爱杜甫、贾浪仙诗
张籍取杜甫诗一帙,焚取灰烬,副以膏蜜,顿饮之,曰:“令吾肝肠从此改易。”
李洞慕贾浪仙诗,铸铜像,事之如神,常念贾岛佛。

ps:张籍中晚年的诗句还真有杜甫的现实注意风范;

梦龙注:袁石公曰:“陶之菊,林之梅,米之石。非爱菊、梅与石也。吾爱吾也。”
僧敄周有端州石,屹起成山,其麓受水可磨。米后得之,抱之眠三日。
ps:看到米芾看完<灌篮高手>之后的情形,抱着篮球睡
柳三变
柳耆卿为屯田员外郎,初名三变。自作词云:“才子词人,自是白衣卿相。”
后有荐于朝者,仁宗曰:“此人风前月下且去填词!”
由是不得志,无复检率,自称“奉圣旨填词柳三变”。
梦龙注:按柳永死日,家无余财,群妓合金葬之郊外,每春月上冢,谓之“吊柳七”。 子犹曰:“生虽白衣贱,死得红裙怜。北邙冢累累,白杨风满天。卿相代有作,谁复追黄泉。呜乎柳三变,风流至今传。”
ps:霸气外漏
肉唾壶
苻朗尝与朝士宴。时贤并用唾壶。朗欲夸之,使小儿跪而张口,唾而含出。
南宋谢景仁裕性整洁。每唾,辄唾左右人衣。事毕,即听一日浣濯。每欲唾,左右争来受之。
严世蕃吐唾,皆美婢以口承之。方发声,婢口已巧就。谓曰“香唾盂”。
张鹭鹚
开宝中,神泉县令张某,外廉而内实贪。一日自榜县门云:“某月某日是知县生日。告示门内典级诸色人,不得辄有献送。”
有一曹吏与众议曰:“宰君明言生日,意令我辈知也。言不得献送,是谦也。”众曰:“然。”
至日各持缣献之,命曰“寿衣”。宰一无所拒,感领而已。复告之曰:“后月某日,是县君生日,更莫将来。”无不嗤者。
众进士以鹭鹚诗讽之云:“飞来疑似鹤,下处却寻鱼。”
ps:跟目前官场一样, 古今都一样
食人胆
五代赵思绾反。尝言“食人胆至千,刚勇无敌”,每杀人,辄取胆以酒吞之。后为郭从义所擒。
生食人耳
宋王彦升俘获胡人,置酒宴饮,以手裂其耳,咀嚼久之,徐引卮酒。俘者流血被面,痛楚叫号,彦升谈笑自如
ps:如此悍将给赵匡胤守西北,不愧是心腹,但是这个太恐怖了把 
程师孟、张安国
程师孟尝请于荆公曰:“公文章命世,某幸与公同时,愿得公为墓志,庶传不朽。”公问:“先正何官?”
程曰:“非也。某恐不得常侍左右,预求以俟异日。”
又王雱死,张安国披发籍草,突于柩前,曰:“公不幸未有子,今夫人有娠,某愿死,托生为公嗣。”
京师嘲曰:“程师孟生求速死,张安国死愿托生。”

 ps:难道此张安国正是被辛弃疾五十骑于万军中擒拿到的叛将?
不敢须
少司徒王祐谄事太监王振。振一日问曰:“王侍郎何故无须?”曰:“老爷无须,儿子岂敢有须?”
车武子妇
车武子妇妒。武子偶偕妇兄夜归,留宿外馆,取一绛裙挂屏上。妇出窥,疑有所私,拔刀径上床,发被,乃其兄也,惭而退。
ps:车胤(也就是车胤囊萤夜读)典故来由之人,居然有如此悍妇, 不得不唏嘘,不过也许他说不定是smart gay
头断复连
正德时,济下一秀才遭流贼乱,奔避不及被贼砍,觉头落胸间而喉不断,亟以手捧头置之项上,热血凝结,痛极遂死。
久之稍苏,卧野田间。寇退,家人求尸舁归。旬日不死,颇能咽汤粥。百日痂脱,视其颈瘢痕如絙入腮下。
ps:坑爹阿,不可能把.不过说不定人的求生意识也有可能
爱东坡
陆宅之善谐谑,每语人曰:“吾甚爱东坡。”时有问之者,曰:“东坡有文,有赋,有诗,有字,有东坡巾。君所爱何居?”
陆曰:“吾甚爱一味东坡肉!”闻者大笑。
ps:东坡要是生在现在,恐怕也只能望猪肉兴叹
解缙
解缙尝从游内苑。上登桥,间缙:“当作何语?”对曰:“此谓‘一步高一步’。”及下桥,又问之。
对曰:“此谓‘后边又高似前边’。”上大悦。一日,上谓缙曰:“卿知宫中夜来有喜乎?可作一诗。”
方吟曰:“君王昨夜降金龙。”上遽曰:“是女儿。”即应曰:“化作嫦娥下九重。”上曰:“已死矣!”
又应曰:“料是世间留不住。”上曰:“投之水矣。“又应:”翻身跳入水晶宫。”上本欲诡言以困之,既得诗,深叹其敏。

解缙四岁出游市中,偶跌,众笑之。吟曰:“细雨落绸缪,砖街滑似油。凤凰跌在地,笑杀一群牛。”
ps:这可可以跟纪晓岚有得一拼,不愧是明朝第一位大三元的人,才思敏捷, <永乐大典>的编辑者.
公猴
三杨当国时,有一妓名齐雅秀,性极巧慧。一日命佐酒。众谓曰:“汝能使三阁老笑乎?”对曰:“我一入便令笑也。”
乃进见。问:“何来迟?”对曰:“看书。”问:“何书?”对曰:“《烈女传》。”三阁老大笑,曰;“母狗无礼!”
即答曰:“我是母狗,各位是公猴。”一时京中大传其妙。
ps:妙不可言~  "公猴" 谐音似 "公侯", 三杨都是公的爵位,这么位高权重的人,这么有名的三杨,却被一妓女调侃.佩服
苏小妹
东坡有小妹,善词赋,敏慧多辩,其额广而如凸。东坡尝戏之曰:“莲步未离香阁下,梅妆先露画屏前。”
妹即应声云:“欲扣齿牙无觅处,忽闻毛里有声传。”以坡公多须髯,遂亦戏答。时年十岁耳。
李东阳
焦阁老芳,面黑而长,如驴。尝谓西涯曰:“君善相,烦一看。
”李久之乃曰:“左相象马尚书,右相象卢侍郎,必至此地位。”“马”与“卢”合,乃一“驴”宇,始知其戏。

邃翁冬天气盛,而西涯怯寒。二公同坐,西涯屡以足顿地作声。
邃翁曰:“地冻马蹄声得得。”西涯见其吐气如蒸,戏云:“天寒驴嘴气腾腾。”
李西涯居政府时,庶吉士进谒,有言“阁下李先生”者。公闻之,既相见,因曰;“请诸君属一对,云‘庭前花始放’。
”众疑其太易,转思未工。各沉吟间,公曰:“何不对‘阁下李先生’?”相赞而笑。
ps:李东阳这位首辅果然是才气外漏,有意思,搞笑. 而杨一清跟李东阳两位明代诗歌的先驱者,私底下如此搞笑,读起来倍感亲切.
石学士
石曼卿尝出游报宁寺,驭者失控。马惊走,曼卿堕地,戏曰:“幸是石学士,若瓦学士,岂不破碎!”
ps: "天若有情天亦老,月如无恨月长圆" 对得起下一句的石学士生活中也是宽容大气. 人品,才气哪一样能不让欧阳修,梅尧臣等诚服
十七字诗
正德间,有无赖子好作十七字诗,触目成咏。时天旱,府守祈雨未诚,神无感应。
其人作诗嘲之曰:“太守出祷雨,万民皆喜悦。昨夜推窗看,见月!”
守知,令人捕至,曰:“汝善作十七字诗耶?试再吟之,佳则释尔。”
即以别号“西坡”命题。其人应声曰:“古人号东坡,今人号西坡。若将两人较,差多。”守大怒,责之十八。
其人又吟曰:“作诗十七字,被责一十八。若上万言书,打杀!”守亦哂而逐之。

一说:守坐以诽谤律,发配郧阳。其母舅送之,相持而泣。
泣止,曰:“吾又有诗矣:发配在郧阳,见舅如见娘。两人齐下泪,三行。”盖舅乃眇一目者也。
ps:有创意

有兴趣者可以去看看<古今谭概>这本书

0