驾校教练平台

2014-10-26 评论(7) 分类:随记

前几天听在学车的朋友说教练正在狂骂学员,还是女学员,骂到快哭了,还没有停止的意思,快看不下去了。在当前驾校这种事情司空见惯,之前学车就体会到了,我的教练还算是中等凶残的,骂同车的学员你是猪啊这么笨,出现一点操作不对分贝直接上涨1000%,我都快受不了了,还有大批更过分的,这个行业真是很奇葩,这种情况竟然还一直到现在,网上搜“驾校 骂人”能搜出一大堆来,很不科学。当然运气好也能碰到和蔼可亲的教练,但几率不是很大。

整个行业这样,就跟个人素质关系不大了,是制度规则的问题,似乎教练们对学员态度怎样都丝毫不影响他们的工作晋升和收入,教练的工作是稳定的,每天照常上下班,做得好做得不好,骂学员不骂学员对他们来说都一样,反正公司缺人不会炒鱿鱼,也没什么晋升空间,合格率不要太低就行了。驾校对教练骂人这种事情是不管的,反正不会影响报名学车的人数,驾校是位置相关的资源,再差也有人贪近来报名,现在学车的人这么多,更不怕没生源。要是教练把学员骂到完全不想来学,那对驾校还更有利,因为有时间限制,过了两三年时间就得重新报名交钱,之前交的钱就给驾校吞了。

以上说的教练属于打工型的教练,在公司的温室下,没有竞争没有压力,做得好做得差没区别。现在市场上还有另一个种教练,叫加盟型教练,他们挂靠在驾校上,但跟驾校不是雇佣关系,他们自己买教练车,自己拉学员到他这里学,只是让驾校提供教学场地和搞定约考收费这些事。加盟型教练相当于自由职业者,自己可以自由控制上下班时间,跟打工型教练最大的不同是打工型都是公司给他们分配学员然后按时发工资,加盟型则是自己去拉学员,拉多賺多拉少賺少。这种类型的教练让学员与教练之前有了种制衡,因为加盟型教练的生源大多靠口碑传播,要是他跟打工教练一样骂人,就没人向朋友推荐他,生源就变得难找,自己钱也就賺得少了。

有了这种制衡关系应该好挺多了,不过还不够,要是这个教练看你不爽决定不要你这单个人的口碑了,那还是照骂,而且被骂惨了你也不能投诉驾校,以你单个人的力量对他的声誉能造成的影响也有限。最好的方式还是做一个网上平台,教练在上面招生,学员可以在上面评价教练。这个平台如果面对打工型教练应该是很难做起来,面对加盟型教练就很有吸引力了,他们本来就要想方设法多招些学员,如果有这个平台应该会蜂拥而上。有了这个平台学员就有了更大的权利,他们对教练的评价很大影响以后的学员选不选这个教练,这时教练要敢再骂人,这个自由职业恐怕是做不成了,另外要是有多个学员向平台投诉这货骂人,平台也可以把他加入黑名单。在这平台上,教练可以获得生源和公平的评价,学员可以获得好服务的保障。

这种平台一般都有先有鸡还是先有蛋问题,像打车软件那样,有很多的士司机用,用户才会用,有很多用户用,的士司机才会用。但这个教练平台没有这个问题,对加盟型教练来说,即使初期上面潜在学员不多,也乐于在上面登个记,多个打广告的地方。另外想找到这些加盟星教练也不难,这些教练都会在加盟驾校上登记,一网打尽得到每个人的联系方式一个个推销也是可行的。对于用户端(想学车的人),可以以普通驾校教练骂人为广告点宣传,跟大伙说咱这的教练素质高,一个区域一个区域地推,具体就看团队的地推能力了。这个平台如果能顺利发展起来,加盟型教练每年就可以招到稳定的生源,賺得比打工型教练多很多,好的教练还能有溢价的能力,可以吸引越来越多的打工型教练转为加盟型教练,市场环境可以变好,规模可以变大。

最后一个问题,就是用户学完车拿到驾照后,就会跟这个平台say goodbye了,以后也不太可能再上这个平台,流动性太大,除非这个平台是挂靠在一个更大的平台上,可能用户多了后可以逐渐发展成把车相关的东西包进来,例如摇号买车交车险年审违章缴费,这得再慢慢细想。初期可以在这个平台以外加多一些资讯/教学类的内容,吸引想学车的人即使不在这里选教练,也可以把它当工具使用。

