年初我换了一份工作,在加入百度之前,我曾经和部门的高经谈过这个职业迷茫的问题。他讲了他的故事,也告诉我迷茫哪里都有,干我们这一行,必须拥抱变化。这一年来看似什么都没发生,但其实暗流涌动,从一开始的兴奋,到后面因为项目变动而team剧烈变动,并一度考虑离职,再到新的team,再到非常开心,再到归于平静。中间也发生了一些有趣的事情,一次被动面试被拒,一次主动拒绝了一份待遇丰厚的offer。感受到了不变化,也是一种选择。
我非常的幸运,我的工作是我最大的兴趣,而这个也给我带来了别人无法体会的迷茫。工作对我来说不仅仅是钱,我不会因为更多的钱而换工作。我更在乎我是否在做着我喜欢做的事情,我是否有机会施展我所谓的“才华”,是否有足够的时间,让我学习我感兴趣的东西。
听上去似乎有些复杂,但这个问题不仅仅只有我有。很多同事老大都有,而通常的解决办法就是耐心等,做好平时积累,沉下心来学习。另外赶快去找个女朋友吧。
2014年在生活角度来看是个好兆头,我开始学会独立生活,交水、电费。一开始非常开心,但是后来却发现,独处是一件非常难的事情。发现在一个只有自己的空间中,我的确变得懒了。看书少了,颓废多了。早起少了,晚睡多了。仅仅是一个细微的变化————一个人住,就改变了这么多,这让我惊讶,也让我兴奋。我感到了这是一种挑战。别人称为”君子慎独”。感到了更多的压力与责任。是的,自己是否能够约束自己,为自己的行为负责,照顾好自己。
年底的时候,自己感冒了。可能对很多人都不是个事儿,但是对于一个高中之后就再也没有感冒吃药的人来说,还是一件蛮重要的事情。我突然发现自己的身体,出了问题。变得弱了很多。走在路上我想到了很多,我感觉害怕,是的,因为我发现我的身体没有往日的灵活与力量,那些马路上快速来往的汽车让我感到恐惧。我想到了老妈老爸,还有家里已经8岁的小白。年纪大的家伙们,需要人的照顾。
学习在我毕业之后就成了我生命中的主线,今年我参加了非常非常多的网课。我非常开心前几年对英语的不断积累,让我可以几乎无字幕的情况下,完成大部分课程。并且有不少课程深深的改变了我对这个世界的认识,打开了另一扇窗户。2014我对历史产生了极大兴趣,对古书中蕴含的道理也非常赞同,他们对人性的把握让我脑洞大开。他们对失败的看法,对成功的定义,面对问题,解决问题的思路让我感到深深的不如,也感到由衷的开心。求知的道路还有很多需要努力。唯一的问题在于看书太少了,附上DNA的统计
2014年我有很多的时间和精力放在了如何找到女朋友这个问题上面。在问了不少“有经验的前辈”,并且“打入敌人内部”之后,我得出了一个结论——我的处境非常非常糟糕。很多人在10几岁的时候就已经可以开始追妹子,并且已经开始看各种书,积累各种方法论了。对于一个26岁。还不能看下一本书的我来说,已经落后了整整10年。扪心而我,我发现我做了很多感动自己,恶心别人的事情。妹子们早已被表白到无所谓,拿礼物拿到手软,当然不会在意多一个我,少一个我。世界无外乎有用和无用,在深深的感受到这个世界的恶意之后,我不得不开诚布公的和老妈进行了长谈,表示我注孤的情况,并得到了她的同情。然后开始了相亲之旅。
和绝大多数人一样,非常不顺利,相亲就像是打怪升级,不断的练习,积累经验。虽然没有结果,但是最用心的那个人必然收获最大。再见了10多个之后,真的很不容易不容易遇到一个合适的妹子。可以让我跳入下一个学习区——如何维持一段长期的亲密关系。
追妹子的过程中让我慢慢意识到一个问题,如果你始终都没有办法追到一个妹子,这就说明自己在建立人与人信任关系上面,有问题,至少也是你不愿意向另一个人share你的时间,另一个人也不愿意share她的时间给你。建立信任的过程很难,妹子又是个体差异极大的生物,你无法预知眼前的人是不是最后一个,所能做得只能是尽自己最大努力,珍惜彼此之间的缘分。
这就是我的2014,4个维度。看似平稳,实则充满变化的1年。上海的事情告诉我们,生命有时候很短暂。我希望我的家人,朋友,自己能够平平安安就好。
]]>information flow
的概念。这篇文章简单介绍另一个在编程中非常重要的思想或工具——状态机(State machine)
。对大多数计算机专业的家伙们来说,这应该是一门比较难学的课程,里面包含一大堆揪心的名字比如DFA,NFA,还有一大堆各种各样的数学符号,又是编译原理的基础。不过很遗憾,似乎在做完编译原理课程作业之后,很多人再也没有实现过或是用过状态机了。本文通过一个游戏demo来简单描述一下状态机在实践中的应用。demo code
首先看下我们的使用场景,假如我们需要设计一套联网对战的小游戏。第一个难题可能是如何建立一个通道,让2个手机相互发送消息。这里我并不打算引入server端开发,希望只是通过客户端来实现这个逻辑,这里使用LeanCloud API来简化这个过程。这样我们可以暂时不考虑技术细节,直接站在业务角度去思考如何建立这个游戏。
正式开始游戏之前,总会有一个邀请的环节。假如我们有2个用户,分别是Host,Guest。Host创建游戏,Guest加入游戏。游戏的整个流程和我们平时玩的对战游戏流程并没有多大不同。
join message
join message
。由于我们只是选择1vs1。这里假定Host同意Guest加入游戏。Host向Guest发送join confirm message
join confirm message
, 向Host发送Go
消息,表示Guest已经进入游戏Go
消息。也进入游戏。现在的构想的逻辑只有5步,但其实还会包含很多逻辑,比如超时机制,重发机制。由于中间状态很多,还可能有我们没有想到过的问题。在面对这种复杂逻辑时,会通过状态机来帮助我们理顺逻辑。这时,我们脑中思考的业务其实是一个状态到一个状态的图。 如下
上半部分是游戏的创建者,下半部分是游戏的加入者。
一开始,尽量简化模型,这里红色剪头表示我们的正确主流路线,黑色表现出错路线。也就是说,一旦错误,就回到原始Idle
状态。
在想清楚所有逻辑,并考虑清楚正常路线和错误路线之后,就可以开始写代码了。为了方便,这里直接使用第三方的状态机框架TransitionKit。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Event 是建立State到State的路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
|
state machine 是一个蛮厉害的锤子,只要是一个工具,就肯定会被滥用。。。state machine最大的好处是在于,方便我们思考清楚所有细节,主线,和错误流程。避免因为考虑不周全而产生的bug。结合之前的information flow
的思路,会让我们的软件设计更加清楚。
在分布式系统中,某些情况下我们依然需要实现原子操作,有很多方式,其中No Redo(Undo) Log 便是在工程中运用最广泛的思想之一。他的过程非常简单。
下面是一个简单系统的状态。
为了实现原子的修改 A,B,C的值。我们把A,B,C 看成一个集合,或是一个“目录”。
1.做一次copy
2.对于每个更新的操作,创建一个新的item,然后在新的目录中保存修改后的新值。
3.原子性的修改生效目录指针。
通过原子性的修改一个值,切换一个状态,完成一系列分布式操作原子性的修改。
]]>在分布式系统中,宕机是需要考虑的重要组成部分。日志技术是宕机恢复的重要技术之一。日志技术应用广泛,早些更是广泛应用在数据库设计实现中。本文先介绍基本原理概念,最后通过redis介绍生产环境中的实现方法。
数据库设计中,需要满足ACID,尤其是在支持事务的系统中。当系统遇到未知错误时,可以恢复到一个稳定可靠的状态。有一个很简单的思路,就是记录所有对数据库的写操作日志。那么一旦发生故障,即使丢失掉内存中所有数据,当下一次启动时,通过复现已经记录的数据库写操作日志,依然可以回到故障之前的状态(如果在写操作作日志的时候发生故障,那么这次数据库操作失败)。
操作流程简单如下(假设每次数据变化,都提交):
恢复流程:
优点:
缺点:
先具体化下,如果我们内存中保留一个a的值,记录了写操作比如 a = 4; a++; a--;
当这些操作上千万、亿之后,恢复非常慢。甚至可能最后一条就是a=0
,按照之前的算法,我们却跑了很长时间。
那么根据这个场景,很容易想到一个解决方案。
操作流程:
begin check point
end check point
恢复流程:
end check point
中配对的begin check point
。优点:
一些棘手的问题:
在做snapshot的时候,往往不能停止数据库的服务,那么很可能记录了begin check point
之后的日志。那么在重新load begin check point
之后的日志时,最后恢复的数据很有可能不对。比如我们记录的是a++
这样的日志, 那么重复一条日志,就会让a的值加1。反之如果我们记录是幂等的,比如一直是 a=5
这种操作,那么就对最后结果没有影响。很显然,设计幂等操作系统很麻烦。
设计一个支持snapshot的内存数据结构,也比较麻烦。
典型的是通过copy-on-write机制。和操作系统中的概念一样。当这个数据结构被修改,就创建一份真正的copy。老数据增加一份dirty flag。如果没有修改就继续使用之前的内存。这样在做snapshot的时候,保证我们的dump数据是begin check point
这个时刻的数据。显然这个也比较麻烦。
还有一种支持snapshot的思路是begin check point
后,不动老的数据。内存中的数据在新的地方,日志也写在新的地方。最后在end check point
做一次merge。这个实现起来简单,但是内存消耗不小。
Redis 是一个基于内存的database,不同于memcached,他支持持久化。另外由于redis处理client request 和 response 都是在一个thread里面,也没有抢占式的调度系统,核心业务都是按照event loop顺序执行,而磁盘写日志又开销很大,所以redis实现日志功能做了很多优化。并且提供2种持久化方案。我们需要在不同的场景下,采用不同的方式配置。
某个时刻,redis会把内存中的所有数据snapshot到磁盘文件。更通俗的说法是fork一个child process,把内存中的数据序列化到临时文件,然后在main event loop 中原子的更换文件名。redis,利用了操作系统VM的copy-on-write机制,在不阻塞主线程的情况下,利用子进程和父进程共享的data segment实现snapshot。具体是代码实现在rdb.c
, function at rdbSaveBackground
优点:
缺点:
为了解决上面的问题,redis增加了AOF。
在database术语中,也被叫做WAL。如果开启的AOF的配置,redis会记录所有写操作到日志文件中。那么redis同样会遇到之前我们提到过的问题。
+1
日志。那么redis是如何解决的呢?
rewriteAppendOnlyFile
来处理AOF文件过大情况。前面我们知道了,这种check point
的机制还是比较麻烦的。那么redis是这么设计的。
优点:
这些都是性能和稳定性之间做的权衡,根据不同场景需要调整。
【作者徐冰,1994年从中国成都到新加坡。目前修读中医学士学位,兼职电台主持】
无论是谁,有怎样的社会地位,在光鲜亮丽或毫不起眼的外表下,谁没有自己的故事?谁不是一路走来跌跌撞撞留了或深或浅的伤?在独自一人的时候,有多少人可以真正自在地和自己相处?
一次在“医学心理学”课上,老师暂停上课,将课室灯光调暗,让我们做个冥想的练习。
她让我们沉淀思绪,在尽可能放松的状态下,回溯过往。
我们一页页翻过我们各自的故事,重回生命中的重要时刻,走过一级级或快乐,或悲伤,或怨恨,或遗憾的台阶,回到原点。
想象,我们经由时光隧道回访旧时的家。走过熟悉的街道,看到家的门牌,走进去。家居陈设是否和过去一样?家里都有谁?他们的样子如何?他们之间的互动如何?然后,我们看到童年的自己。
这时,容许自己好好看看童年的自己。他是什么样子?他是什么表情?看进他的眼睛,他是否快乐?感受他的各种情绪,盼望和恐惧,愤怒和无助,自责和愧疚。容许自己,陪伴童年的自己片刻,听听他有什么话想说。
把他抱上膝头,轻轻地,拥抱他。
如果他哭,就让他哭。
容许自己,给童年的自己深深的理解和安慰,告诉他,他现在十分安全,没有任何人任何事可以再令他受伤害;告诉他,他只是一个孩子,没有做错什么,在能力许可内,他已经做得够好。跟童年的自己道歉,抱歉忽略和冷落他太久,然后,承诺你再也不会苛责他,嫌弃他,在任何时候,你只会爱他……
我不是第一次接触这样的练习。之前在台湾参加“萨提尔模式”课程时,老师也带着我们做过治疗性的冥想,我已经走过整个过程而且得到了很好的疗愈,因此对我来说,不再有强烈的心理冲击,但我仍感受到明显的情绪涌动。
而许多同学,包括四五十岁的大男人,在那一刻,都不能自已,泣不成声。
多数人,可能从未想到自己会有这样激烈的反应,从未意识到在内心深处,有这样深和痛的创伤。
我们每个人的心里,都有一个内在的小孩。他是长久以来被我们努力压抑的各种情绪,被刻意忽略的伤痛的累积。无论是谁,有怎样的社会地位,在光鲜亮丽或毫不起眼的外表下,谁没有自己的故事?谁不是一路走来跌跌撞撞留了或深或浅的伤?在独自一人的时候,有多少人可以真正自在地和自己相处?
在亲密关系中,又有多少人却感受到难以言说的寂寞?有多少人一直被困在过去走不出来?又有多少人把痛苦转嫁他人造成新的伤害?
那内在的小孩,一直在那里,以各种方式,也许是身体的不适,病痛,或精神上的失调,或人际关系的危机,来呼求我们的关注。我们却从未去理会和照顾他,更不知道那就是我们所有痛苦和不幸的根源。
让我们回到源头,与自己和解。
不要觉得这很荒谬,下次当你一个人的时候,让自己独处在一个不被打扰的环境里,让心静下来,走过岁月的小径,去探望幼年的你。跟他一起玩耍,陪他,注视他,抚摸他,温柔地待他。好像你是他的父母一般,无论他说什么做什么,你都不加评判地接纳。他跟任何一个你爱的人一样,值得你去爱。尤其是,如果那小小的人儿从未得到过足够的爱的话,你,只有你,能带给他补偿,改变他的命运。改变了他的命运,你就改变了自己的命运。
]]>software architecture 听上去是一个很大的概念,实际上也包括很多东西,里面的争议也很多。在我看来软件架构最好放在小的场景中理解。
我们有2个页面。
2个页面分别显示一个数字,这个数字应该相同。详情会修改这个数字,这里我们发现,详情页面和主页面数字不一样。
这里首先的感觉就是,详情页面返回,主页面数据没有刷新,导致数据不一致。 那么Fix这个Bug的方法,就是在主页面出现的时候刷新界面
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.displayLabel.text = [[CUDataDAO selectData].data stringValue];
}
现在来看,还不错。但是,我们调用selectData的次数则变得非常非常多。数据不是经常变化的。
我们发现既然数据的改变是在页面B进行的,那么页面B修改这个数据的时候,应该把数据变化”通知”给页面A,那么我们写了一个Delegate
@protocol CUDetailViewControllerDelegate <NSObject>
- (void)detailVC:(CUDetailViewController *)vc dataChanged:(NSNumber *)data;
@end
在页面B修改数据之后,通过delegate 通知给页面A。
- (IBAction)changeButtonClicked:(id)sender {
int value = arc4random() % 100;
[CUDataDAO setData:value];
self.displayLabel.text = [@(value) stringValue];
if ([self.delegate respondsToSelector:@selector(detailVC:dataChanged:)]) {
[self.delegate detailVC:self dataChanged:@(value)];
}
}
到此场景1得到了不错的解决。
这时我们增加了另一个页面C。这个场景会稍微抽象一点,我们定义了3个数据
问题1中 dataA = dataB。在问题2中dataA = dataB + dataC;
也就是说页面C的修改,也会影响页面A的数据,那么我们是不是也要写一个XXXXDelegate呢?
这时我们的大脑嗅出了一些不好的味道,如果再来个什么dataD,dataE,我们要写这么多的Delegate么?对于多对一”通知”这种味道,很自然的想到了不用Delegate,而是用NSNotification
来做。让我们未雨绸缪一下,定义一个Notificaiton
NSString *const kCUDataChangedNotification = @"CUDataChangedNotification";
[[NSNotificationCenter defaultCenter] postNotificationName:kCUDataChangedNotification
object:nil
userInfo:nil];
那这个变化broadcast到listener,看上去是一个很赞的idea。
过了一段时间,我们发现问题2的方法有一个Bug,当界面停在页面B的时候,切换到页面C,修改数据,B中再返回时,数据和页面A的数据不一致。
那也可以类比解决方法B,得到了下面的方法
既然A和B的数据不一致,而A的数据比B的新,那么保留一个B的指针,然后A变化的时候,更新B就好了。
- (void)handleDataChangedNotification {
[self updateLabel];
[self.vc updateLabel];
}
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:@"push"]) {
CUDetailViewController *vc = [segue destinationViewController];
if ([vc isKindOfClass:[CUDetailViewController class]]) {
self.vc = vc;
}
}
}
页面C实在是太简单了,这次我们希望在页面C中显示页面A的数据。因为上次我们就产生了一个数据不一致的问题,这次我们注意到了,那么怎么修改呢?
在看了看整个APP各种通知之后,觉得挺麻烦,准备用一个取巧的方法。可以类比解决方法A。在页面C出现的时候,刷新数据,至于什么性能问题,不管了,先fix bug。
- (void)viewWillAppear:(BOOL)animated {
[self updateLabel];
}
- (void)updateLabel {
int dataB = [[CUDataDAO selectData].data intValue];
int dataC = [[CUDataDAO selectOtherData].data intValue];
self.dataLabel.text = [@(dataB + dataC) stringValue];
}
这时的数据需要不断的变化,我们在CUDataDAO
加了一个timer 模拟数据变化,数据变化的原因可能是server push 一些数据。client 本地数据库更新了数据,需要在页面A、B、C中显示。
页面C的数据又不一致了。。。。
走到这里,我们需要重新思考为什么这个问题会不断的重复出现呢?software architecture
就是来解决这个问题的。但是在提出一个合理的方案之前,先思考一个概念。
我们把数据库中的数据,显示到屏幕上,或是传递给View时,这个过程其实是对data 做了一次copy。而且只要不是通过引用或是指针这些方式,通过值传递的方式都是对data做了一次copy。而这个copy的过程,非常类似Cache。
通常建立一个Cache会遇到2种问题。
让我们用这个思路来看我们的解决方案
这是一个非常典型的Cache情况B
。数据库的数据并没有变化,但我们却多次重复计算cache
页面之间的关系可以用下面来描述
这里我们隐隐能够感觉到问题,A的数据变化依赖于2个地方。不急,再往后看
事情变得更糟了
和解决方法A类似,同样的重复计算Cache问题。
现在还是一个简单的Model,如果project变得很大,那么就会变成这个样子
每一个X
都可能是一个Bug。
《Advanced iOS Application Architecture and Patterns》 中,把这个图叫做information flow。我们的直觉会告诉我们,这个信息的传递,应该是自上而下的树或是森林,而且最好是一个层次平衡结构,要清晰,每一个位置都有相对于的职责。那我们就需要制定一个规则。
在想这个规则之前,如果把上面的图背后的数据忘记,我们感觉这很类似内存模型。当然内存模型会比较复杂。但是我们可以借鉴很多”内存管理中的规则”,比如谁创建,谁销毁。同样,在我们的information flow中,我们希望谁创建Cache,谁更新Cache变化
DAO的数据库似乎很难做这件事情,我们引入了一个新的元素dataSource
(当然他本身又是DAO的一个Cache)。其中A、B、C3个都会显示数据,那么他们应该在一个层级,其中B、C会修改数据,他们会把这个数据返回给dataSource
,而通过dataSource
来把这个变化通知到A、B、C。
这样带来的好处很明显,我们再添加一个D,也不会对其他地方的数据产生任何影响,我们的Unit Test、Mock也更加好写。
从局部来看,我们之前的思路都没有任何问题,但是整体来看却把问题隐藏化。关键的问题是在于没有找到Truth
,找到问题真正的地方。而找到真正的地方,需要我们在大脑中有一个清晰的information flow
或是data flow
。了解之间元素的相互关系,才能建立一个个的层。才能坐到真正的解耦,解耦并不是仅仅一个个的Manager
,更重要的是建立一套清晰的flow机制,或是消息机制,如果没有一套flow,中间引入的各种各样的方法,即便使用了各种设计模式,整个software 依然是深度耦合。
上面的model,有些同学还可能觉得这是交互上面的问题,这个交互看上去非常的复杂,不是一个好设计。
我这里列举一个实际的例子:
A页面要创建动画,动画背后包括很多数据,这些数据会在B,C甚至更多的页面,或是后台被修改。动画本身实际上体现在View,而这些view可能不仅仅在A中有,B,C可能也会有部分的View。
当然我们可以用单例的法子。单例是个魔鬼,被很多滥用,这个场景用单例,其实仅仅是把全局变量合理的封装在了单例下,因为这份数据,并没有任何理由要一定是一份copy。
在了解这个概念后,再看一些server的架构,规则时,也会更容易理解这些层之间的关系。包括
software architecture 涵盖的东西非常多。这篇只是一个引子,介绍了设计之前的准备工作。但是在实际过程中,我们的模型可能要比我这里写的还要复杂很多。下一篇会介绍一种策略用来处理更加复杂模型的情况。
最后附上一个完整功能的 demo code
这是上一篇提到的hypothesis的计算公式。
当计算这个表达式值的时候,往往第一个感觉是写一个for loop 然后累加求和
prediction = 0;
for (int i = 0; i < n; ++i) {
prediction += theta[j] * x[j];
}
但是在machine learning中更倾向于使用矩阵的方式。 比如同样的公式,会看成矩阵相乘。
其中theta和X分别是
这里通过矩阵或是向量来代替之前的loop。
这是上一篇提到的算法
计算function J如果用octave来实现则是这个样子
function J = computeCost(X, y, theta)
%COMPUTECOST Compute cost for linear regression
% J = COMPUTECOST(X, y, theta) computes the cost of using theta as the
% parameter for linear regression to fit the data points in X and y
% Initialize some useful values
m = length(y); % number of training examples
% You need to return the following variables correctly
J = 0;
% ====================== YOUR CODE HERE ======================
% Instructions: Compute the cost of a particular choice of theta
% You should set J to the cost.
t = (X * theta) - y;
J = (sum(t .* t)) / (2 * m);
% =========================================================================
end
而求偏导数迭代更新theta的代码则是这个样子
function [theta, J_history] = gradientDescent(X, y, theta, alpha, num_iters)
%GRADIENTDESCENT Performs gradient descent to learn theta
% theta = GRADIENTDESENT(X, y, theta, alpha, num_iters) updates theta by
% taking num_iters gradient steps with learning rate alpha
% Initialize some useful values
m = length(y); % number of training examples
J_history = zeros(num_iters, 1);
for iter = 1:num_iters
% ====================== YOUR CODE HERE ======================
% Instructions: Perform a single gradient step on the parameter vector
% theta.
%
% Hint: While debugging, it can be useful to print out the values
% of the cost function (computeCost) and gradient here.
%
s = sum(bsxfun(@times, X * theta - y, X));
theta = theta - (alpha / m) * s';
% ============================================================
% Save the cost J in every iteration
J_history(iter) = computeCost(X, y, theta);
end
上面的2部分代码如果做一些合并分别可以简化成1行代码。说到这里自己还是相当羞愧的。今天早上花了3个小时才搞定这2行代码…主要时间花在了 2个地方。
在费了老半天力气搞定Vectorization的转变之后,不得不想想为什么要用这个方式做。obviously有2个好处,Andrew课上也提到了好多次。
第一个很好理解,而且把循环的一大堆代码写成一行,显得逼格很高。 第二个会比较麻烦,涉及到了并行计算优化。
在之前的算法中,我们看到了每一次调整theta都需要iterate整个所有的example,但实际中往往需要处理上百万个examples,而这样的iteration显然是不能接受的。实际上会随机选取一部分examples然后去迭代theta,最后得到一个较为可靠的theta向量。
最后附上Andrew作业的图片,虽然Andrew 不希望把答案放在网上或是论坛什么的,不过我觉得都过去2年多了,应该没关系了。
]]>
秦国百战百胜,不在于它真能变法,而在于列国不能真正变法 历史说明了一个道理,不能适应时代的,只有被淘汰消失
历史上的所谓的成功和失败,就看你站在什么位置,用什么尺度来衡量。而判断成功和失败的标准,最后还是问自己
想要成功,就要懂得把理智放在感情之上。
谋事在人这是真得有用,有人用很多的时间去提高效率,但是节约的时间,并没有被真正利用起来。那么这个办法也很一般。 自己需要自己不断的努力,才能让之前的办法真正变成好办法。。。
方法和人关系太大了
这个社会不外乎需要和有用。
你需要知道别人需要什么,你才能变得有用
富家子弟处逆境难 穷家子弟处顺境难
人生总会有赌博的时候,对错往往是命运差别巨大。 百折不挠的民族
人生:运气 ,自我要求。好好准备,让自己成为那样的条件。等待机会。
理想是,你知道你下一步应该做什么 妄想是,你只有目标却不知道从何做起
自强的第一步,不自欺
在中国文化中,最高的道德和最高的智慧必然是合一的
学会认错,改过。
改变环境,需要改变自己,让自己适应那个自己想要的环境的要求。 如何改变,通过学习
做事之前,为虑胜,先虑败,方能考虑周全
历史启发智慧,所有的学问都能带来智慧
审时度势:
时:你所处的环境。 势:环境变动的方向
变的是时间和环境。 不变的是人性和良知
不仅仅是自己改变,需要改变周围的人。
历史用真,去伪。。。 改动人心
]]>机器学习很久之前就已经热得不行了,直到最近这几个星期,自己才打算了解一些这方面的东西。原因大概有这么3点。
有了这3条,足够我忙活好几个月了 : )
在机器学习中,有2个很大的思路监督学习(supervised learning)
和非监督学习(unsupervised learning)
监督学习,用通俗的话来说就是你知道问题的答案,需要计算机给出一个更标准的答案
。
非监督学习,用通俗的话来说就是物以类聚,人以群分
。我们拿到了很多数据,但是不知道问题的答案,希望计算机给我们提供思路。
在生产环境中,往往采用混合模式。比如图片搜索,如何能够查找网页中判断那个图片是老虎,那个是狗。就有2个思路。
2个角度相互校验,稳定之后,就可以产生足够的标注信息了。
线性回归主要用于手环的里程部分的计算,涉及到更细节的是 最小二乘,梯度下降。这里从先从最简单的一元线性回归开始。
Regression Problem : Predict real-valued output
最关键的在于如何描述hypothesis。
那么应该如何选取参数呢?直觉告诉我们这个直线需要尽可能的拟合我们的数据集。
通过下面的cost function 来评估参数的好坏。算法的目标也很清晰,让函数越小越好。
那个这个cost function 到底是个什么样子呢?
当然这个图还是看起来比较麻烦,Andrew 用了更为简单绘制的图来表示(有点类似等高线)。 相同的圆圈上,有着相同的cost function value。这里可以看到和上面的图一样,有一个极值。
梯度下降,不仅仅是用于线性回归,也可以用在其他机器学习的场景下。
我们的目标是寻找这个图形中的最小值,也就是靠近蓝色的地方。直觉告诉我们,我们先随机一个点,然后沿着最大的坡度向下走最后就可以走到一个极值里。
这个算法也有问题,随着第一个点的位置不同,我们可能找到一个局部最优的解,而不是全局最优。
好在在很多实际问题中,我们遇到的情况要好很多,往往只有一个极值。
那么梯度下降的算法就可以简单的描述出来,分别计算2个维度的偏导数,直到函数收敛
通过分别计算偏导数,a 为learning rate,决定每一步的步长,太小函数收敛很慢,太大则可能无法找到极值,甚至函数无法收敛。
这里Andrew 着重指出了一个叫做同步更新的概念
如果不同步更新,最后也可以得到极致,但是Andrew 更推荐计算完成所有的参数之后,再一起同步更新。
将图1-4分别偏导后
很多时候我们不仅仅满足2个参数,决定事情的因素很多,我们需要更一般化的公式。
算法
分别求偏导后
思路: 希望所有的feature在相同或是类似的范围之内,这样梯度下降会更快收敛。
下图是feature的范围不在一起的运算过程,可以看出来不是圆形,2个维度调整的步长不一样,导致很多反复
下图则是调整过的feature,好了很多
更一般的,Andrew 推荐每一个feature放在[-1, 1]区间范围内
说到Learning Rate 就不能不提收敛(convergence)。一般应该定义多大的阀值来判断是否收敛呢?
Andrew 更推荐用图表的形式,因为这个不仅仅可以看到是否马上收敛,而且还能看到算法是否运行正常,是不是一些参数的问题,导致算法无法收敛。
下图是2个出了问题的J函数,通常来说是Learning Rate 过大。
最后Andrew 还提供了一些practice的Learning Rate 选取方法,比如一些0.001, 0.003, 0.01, 0.03, 0.1, …
说到Core Animation 不能不说Layer, 一个个Layer通过tree的结构组织起来,在Display的过程中实际上有3种Layer tree。
model Layer tree
中的Layer是我们通常意义说的Layer。当我们修改layer中的属性时,就会立刻修改model layer tree。
layer.position = CGPointMake(0,0); //这里的修改会直接影响model layer tree
presentation tree
是Layer在屏幕中的真实位置。比如我们创建一个动画
1 2 3 4 5 6 7 8 9 |
|
下面是屏幕输出结果
model:{73.5, 155.5}, presentLayer{73.5, 155.5}
model:{200, 400}, presentLayer{73.559769, 155.61552}//开始动画
model:{200, 400}, presentLayer{73.814095, 156.10709}
model:{200, 400}, presentLayer{74.267357, 156.98315}
...
...
...
model:{200, 400}, presentLayer{199.99576, 399.99182}
model:{200, 400}, presentLayer{200, 400}
Note: render tree 在apple的render server进程中,是真正处理动画的地方。而且线程的优先级也比我们主线程优先级高。所以有时候即使我们的App主线程busy,依然不会影响到手机屏幕的绘制工作。
了解cocos2dx对CADisplayLink一点也不陌生,对APP开发者可能就有一点远,但是facebook的Pop一下子拉近了我们和CADisplayLink的距离。通过设置callback函数,当屏幕刷新的时候,就可以执行我们的代码。当然,我们也可以利用NSTimer 或是GCD来实现类似的功能。但是CADisplayLink是最优的,因为不管是哪种类型的Timer,即使我们的刷新间隔和屏幕刷新保持一致。我们都无法知道系统什么时候刷新屏幕。
facebook的Pop非常类似UIDynamic,但是我们需要注意一点,相对于传统的model动画来说,CADisplayLink导致部分绘制工作放在了我们APP的地址空间中,也就是说,增大了APP内存,CPU的开销。也更容易遇到性能瓶颈。
Note: model layer的这部分绘制是完全在render server,而render server运行在比APP更高优先级的进程中,而这个也意味着会有进程间通讯的开销。传递的数据包括整个render tree还有动画,所以,Apple 并不推荐我们手动commit transaction, Core Animation 默认会在run loop 中提交transaction。
Apple 最近在推荐一些Modern APP的设计,其中有一条是希望responsive。比如下面的场景,启动一个动画之后,在动画还没有完成之前取消这个动画。
这里我们看到了3种情况。
UIKit的动画最后都会通过Core Animation 来实现, 那么当我们修改layer(model layer)的数值时,系统是如何理解并创建动画呢? 比如这里有一个线性的动画,将animationView的坐标从(0,0)移动到(0,500)
1 2 3 4 5 6 7 8 9 |
|
animationView.center = CGPointMake(0, 500);
之后会立刻修改animationView
的model Layer中的position
的值为(0, 500)。animationView
当前在屏幕的真实位置(渲染位置),因为还没有”动”起来,所以还是(0,0)Note: Animation的部分如果没有明白,可以结合后面的回头再看
这是在0.4s时刻之前的状态。Model Layer的数值没有变化,而Presentation则在变化,和真正的屏幕动画保持一致。
如果我们在0.5时刻创建一个reverse动画,animationView.center = CGPointMake(0, 0);
1 2 3 4 5 6 7 8 |
|
到目前为止,我们可以清楚的理解为什么红色的view会有一个大的跳跃,在我们这里的理解就是presentation layer的一个不连续的修改。
在上面的基础之前,绿色的就可以简单说一些
可以看出来2个动画相接的曲线不平滑,而造成这个不平滑的原因在于把之前的动画覆盖了, 丢掉了之前动画的速度,如果要实现一个更一般化的解决方案,我们很自然的想到了动画合成。
蓝色的动画比较复杂,使用了Core Animation中的additive属性,动画被设置成相对的,那么就和动画具体的位置无关。最后还合成了2个动画。
首先,解释一下什么是相对的动画。
这里很容易看到,view的真实位置是Animation 的值 + Model的值。系统的理解就是相对目标值(0, 500)来说,创建一个从-500 到 0 的动画。
其次,相比之前的动画,在0.6时刻(为了方便计算,把之前的0.5时刻移动到了0.6时刻)并没有删除掉之前的动画,而是添加了一个新的动画Animation2。也就是一个相对目标值(0,0)来说,创建一个从500到0的动画。整个运动变成了2个动画的合成。
Note: Animation2的duration修改了,在demo code里面并没有修改 :)
这里,我们就得到了一个一般化的解决方案。
Core Animation 有一个additive的属性实际上已经存在很久了,但是却很少被大家知道(我自己也是)。在iOS8 之前,UIKit创建的动画默认是不使用additive的,而在iOS8之后,默认是Additive的。有兴趣的同学可以试一试download demo code用Xcode6(这会还是beta)并打开macro#define USING_UIKIT 1
看一下新的UIKit animation效果。
在了解背后的机制之后,其中的变化也很容易理解。
……
最近Apple的动作还是蛮多的,其中有3条很有意思。
cocoa touch 开发中适配各种屏幕尺寸已经是能够预测的了,那么跟进AutoLayout 也就是必备技能了。
一开始接触iOS的时候,我还是蛮喜欢他的布局系统。简单来说,一个图像,我们通过中心点坐标,旋转角度和轮廓大小来定义他在窗口中的位置
这里的坐标和笛卡尔坐标系不同的是Y的方向
这里表示了anchorPoint含义,用于表示position相对bounds的位置,比如(0.5, 0.5)表示中心,(0,0)表示左上角
下面表示了frame bounds position anchorPoint之间的关系,你可能觉得这个anchorPoint似乎没有什么用
但是当我们旋转一个View的时候,好处就来了
传统布局是非常高效的,组合各种变化,可以轻易得实现任意的2D动画,当然也可以轻易的解决静态的布局问题。但是在面对多个屏幕,屏幕旋转时,或是需要在2个View 中间动态增加一个View的时候显得非常繁琐。需要不断的写一些计算距离,位置的代码(甚至还有一些magic number)。网上有很多例子,比如beginning-auto-layout-part-1-of-2,或是大家在平时工作中遇到的3.5inch和4inch屏幕之间的适配。
AutoLayout使用非常简单,Xcode的支持也非常直观。但是因为和之前的方式有很大的不同,新手一开始很容易遇到一大堆的异常,crash在main函数里面,让人非常沮丧。但是在了解AutoLayout之后,就会发现这是一个非常非常elegant的布局解决方案,也很容易理解为什么crash,以及应该如何debug。
AutoLayout 是一个描述各种约束的行为,比如,一个View 距离父View上边距多少,相邻之间的间隔,各个View之间的宽高关系等等。这一系列的条件就是为了最终确定之前提到的传统布局中需要的东西,这个View的大小,位置。所以,当我们设置的条件不足,或是条件冲突时,就会产生异常。
在使用AutoLayout的时候,UILabel 我们只需要设定他的position,不需要设置宽高,而一个自定义的UIView,我们不仅仅需要位置,还需要设定宽高,这是为什么呢?
每一个View 都有一个特别的属性叫做Intrinsic Content Size,这个可以理解成是一个View的最合适而且最小的宽度和高度。对于UILabe来说,就是至少得把我设定的文字都显示完整吧,所以系统只需要知道UILabel的位置。而UIView的Intrinsic Content是(0,0)所以需要设置UIView的宽高(或是设定周围的边距等等其他关系可以让系统知道这个View应该多宽,多高)。而Intrinsic Content Size,也是未来自定义View显示到Xcode中必须设置的属性之一。
使用AutoLayout之后,把view显示到屏幕上面大体分成3步。
一般来说layoutSubviews
负责布局,比如调整View之间的距离,大小,drawRect
负责绘制,比如使用什么颜色。而AutoLayout则是在layout之前增加了一个设定约束的过程,也就是上面提到了update constraints
。
在view的layoutSubView
中,如果我们调用了[super layoutSubView]
系统就把设定的这些约束计算成每个view的bounds,center属性。当然我们也可以基于AutoLayout的结果,再做布局的调整。
Display 不是这篇文章的重点,这里略过
仔细阅读文档的同学会发现在Apple AutoLayout document中可以看到Alignment Rect 这个家伙。
AutoLayout中的Left,Right等约束,并不是针对View的frame。而是根据Alignment Rect。在绝大多数情况下Alignment = Frame。但是如果对某些需要交互的元素,而图片素材很小的时候,就可以利用Alignment把交互区域变大。可以参考UIImage 中的 imageWithAlignmentRectInsets
。
AutoLayout也可以配合传统的animation方法,整体代码结构如下。
1 2 3 4 5 6 7 8 |
|
使用AutoLayout也可以轻易的实现之前的设置frame很难实现的动画效果。比如下面的例子(很奇怪,优酷吃掉了后面几秒的动画…)
使用之前传统的动画,实现这个过程,需要计算所有subView之间的距离,位置。而且在修改一个view的frame时,很难做到和其他View的移动速度同步。除非是custom layoutsubview
。做起来相当麻烦。但是用AutoLayout则非常简洁直观,只需要设置第一个View的position,然后其他view约定好高度和间隔依次排列就好了。
当然AutoLayout做动画的时候有的地方也很麻烦,比如希望旋转view A 的时候,或是使用transform时,很容易产生奇怪的结果。一般来说会设置一个host View通过AutoLayout设定位置,然后在旋转view A。一句话就是混合起来,各取优点。
简单的来说Compression Resistance 设置view有多大意愿(优先级),愿意压缩里面的内容。Content Hugging设置view 有多大愿意(优先级),愿意显示里面内容之外的部分。
stackoverflow上面有一个很清晰的通过UIButton解释的[例子],可以很容易理解这2个属性。
很多人认为自己的独一无二的,包括我自己。这个独一无二的理解主要部分是认识到了人与人之间的不同。这种心理在面对选择的时候可以更好的follow your heart,虽然自己的认识很浅薄,但是却受到了上天的眷恋,好运的几个选择都看上去不错。但是上天是公平的,总会有做出错误选择的时候。这时候不得不思考是不是自己从一开始就错误了。
现在看来当然是错的,因为每个人本来天生就是不一样的,这不是明摆着么,不同的长相,不同的家庭,往基础了说,DNA 每个人都完全完全不一样。每个人都会有自己的特殊情况。但是往长远了看,人们的社会地位是一样的。
所以,从某种角度来看,你和别人是相同的
在遇到困难的时候,人们往往觉得自己是最痛苦,最迷茫的那个人(相同的倒霉事情人们往往觉得自己最痛苦,而发生在别人身上的时候却不这么认为,所以得出这个结论并不难),这个时候其实最治愈的话不是一些什么解决方案,而是“你不是一个人”。当真正的意识到这些事情别人也会遇到,别人也会有相同的感受时,才能真正的面对眼前的问题,并最后找到解决办法。相反,当好运的时候,相信这个世界上还有和你一样交好运的人,其实这并不是一件什么值得炫耀的 :)
你和别人是相同的最重要的好处在于,你可以更容易的听取别人的意见,更容易去做改变。
李笑来老师有一段我很喜欢的对井底之蛙的解释。
井底之蛙是不可能知道这世界还有很多其他的井,与它所在的那个井没什么不同。可能是大井,可能是小井;井口可能是方的,可能是圆的,但有一点是相同的,并且至关重要的,这些井的深度是青蛙跳不出去的深度。那只井底之蛙于是更不可能知道其他的井里可能有着与自己一模一样的青蛙,也可能是大一点的,也可能是小一点的;它(们)也不可能知道哪只(哪些)青蛙与自己有着一模一样的想法——尽管它可能从细微角度出发,都是独一无二的,并非100%相同的青蛙。
一开始知道自己是一只青蛙的时候,还是挺沮丧的。因为在面对现在信息膨胀的社会,我就不拿那些太成功的例子来说好了,就拿简单的考试来说。很多人都说考试是一件痛苦的事情,要大量的记背,枯燥又乏味。我们因为做不了这么苦的事情,所以考试没有考好。这是一个非常常见的说辞。类似还有很多,比如人家2个人感情好是因为经历了很多人没有经历的事情,我反正平平淡淡的,就没机会拥有好的感情等等。这些说法本质上就是承认了你和别人是不同的,并以此为理由而拒绝改变。
大家不能接受平平淡淡,似乎所有的成就必须经历一段常人不能忍受的苦难。所以很多人越来越愿意追求刺激,迫切的寻找翻身的机会。但是不能不面对的就是,绝大多数情况下生活都是平平淡淡的,而这些平平淡淡生活时候的态度则决定了一个人是否能够挺过不好的事情,是否有机会抓到好运。
最近自己最大的收获,就是感恩,发现自己的人生路虽然走过了大量的弯路,自己的人生信条有超级多的不足,居然还是“成功”的活到现在,还真是受到了上天的保佑,真的应该好好珍惜这份来之不易的运气。当然受到上天眷顾的人也不仅仅是我一个人。其实每个人都受到过上天的眷顾,只是看发现了没有。
承认你和别人是相同的 需要极大的勇气~
]]>最近facebook开源了2个很有价值的project pop和Tweaks。 facebook提供了一个非常赞的topic-Building Paper。
这篇文章来简单介绍一下pop的使用,最后使用Tweaks来微小调整动画参数来达到我们最希望的效果。
这是我们最后的效果:
这个动画效果很简单,有很多方式都可以做到,但是pop来实现它,只需要下面几行代码。
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
哈哈,搞定了。pop太强大了。但是细心的同学会发现动画似乎不是我们想要的,我们希望做到那种一开始很快速很激动,最后却有一点慢慢的“欲求不能”的感觉。
很直观的,我们使用了万能的EaseOut动画
1 2 3 4 5 6 7 |
|
增加了一行代码,但是发现这个动画变化的时间还是不能让我们满意,一开始变化的还是慢,后面变化的又有点快。
动画的实现其实很简单,抛开性能,就是一个个不断变化的图片,对于我们这个简单的动画,就是一个从0到8000的变化,如果x轴为时间,y轴为大小。我们第一个动画其实是这个样子
easeout好一点是这样子
我们其实希望是这个样子
CAMediaTimingFunction 实际上还提供另一个方法,不是很常用,但是却非常适合我们现在的场景。
1
|
|
这里我们描述的“时间函数”其实就是贝塞尔曲线。
这里推荐一个网站可以很直观的生成贝塞尔曲线。 这里我们得到了参数(.12,1,.11,.94)。
1 2 3 4 5 6 7 |
|
这里我们已经得到我们想要的动画效果了。而且看上去相当不错。
如何才能有更好的效果呢?动画的速度,时间,等等参数都会影响到动画的效果是不是会完美。如何判断动画效果是否足够好。的确是个很难的问题。而解决这个问题的关键,不在于工程师自己折腾,应该找专业的人来做。而这时Tweaks就闪亮登场了。
初始化的时候创建2个tweak用来动态调整时间和目标数值。并修改一下默认的UIWindow为FBTweakShakeWindow
1 2 3 4 5 6 7 8 9 10 11 12 |
|
再把原来创建动画的代码稍微修正一下
1 2 3 4 5 6 7 8 9 10 11 12 |
|
这样当摇晃手机的时候就可以动态调整动画参数了,最后数据会保存在plist :)。
越简单的越强大~
]]>天天使用的framework确实是一个庞大的项目,从framework的设计中可以找到很多设计模式的影子,而且还是一个很好的生产化的例子。这里先介绍 Class Clusters
Class Clusters 几乎涉及到iOS日常的所有开发过程中,也可能正是这样,导致我们很容易把它彻底遗忘。这里就拿最常用的 NSString 来讲。
1 2 3 4 5 6 7 8 9 10 |
|
不知道有多少人试过哈,string3的返回还是让我吃了一惊。下面的结果是在Xcode5.1 SDK7.1 下的结果。
__NSCFConstantString
__NSCFString
NSPathStore2
NSBigMutableString
通过上面的方法创建的 NSString 最后都产生了不同的子类。有人可能会奇怪为什么需要不同的 NSString。因为对于大部分的以阅读内容为主的App来讲,很大部分资源消耗在了字符串处理上面(存储,解析,比较等等),所以对于字符串的存储需要有不同的方式来满足不同的情况,这样才能有性能上的提高。
Note: 设想一下,在这些场景上面,如果Apple直接把这些类扔给开发者,会有什么问题呢?
那么开发者需要自己在不同的场景决定使用不同的子类,不仅学习成本提高,而且也容易生成性能不太好的代码。
现在简单的 NSString 就可以直接覆盖上面的所有场景。而且随着iOS的软硬件的后续开发,开发者还可以在不修改代码的情况下获得性能提升。
既然看到了它的强大之处,那么就开始了解吧。 既然这是第一篇DesignPattern那么就从最简单开始 :)
这里引用一下Mike的内容
An abstract class is a class which is not fully functional on its own. It must be subclassed, and the subclass must fill out the missing functionality.
An abstract class is not necessarily an empty shell. It can still contain a lot of functionality all on its own, but it’s not complete without a subclass to fill in the holes.
Abstract Class 的概念很简单,类中所有的方法不需要全部有具体的实现,相当于定义了很多的接口。比如一开始的 NSString
A class cluster is a hierarchy of classes capped off by a public abstract class. The public class provides an interface and a lot of auxiliary functionality, and then core functionality is implemented by private subclasses. The public class then provides creation methods which return instances of the private subclasses, so that the public class can be used without knowledge of those subclasses.
Clusters的角色不仅要实现 Abstract Class 的方法,还需要自己实现自己的特殊化需求。Abstract Class 负责提供一个“外壳”,真正“干活”的就是Cluster class。这样外部就只需要了解Abstract Class就可以了。
比如 __NSCFConstantString 负责 const string,类似 @”helloworld”这样的字符串。这样的字符串有一个特点,不会被修改,当真正处理的时候,可以分配大小合适的内存,甚至可以分配在只读 data segment上面,而不需要分配在堆上面,如果有相同的字符串引用就可以完全赋值相同的地址。那么在retainCount上面的处理也就和其他字符串处理有很大不同。
NSPathStore2 看上去是处理有Path相关的字符串,因为没有源代码,这里我们可以大胆猜测一下,path相关的主要是做字符串的拼接操作,而这些字符串通常很长,占用空间大,但是重复的概率缺很高,那么就可以缓存一些字符串,这样可以减少一些内存的分配释放开销。
The class cluster architecture involves a trade-off between simplicity and extensibility: Having a few public classes stand in for a multitude of private ones makes it easier to learn and use the classes in a framework but somewhat harder to create subclasses within any of the clusters.
就像Apple文档中提到的,Class Cluster 是在简单和扩展性上面做了一个妥协。Class Clusters 的子类化比较麻烦,而且也看上去也非常trick,Apple 更推荐的方法是用组合的方法来扩展。
大家都知道设计模式有一个非常重的坑就是被过渡设计。Class Cluster 可以帮我们
那么这个非常适合应用在适配上面,比如不同屏幕的适配,不同厂家可能的不同的需求。
1 2 3 4 5 6 7 8 9 10 11 |
|
上面是代码来自BJ Miller’s blog A Cluster to Remove Clutter 是用于适配iOS6,iOS7的简单例子。
很多设计模式都很像,也很容易糊涂,比如工厂模式和Class Clusters在某些地方就很类似,我自己也并不能很好的分清楚。 设计模式的本质是为了解耦。不管使用哪个设计模式,我们最后追求的都是简单、容易维护和扩展的代码。
]]>
这里我就把这个功能照搬到octopress中了。
1.在plugins目录创建一个notebox.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
2.在sass/custom中的文件_stype.scss的最后添加下面的代码
1 2 3 4 5 6 7 8 9 |
|
3.markdown的语法(因为格式问题写成了%,需要替换成%)
{% notebox %}
the text to note
{% endnotebox %}
效果
Note: text
Bluetooth low energy (BLE,还有地方叫做BTLE,最恨各种简写了) 简单说是一种低功耗的短距离无线传输技术,主要用于低功耗设备传输,比如心率、记步器、智能家居方向,还有连接其他iOS设备。
Core Bluetooth API 支持BLE4.0,做了协议封装,让开发者不需要完整了解BLE协议就可以快速开发APP。
BLE中有2个非常重要的概念就是Central和Peripheral,有一点类似Client Server。
Peripheral把advertising packets广播出去,advertising packet 包括会包含一些重要的信息,比如设备名字,所提供的服务。
Central 则是扫描自己感兴趣的advertising packet,比如一个APP需要查找当前家里的室温,会通过参数设定,只是检索温度设备发来的packet。
YmsCoreBluetooth 是个不错的框架,有很详细的介绍,这里就不赘述了。
]]>文本档覆盖的内容中除特别描述外,和字节序相关的都是用Little-endian (小端模式)。 文本档覆盖的内容中除特别描述外,和字符串传递相关的都是UTF-8
Note: ANCS 并不保证始终存在,服务开启,关闭机制由iOS系统决定,Device 需要一直检测,查找ANCS是否存在。
ANCS的uart profile UUID : ” 7905F431-B5CE-4E99-A40F-4B1E122D00D0”
uart profile 中包括3个 characteristic
Device端Notification Source 是必须实现的。Centrol Point 和 Data Source 可选。
Notification Source characteristic 包括3个功能
当Device端 subscribes Notification Source characteristic时,GATT Notification 会立刻分发出去。所以,Notification consumer (Device)在subscribe之前就需要做好立马接受和处理消息的状态。
表格3-1:CategoryID的描述
Device 获取的来此Notification Source的数据是“00 01 00 01 43 00 00 00”
图2-2一个iOS通知的生命周期。
比如当iOS设备(如iPhone)收到一个iMessage消息,iOS NotificationCenter会产生一个Notification,ANCS会通知device 有一个新的通知。当iOS设备阅读这个iMessage消息之后,iOS Not ificationCenter 会删除掉这个通知,ANCS把这个删除通知push到device
只是Notification Source 不能获得足够的信息, Control Point 和 Data Source characteristic用来解决这个问题。 Device 向Control Point characteristic 写一个命令,如果成功,会从DataSource characteristic 获得response。
该命令根据NotificationUID 查找通知的详细内容(通知属性)。
图2-3 获取通知属性命令格式 CommandID: 必须设置为0 NotificationUID:通知的唯一标示(Notification Source 获得) AttributeIDS:需要检索信息list
图3-5 可以检索的通知属性列表,其中Title, subtitle, Message 需要增加2个bytes的字段表示长度。
获取通知属性命令Example “00 43 00 00 00 00 01 FF FF 05”
图2-4 获取通知属性命令返回数据格式
CommandID: 0 NotificationUID:通知的唯一标示 AttributeList:具体的属性返回数据列表. 如果返回的属性空,长度是0
如果返回的数据长度大于 GATT MTU,那么数据会被分几段传输。Device 需要对数据拼接。
下图查找 NotificationUID为2的 AppIdentifier、Title、SubTitle、Message Date属性返回数据
该命令通过APPIdentifier查找iOS设备中安装的APP的属性。
图2-5 获取APP属性命令格式
CommandID: 必须设置成1 AppIdentifier:字符串’\0’ 结尾。 AttributeIDS:查找ID列表
查找AppIdentifier 为 “com.apple.mobilemail” 的APP属性
图2-6获取APP属性命令返回数据格式
如果返回的数据长度大于 GATT MTU,那么数据会被分几段传输。Device 需要对数据拼接。
查找AppIdentifier 为 “com.apple.mobilemail” 的APP属性返回数据 汉字 “邮件”
ANCS 的 session 从设备订阅characteristic 开始到取消订阅或是disconnect结束。所有的Identifier 比如 NotificationUID,AppIdentifier 只在当前的session有效。
当session结束后,设备需要删除掉所有的在session中获得的Identifier信息,这些信息会在session建立的时候重新通知设备.
注意:如果产生了上面的错误,都不会再收到任何的GATT 通知。
首先编写一个Plugin还是需要不少额外的配置,这里推荐Xcode Plugin Template。用这个templage来帮助我们开发Plugin。
另外,编写插件和之前的iPhone or Mac上的APP不太一样。从某种意义上来说就是用Xcode调试Xcode。所以这里需要额外配置一点东西。
当我们Build & Run Project的时候就可以看到启动了一个新的Xcode进程,当然除了Xcode, Mail或是其他程序我们都可以调试。
因为Apple至今并没有公开Xcode Plugin的文档,所以我们需要通过一些其他方法寻找思路。
1 2 3 4 5 6 7 |
|
这里稍微有一点特殊,参数notificationName 设为nil,下面是Apple的文档,不是很清楚。
notificationName If you pass nil, the notification center doesn’t use a notification’s name to decide whether to deliver it to the observer.
但是目前来看,似乎可以看到所有的通知。当然绝大部分是重复的,对我们没有意义。很幸运最后我们找到了2个通知是我们需要的,下面的代码,已经做了过滤。
1 2 3 4 5 6 7 8 9 |
|
这2个通知分别是
这个我们不得不赞一下cocoa的命名方式,大家都可以猜出这2个通知的含义。剩下的事情就很简单了。统计build时间。
这是项目源代码。有兴趣的同学可以玩玩,看一下自己的编译时间有多长。另外最终的代码中还增加了2个小的features。
Have fun!
]]>因为小时候很贪玩,并没有考好大学。07年上了一个普通大学。08年正好大二,本来我是打算金盆洗手好好学习的,而且大一做的还不错。但是一个叫做Dota的游戏又给了我一次玩的机会。当时玩这个的人还很少,我和室友和另外3个其他学院的家伙们组成了一个电竞战队。我们的能力起初并不被看好,但是我们却拿到了当年全省第一届电竞Dota项目的冠军,也获得了人生第一桶金。再后来还参加了另一个比赛,最后去E世界演播大厅打比赛。一路上来每局都是逆风,但我们都一次次靠团队配合和抓对方失误扳了回来,最后成为一个黑马进入华北赛区8强。
游戏对很多人来说都是玩玩的娱乐项目,但是对我和我的小伙伴们来说,里面有很多个日日夜夜看比赛录像,锻炼意识,枯燥的基本功训练在里面。里面有成功的欢乐(冠军之后,还被媒体采访过)也有遗憾的泪水。我还记得打完最后一场在回旅店的路上。我挺自责的,因为团队中我是中单的Gank发起者(Dota游戏里面一个比较重要的位置),而我觉得我发挥的不够好,连累了大家。我的队长对我说,咱们没有北京这些学校这么好的电竞氛围,能走到这里已经很厉害了。Anyway,此生对游戏再也没有当年的执着劲了。因为能够在正确的时间,在一个玩游戏被众人鄙视的氛围里面遇到一帮子一起玩的人,组成一个团队,并且最后拿到冠军,已经是我能做到的极限了而且还有极大运气。
在游戏人生将近10年之后,我发现,我还是得回到现实,为自己的未来做打算。因为当时在学生会网络部混日子,给学院和学校做个烂网站。然后有一天其他学院的同学找到我说咱们一起联合做一个项目,参加全国的比赛。也托玩游戏的福,我妈妈觉得她的儿子,这么多年来第一次战胜了“别人家的孩子”,给我买了一个多普达的手机,是我的第一部智能手机。极大的震撼了我,我觉得电脑太笨重了,便携的移动设备一定是王道,因为有大量的碎片时间可以利用,我觉得智能手机一定会超过PC,我的未来一定是在移动方面的。也托这个比赛的福,更刺激了我在移动方面的兴趣,从而在网上认识了一些IT界的大牛,都在大公司,还有在国外工作的,并且参与了他们的开源项目。大二的一个暑假我都在编程中度过的,最后那个比赛获得了优秀奖,全国前20。和他们一起工作,对我的帮助极大,不仅是理论还是实践。而且我相信给我更多的时间可以做的更好,因为我参加的那个开源项目是当时windows mobile上面最好的也是唯一的UI Framework。
不知不觉大四了,之前一起参加开源项目的一个家伙,准确说是2个家伙要出来创业了,不准备在大公司呆了,(他们一个在微软一个在中国移动),然后这个家伙给我吹了一个大泡泡,现在想想挺有趣的,当是准备做基于手机联系人的IM,那会还没有米聊和微信。我觉得挺有意思的,这个不就是做一个移动版本的QQ么,而且当是的手机发短信还是挺费钱的,一条信息的流量几乎忽略不计,而且没有字数限制。一定可以改变人们的沟通方式,后面还可以发图片发声音,而不是只是文字。当是的产品原型是国外的Kik Messenger。当然,我们并没有做到最后,因为各种各样的原因,最后我们放弃了这个项目。
在2010年底,我们开始把精力转向在线教育。更准确说的是在线英语教育。我接触在线教育是在小学的时候,在线教育能够把高质量的教学资源给更多的人,而且中国学英语的人会越来越多,市场会越来越大。另外结合手机本身就可以发音的特性,这些都是普通书本不能提供的。最后我们还找到了一家著名的英语培训机构作为我们的内容提供商。另外参考了一家广州企业的盈利模式,然后开始尝试。我在这个过程中开始把之前做IM项目积累的经验,快速应用到这个项目中。而且最后作为我的毕业设计,拿到最高分90,做为我大学生活的结束。
我第一次看到iPhone是大二的时候,当时我觉得这东西就是一个掌机,后面才发现是手机(别笑话我),而且一直认为这是一个纯装逼的行为。但是当真正做到移动领域的时候,开发设计windows mobile上面App的时候,很多的设计的确都是参考iPhone的。也开始接触iPhone上面的开发,到后面发现windows mobile真的不行了。不管是工业设计,还是App的用户体验都差距巨大,但是价格缺没差多少。。。而Android的开放和便宜(相对于windwos mobile 的license),windows mobile 必然是没有前途的,于是乎开始了和AppStore 斗智斗勇的日子:),最后成为了国内比较早的一批iOS开发者。
创业的这个项目也遇到了问题,很多问题,盈利模式很难复制,用户没有在移动产品上面的付费习惯也缺乏渠道,而且用户量不仅不够,还在不断下滑。团队有部分人因为家庭问题,先走一步了(因为之前日子过的太苦逼,女朋友表示日子没法过)。我也考虑过离开,但是觉得不能以这种方式离开,主要是不能接受这种方式的失败,然后就继续做下去了。付费很难做,但是用户量还是可以有的。最后我们做了一个垂直社交————英语教育社区。当时英语教育软件里面为数不多的(可能是唯一的)移动社区App,我们被AppStore 推荐,iOS下载量一天增长了1W倍,加上Android成功内置电信,最后拿到了200W天使投资。这段日子虽然苦逼,却是我工作上面成长最快的时候,人总是被逼的么,拿着倒计时牌算日子过。中间遇到些问题并请教了很多国外的开发者,自己也写了一些内容比较深的blog(相对在那个时候),自认为在iOS设计和开发上面自己在业内可以排到top 5%,因为不仅产品被资本和Apple Store认可,而且那会自己的很多blog也在Google搜索的最前面。
另外,一个同学去CMU了,我觉得不管是我的工作,还是其他什么的,我都需要把英语学好了。我问他你准备了多长时间,他说4年。从英语到字幕组到论文到申请。 我知道我的大学成绩是这辈子不太可能去CMU了,但是我还是蛮想出国的,然后介于我低下的语言天赋,我觉得我至少需要准备3年英语。
2012年的开始还是蛮不错的,团队终于没有钱的压力了(暂时)。然后我们搬到了美团,美丽说他们曾经在的地方,创业公司么,大家都希望沾点喜气,而且交通也方便,而且便宜。然后我们3个月没有做事情,因为我们暂时走到了前面,而且前一段时间我们一直在转变,需要想清楚我们应该做什么。当然不是完全没做,只是主线产品没大的升级,做过一些其他的小尝试,但是效果都不好。整个团队都很迷茫,我也不例外。我觉得这样下去,我在年底就没有什么东西可以总结的了(最后显然我把事情想简单了),然后就报了EF的班,开始我的3年英语计划。另外开始健身,减肥。一个月减了20斤,每天游泳1.5km。工作方面开始负责服务器,不仅仅是负责iOS方向。实现并设计了新版本的服务器。时间转眼就到年底了。因为各种原因吧,我们和投资人谈崩,团队整体撤出,产品留给投资人。然后就是换办公室,无外乎就是换一个小房子,然后再清退一些人,有意思的是,这个时候也和女朋友分手了。这就是所谓的祸不单行吧。
2012挺特别的,我的生日正好是末日的那一天,可惜万分期待的末日并没有到来。我意识到我并不是真正的足够了解自己,也并不是真正的清楚自己在做什么,几年前我自认为我在做正确的事情,但是我在那个时候觉得我看不清楚。那会我开始接触MBTI,了解积极心理学,给自己灌超大量的鸡汤,感谢我的老大(leo,cube),感谢我周围的朋友(强X和顺X),当然还有默默关注着我而且始终在背后默默帮我的风哥(突然想起来,最近实在太忙,坑了风哥了,对不住了),最后还要感谢一个远方的笔友。
2013年,移动互联网发生了天翻地覆的变化,一大堆的开发者涌入了这个行业,也有大量的App走到大家面前,我的tutor或是close friend,和他同甘共苦的另一半修成正果了,而且他也比较给力,前一段时间他们团队拿到$2000W的B轮,宝宝也有了,祝福。另外强X拿到Morgan的offer开始自己的职业生涯了。我呢?我觉得前几年过的太累了,按照老大的话,就是过的没有尊严。2013年准备挣挣钱。这一年来说我可以算是一个Freelance。平时就是看看书,学学英语,健健身,遛遛狗。有事情了,然后做一点。到头来还算不错,至少比一般大公司挣得多。不用上下班,没有人约束你做什么,想睡几点睡几点。一年也就工作不到4个月,大部分在休假。但却是我最难受的1年。因为这个生活显然是50岁的节奏,不是一个20多岁的人干得事情。当然这也有好处,就是我有大量的时间和负能量去支持我这个龌龊的灵魂又减了将近20斤,练出6块腹肌并且在年底来了一次马拉松。因为我如果不做一点东西的话,2013年总结的时候我该写点什么呢?
扯远了,移动行业2013年是一个起点,很多传统行业开始意识到移动领域的价值。而这一年中,我们的很多项目也和他们相关。年底也出现了很多将互联网概念和传统行业结合创业的例子,不管是黄太吉还是马佳佳的情趣用品,都在告诉我们移动互联网还有一波浪潮可以淌。很多传统企业在2014年会加大在移动方面的探索。这里面还是有很多机会。我们现在的能力在2014年做到2013年甚至可能比2013年还要好也并不是不可能。但是我却不得不看清楚一个现实就是移动互联网入门的门槛是如此之低。很多一线大公司的平台级别产品极大的简化了开发的风险和时间。而且投行是这个一个行业的方向标,我自己也并不看好自己现在处的行业中的位置————一个纯互联网方案提供商。
小米通过他的品牌不仅卖手机,还要卖路由器,电视等等。我依稀感觉到互联网不仅仅是改变人们的沟通方式,会开始慢慢的走近人们的生活,而且事实上有些地方已经走到前面,包括交通(机票,火车票,打车),运动健康(可穿戴设备),还有一些政府的项目也开始增加了不少移动部分。另外就是后端的XX云概念。而这些都是我现在没有办法做的事情。所以,我又准备转行了。
在创业的风风雨雨中,认识了一些朋友。有的走上正轨,拿到B轮,得到市场认可,而且资本也会让他们继续平稳的走下去。线上教育一直是我喜欢做的事情,但是教育本身就是一种回报很难估算的投资(不像一件衣服好看不好看那么直接),而且还有大量的时间成本。再他们赢得一些用户口碑之后,一段时间不容易被其他产品影响,还会越滚越大。另外一些朋友开始在大公司里面摸爬滚打,也蛮不错的。因为毕竟是主流价值观。有一大推可以借鉴的经验和路子。还有一些朋友准备考GMAT。
我家是一个普通家庭,可能唯一特别一点的就是我有一个特别开明的妈妈,我妈妈认为她最大的遗憾就是当初她老师告诉她要学好电脑,而她没咋当回事。所以她一直很支持我玩电脑。有一天我有意无意的在她的书柜里面找到的一本有关Basic语言的编程书,然后自己写下了我的第一段程序,一个简单的计算器,那会我上小学6年级。我一直觉得我有一个很特别的妈妈,我妈妈是一个大网虫,我的QQ号都是她替我申请的,而且特别喜欢电子游戏。但是,我显然让她失望了。我并没有按照她给我计划好的路子走,而是按照自己的性子来的。走了一条很大的弯路,而且是不归路。我没有考上一个好大学,也没有找一个可以让她安心的工作。而且现在我也没有办法告诉她我什么时候可以结婚生孩子。
妈妈的身体差一点,爸爸的身体比较厉害,打了30多年的拳,虽然比我大30岁但是在我减肥之前我确定他身体比我好。他们2个年轻的时候做过几次正确的投资,现在衣食无忧,自己没事弄点小买卖充实一下无聊的日子,按照我妈的话,这辈子剩下的事情就是等我结婚报孙子。
但是很明显,作为一个26岁,要啥没啥,连个稳定收入都没有的闲杂人等,到哪里给她找儿媳妇去?
2014年我有2个比较大的计划需要尝试。抽象来说,就是给自己找一个大的平台。在这几年来,我深刻的认识到了自己的见识、理解还是能力都远远不够。不得不承认我可能错过了我人生中的第一次机会。我需要的是一个大的平台,让我从中学习理解这个行业。一个是出国,一个是去一家大公司,而且在一个不错的部门里面。我还没有找到这个大平台,但是我能确定一点的是,这个平台是符合主流价值观的。因为我这几年来偏离太多,虽然我自己收获不小,但是我希望能够走一些别人走过的路。最近的1个月来,自己一直在忙这2件事情,估计最晚需要忙到明年6月份。那句老话说得好,人无远虑,必有近忧。我现在还没有办法估计5年后的事情,因为我5年前每一年做的事情,都没有重复的,而且都是某种程度上面不一样的。我唯一能确定的就是2014年,也是一个完全不一样的一年。而且是我人生中最重要的一年。如果我找到一个平台,那么意味着我2013年成为一个美丽的间隔年,而如果没有,那么就是又一个非常大的教训。
上面的是我在一个月前写的,这几天我一直再思考一个问题,今天又失眠了。我未来的5年应该是什么打算呢?一直想不清楚,所以也不知道该如何写下去。其中有一个很重要的事情,就是成家是否在我的计划之中,同时它还附带了户口,房子,小孩上学等一系列问题。太原眼下,甚至是3年都很难有适合我的工作,但是生活还是未来小孩上学太原都要比北京的条件要好不少。花大量的时间和金钱拿一个北京的户口和房子值么?还是在太原的投资回报率高?抛开工作,太原的生活显然比北京舒适。但问问自己,甘心么?这一切都好难说。而且很多事情还涉及到父母,不是我一个人说了算,头疼。希望2014年底,我可以有一个自己的答案。恩,是希望,不是必须完成的事情。
]]>runtime有一点追问,category,method 的实现机制,class的载入过程。 1面整体感觉不错,40分钟不到,感觉回答的还可以。被通知一会儿二面。
二面的时间非常长,差不多将近3个小时,直接面到快下班了。1面问的主要是知识点。2面问主要考察的是设计解决方案的能力,另外辅助追问的方式,考察深度和广度,回答过程中需要列出适合的具体例子,方案还需要细致到具体的关键的函数名称,方法。另外考察设计模式的理解,最后还考了算法。因为时间太长,这里记录一些重要的问题。
算法考了2个。一个是如何求2个集合的交集。另一个是百亿数据中查找相同的数字以及出现的次数。
最后还补充了几个小问题
一会被告知3面,但是因为太晚,约到次日下午3面。
3面的时间和1面差不多40分钟,问了几个问题,主要是考察精神层面的东西。
后面就是他说的多一些,介绍团队遇到的困难以及14年团队的打算。最后他给了我2句话的评价,我觉得还是蛮对的。
有意思的是,他说他也wanna make a difference。
整体还是挺尴尬的,几乎所有的知识点都是1,2年前积累的,13年积累的东西基本上没有,都是一些虚的东西。 2面挺好的,暴露了自己不少问题,设计模式那部分几乎没有概念了。
面试通过,我个人觉得2面我的问题在思考设计模式上面少,另外在window hierarchy 上面有不足。设计 KVO 上面在 didChange上面考虑不足。这些都是被当场戳穿的。 有一点疑虑的是整个面试中并没有问到Core Animation。这个还是我蛮喜欢的部分。Anyway 个人很喜欢追问的方式,很容易考察出来理解的深度和广度。
整个面试收获很大,发现了很多不足。另外1,2面的面试题目个人觉得也不错。这里分享给大家。
]]>