我住在2号塔楼,每天早上8点起床,8点半出门,这是我一天的开始。
下楼,等待我的可能是头戴花帽、和蔼又腼腆的阿姨对着我微笑着说早上好,也可能是一身黑色皮夹克、不苟言笑的叔叔沉默地看着我走出大门。
出门,往南500米,来到志新路公交车站。如果是夏天,我会骑着小蓝车沿着学院路直行,感受着片刻的放松。现在到了冬天,放眼望去整个街道都被金红色的朝阳铺满,流光溢彩。我面朝东方,迎向太阳,闭眼微笑,整个人仿佛置身于一片金色的世界,那片刻恍若天堂。
我常常是这样平淡地生活。
我住在2号塔楼,每天早上8点起床,8点半出门,这是我一天的开始。
下楼,等待我的可能是头戴花帽、和蔼又腼腆的阿姨对着我微笑着说早上好,也可能是一身黑色皮夹克、不苟言笑的叔叔沉默地看着我走出大门。
出门,往南500米,来到志新路公交车站。如果是夏天,我会骑着小蓝车沿着学院路直行,感受着片刻的放松。现在到了冬天,放眼望去整个街道都被金红色的朝阳铺满,流光溢彩。我面朝东方,迎向太阳,闭眼微笑,整个人仿佛置身于一片金色的世界,那片刻恍若天堂。
我常常是这样平淡地生活。
你脑袋想的是什么,你就会看到什么。
我想到社交,于是我看到了很多社交 APP。
Soul、积木、POP、Echo、ZEPETO、一罐、饭否、豆瓣,这 8 款 APP 都是社交 APP。其中前面 6 款都是这两年才有,饭否是 2007 年 5 月 12 日创立,而豆瓣则更早,是 2005 年 3 月 6 日成立。饭否和豆瓣都有超过 10 年的历史,对于互联网的历史来说,无疑这是很长的一段历史。
这些 APP 有匿名、有树洞、有照片、有虚拟角色等等。
POP、Echo 是由微信出来的产品人做的,他们都认为下一代社交的入口是相机。
用照片或视频来替代以文字为主的表达,其实我现在不是很能够体会,就算我已经用 Instagram 一段时间,可是我还是找不到感觉,也许是在上面没有朋友的缘故吧。
或许也是我想的是什么,看到的就是什么。
在微信朋友圈,因为各种顾虑,可能你已经不会无所顾忌地在上面表达自己想法。
难得你还有兴趣思考这个问题。我觉得最主要的原因是绝大多数人只加好友不删好友,好友多到一定程度就觉得杂乱了,于是慢慢就放弃了。———王兴。
对于大多数人来说,可能好友的关系分量太重,点赞之交就够。
你看到我就看到,看过了就过了。
饭否、豆瓣的关注功能我觉得挺好,这种关系的链条不至于太粗,或许不会有那么束缚。
如果我一整天都没看到、想到、或做过什么值得在饭否上说的事,那这一天就太浑浑噩噩了。
也许我就是浑浑噩噩吧。
在北京生活,有特别多的活动(如各种展览、会议等等)以及各种各样的资源(博物馆、图书馆等等,还能见到很多知名人物哈哈),而这些是在小城市生活的人们根本想象不到的。
但是!空气差是真的,天气寒冷也是真的,吃饭吃不好更是真的。
现在我们每天中午会去科技园的食堂吃饭,这是固定好的。因为食堂离工作地方很近,更重要的是每天都能够提供稳定口味的饭菜,不说多好吃,但至少不难吃。这家店是我们吃遍了附近的十几家后才最后确定下来。
本来我们一开始并不是选择的这家,而是在一家距离工作地点走路大概八分钟的样子饭店,一家在北京还算知名的连锁饭店,叫宏状元。
这家饭店不能说离工作地点近,因为工作的楼下就有不少吃饭的。而为什么舍近求远地选择它呢?因为它能够点菜以及口味也还行,特别是它家的炒合菜(粉丝、鸡蛋、豆芽、木耳一起炒,并配有一盘卷饼,饼可以用来卷着合菜吃)还蛮好吃的,分量也足。能够点菜意味着大家能够都吃几个菜,而不用像快餐一样每餐只能吃一两个菜,口味还行意味着大家都能够接受。
就这样我们去宏状元吃了几周,但是渐渐地我们都不想在那吃了。因为他家提供的菜品质量不稳定,且米饭也老是生的,每次都要告诉服务员帮我们换熟米饭,并且换米饭又要等半天。特别是关于米饭的问题,他们简直就是太不负责了。有一次吃饭的时候,我们说米饭又是生的,餐厅的负责人竟然说他们知道米饭是生的,后面也是等了好久才换上熟米饭。
我们只是希望它提供的米饭,每次都能够像第一次那样——是熟的。可惜它做不到。
后面我们就很少再去。
在生活中,总是期待服务是确定性的。
比如我去设计师张那剪头发,因为我知道他还是能够像上次一样让我满意;
比如我去我家对面的物美超市买牛奶,因为我知道我一定能够买到牛奶;
比如我用微信和你聊天,我知道你一定能收到我发的微信。
大多数时候,我喜欢确定性带来的符合预期可依赖的生活。
但有时,我也憧憬不确定性带来的充满未知的惊喜。
最近在看李娟的写的散文《九篇雪》,写的大多是作者一家人在贫瘠且语言习俗迥异的阿勒泰地区与哈萨克族做生意时期的生活。虽然他们生活极其艰苦,但是作者表达出来确是积极乐观向上的生活态度。有感于作者把生活中的每一件小事都能写的极其生动,而自己却陷入无事可表达的地步。
再说野草灰灰条,听说把嫩尖掐了用开水烫一烫凉拌起来味道也不错。不过我从没吃过,看它那个样子,那么难看,想必也不见得好吃。而我们所有人都喜欢的,莫过于亲爱的蒲公英了。蒲公英当地人又叫“苦苦菜”,苦是有些苦,不过苦得很吸引人。叶子非常细碎,我们摘回来在河水里一片一片洗净,用开水一烫,攥干,淋上酱油醋,搁进葱姜蒜,拌上粉丝海带丝,着几滴香油,另外加热少许青油,放进干辣子皮,花椒粒、芝麻,煎出香味再往菜里一泼,“滋啦啦——”香气四溢。……可是,我只不过形容一下而已。现实中哪能如此诱人呢,这深山老林的,哪来的葱姜蒜,粉丝海带丝啊?还“淋点香油”“搁点芝麻”呢——只能想象而已。
上面摘录自书中《吃在山野》 。
看上面的文字,是不是都流口水了,生动形象极了。
我很忧愁。我每天路过的街道、去过的饭店、吃过饭菜可能我都不能说是熟悉,很多我甚至我都叫不上名字。我似乎对生活太不敏感了。
我很喜欢小区门口的一家牛肉面,是我在北京甚至是我所有吃过的最好吃的牛肉面。平常没有时间,但是一到周末我必定会去他家吃。
他家中午是上午11点到下午2点,下午5点到晚上9点营业。一到周末,我12点就会去他家。他家周末的时候人特别多,排队的人都快到门口了。老板也很有意思,身材高大、剑眉星目,长相威严又不苟言笑,每次都是穿着一件黑色对襟衬衣,一条白色长裤,看起来就是一个值得信任的人。排队期间如果有人插队的话,就会很严厉地说去排队去排队。
一碗牛肉面,主要就三样东西,面、牛肉、牛肉汤。面条熟了后,从滚水中捞出来,盛放在大碗里,浇盖上满满的一碗牛肉汤,然后是洒一勺香菜、一勺牛肉,一碗香喷喷的独家宇飞牛肉面就诞生了。我拎一个盘子盛着面条找一方桌子,看着面条热气腾腾,我凑着脸闭上眼闻了上去。哇,好香!开吃!
心怀满足的我吃完已是满头大汗,在北京寒冷的冬天里那种满足感是无语伦比的。其实我还很想写老板是怎么熬汤的,但我实在是词穷,对很多食材也无甚了解。
昨天天气不错,和两个朋友约定去国家图书馆。嗯,本来我自信地说会在9、10点搭车过去,结果最后10点才起床。
我早上7点23分就醒了。我有定闹钟,7点30分,无论是否需要上班。我很奇怪自己为啥每天都会比闹钟提前10分钟左右醒来,从来没有例外过。
在图书馆看了一本《我的政治人生》的说,做了一丢丢笔记:
最近社区电商很是火爆,主打生鲜品类(如每日一淘),高频、较少SKU、前置仓。模式类似于微商,引流有佣金。这种方式目前来看,效果挺不错的。随着线上购买生鲜的普及,进一步地引导、方便消费,这部分流量应该会有较大增长。
被快手改变命运的“迷藏卓玛”:乡土网红的崛起和老铁经济学,文章讲述了家在四川甘孜藏族自治州偏远高原地区的格绒卓姆通过“短视频平台–快手”卖虫草,实现了月销售额达到了30万元的成绩,而在当地县城打工一个月的工资不过3000元。
而这也离不开通信基础设施、现代物流体系对偏远地区的渗透。但从实际来看,目前的渗透应该是能有较大成长空间。截止今年6月底,中国网民达到8亿,其中移动端7.88亿,相对于全国14亿的人口,剔除掉14岁以下人口3亿,还能够有3亿以上的增量。当然,越是不发达地区,建设的成本越高,回报的周期也更长。
一点也也不意外现在直播卖货的火爆,我都有点想去尝试。
为什么我觉得爱情虚无缥缈,有的只是陪伴。
毫无疑问,我想有人陪伴,聊天、逛街、旅游甚至是监督。
但是我很怕自己成了别人的负担。
虽然自己觉得人生没有什么神奇,终会走向死亡,不用考虑有无意义。回首往事,不后悔足矣。
来北京刚好半年。回顾这半年,似乎没有什么值得说的,自己似乎也没有什么长进。不过与团队也相处融洽,也花不少精力做了一些基础支撑,效果也还不错。也吐槽下美拍的产品团队,感觉不是很自信。兴许他们压力大,但终究是不爽。
公司倒是有不小的变化。比腾讯更早的组织架构变革,9月初美图基于产品特性成立了各事业群,希望能够借鉴各产品成功经验以及提升跨部门沟通效率。当然也不可避免的出现了裁人。哦,上周美图美妆宣布停止运营了,可怕,这么大一个部门。。。
我自己本身还是看好短视频的,毕竟视频的承载内容的能力还是很不错的。在我自己的认知范围内也是认可目前美拍的方向。也希望自己能够尽可能的贡献一份力量吧。
关于工作的话,接下来会重点放在性能优化的方面,至少不能比竞品差对吧。
至于下一步,如果自己有能力,当然是想创业啦。当然这是一个长期的过程,我也对自己说不要着急,怎么说来着,不忘初心哈哈,加油 up up。
然后就是可以每年去外面面试一下。我打算明年面试微信,上次简历都没过,当然也是简历写的太渣,后面慢慢修改才终于有了其他的面试机会。
毫无疑问,目前我最大的瓶颈就是算法,但是算法这东西需要花费不少时间精力。一看算法的书我就想睡觉,无疑因为看不懂,然后需要慢慢看,然后需要各种想,我的脑细胞不够用呀。头疼。关键看懂了还不行,还需要多实践。总结来说,这是真的要下大血本的。
不过也觉得不错,这就相当于一个门槛,你跨过了就能够领先大部分人。
看完 《算法》(大概 360 页)、《剑指 Offer》(大概 300 页)。预计是每天看 10 页左右,期望 4 个月内能全部看完并实践。
以后每周汇报下进度。
嗯,想想就是个艰巨的任务哈哈。万事开头难呀,希望自己能够渡劫成功。
The equivalence of observer pattern (
Observable<Element>
sequence) and normal sequences (Sequence
) is the most important thing to understand about Rx.Every
Observable
sequence is just a sequence. The key advantage for anObservable
vs Swift’sSequence
is that it can also receive elements asynchronously. This is the kernel of the RxSwift, documentation from here is about ways that we expand on that idea.
Observable
(ObservableType
) is equivalent toSequence
ObservableType.subscribe
method is equivalent toSequence.makeIterator
method.Observer (callback)
needs to be passed toObservableType.subscribe
method to receive sequence elements instead of callingnext()
on the returned iterator.
这是 RxSwift 文档最开始的部分,我们可以看到,Observable
和 Sequence
等同。ObservableType.subscribe
类似于 Sequence.makeIterator
方法。向 ObservableType.subscribe
传递的参数是 Observer
,用来接收 Observable
产生的 event。
从字面意思来说,就是 Observer
订阅了 Observale
,而事实也确实如此。Observable
产生序列 event,通过 ObservableType.subscribe
将序列event产生通知 Observer
。 调用 ObservableType.subscribe
会返回一个 Disposable
对象,用来随时取消订阅(subscription
)。
在 Rx 中,一个 sequences 可以由 0 个或多个 event,一旦 error 或者 completed 时间发生,这个 sequences 就不能再生成新的 event。
这是 RxSwift 中定义的一些基本接口:
1 | enum Event<Element> { |
在 RxSwift 中,Observable
是遵守 ObservableType
协议的一个具体 Class。
1 | extension ObservableType { |
常用的 create
方法是定义在 ObservableType
协议的 extension 中,并有默认的实现,返回一个 AnonymousObservable
对象。
AnonymousObservable
是一个 继承 Producer
,而 Producer 继承自 Observable
。
看如下代码:
1 | let sequence = Observable<String>.create({ (observer) -> Disposable in |
上面两个方案是等价的,因为方案1传入的闭包最终会封装成为一个 Observer。并且只要调用 Observable 的 subscribe 方法,最终都会回到方案2的方式,这就对应了 ObservableType protocol。
当然这里 Observable 的 create 方法传入的闭包中的 observer 并不是我们调用 subscribe 方法传入的 observer。RxSwift 具体实现中,会对我们传入的 observer 再进行一些封装,一般是由 Sink 的子类作为容器。这样就能方便对传入的 event 进行一些处理及操作。
我们调用 Observable 的 subscribe 方法,就打开了数据的闸门,数据就自顶向下开始流动,中间我们可以调用例如 map、flatMap、concat 等操作符来处理数据。
这个有点像军训的时候我们报数,我们从一端开始报数,直到末端才算完成。而在 RxSwift 我们调用 subscribe 方法报数,直到最顶上。当到达了最顶上,数据开始生成(仅针对于 Cold Observabels,Hot Observabels 并不是如此,具体请参考 Hot and Cold Observables),又从最顶上通过各种操作符,依次向下传递,直到末端。
所以我们看到,数据流动是需要依赖中间每个“人”的配合才行的,如果中间某个“人”使坏,把数据给吞掉,不再向下传递,那么最终 observer 就不会收到数据。因此在 RxSwift 中,每个操作符都是被精心设计的。
整个数据流动的流程:
需要特别说明的是第5步,一个 event 并不总是从最顶部的 Observabel 生成。例如使用了 flatMap 操作符,那么 event 可能是直接从 flatMap 方法返回的 Observabel 生成的,但是第一个 event 必定是从最顶部生成。下面是一个例子:
1 | let disposeBag = DisposeBag() |
输出:
1 | 80 |
在 iOS 开发的过程中,我们需要做的事情一般可以归类于:
在开发过程中,我们的大部分时间可能都花在了这里。各种复杂的 UI 布局以及交互都会让我们头痛。而在 iOS 开发过程中使用最多的控件可能就是这些:UITableView、UICollectionView、UIButton、UILabel、UIImageView。
而所有页面的骨架基本由其中 UITableView、UICollectionView 提供,那么怎样优雅便捷地使用这两个控件就显得尤为重要。
在使用这两个控件的过程中,我们需要一般会实现其的 delegate 与 dataSource。
在使用 tableView 的时候,我们一般会有如下代码:
1 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section |
上面的代码会判断 section 来返回不同的 cell,而在实际情况中逻辑会比上面复杂。 假如业务需求变化了,需要更改 sectin 的位置,那么我们需要修改 tableView:cellForRowAtIndexPath: 方法的逻辑判断。当业务复杂的时候,这种修改会变得非常困难。
为了解决这个问题,于是我们参考了 IGListKit,他基于 UICollectionView 实现,基本思想是把每个 section 的管理交给 IGListSectionController,然后 IGListSectionController 再管理自己的每个 Item。并且实现了:
同样的,我们根据上面的思想基于 UITableView 实现了自己的 tableView UI 组件,彻底地解决了 UITableView、UICollectionView 的使用问题。
由于项目比较多的地方接入了 wap 页面,而为了兼容 wap 页面在 app 内的跳转(如在 app 内,如果 wap 跳转的页面是 native 支持的页面,那么就不需要跳转到 wap,而应该直接跳转到 native 页面),所以我们根据URL实现了一套路由跳转机制,从而可以不需要 wap 做任何需改,就能从 wap 跳转到想要的页面。
我们引入了 QMUI,从而解决了平常的用户界面开发困难且低效的问题。当然我们在使用的过程中也不断地发现框架的一些问题,并且提交了部分 PR。
在处理网络请求时,我们有以下问题需要面对:
为了应对这些问题,我们引入了 YTKNetwork,一个请求相当于一个资源,很好的解决了上面所说的部分问题,并且针对防劫持,我们引入了 HTTPDNS,并且由于不需要再次进行域名解析,相应的加快了请求访问速度。
而我们最终需要的数据一般都是 model,因此在基础上增加了
1 | - (Class)responseClass; |
requsest 子类可以自行实现这两个方法,这样就基本解决了数据解析的问题。
值得一提的是,我们根据 YTKNetwork 的 accessory 机制实现了自动空页面、上下拉刷新等
用户数据缓存方面我们使用了 UserDefaults 以及 sqlite
除 UserDefaults,我们没有直接使用 sqlite,而是使用了更加易用的 YYCache 以及 GYDataCenter
YYCache:是基于 sqlite 实现,具有 diskCache 和 memoryCache,并且实现了 LRU 淘汰算法、更快的数据统计,更多的容量控制选项。
GYDataCenter:基于 sqlite 的实现,但是使用过程中发现了一些问题,如不能简单的支持计算 sql、不支持约束等。在使用的过程中也提交了 PR
这里重点说下图片的缓存,因为图片缓存在 app 占的比重是最大的。目前项目的图像加载使用了 YYWebImage,其自带缓存,由 YYImageCache 提供缓存支持,针对图片缓存做了更细致的处理。在使用过程中遇到了如下问题:
根据不同机型图片尺寸加载问题,避免加载过大图片浪费流量
同一张图片,项目有的地方需要圆形图片、有的地方只需要一点圆角,同时为了避免离屏渲染,我们会使用 YYWebImage 提供的图片裁剪功能,其会将裁剪过的图片进行保存。但是这是不能够接受的,因为这很浪费流量。
针对问题1,由于我们使用了阿里云的 OSS 服务,可以方便的使用其自带的图片裁剪功能做适配。所以我们针对不同机型做了适配,保证不能放大的图最大只下载本机尺寸的图片。
针对问题2,我们修改了 YYWebImage 的图片裁剪功能,让其可以支持对一张图片进行多次裁剪,并且保存多张裁剪过的图片,这样就解决的一张图片因为需要支持不同的圆角而进行多次下载的问题。
针对上传的优化,主要是做了分段上传、资源压缩
为了节约开发成本以及公司的活动运营,我们比较多的接入了 wap,而为了解决 native 与 wap 间的通信问题,参考了大部分的开源实现,也参考了微信源码及微信的 JSSDK,最终实现了一套自己的基于插件化的 native 与 wap 通信的组件。
native 与 wap 间的通信,在 iOS 8 之前,不调用私有 API,基本都是:
通过拦截 URL 的方式实现,而到了 iOS 8,系统的 WKWebView 通过
WKScriptMessageHandler 原生支持 native 与 wap 间的通信。
由于项目从 iOS 8 开始支持,所以我选择了后一种方案,并申请进行了开源,项目地址:TPJsBridge
参考了神策数据的方案,设计出一套适合自己的埋点方案。
这里一开始的时候太想依赖于 AOP 去解决所有的埋点,后面发现是不可能的,走了弯路。
并且由于我们还针对曝光埋点,而曝光埋点没有那么容易,因为不管是 tableView 还是 collectionView 都会有复用机制,所以比较麻烦。这里有两种方案:
性能优化主要是检测 crash 率,以及检测页面的卡顿情况
通过接入 bugly,不断的发现问题解决问题,我们从 0.01 的 crash 率降低到了 0.001。
并且发现大部分的 crash 都是由于数据类型错误以及临界条件判断不够导致的。
我们通过 code review 以及代码静态检查,这些问题大部分得到解决。
通过检测页面的 FPS 来发现卡顿,对于有卡顿的地方使用 instrument 来发现耗时的地方。
一般造成卡顿的原因有这些:
针对1,我们使用 viewModel 避免重复计算一些属性,并且对 cell 做高度缓存(UICollectionView 自带 cell 高度缓存)。
针对2,我们的图片会在第一次使用的时候裁剪成希望的样式,避免了圆角的设置。并且我们会避免过多 view 层级的嵌套以及 alpha 的使用。在需要的时候也可以使用 AsyncDisplayKit来提高性能。
回家路上,行人匆忙地从路边瘫倒的共享单车旁经过。
吹着晚风,感受着空气中特有的凉爽,心情顿时特别放松。回首最近的生活,不知道从什么时候开始,我对工作渐渐丧失了激情。正因为没有激情,所以有时候就算发现了问题却假装看不见,明明可以优化也因为不是必须而置之不理。
认为在工作中没有成就感、回报,感受不到工作方向,没有目标,结果不可度量。做的事情如同分子,而分母却是无穷大,有种深深的无力感。
目前阶段自己最想做的就是专注“埋头拉车”,可能觉得“车”都还拉不好。也不认为自己找的方向能产生什么影响,担心做无用功,努力与付出不成正比。但是还是会焦虑。(这也许是团队协作能力欠缺的一种体现。
虽然自己“埋头拉车”,但还是会去比较。当其他业务攀登一个又一个高峰,而自己却原地踏步,会质疑自己在公司的存在价值。如同公司虽然在成长,但是那是别人在推动公司成长,与自己无关。(到这可以肯定自己欠缺团队协作能力)
那么又应该怎么去解决呢?我想了很久,依然没有答案,也许 all in 真的是一件很难的事情。