以上是一个驾校O2O项目的设想,真希望尽快有人能做起来。O2O实在太有前途,除了给用户提供便利,信息透明+评价体系还可以改善行业的服务水平,提供实实在在的价值,很多行业都可以引入这个模式,快餐/的士/代驾的O2O已经发展得挺好,小区服务还在路上,O2O可以把很多行业都变得更好,真有点新时代开端的感觉。

扯扯

2014-10-24 评论(6) 分类:随记

从博客有那么一些认识的人看开始,我写博客就畏手畏脚了,以前的博客是什么都往上写,后来就想写点有主题有内容的东西,后来越来越眼高手低,想写些好的东西,又没那个能力,怕写的东西见不得人,就越写越少了。这次决定整一整这样的风气,写一篇乱七八糟的博客。

博客的时代早就过去,写博客的很少了,我一直写到现在只是跟自己有个约定,一定至少每月写一篇博客,无论是什么内容的。我对自己类似的约定还有每天写日记、保持记账,至今已经连续写了1688天的日记,记账也连续记了4年。其实写这么久日记至今我还没看出有多大好处,大多是流水账,写了也挺少再回去翻看,不过就是形成习惯了,有点强迫症,一天不写就破坏了这个事情的完整性,没有连续了,所以一天没拉下,不过后来发现每天在日记附上一张照片挺有意思,照片很容易翻看,不过这个实施起来有点困难,照片经常忘了拍,没法连续,也就没了那个强迫症。记账倒是带来挺多好处,很清楚地知道自己的财务状况,还有买东西的时候想到待会还要记账,可能就懒得买了,省了点钱。

一直坚持做上面说的几件事,可别觉得我是个很有毅力的人,很多事情我都没坚持下来,没有坚持学英语,没有坚持锻炼,没有坚持一大堆显然有益的东西,不仅如此,拖延症还十分严重,有颗牙就是因为拖延不想去医院,被我拖到不能修复只能拔的程度,工作上的拖延导致很多事情没完成。其实拖延就是懒惰,是人性的一部分,要说拖延和懒惰的区别就是说自己拖延的人意识到这样的懒惰行为是对自己不利的,不应该这样做,但又不由自主地懒了下去,给人感觉这是一种病,才有拖延症这说法。大家都说自己有拖延症,就是环境竞争太激烈,大家都太想往上爬,想获得名利,都觉得自己的懒惰太不应该,所以才成了现代社会人的普遍存在的“症状”。要是大家都优哉游哉,社会没什么竞争压力,工作拖着不做算哪门子拖延症,那是生活。

半年多来上下班路上大部分时间都在听电台,很有意思,电台应该是上下班路上最好的伴侣了,无论是坐地铁坐公车打的开车还是走路都能听,其他像视频,文章,电子书都无法做到这样,所以移动互联网时代电台很有前途,好的节目也很有前途。至今我就听三个,一席,逻辑思维和晓松奇谈。一席就是想做中国的TED,可以听到各种不同领域的佼佼者出来发表他们的观点,展现他们的生活和工作,因为是各种不同的人演讲,不同人的落差还是比较大的,有些讲得让人拍案叫绝,有些不感兴趣。逻辑思维听得比较多,很多观点都是挺不错的,但听多了会觉得很多重复,讲市场万能和互联网万能的太多了,渐渐的观点也不怎么能说服人了。晓松奇谈最近才发现挺好听的,这货实在太能说了,这富家子弟知识分子还能兼有屌丝气质,挺难得,故事说起来引人入胜,当然少不了一些夸张的修辞和很多的一家之言,但总体听起来很舒服,让人觉得历史太有趣了,挺佩服的。可能接下来百家讲坛要转移到电台了,不是整个节目转移过来,而是很多那些能说会道的人会自己在电台开个专栏讲,因为成功案例越来越多了。

在电台听到一个挺有意思的东西:马尔塞斯陷阱,就是历史上战乱时期人口变少,人均拥有土地资源变多,每个人都变得富裕,富裕了不想打战,安定下来生很多娃,人口增多,出现繁荣发展太平盛世,人口增多到一定程度,人均拥有土地资源剧减,人吃不饱饭,对政府意见大,豁出去造反,战乱人口不断减少,又进入下一个循环,所以那些太平盛世都是战乱后的几十年内发生,那些帝王不一定有多英明,只是他刚好处在这个时期,亡国的帝王只是不走运刚好处在另一个时期,电影《雪国列车》就是这个过程的缩影。这个循环的原因是指数级增长的人口与有限的资源间的矛盾,但自从工业革命以来,这个循环打破了,科技的发展使人类能从地球上得到比以前多得多的资源,从而能支撑这么多人口在地球上存在而不用发生战争强制清除人口。这样说来从人类历史上看,无论多么伟大的帝王/军事家都不值一提,真正能影响人类历史进程的是科学家们。第一次接触到这样的历史观还挺震撼的。

听南明历史的时候同时在跟爸妈看抗日神剧,忽然觉得日军入侵跟当时满军入侵性质是一样一样的,在那个时代人们的反应跟我们现在对日军的反应也是一样的,群情激奋抗争到底,最后区别只是满军把事办成了,结果几百年后我们多数人在看待清朝时,大部分对其中英明的皇帝大家褒奖,全国头发都给剃了还说满族人被汉化,黑猫白猫抓到老鼠就是好猫,治得了国就是好政府,而忘了他们之前侵略时是怎样残暴屠城,不知这是因为人的遗忘和适应能力太强的原因,还是胜利者书写历史的原因。

好了,今天就先扯到这里吧。

AFNetworking2.0源码解析<三>

2014-9-15 评论(9) 分类:技术文章 Tags:

续AFNetworking源码解析<一><二>

本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题。

HTTPS

HTTPS连接建立过程大致是,客户端和服务端建立一个连接,服务端返回一个证书,客户端里存有各个受信任的证书机构根证书,用这些根证书对服务端返回的证书进行验证,经验证如果证书是可信任的,就生成一个pre-master secret,用这个证书的公钥加密后发送给服务端,服务端用私钥解密后得到pre-master secret,再根据某种算法生成master secret,客户端也同样根据这种算法从pre-master secret生成master secret,随后双方的通信都用这个master secret对传输数据进行加密解密。
(更多…)

AFNetworking2.0源码解析<二>

2014-9-3 评论(9) 分类:技术文章 Tags:

AFNetworking2.0源码解析<一>

本篇我们继续来看看AFNetworking的下一个模块 — AFURLRequestSerialization。

AFURLRequestSerialization用于帮助构建NSURLRequest,主要做了两个事情:
1.构建普通请求:格式化请求参数,生成HTTP Header。
2.构建multipart请求。
分别看看它在这两点具体做了什么,怎么做的。
(更多…)

AFNetworking2.0源码解析<一>

2014-8-28 评论(31) 分类:技术文章 Tags:

最近看AFNetworking2的源码,学习这个知名网络框架的实现,顺便梳理写下文章。AFNetworking2的大体架构和思路在这篇文章已经说得挺清楚了,就不再赘述了,只说说实现的细节。AFNetworking的代码还在不断更新中,我看的是AFNetworking2.3.1

本篇先看看AFURLConnectionOperation,AFURLConnectionOperation继承自NSOperation,是一个封装好的任务单元,在这里构建了NSURLConnection,作为NSURLConnection的delegate处理请求回调,做好状态切换,线程管理,可以说是AFNetworking最核心的类,下面分几部分说下看源码时注意的点,最后放上代码的注释。
(更多…)

朋友圈那些谣言

2014-8-10 评论(1) 分类:随记

好像不止一次因为朋友圈那些“健康谣言”跟人争论了,我个人特反感这些谣言,特别希望朋友能不要轻信它们转发它们,但太困难了。

为什么这些健康谣言有那么多人转发那么多人信?因为:

1.它们看起来是“善意”的,是对自己好的,多是保养保健,生活注意事项,食品安全等方面,朋友看了觉得咦还有点道理,转发给我的朋友们看看,让他们也健康生活。完全没问题啊,我是善意的,就算它最后被大众公认是谣言,也无伤大雅,善意是挡箭牌,但实际上善意造成恶果的例子在这社会上比比皆是。

2.思考和求证太麻烦。“我给大家分享一篇健康的文章,还要先去思考和考究这篇文章的真假?”我想说是的,应该这么做,而且现在的考究不过就是动动手指搜索一下,但大部分人无法接受这样的做法,太麻烦了。大家对这些文章采取的态度是宁可信其有,懒得思考它的真假,信了没什么成本,不信万一是真的呢?这可能算是现代迷信,古代迷信是:有个穿着道袍的人跟你说,不把村里一个处女扔河里,龙王就不会让天下雨,大家就信了。现在迷信是,一位名字写着“健康养生专家”的人跟你说,黄瓜是喂了避孕药的,微波炉加热食物会致癌,大家就信了。

另外看到两个观点:

1.不可能平白无故的有人这么说,肯定还是有一定的依据才会有这些言论。
深谙哲学中的“存在即合理”,其实我也不太理解为什么会有这么多人杜撰这些谣言,可能是一些平台为了流量雇写手写这些极易被转发的文章,用于积累用户和賺取广告费。

2.你说谣言文章是错的,你怎么证明辟谣文章是对的?
这问题让我有当年方舟子置疑韩寒代笔和身高的感觉,有点无奈,你说辟谣文章不信,我去找相关科学论文,你说相关科学论文也不信,那我就没办法了,现代还没有绝对的真理,还未能绝对解释任意事物的原理,对人体更是一知半解,很多健康问题无法用绝对的科学实验证明。其实很多谣言用常识就可以判断,根本无需辟谣文章,例如晚上几点肝排毒,几点胆排毒,且不说排毒这个概念,时间是人定的,中国共用北京时间,不同地区有时差,人体怎么知道这个时差?但即使这样要说服对方也是不容易。

这些谣言在朋友圈已经流行了一年了,在朋友圈这样封闭的空间里传谣容易辟谣难,而微信对它采取的是放任的态度,没看到任何实质性的改进,不知这是为什么,最后竟然是国家出手干预整治,真是一件神奇的事情。

推荐阅读:朋友圈那种高质量的科学谣言是如何被编造出来的?

iOS APP可执行文件的组成

2014-7-30 评论(31) 分类:技术文章 Tags:

iOS APP编译后,除了一些资源文件,剩下的就是一个可执行文件,有时候项目大了,引入的库多了,可执行文件很大,想知道这个可执行文件的构成是怎样,里面的内容都是些什么,哪些库占用空间较高,可以用以下方法勘察:

1.XCode开启编译选项Write Link Map File
XCode -> Project -> Build Settings -> 搜map -> 把Write Link Map File选项设为yes,并指定好linkMap的存储位置
linkmap

2.编译后,到编译目录里找到该txt文件,文件名和路径就是上述的Path to Link Map File
位于~/Library/Developer/Xcode/DerivedData/XXX-eumsvrzbvgfofvbfsoqokmjprvuh/Build/Intermediates/XXX.build/Debug-iphoneos/XXX.build/

这个LinkMap里展示了整个可执行文件的全貌,列出了编译后的每一个.o目标文件的信息(包括静态链接库.a里的),以及每一个目标文件的代码段,数据段存储详情。

1

以伊书项目为例,在LinkMap里首先列出来的是目标文件列表:

# Object files:
[ 0] linker synthesized
[ 1] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator7.0.sdk/usr/lib/crt1.o
[ 2] /Users/bang/Library/Developer/Xcode/DerivedData/yishu-eyzgphknrrzpevagadjtwpzzeqag/Build/Intermediates/yishu.build/Debug-iphonesimulator/yishu.build/Objects-normal/i386/TKPFileInfo.o
...
[280] /Users/bang/Downloads/yishu/yishu/Classes/lib/UMeng/MobClick/libMobClickLibrary.a(UMANJob.o)
[281] /Users/bang/Downloads/yishu/yishu/Classes/lib/UMeng/MobClick/libMobClickLibrary.a(UMANWorker.o)
[282] /Users/bang/Downloads/yishu/yishu/Classes/lib/UMeng/MobClick/libMobClickLibrary.a(MobClick.o)
[283] /Users/bang/Downloads/yishu/yishu/Classes/lib/UMeng/MobClick/libMobClickLibrary.a(UMANLaunch.o)
...

前面中括号里的是这个文件的编号,后面会用到,像项目里引用到静态链接库libMobClickLibrary.a里的目标文件都会在这里列出来。

2

接着是一个段表,描述各个段在最后编译成的可执行文件中的偏移位置及大小,包括了代码段(__TEXT,保存程序代码段编译后的机器码)和数据段(__DATA,保存变量值)

# Sections:
# Address   Size     Segment   Section
0x00002740 0x00273890 __TEXT __text
0x00275FD0 0x00000ADA __TEXT __symbol_stub
0x00276AAC 0x00001222 __TEXT __stub_helper
0x00277CCE 0x00019D9E __TEXT __objc_methname
0x00291A70 0x00012847 __TEXT __cstring
0x002A42B7 0x00001FC1 __TEXT __objc_classname
0x002A6278 0x000046A7 __TEXT __objc_methtype
0x002AA920 0x000061CE __TEXT __ustring
0x002B0AF0 0x00000764 __TEXT __const
0x002B1254 0x000028B8 __TEXT __gcc_except_tab
0x002B3B0C 0x00004EBC __TEXT __unwind_info
0x002B89C8 0x0003662C __TEXT __eh_frame
0x002EF000 0x00000014 __DATA __program_vars
0x002EF014 0x00000284 __DATA __nl_symbol_ptr
0x002EF298 0x0000073C __DATA __la_symbol_ptr
0x002EF9E0 0x000030A4 __DATA __const
0x002F2A84 0x00000590 __DATA __objc_classlist
0x002F3014 0x0000000C __DATA __objc_nlclslist
0x002F3020 0x0000006C __DATA __objc_catlist
0x002F308C 0x000000D8 __DATA __objc_protolist
0x002F3164 0x00000008 __DATA __objc_imageinfo
0x002F3170 0x0002BC80 __DATA __objc_const
0x0031EDF0 0x00003A30 __DATA __objc_selrefs
0x00322820 0x00000014 __DATA __objc_protorefs
0x00322834 0x000006B8 __DATA __objc_classrefs
0x00322EEC 0x00000394 __DATA __objc_superrefs
0x00323280 0x000037C8 __DATA __objc_data
0x00326A48 0x000096D0 __DATA __cfstring
0x00330118 0x00001424 __DATA __objc_ivar
0x00331540 0x00006080 __DATA __data
0x003375C0 0x0000001C __DATA __common
0x003375E0 0x000018E8 __DATA __bss

首列是数据在文件的偏移位置,第二列是这一段占用大小,第三列是段类型,代码段和数据段,第四列是段名称。

每一行的数据都紧跟在上一行后面,如第二行__symbol_stub的地址0x00275FD0就是第一行__text的地址0x00002740加上大小0x00273890,整个可执行文件大致数据分布就是这样。

这里可以清楚看到各种类型的数据在最终可执行文件里占的比例,例如__text表示编译后的程序执行语句,__data表示已初始化的全局变量和局部静态变量,__bss表示未初始化的全局变量和局部静态变量,__cstring表示代码里的字符串常量,等等。

3

接着就是按上表顺序,列出具体的按每个文件列出每个对应字段的位置和占用空间

# Address Size File Name
0x00002740 0x0000003E [ 1] start
0x00002780 0x00000400 [ 2] +[TKPFileInfo parseWithDictionary:]
0x00002B80 0x00000030 [ 2] -[TKPFileInfo fileID]
...

同样首列是数据在文件的偏移地址,第二列是占用大小,第三列是所属文件序号,对应上述Object files列表,最后是名字。

例如第二行代表了文件序号为2(反查上面就是TKPFileInfo.o)的parseWithDictionary方法占用了1000byte大小。

使用

这个文件可以让你了解整个APP编译后的情况,也许从中可以发现一些异常,还可以用这个文件计算静态链接库在项目里占的大小,有时候我们在项目里链了很多第三方库,导致APP体积变大很多,我们想确切知道每个库占用了多大空间,可以给我们优化提供方向。LinkMap里有了每个目标文件每个方法每个数据的占用大小数据,所以只要写个脚本,就可以统计出每个.o最后的大小,属于一个.a静态链接库的.o加起来,就是这个库在APP里占用的空间大小。

写了个nodejs版统计程序可供使用:https://gist.github.com/bang590/8f3e9704f1c2661836cd

设计符合直觉的用户界面(WWDC2014 #211)

2014-7-1 评论(1) 分类:互联网

看了WWDC2014 Session 211 – Designing Intuitive User Experiences,觉得挺不错,写下笔记。

用笔写字这个技能是我们从小就开始一直锻炼的,刚开始我们写得乱七八糟,后来慢慢变好,在这个训练过程中,书写这个技能已经变成肌肉记忆,在书写的过程中完全不用去思考,不会去注意到是怎样书写,怎样握笔,用了什么笔什么纸,一切都在无意识中完成,书写这一行为已经成为直觉。熟能生巧,这就是直觉的来源。

直觉是训练出来的,专业技能的直觉普通人是没有的,飞机几百个按钮飞行员可以不假思索找到需要的按钮,普通人不行,因为你可能有着别人没有的直觉,所以若纯粹按照你自己的想法做产品,可能会让部分用户用起来感到沮丧,你需要站在用户的角度和情景去设计,了解他们知道什么,不知道什么,直觉是什么。(按国内的说法就是让自己进入小白模式)

为什么产品设计要符合用户直觉?因为这样用户用你的APP不用学习,不用思考,可以随心所欲,用起来很爽,就会去给好评,就会有更多人使用。

设计符合直觉的产品,有5个特点。

5.平台共性 (Platform Savvy)

世上有数不清的各种笔,但你知道他们都可以用于书写,你会用同样的握笔方式,书写方式使用它们,这是笔的平台共性,对一个个体的认识可以延伸到同一平台上其他相似物的认识。

iOS平台上有很多这样的共性,例如在iMail列表里,向左滑动会出现删除按钮,一旦你知道了这个操作,在iOS上其他APP的列表里,你想删除其中一项时你会尝试左滑,预期中它是应该会出现一个删除按钮的,如果没有出现,用户会觉得很沮丧,出现了会觉得很爽很流畅。如果你在一个平台上做的东西不符合这个平台的共性,用户就会觉得你这个APP很奇怪。

建议用iOS平台约定成俗的东西,例如返回按钮位置,按钮点击位置,图标大小,图片缩放等等,这样用户没有学习成本。如果你想打破这些规则,做标新立异的APP,那你需要通过各种途径告诉用户。

4.易于导航 (Easy to Navigate)

1.告诉你现在你处于APP的什么位置。Tab Bar标签/Navigation Bar标题都是干这事儿的。
2.告诉你还能去哪里。用tab bar的优势是把你能去的地方全列出来了。
3.告诉你去到那个地方会有什么东西。把一堆功能分成清晰的几类,让人能从分类中就大概知道那里有什么。

逐级前进

在用户真正需要一个功能之前,不要把这个功能展示给他。可以通过加深分类层次做到这点。虽然这会增加用户选择点击次数,但如果分类清晰,用户不用思索就可以找到他想要的功能,这是更好的体验。

可预期

不要轻易改变用户已经习惯的东西,有重新学习的成本。例如根据使用频率把导航列表的各选项调上调下,用户体验是很糟糕的。

让选择中状态清晰可分辨

Tab Bar的选中状态应该做成什么样?若是仅仅换个颜色,并不够突出选中状态,iOS的做法是把图标填充满,再换显眼的颜色。做选中状态的原则是,要让图标不换颜色时,用户都能隐约分辨这是选中状态。

连贯体验

iOS里相册点击一张图片,图片是从原来的位置放大的,让你确认点击的就是那张照片,返回也是,从大图缩回小图,让你知道这张图片在你缩略图列表的位置。若点击图片然后从右滑进一个大图则没有这种连贯的体验。

提供暗示

一些不易发现的交互方式要提供足够的提示。举例一个星辰APP,点击星辰本身不会去到详情页,要点旁边的(i)按钮才会,但点击星辰会让i按钮闪一下,表示可以点击这里。

少即是多

提供更少的选择,会让更多的用户去选择。若选择多了,用户不知怎么选,反而不选了。在苹果和梨中选一个很容易,在很多种水果中选一个很难。

抽屉式侧滑导航问题

优点:
1.不占用屏幕空间,省去了Tab Bar的空间
2.导航栏位置大,占满屏幕,甚至可以上下滑动

缺点:
1.无法解决上述“我在哪里”“我还能去哪里”这两个问题。导航被隐藏起来,不像Tab Bar一直在那里,让用户知道自己在哪里,还能去哪里。
2.切换点击次数多。Tab Bar只需要一次点击,瞬间去到目的地。侧滑需要点击汉堡图标,等动画做完,再选择目的地,再等动画做完,效率低。
3.要占用Navigation Bar一个按钮的位置。放左边会跟返回按钮冲突,一般解决方法是返回到最上级才出现这个导航图标,这是不好的体验。若导航按钮放右边,右边就不能放一些iOS上习惯放的按钮,如编辑,分享等,导致用户体验很怪。
4.侧滑导航把很多不常用的东西都放进去了,显得很杂乱。

3.清晰 (Clear)

语言

No big words 不要用晦涩的词,用所有人熟知的词。
Avoid jargon 不要用专业术语,除非你的用户群是专业人士。
Be descriptive 用语义清晰的词
Be succinct 简洁
Avoid truncation 避免句子太长被系统用省略号截断
Make text legible 选易读的字体,不要用乱七八糟的花哨字体。

图标

用大家都熟知含义的图标,例如信息图标和搜索图标。
用跟现实中大家熟知的物品形状相近的图标,如闹钟图标。另外别以为软盘是大家熟知的物品。
不要用一个图标代表不同意思,例如用搜索图标代表检查。
图标是不能被用户学习的。
图标是很小的,避免用复杂的图形,不然缩小了不易辨识。

动画

有时用动画可以清晰引导用户。如iOS相机的聚焦,首先聚焦框从大到小缩放到聚焦位置,让你注意力集中到这个聚焦框上,然后框闪了两下,提示你聚焦已完成,可以拍照了。
另一个例子是锁屏输入密码,若输错密码,四个密码原点会跳一下,可以让用户很直观地知道密码错了,而不用去读文字。

2.简单 (Simple)

不要做一个复杂的APP,繁杂的功能会把真正用户需要的功能掩盖隐藏在深处(不适用于中国)。不要给用户一些他们不需要的功能(或者是80%的用户不需要),过多的功能让用户分心,难以找到他们想要的东西。

1.专注 (Focused)

一个APP应该专注做一到两件事。小而美,打磨精品,为用户提供最好的解决方案,才能在120万APP中脱颖而出。

一个CoreText排版性能问题

2014-6-19 评论(2) 分类:技术文章 Tags:

伊书的阅读器是用CoreText排版的,在中文字体的选择上,自带的字体中只有黑体,后来发现日文字体Hiragino也是可以用于中文,而且显示效果比黑体好很多,于是选用了这个字体。但在CoreText排版中遇到个问题,用Hiragino字体排版速度非常慢,几乎是默认黑体的100倍,以下是用Instrument Time Profiler查看排版同一章内容的时间消耗:

Hiragino:

1

黑体:

2

时间都消耗在CTFramesetterCreateWithAttributedString里,CoreText又不开源,看不到内部实现,在TimeProfiler里再继续展开,到最后只能看到时间分别耗在两个TRunGlue::ComputeEndIndex()里,看不出什么头绪。

后来用Instrument Allocation查看APP的内存分配时,发现用Hiragino字体排版生成的CTRun非常多,一章内容有2万多个CTRun。按理说,若一篇文章字体格式全部一样,渲染到frame之前CTRun只会有一个,渲染到frame后一行一个CTRun,怎样都不应该出现这么多CTRun。

3

再测了用黑体排版生成的CTRun次数,这次符合预期,只有70多个:

4

可以猜测到用Hiragino字体速度慢就是因为生成了大量的CTRun,那为什么会出现这样的情况?断点进入渲染处,打出其中一行CTLine数据,如下:

5

发现一些文字自动被转为黑体了。原因是Hiragino只支持部分中文字体,而那些不支持的中文字体就要用黑体代替,又因为一篇文章里会交错出现很多Hiragino支持和不支持的字,于是无法用一个CTRun表示一整段文字,每个交错都用一个新的CTRun,导致CTRun非常多,排版处理逻辑变复杂,耗时变高。

终于找到问题的原因,但如果想用Hiragino字体显示中文,这个问题还是无解的。其实在iOS4.x里是没有这个问题的,估计当时CoreText还没有为字体问题拆分多个CTRun,在5.0才开始这样做。除了我使用Hiragino字体会遇到这个问题外,在中英混杂的文字里用英文字体也有这样的问题,因为英文字体不支持中文显示,还是会被拆分成很多个CTRun,用CoreText注重性能的人可以注意下这个问题。

两种增量更新方案

2014-6-5 评论(3) 分类:技术文章

在邮件/日历/SNS等客户端里,客户端数据要不断与服务端进行数据同步,在同步过程中,只拉取有修改的数据,称为增量更新,增量更新方案一般有两种,一是对比,二是日志。

对比

对比就是客户端请求服务端所有关键数据,跟本地已有的数据进行对比,筛选出增删改的数据进行更新。

用对比方法的好处是服务端什么都不用做,坏处是客户端逻辑复杂,耗网络流量。在这种方案里,数据的新增和删除很容易判断,根据客户端数据的id列表和服务端数据的id列表进行对比就行,若要判断哪个数据有修改则比较麻烦,需要取回数据进行对比,如果从服务端拉回所有对所有数据进行对比会很耗网络流量,有一个优化方式,就是对每个数据的修改进行标记。
以日历为例,一个日历可修改的字段很多,例如时间段,内容,邀请人等,全部拉回来对比不现实,对此可以在服务端给每个日历事件新增一个字段tag,表示这个日历事件的版本,服务端更新一个日历事件时会同时更新这个tag,客户端只需要取回每个id对应的tag,跟本地保存的tag对比,不一致表示这个日历事件已经更新,再去获取日历实体就完成更新了。

若服务端因为某些原因无法给每个数据保存一个版本标记,可以实时计算,在客户端和服务端约定一个算法,把所有可变参数拿出来,通过特定算法hash出一个值,对比这个hash值判断是否需要更新。

邮件协议IMAP,日历协议CalDAV就是用这种方式做增量更新,IMAP并没有做上述的优化,在判断邮件有没有更新时只能乖乖把所有数据请求回来对比,数据是XML,算是相当低效的协议。CalDAV给每个日历事件加了上述的tag,直接对比即可知道是否需要更新。

日志

日志指服务端记录数据的每一次增删改,用一个类似版本号的sync-key标记这次修改,客户端通过一个旧的sync-key向服务端请求,服务端返回这个sync-key与最新sync-key之间所有的修改给客户端,完成增量更新。

这个sync-key在服务端的实现上可以是时间,也可以是一个自增的id,sync-key之间有顺序关系就行。在一个数据集里,每次数据有更新,就新增一个sycn-key,并记录这次更新。图示这个过程:

Untitled Diagram

这个方案客户端逻辑很简单,但服务端负担较大,每次数据更新都要记录,客户端请求时需要查询给出相应的数据。这个方案在实际操作中还有两个问题:

一是时间长了服务端保存数据量过大。可以通过限制记录的条数解决,超过限制就删除最旧的记录。这样做会出现一个问题,若客户端带着在服务端已被删除的sync-key上来请求,该如何处理?一般做法是返回一个错误给客户端,让客户端重新拉取所有数据。

二是若客户端sync-key过旧,增量数据可能过大。客户端数据太老,有太多数据需要更新,若一次性返回所有增量数据,这个请求可能会很大,请求时间太长,成功率也会很低。解决方式是分多次请求,客户端和服务端可以约定一个字段作为阀值,服务端每次返回的增量数据量不超过这个阀值,若总数据超过这个阀值,则分多次请求,通过每次请求返回的sync-key定位下次请求该返回哪些数据。例如客户端sync-key是100,服务端最新sync-key是1000,阀值是50,客户端第一次带sync-key=100请求,服务端第一次返回sycn-key 100-150这一段增量数据,并返回sync-key=150,并有一个值告诉客户端这个sync-key还不是最新,客户端再带上sync-key=150请求,以此类推,直到sync-key=1000。

微软的Exchange/ActiveSync就是用这种方式实现增量更新,ActiveSync还用WBXML压缩了数据,更适用于移动端。此外日历协议CalDAV的也有一个扩展协议RFC6578使用这种方式。ActiveSync和CalDAV扩展协议都有分多次请求增量数据的策略。

————

对于Timeline式的数据,增量更新方式多是以上两种,或者这两种的变体,可以根据业务特性修改或简化其中的逻辑,例如对于微博Timeline,它可以不考虑微博的修改,不考虑同步评论转发数的变化,不考虑同步删除的微博,并且每一条微博都有一个递增的id,那它的增量更新逻辑就很简单,只需要把客户端最新一条微博的id作为since_id传到服务端,返回比这个id更新的微博就行了,这里微博id相当于日志方式的sync-key,算是对日志方式的一种简化。