2016

2016-12-31 评论(12) 分类:生活

,照例回顾一下这一年。

工作

微信读书

今年在公司一整年都在做微信读书,用户量一直在增长,UGC方面每天都在产生很多不错的书籍想法和书评,整个产品度过了生死期,进入发展期。产品上有不少地方做得不错,一是保持了简洁的调调,这也是微信相关产品应有的模样,不会有什么乱七八糟的东西,也是很多人喜欢这个APP的原因。二是开放了陌生人想法,一开始微信读书只能看到好友写的想法和书评,跟朋友圈一样保持私密,事实证明在读书这个产品上,开放所有内容才是正道。三是做的阅读时长换书币和赠一得一都取得不错的效果,时长换书币让读书形成正循环。四是做了一些不错的小功能,获得了一些好的口碑,像划线想法制作图片分享,购买/赠送书籍的扉页等。

产品上一直在做一些尝试,但都比较谨慎,很多新需求功能都会经过不断讨论去决定要不要做,也有不少本来已经讨论完觉得可以做,但做到一半或做完时觉得还是不行直接砍掉的需求,谨慎导致不够快速,也做了不少无用功,这里也是我们缺乏经验导致,接下来一年会在这方面寻找解决方法,希望能做到快速迭代,减少无用功。

技术上回顾起来还是做了不少事情的,团队开了个技术博客,今年写了15篇技术文章,几乎每个成员都有贡献,开源了5个库(MLeaksFinder/GYHttpMock/GYBootingProtection/GYDataCenter/GYMonitor),在 APP 质量保证和监控方面做了一些事情,在团队博客上都分享了,另外实施了 code review,团队偶尔也会在项目里做一些新尝试,像尝试使用 RAC,尝试用数据不可变的方式开发,尝试用推荐算法去优化书籍的添加率等等,总的来说还算不错吧。接下来在技术上除了继续保证 APP 质量和开发效率,还应该尝试寻找能帮助产品前进的点。

JSPatch

JSPatch 这一年继续在完善,致力于让 JSPatch 变得更好用,优化了性能,做了一些扩展,填了一些坑,支持了动态调用C函数,做了代码自动补全工具 JSPatchX 等,改进最多的是 JSPatch 平台,从内测到开放注册,添加了灰度下发、条件下发、自定义密钥、在线参数、实时监控、历史补丁、在线编辑、转换器等等功能,已经是一个完善可靠的补丁分发系统,这个以前没有人做过,可以说开拓了在线补丁分发平台这个领域,算是今年主要的作品了,接下来也会继续探索提供更多的服务。

JSPatch 发展到现在已经被很广泛地使用,没有具体统计过接入的 APP 数,仅接入 JSPatch 平台每天有补丁请求的 APP 就有近3000个,在 iOS hotfix 市场占有率应该在 95% 以上,github star 也达到了 8300+,还算不错。

对于 JSPatch 的发展一直想扩展到覆盖 iOS 动态化的全部需求,包括开发功能,上半年也针对这个目标做了些事情,做了性能上的改进,优化开发体验的 JSPatchX 和 playground 等,有一些效果,但开发体验上还是不够好,下半年考虑过做一个100%准确的转换器,实现直接用原生OC开发,动态下发,进一步提升开发体验,但下半年杂事缠身,没能实施,最近看到滴滴和手Q分别实现了这样的方案,也就没打算再做了,后续再看情况。
(更多…)

iOS 动态化的故事

2016-12-21 评论(10) 分类:技术文章

今天聊聊 iOS 动态化的故事。

问题

在开发模式上,web 的方式是比较先进的,有各种优点,包括跨平台/UI开发效率高,最重要的是可以时刻保证用户看到的程序是最新的,没有版本概念,整个系统时刻保持在掌握之中,而客户端开发模式相对 web 开发是一种倒退,客户端做不到这样的动态化,无法随时更新,目前一个客户端程序要更新成本是很高的,需要发布版本,也无法保证所有人都能更新到这个版本,这是最大的弱点,也是非常大的一块需求。

原因

为什么会有这种倒退,最主要原因是:苹果引领的体验优先规则。

在 iPhone 出现之前大家并不太在意一个软件的动画体验,一个 web 应用是很少有动画的,点一个按钮,一整块内容直接刷新,再点个链接整个页面变白刷新,PC上网页滚动都是一格格滚动的,而不是现在手机上那种顺滑流畅的滚动,PC客户端软件也一样,大家都觉得没什么问题,用得挺好,但苹果改变了这种情况,iPhone 刚推出时页面间切换的动画,60fps 的丝滑滚动,点击的即时响应,微软的人都惊呼是黑魔法,让人用了就上瘾,再也回不去,而 web 的方式还不足以像原生客户端那样支持这样的流畅性,做不到好的体验,无法被人接受,开发上优势再多也无法干过客户端,参考 facebook 初期用 web 技术构建 app 的惨状,没办法,服了苹果,大家只能按照苹果的方式干,做原生客户端。

当然这里还有手机环境网络不稳定,流量费贵的原因,但这些都可以在技术上通过缓存解决,最主要还是体验问题。

那发展到今天,这个体验问题解决了没有呢?没有,即使发展到今天手机性能已上天,但 web 做出来的东西体验仍然跟客户端有差别,大家也已经习惯了 APP 的方式,也被流畅的 APP 惯坏。现在 APP 里也有不少功能是 web实现,但大家都知道这是牺牲了一些性能体验去换取开发和发布效率,只是一种权衡。

解决方案

那这个问题怎么解决呢,现在业界有两种方案。

1.优化 web 性能

既然用 web 方式开发的劣势只是性能体验跟不上,那就优化性能吧。web 性能瓶颈在哪里?在那个有悠久历史的 webkit 引擎,它有各种历史问题,要改进它并不容易。我们要的是 web 的开发和发布方式,不需要 web 的全部,那能不能重新实现一个渲染引擎呢,这个引擎可以针对原生客户端优化,不需要兼容繁杂的 web 标准,不跟 web 那些历史问题扯上关系,于是就有了 React NativeWeex 这种方案,web 的方式开发,原生的方式渲染,拥有 web 优秀的开发和发布方式,又有不错的性能体验,看起来很完美,很有前途的方案。

一个方案能不能推广开让大家都使用,主要看成本和收益。目前 React Native 和 Weex 等这些方案的接入成本是很高的,一是它们本身就是大型框架,学习成本高,后期维护成本以及团队学习成本同样高。二是它们还不够成熟,还在继续填坑中,使用的过程可能要一起去填坑。收益上也不够理想,就目前状况它们能代替的是那些本来用 H5 实现的模块,换成这种方式实现后性能体验会更好,但也不能保证像原生那样好,很多场景需要深入框架进行优化。整个 APP 都使用这种方案构建还不靠谱,部分使用又无法使整个 APP 保持动态化,总体上来说收益也没有达到有绝对优势的程度,成本高收益低,推广起来会比较困难,还需期待其继续发展。

2.原生动态化

另一种是方案是,我可以放弃用 web 的开发方式,放弃 web 跨平台/UI开发简单的优越性,我想继续用原生的方式开发这个APP,但又希望这个 APP 随时可以更新,让程序时刻在自己掌握中,出了问题可以快速修复,还想要可以随时更新版本快速迭代,可以不?完全可以,用动态库就行了。

动态库

技术上要在 iOS 上做到原生动态化比 Android 更容易,iOS 开发语言 Objective-C 天生动态,运行时都能随意替换方法,运行时加载动态库又是项很老的技术,只要我把增量的代码和资源打包到一个 framework 里,动态下发运行时加载,修 bug,加功能都不在话下,性能完全无损,这件事就结束了。

但是呢。苹果把加载动态库的功能给封了,动态库必须跟随安装包一起签名才能被加载,无法通过别的途径签名后再下发。

为什么这么做呢,这又涉及到苹果另一个创举:审核模式(苹果的创举之多令人发指)。一个软件,要在一个平台上发布,需要先通过这个平台的人工审核,这个好像在苹果之前没见过有别人这样做过,windows 不用,mac 不用,各种 unix 不用,web 也不用,苹果为了对自己的平台有绝对控制权,搞了这样一个东西,审核模式就跟动态化冲突了,如果我一个 APP 可以不经过审核不断下发 framework 添加修改功能, 还需要苹果审核做什么?

因为这种限制,没法用最方便的方式进行动态更新了,整个 APP 发出去后就不受控制,有什么严重 bug,需要添加什么功能,都乖乖打个包提交给苹果审核,再等用户慢慢更新,对于急性子的中国人来说,这种事难以容忍。

绕道

苹果把动态库这扇门关了,我们可以绕个道从另一个门进,动态加功能可以缓缓,大家需求还不那么迫切,但修 bug 的需求就很急,很多公司 APP 的 crash 率是 KPI 来着,看着线上 crash 不断增多又毫无办法是很不爽的事,于是有了 waxPatchJSPatch 这种方案,曲线救国。

JSPatch 把 OC 手动翻译成 JS,在运行时通过 OC 的动态特性去调用和替换 OC 方法,实时修复 bug。修 bug 这个需求基本是满足了,虽然小绕了下道,但成本还是很低的,引擎本身也很小很轻量,接入对 APP 不会有任何负面影响,在关键时刻又可以帮大忙,成本低收益高,于是很容易推广开。

人欲望是无穷的,技术宅的折腾是无止境的,JSPatch 满足了修 bug 的需求,但还是无法满足动态化的全部需求,最大的缺点在于需要手写 JS,虽然已经有转换器辅助,但还没做到100%准确,用来修 bug 还好,用来添加功能的话学习成本和开发效率还不够。

于是有了滴滴的 DynamicCocoa 这种方案,绕了一个更大的道,从编译阶段入手,通过 clang 把 OC 代码编译成自己定制的 JS 格式,再动态下发去执行,做到原生开发,动态运行,主打动态添加功能,当然顺便把修 bug 也给支持了。手机 QQ 内部也有一个类似的方案,不过更进一步,他们通过 clang 把 OC 代码编译成自己定制的字节码动态下发,然后开发一个虚拟机去执行(惊呆了),同样实现了原生开发,动态运行,都是 NB 得很的方案。只要底层处理做得足够好,也是个成本低收益高的方案,不过目前都还没开源,还没能看到实际效果和 NB 的源码,挺期待。

审核

这种方案有没有什么问题呢,问题在对于苹果审核比较尴尬。这种方案做到极致后(所有OC/C语法都支持),实际上是绕道实现了动态加载 framework 的全部功能,开发体验还是一致的,如果苹果同意这种方案,那相当于允许加载动态库,那还不如直接把门开了,让大家直接用动态库去做这个事情,用动态库还能在签名时禁止使用私有 API,用这种编译成脚本/字节码下发的方案可就禁止不了了。

这跟 JSPatch 还不太一样,JSPatch 虽然我也想推广动态添加功能的用法,但因为开发体验问题大部分还是用于修 bug,苹果审核对 JSPatch 开始也是有一些拒绝案例,后来估计看到大家只是用它来修 bug 和 crash,提升 APP 的质量,就默许了。但 DynamicCocoa 和手Q的方案一开始目标和效果就是跟加载动态库对齐,大规模推广后苹果会怎么看就不知道了。

我觉得苹果现在的审核方式挺有问题的,有多少APP是审核时用一套,审核通过后又通过后台一些开关把不符合规则的一些功能开放出来?只要能连上网,就有N种方式修改 APP 里的功能,苹果完全拦不住。个人认为审核方式应该改为只在发布新 APP 时审核,发布后允许动态下发代码更新,版本更新也不需要重新审核,而是通过举报和抽查的方式去审核已上线的 APP,这样既能顾及开发效率,方便开发者快速迭代做出更好的 APP,也更能确保审核效果,只是实施起来没有现在简单粗暴。

最后

故事讲到这里已经差不多了,再多说一点,有个让我觉得很奇怪的问题,就是国外开发者只热衷于使用第一种方案解决问题,也就是使用web技术,用 React Native / NativeScript 的方式去做这个事情,对第二种方案很冷淡,包括 iOS Android 都是,原生热更新只在国内火,国外根本不感冒,国外有个 rollout 热更新平台也不温不火,为什么呢?是国外用户更守规则,或是用户对线上 bug 容忍度高,开发者对线上 bug 并不那么着急?还是 Android 被 Google Play 卡死断了这念想,iOS JSPatch 之类的方案推广不利?缺少国外一线开发者的支持,让系统原生支持动态化就比较困难了。

个人认为由系统支持动态化(允许加载动态库)在当前环境下是最好的,兼顾开发效率和 APP 体验,虽然不能跨平台,但也还能接受,可惜这个主动权掌握在苹果手上,开发者无能为力,才会出现这么多强行绕道突破的方案。web 的方式可能是未来,但目前适用范围有限,接下来怎么发展,拭目以待吧。

APP 缓存数据线程安全问题探讨

2016-11-22 评论(11) 分类:技术文章

问题

一般一个 iOS APP 做的事就是:请求数据->保存数据->展示数据,一般用 Sqlite 作为持久存储层,保存从网络拉取的数据,下次读取可以直接从 Sqlite DB 读取。我们先忽略从网络请求数据这一环节,假设数据已经保存在 DB 里,那我们要做的事就是,ViewController 从 DB 取数据,再传给 view 渲染:
cache1

这是最简单的情况,随着程序变复杂,多个 ViewController 都要向 DB 取数据,ViewController本身也会因为数据变化重新去 DB 取数据,会有两个问题:

  • 数据每次有变动,ViewController 都要重新去DB读取,做 IO 操作。
  • 多个 ViewController 之间可能会共用数据,例如同一份数据,本来在 Controller1 已经从 DB 取出来了,在 Controller2 要使用得重新去 DB 读取,浪费 IO。

cache2
(更多…)

汽车杂想

2016-10-30 评论(5) 分类:生活

我一直对汽车不怎么感冒,去年五月因为家庭需要买了丰田雷凌,开得比较少,只有周末和有需要的时候才开下,说说对汽车的一些想法和感受。

刹车油门

在自动挡的汽车里没有了离合,只剩下两个踏板:刹车和油门,本来这两个踏板刚好对应两只脚,左脚刹车右脚油门,但现在还是全部由右脚控制,左脚空闲着,这样有两个优点:

  1. 避免刹车和油门同时踩。若两只脚对应两个踏板,慌乱的时候可能会两个踏板同时踩,不过这点很容易从技术上弥补,踩刹车的时候让油门失效就行了。
  2. 跟手动挡的车兼容。手动挡左脚是需要踩离合的,右脚就必须兼顾这两个踏板,若自动挡改成两只脚控制,在这两种车间切换的学习成本会变得很高,也会很混乱,估计这是最主要的原因。

同样也有两个缺点:

  1. 右脚会很累。特别是在市区拥堵路段,右脚需要不断在刹车和油门之间变换。
  2. 从油门到刹车切换需要时间。这时间可能已经够快,但在关键时刻还是浪费了些宝贵的刹车时间。

总的来说这种方案是缺点大于优点的,如果世上没有出现过手动挡汽车,可能还是会设计成左脚踩刹车,右脚踩油门。

还有一个小细节,现在由一只脚控制刹车和油门,那就需要尽力避免右脚混淆这两者,避免把刹车当油门,把油门当刹车的情况。就我的车雷凌来说,做了两点去保证这个区别,一是刹车面积比油门大,误触刹车比误触油门安全得多。二是刹车和油门这两个踏板不在一个平面上,刹车会稍微高点,稍微往前点,让脚放在刹车上跟放在油门上是两种不同的感觉,让脚抬起的度数去感觉现在是踩在刹车上还是油门上,这种身体的记忆是最可靠的,同时刹车高点也会比油门更容易踩。不知是不是所有车都这样。

空调

开车到现在碰到最大的痛点就是空调问题了,夏天只要放在路边,没多久车内温度就会非常高,没法待,这时候通常都是先进去发动汽车开空调,再在车外等车内温度稍微降低后再进去,等待的时间也很长,非常麻烦。怎样解决这个问题呢?

第一个能想到的是加强空调的强度,让汽车快速降温,不知道技术上能不能做到。我对汽车本身不太感冒,宣传的那些参数都看不懂,我觉得应该在那些参数里加一项,可以在多少秒内把车内温度从50读降到25度,这点应该很有参考价值。

第二是支持远程开启汽车空调,一查发现原来已经有不少汽车支持这个功能了,有的是远程开启发动机后供电开启空调,有的是用车内蓄电池供电开启,但远程开启发动机有点危险的感觉,车内普通的蓄电池电力又不足,这个功能应该更适合那种混动或电动汽车。

屏幕

车上装了个屏幕,是上个时代的产物,虽然有很多功能,什么电台/导航/音视频什么的,但很不好用,屏幕质量也差,非常鸡肋,唯一的作用是倒车时显示后视摄像头,平时其他功能都是直接使用手机,然后我还得买个手机支架挡在汽车空调风口去使用手机。

这里的问题是汽车换新的速度比手机差远了,一般人八年十年才会换一辆车,但一两年就会换一次手机,导致手机屏幕和功能一定秒杀汽车屏幕。另外像特斯拉那种放一个巨大触摸屏在汽车也不是好的方案,调个空调什么的都要通过屏幕,没法通过位置和触感盲按,在驾驶过程中调节麻烦又危险,大屏幕在汽车上并没有什么大用处,而且这块固定的屏幕也很快会过时。手机在未来很长一段时间应该还会继续在屏幕方面处于统治地位,新型汽车应该把本来留给屏幕的位置换成留给手机,做好手机和汽车的连接体验才是正道。

自动驾驶

自动驾驶目前在 AI 领域很火热,它带来的好处自然是很吸引人,驾驶的事交给机器,自己在车上就可以看看电影玩玩游戏,取车停车都能交给机器做,相当于每个人都有专属司机,目前号称自动驾驶出事故的概率比人低得多,机器比人更可靠,自动驾驶大规模使用指日可待,但这里感觉有个很大的隐患,就是程序病毒。从概率上说虽然人可靠程度不如机器,但人可不会突然中个病毒,然后在开车的时候乱来,但机器会,无法预测程序中病毒以后的行为,也无法事先感知程序中了病毒,现在在起步阶段,自然不会有这样的问题,等到真正大规模使用了,受到黑客的关注多了,自然攻击会变多,这里有很大的不确定性,把自己的生命交给这样的不确定性恐怕大家难以接受,估计也只敢在停车和取车时空车模式使用。

其他

私家车相对于打车和公共交通有好处也有坏处,好处是干净舒适,无需等待,无需步行到车站,也不用担心没有车,心情好的时候驾驶的感觉很不错。坏处也不少,开车时除了听歌听电台无法做其他事,相对地铁有塞车的烦恼,停车是个巨大的麻烦。另外成本也很高,得交停车费,得保养车,路上擦碰要修理,一不小心还得违个章。这样算起来,在公共交通和优步滴滴这样的出行方案成熟的情况下,开私家车真是挺奢侈的。

如何面试iOS工程师

2016-9-1 评论(10) 分类:互联网

参加了内部面委会的一个分享,结合我自己的方式,说说怎样面试一个普通的iOS工程师。

一般我倾向的考察分两个主要的部分,第一是在简历里提到的项目经历中找挖掘点,第二是基础知识考察。另外也会看情况做一些软实力的考察和性格特征的判断。

项目经历

如果顺利的话这第一步占的比例会很大,因为每个程序员都不会方方面面知识都熟悉,但至少他写在简历上的做过的项目是熟悉的,讲自己熟悉的东西容易让他进入状态,展示好的一面。这里主要考察两方面,一是有没有在某些点上有过深入研究。二是对项目整体了解如何。

深入研究

在中大型的公司里比较注重工程师有深入研究的能力,如果能把一个功能讲得很清晰是比较好的加分项,这里会问实现的思路,通过追问去了解候选人在这块深入的程度,从思路到方法,从上层API调用到框架流程再到底层实现。如果候选人在讲述时有一条逻辑主线,例如讲述业界普遍是怎么做的,自己在业界方案基础上做了什么改进,怎样做到更好,进一步改进的思路是怎样,这是最好的。如果还能把解决问题的方法归纳起来运用在其他地方,能举一反三,包装成通用解决方案,或者做开源贡献,就更好了。

一般会问候选人哪一个项目技术点最能体现自己的技术,然后不停追问技术细节,例如做了一个相册项目,觉得列表优化是最能体现技术点的,会问这里优化的思路是什么,怎样评估,遇到过什么困难,怎么解决的,如果用到图片缓存开源项目,说说它具体做了什么事,缓存策略是什么,从下载到显示的整个流程是怎样的,还有没有更好的方案,追问到一定程度后也会发散去问跟这个话题相关联的问题,例如如果有部分用户反馈图片显示不了,你会怎样排查问题,排查修复后怎样监控,就会过度到一些网络和运营监控方面的内容,也会顺便问到一些基础知识。

整体了解

问完自己职责范围内的功能技术点后,还会看看对项目里其他的实现有没有了解,特别是项目的大致架构和核心功能,最好能画出项目大致结构,看情况问问网络层和数据层是怎样实现的,为什么这样实现,项目最核心功能是怎样实现的,例如做读书的至少要知道项目里的排版引擎的大致实现方式,做QQ的要知道消息收发的机制,如果不知道,也可以说说如果自己实现会怎么做。这里主要看看有没有技术好奇心,会不会积极主动了解项目里已有的非职责范围内的技术点,主动和好学这两点是很重要的。

基础知识

如果项目经历里能问出大部分东西,这部分比例就会比较少了,这是比较好的情况,否则就按套路去多考察一些基础知识,包括iOS开发的基础和计算机基础,像内存/网络/存储/线程等,例如 ARC 是怎样做到自动管理内存的,跟java/js的垃圾回收的区别,网络http协议是怎样的,用过什么数据库框架,db索引是什么,多线程开发要注意什么,跟runloop的关系是什么等等,这类问题在网上都有很多,就不多说了。数据结构和算法在笔试时会涉及,面试会比较少,如果问算法的话只会问问思路,一般我觉得如果项目经历方面不太好,才会考虑考考算法作为辅助判断。

软实力

一些通用能力像逻辑思维能力,沟通能力,自我驱动能力等都可以在上面那些问题的交流中表现出来,另外像团队协作能力、抗压能力和性格特征这些也会看情况考察一下,例如问问如果产品让你做个需求,你觉得不靠谱,会怎样做,设计让你做个很难实现的效果,你会怎样评估?或者问个低级问题,故意说个错误的答案,看看他的反应是怎样,是表现出嘲笑和攻击性,还是怀疑自己,还是细心求证。抗压能力的考察有些人比较喜欢,我是觉得面试还是轻松一点好。软实力方面的考察在一面会比较少,或者不会涉及,实际上这方面我也没太多经验,也在摸索中。

其他

作为程序员,如果有 github 开源项目是最好的,直接可以看到代码风格,代码质量,处理 issue 和 PR 的方式,如果有技术博客也是很好的,可以提前看到平时的一些技术积累,省了很多事。但如果 github 内容是培训班的那种仿写APP,博客内容是摘抄文章什么的就是负分了。

以上是正常套路,若候选人有特殊经历或技能,例如牛X大学毕业,ACM冠军,通读linux源码,php源码贡献者之类,会另当别论,针对性进行面试,这不是唯一的标准。另外针对不同的工作年限也有不同的问法和要求,工作年限越高要求越高。

最后

其实面试就是想低成本找到合适在团队里一起工作的人,因为如果通过一起工作一段时间去判断是否合适成本太高。这种低成本的代价就是会误判,有些工程师是理论型,有些是实践型,面试的方式会对实践型的人不利,尽管他们如果招进来会是适合的人,而且人会在不同环境下会有不同的表现,只根据过去的经历去判断有时是不准确的。只能尽量采取一些措施去减少误判的概率,例如提高面试官的判断能力,或多几轮面试。一般如果不是急招,策略都会是宁杀错不放过,所以其实就算面试被否了,也不一定代表能力不行。

另外每个面试官可能都有自己摸索出来的一种判断方式,并随着面试经验的丰富不断改进,达到更准的判断概率,这只是我个人在目前有限的经验里的一点小总结,仅供参考。

iOS 动态更新方案对比:JSPatch vs React Native

2016-8-9 评论(7) 分类:技术文章 Tags:

JSPatch 是 iOS 平台上的一个开源库,只需接入极小的三个引擎文件,即可以用 JS 调用和替换任意 OC 方法,也就是说可以在 APP 上线后通过下发 JS 脚本,实时修改任意 OC 方法的实现,达到修复 bug 或动态运营的目的。目前 JSPatch 被大规模应用于热修复(hotfix),已有超过 2500 个 APP 接入。

虽然 JSPatch 目前大部分只用于热修复,但因为 JSPatch 可以调用任意 OC 方法,实际上它也可以做热更新的工作,也就是动态为 APP 添加功能模块,并对这些功能模块进行实时更新,可以起到跟 React Native 一样的作用。我们从学习成本、接入成本、开发效率、热更新能力和性能体验这几个方面来对比一下使用 React Native 和 JSPatch 做热更新的差异。

学习成本

React Native 是从 web 前端开发框架 React 延伸出来的解决方案,主要解决的问题是web页面在移动端性能低的问题,React Native 让开发者可以像开发web页面那样用 React 的方式开发功能,同时框架会通过 JS 与 OC 的通信让界面使用原生组件渲染,让开发出来的功能拥有原生APP的性能和体验。

这里会有一个学习成本的问题,大部分 iOS 开发者并不熟悉web前端开发,当他们需要一个动态化的方案去开发一个功能模块时,若使用React Native,就意味着他需要学习web前端的一整套开发技能,学习成本很高,所以目前一些使用 React Native 的团队里,这部分功能的开发是由前端开发者负责,而不是终端开发者负责。

但前端开发者负责这部分功能也会有一些学习成本的问题,因为 React Native 还未达到十分成熟的程度,出了bug或有性能问题需要深入 React Native 客户端代码去排查和优化。也就是说 React-Native 是跨 web 前端开发和终端开发的技术,要求使用者同时有这两方面能力才能使用得当,这不可避免带来学习成本的提高。

而 JSPatch 是从终端开发出发的一种方案,JSPatch 写出来的代码风格与 OC 原生开发一致,使用者不需要有 web 前端的知识和经验,只需要有 iOS 开发经验,再加上一点 JS 语法的了解,就可以很好地使用,对终端开发来说学习成本很低。

可以看一下同样实现一个简单的界面,React Native 和 JSPatch 代码的对比:

//React Native
class HelloWorld extends Component {
  render() {
    return (
      <View style={styles.btnArea}>
        <View style={styles.btnWrapper}>
          <TouchableHighlight underlayColor="#ED5F37” onPress={this.login}
              activeOpacity={0.7}>
            <Text style={styles.btn}>登录</Text>
          </TouchableHighlight>
        </View>
      </View>
    );
  }
  login(){

  };
}

var styles = StyleSheet.create({
  btnArea: {
    justifyContent: 'center',
    marginLeft: 20,
    marginRight: 20,
    marginTop: 100,
    flexDirection: 'row',
  },
  btnWrapper: {
    backgroundColor: '#FC6E50',
    borderRadius: 5,
    flex: 1
  },
  btn: {
    paddingTop: 10,
    paddingBottom: 10,
    color: '#ffffff',
    textAlign: 'center',
  },
});
//JSPatch
require('UIColor, UIScreen, UIButton')
defineClass('HelloWord : UIView', {
    initWithFrame: function(frame) {
        if(self = super.initWithFrame(frame)){
            var screenWidth = UIScreen.mainScreen().bounds().width
            var loginBtn = UIButton.alloc().initWithFrame({x: 20, y: 50, width: screenWidth - 40, height: 30});
            loginBtn.setBackgroundColor(UIColor.greenColor())
            loginBtn.setTitle_forState("Login", 0)
            loginBtn.layer().setCornerRadius(5)
            loginBtn.addTarget_action_forControlEvents(self, 'handleBtn', 1<<6);
            self.addSubview(loginBtn);
        }
        return self;
    },
    handleBtn: function() {
    }
})

接入成本

接入成本上,React Native 是比较大的框架,据统计目前核心代码里 OC 和 JS 代码加起来有4w行,接入后安装包体积增大 1.8M 左右。而 JSPatch 是微型框架,只有 3 个文件 2k 行代码,接入后增大 100K 左右。另外 React Native 需要搭建一套开发环境,有很多依赖的库,环境的搭建被称为一个痛点。而 JSPatch 无需搭建环境,只需要拖入三个文件到工程中即可使用。

React Native 是大框架,维护起来成本也会增大,在性能调优和 bug 查找时,必须深入了解整个框架的原理和执行流程,此外 React Native 目前还未达到稳定状态,升级时踩坑不可避免。相对来说 JSPatch 接入后的维护成本会低一些,因为 JSPatch 只是作为很薄的一层转接口,没有太多规则和框架,也就没有太多坑,本身代码量小,需要深入了解去调试 bug 或性能调优时成本也低。

开发效率

在 UI 层上目前 HTML + CSS 的方式开发效率是比手写布局高的,React Native 也是用近似 HTML+CSS 去绘制 UI,这方面开发效率相对 JSPatch 会高一些,但 JSPatch 也可以借助 iOS 一些成熟的库去提高效率,例如使用 Massory,让 UI 的开发效率不会相差太多。逻辑层方面的开发效率双方是一样的。

此外 React Native 在开发效率上的另一个优势是支持跨平台,React Native 本意是复用逻辑层代码,UI 层根据不同平台写不同的代码,但 UI 层目前也可以通过 ReactMix 之类的工具做到跨平台,所以UI层和逻辑层代码都能得到一定程度的复用。而 JSPatch 目前只能用于 iOS 平台,没有跨平台能力。

实际上跨平台有它适用和不适用的场景,跨平台有它的代价,就是需要兼顾每个平台的特性,导致效果不佳。

跨平台典型的适用场景是电商活动页面,以展示为主,重开发效率轻交互体验,但不适用于功能性的模块。对 Android 来说目前热更新方案十分成熟,Android 十分自由,可以直接用原生开发后生成diff包下发运行,这种无论是开发效率和效果都是最好的。所以若是重体验的功能模块,Android使用原生的热更新方案,iOS 使用 JSPatch 开发,会更适合。

JSPatch 也做了一些事情尝试提高开发效率,例如做了 XCode 代码提示插件 JSPatchX,让用 JS 调用 OC 代码时会出现代码提示,另外跟 React Native 一样有开发时可以实时刷新界面查看修改效果的功能,目前仍在继续做一些措施和工具提高开发效率。

热更新能力

React Native 和 JSPatch 都能对用其开发出来的功能模块进行热更新,这也是这种方案最大的好处。不过 React Native 在热更新时无法使用事先没有做过桥接的原生组件,例如需要加一个发送短信功能,需要用到原生 MessageUI.framework 的接口,若没有在编译时加上提供给 JS 的接口,是无法调用到的。而 JSPatch 可以调用到任意已在项目里的组件,以及任意原生 framework 接口,不需要事先做桥接,在热更新的能力上,相对来说 JSPatch 的能力和自由度会更高一些。

性能体验

使用 React Native 和 JSPatch 性能上会比原生差点,但都能得到比纯 H5 页面或 hybrid 更好的性能和体验。

JSPatch 的性能问题主要在于 JS 和 OC 的通信,每次调用 OC 方法都要通过 OC runtime 接口,并进行参数转换。runtime 接口调用带来的耗时一般不会成为瓶颈,参数转换则需要注意避免在 JS 和 OC 之间传递大的数据集合对象。JSPatch 在性能方面也针对开发功能做了不少优化,尽力减少了 JS 和 OC 的通信,github 项目主页上有完整的小 App demo,目前来看并没有碰到太多性能问题。

React Native 的性能问题会复杂一些,因为框架本身的模块初始化/react组件初始化/JS渲染逻辑等会消耗不少时间和内存,这些地方若使用或优化不当都会对性能和体验造成影响。JS 和 OC 的通信也是一个耗性能的点,不过这点上 React Native 优化得比较好,没有成为主要消耗点。

在性能和体验问题上,两者有不同的性能消耗点,从最终效果来看两者差别不大。

总结

学习成本
接入成本
热更新能力
开发效率
性能体验
JSPatch
中,不跨平台
React Native
高,跨平台
高 

总的来说,JSPatch在学习成本,接入成本,热更新能力上占优,而 React Native 在开发效率和跨平台能力上占优,大家可以根据需求的不同选用不同的热更新方案。JSPatch 目前仍在不断发展中,后续会致力于提高开发效率,完善周边支持,欢迎参与这个开源项目的开发。

(本文发表于《程序员》2016年8月刊)

JSPatch平台: http://jspatch.com

如何动态调用 C 函数

2016-7-5 评论(13) 分类:技术文章 Tags:

JSPatch 支持了动态调用 C 函数,无需在编译前桥接每个要调用的 C 函数,只需要在 JS 里调用前声明下这个函数,就可以直接调用:

require('JPEngine').addExtensions(['JPCFunction'])
defineCFunction("malloc", "void *, size_t")
malloc(10)

我们一步步来看看怎样可以做到动态调用 C 函数。

函数地址

首先若要动态调用 C 函数,第一步就是需要通过传入一个函数名字符串找到这个函数地址,这里一个必要的前提条件就是 C 编译后的可执行文件里必须有原函数名的信息,才有可能做到通过函数名字符串找到函数地址。我们写个简单的程序来看看它编译后可执行文件的内容有没有这个信息:

//main.m
void test() {
}

int main() {
  return 0;
}

编译这个文件,并用otool看下它的汇编:
(更多…)

北京之旅

2016-6-29 评论(5) 分类:生活

前几天受邀去北京一个技术分享会 GMTC 分享 JSPatch 相关内容,记录一下这次旅程。

分享

这次在 GMTC 分享的主题是 JSPatch 成长之路,PPT现在可以在[这里]看到,视频等后面出来了再补上。主要分享了 JSPatch 一开始碰到的一个难题,在发展过程中怎样完善补全周边功能,以及下一步计划。这是我第一次对外做分享,作为一个非常容易紧张的人,这第一次上台分享时竟然一点也不紧张,出乎我意料,当时在台下等待时还挺紧张,上了讲台后就很放松了,可能因为准备得比较充分,现场也没出现什么意外,都在把握之中吧,状态很好,分享效果感觉也还不错。

关于线下演讲分享和线上文章分享,我一直觉得技术领域要学东西的话线上文章分享是最好的形式,一是它传播广,触达用户多;二是耗时少,写一篇文章或看一篇文章都比听一个分享花的时间少很多;三是可沉淀,读者可以反复看细节,可以沉淀下来不断被人搜索到。

这次分享后我观点没有太大变化,但我也体会到了线下分享的好处,对分享者来说可以展示个人魅力,提升沟通能力,更有动力去认真准备内容,能比写文章更好地梳理自己的思路。对听众来说可以得到分享者这份精心准备的内容,同时可以面对面沟通交流,得到更多信息。当然更重要的是可以认识多些人。相对来说分享者得到的好处会更多些,我就觉得这次分享我自己收获挺多。不过线下分享无论是分享者和听众,时间成本都很高,而且收益会随着参加次数增多而减少,第一次的收益应该是最高的,后面参加多了可能就没什么帮助了,个人觉得不应该频繁参加。

在台下听了一些分享,印象比较深的是覃超的《Facebook iOS App技术演化十年之路》,讲 Facebook 客户端演进的过程,技术选型犯的错误和原因,反思世上没有能一次性解决所有问题的技术,跨平台是个陷阱,它要付出很多代价,牺牲很多东西,导致效果不佳,效率和效果是一种权衡,应该面向用户开发而不是面向工程师。其他精彩分享篇幅有限这里就不多说了,在[这里]都可以看到。

沙龙

这次顺便也参加了一个iOS开发者沙龙,T技术沙龙,跟会议分享不同的是只邀请少数人,鼓励多进行沟通交流,台上演讲分享只是辅助开启话题,这个有点像公司内部分享,还挺不错,跟多人大会比起来各有好坏吧,优点在于这样的交流更有效率,可以交流到更细节的东西,缺点在于分享不会那么精心准备,也就没有前面说的那些好处,另外因为更注重双向交流,效果好不好得看参会的人怎样。

在沙龙同样分享了 JSPatch,交流比较多,大家也提出了很多自己的看法,第二场sunny分享了《Calling Conventions》,阐述一个函数在汇编层面是怎样被调用的,objc_msgSend 的实现,最后还分享了一个 swift 理论上可行的方法替换的实现方式,十分黑魔法,但前提是得越狱,很赞的分享,长了见识。第三场因为有约就提前走了,小有遗憾。

北京

距上次来北京已经时隔五年多了,再次踏入北京的感觉有点亲切和怀旧,感叹时间过得越来越快,现在一回忆起什么事来动不动就是五年前十年前。当时在百度实习三个月,对北京的印象挺好,可能是待的时间短还没来得及看到它不好的地方,没碰到过大雾霾的天气。给我的感受北京跟广州最大的不同是人的多样性,这里汇集了全国各地的人,不同性格不同背景不同经历的人互相交流进步,而且北方人(广东以北的人)大多性格外向豪爽,跟他们聊多了自己也会变开朗些,很有意思。

跟前同事聚了下,五年来大家外貌似乎一点都没变,就是各奔东西了,有的在百度继续发展,有的跳到中小公司,有的创业,境况不一样,有的公司上市了,有的公司下滑严重,北京选择机会多,风险也大,谁能想到如日中天的一家公司说跌落就跌落,看起来一般般的公司说上市就上市,人生啊,不过大家还得工作几十年,机会多得是。

这次过来见到了很多之前只在网上交流过的朋友,有些跟网上交流感觉一致,有些很有气场,有些很有亲和力萌萌的哈哈,也认识了不少新朋友,很感谢这次各路朋友们的热情招待,非常愉快的一次旅程~

浴室沉思

2016-5-30 评论(8) 分类:随记

对于使用的东西,便捷性是比功能性更重要的,雨伞是最好的例子,下雨拿雨伞下半身还是会被淋一身,功能上并不完整,雨衣在这方面更胜一筹,能保证大部分部位不会被淋到,若再发明一种把脚也包起来的雨衣就更完美了,但这些永远做不到雨伞那样流行,因为雨伞非常方便,大家都宁愿为便捷性牺牲一些功能。

互联网影响了音乐/文学/游戏,音乐免费了,音乐人卖不了唱片,变现的路变长了,还不如搞娱乐八卦拍NC电视剧钱来得快,于是音乐也就没落了。文学上大家被互联网碎片信息冲击,信息爆炸,社会节奏快,也就更少人去买书阅读了,另外网上盗版满天飞,传统作家收入也受限了,远不如写写YY网络小说赚得多,细腻经典的文学也就变少了。游戏上以前单机游戏,追求的就是好玩,好玩就会有很多人买,就会赚到钱,而现在网络游戏,追求的不是好玩而是寻找人性的弱点,对人性把握越到位,就有越多人民币玩家投钱在你游戏里,可参照各种人民币玩家当皇帝的页游卡牌手游。

《龙珠》的超级赛亚人濒死状态疗伤恢复后战斗力会大增,应该是取材自现实,正如尼采说的,“杀不死我的,会让我变得更强大”。在受重大挫折后,只要熬过来了,内心就会变得更强大。

进化论像心灵鸡汤,咋一看很有道理,实际是怎么说都行,经不起推敲。例如人为什么会有害怕和恐惧的情绪,因为在老虎面前你不害怕和恐惧就挂了,那些不会害怕和恐惧的人已经灭绝了,但另一方面,为什么那些一害怕就腿软没有行动能力的人没有灭绝呢?他们理应逃不过虎口。

人们看重天赋>努力>外在条件,学生时期少不了一类学生总是说自己没学习,回到家偷偷学到半夜,考试成绩出来后把成绩归功于自己的天赋而不是努力。但靠努力奋斗获得成功又比靠关系靠是富二代这些外在条件获得成功更受人尊重。

很多人表达观点的时候,都会隐藏对他不利的事实,只说对他的论点有利的事实,如果这时候听者顺着他的思路,不思考提出疑问,就会陷入他构造的逻辑中,觉得很有道理,从而接受了观点,被洗脑。

XCode 代码补全插件 – JSPatchX 原理解析

2016-4-26 评论(2) 分类:技术文章 Tags:

JSPatchXJSPatch Xcode 代码自动补全插件,目前在 github 开源,效果见图:

做完一个开源项目照例写篇文章说明下实现原理,主要目的是让想对这个项目做贡献改进的人可以通过文章更容易地了解这个项目的由来,思路,核心原理和流程,降低参与这个项目开发的门槛。

由来

JSPatch 脚本一个不爽的地方就是没有代码补全,而调用 OC 方法时方法名又死长,写起来很不方便。

对此之前做了 JSPatch Convertor,可以自动把 OC 代码转为 JSPatch 脚本,这个工具的使用场景是用 JSPatch 做 hotfix 时,需要重写原 OC 的整个方法,这时用工具把这个方法的 OC 代码直接转为 JS 再进行修改,可以很大地降低工作量,缓解了这个问题。但若要用 JSPatch 开发新功能模块,就不会有 OC 代码可以去转换,这时提高编码效率的唯一方式就是做代码补全插件。

在寻找实现方案的时候得知公司内一牛人 louis 已经实现了 lua 的 XCode 代码补全插件,沟通后还很慷慨地给了源码,省去了很多研究 XCode 代码补全机制的功夫,于是参考他的编码,并且直接用了他 OC 头文件解析的代码,开发了 JSPatchX。所以这个项目算是我与 louis 联合开发的,在此感谢 louis~

插件入门

XCode 有个很坑爹的地方,就是它并不官方支持插件开发,官方没有文档,XCode 也没有开源,但由于 XCode 是 Objective-C 写的,OC 动态性太强大,导致在这么封闭的情况下民间还是可以做出各种插件,其核心开发方式就是:

  1. dump 出 Xcode 所有头文件,知道 Xcode 里有哪些类和接口。
  2. 通过头文件方法名猜测方法的作用,swizzle 这些方法,插入自己的代码实现插件逻辑。
  3. 通过 NSNotificationCenter 监听各种事件的发生。

更详细的开发教程网上有不少文章,有兴趣的自行搜索吧。

起步

对于实现 JS 代码补全这个功能来说,主要分三步:

  1. 在编辑 JS 文件时开启代码补全功能。
  2. 找到用户输入代码时的回调,按 Xcode 要求组装代码补全对象数组返回。
  3. 根据已输入文字对补全对象数组进行过滤

第一步是通过替换 DVTTextCompletionDataSource 类里的 -strategies 方法,在源文件是 JS 时生成一个 IDEIndexCompletionStrategy 对象返回,就可以针对 JS 文件走代码补全逻辑了。

第二步是替换 IDEIndexCompletionStrategy- completionItemsForDocumentLocation:context:highlyLikelyCompletionItems:areDefinitive: 方法,这个方法会在用户输入时被调用,在这里组装好应该出现的补全对象(IDEIndexCompletionItem) 列表返回,Xcode 就会自动应用返回的 items 对输入进行补全。

第三步是在 DVTTextCompletionSession-_setFilteringPrefix:forceFilter: 方法,针对第二步返回的 item 对象根据输入进行过滤。

要让自动补全插件程序跑通,只需实现上述三步。显然核心在第二步如何组装合适的补全对象 completionItem。代码里我们新增了一个 IDEIndexCompletionItem 的子类 JPCompletionItem 去表示,下面统一把这个补全对象称为 completionItem。接下来的问题就是怎样组装这些 completionItem。

实现

先看看我们需要哪些自动补全,概括起来有几种:

  1. 可能会被调用到的 OC 方法名
  2. JS 上新增的方法名,以及出现的类名
  3. JSPatch 自身的一些关键字接口,如 defineClass, require 等
  4. 当前 JS 文件里出现过的关键字

前三点应该没有异议,第四点要解释一下,实际上若要做得精细,应该加上 JS 语言本身自带的 API 和关键字(var / Math 函数 / String 函数等),以及JS 当前作用域上的变量的补全,但这样做一是 API 太多,二是实现复杂,所以用 “当前 JS 文件里出现过的关键字” 代替这两点,只要文件里出现过的单词就会有补全提示,也就是说一些关键字和变量第一次输入时没有提示,但在同个文件第二次输入就有补全提示了,sublime 默认就是这样的补全规则,实际使用效果很好,所以选择用这种简单的方式满足需求。

具体实现上,分三步走,一是解析 OC 头文件,二是解析 JS 文件,三是对解析后的数据进行缓存和组装 completionItem。

解析 OC 头文件

JPObjcFile 负责解析 OC 头文件,因为这里可以认为外部可以调用的 OC 接口都在头文件里,所以只需要解析头文件,这样处理比较简单,解析效率也很高。louis写了个 OC 头文件解析器,把头文件里的 class / protocol / import 解析出来,最终每个头文件都会解析成对应的 JPObjcFile 对象,这个对象保存着文件里 class / protocol 对应的方法的 completionItems,可以按需求直接输出。

解析 JS 文件

JPJSFile 负责解析 JS 文件,这里的解析比较简单,没有用词法语法解析器,而是直接通过正则匹配取出需要的内容,这里通过正则提取了:

  1. require() 里的 className
  2. defineClass() 里的 className
  3. defineClass() 里所有的方法名
  4. 文件里所有 keyword

同样每个 JS 文件都会生成一个 JPJSFile 对象,包含了上述提取的元素,并生成和保存了方法名和keyword对应的 completionItems列表。

组装和缓存

解决了单个 OC / JS 文件的解析,接着就是决定解析哪些文件,以什么样的形式缓存和组装返回给XCode。

JPObjcIndex

先看看 OC 的解析,JPObjcIndex 负责 OC 头文件的解析,JS 可能调用到的 OC 代码只存在于两个地方,一是系统framework,二是项目里的代码,对这些 OC 头文件要以什么样的方式解析呢?这里有两个选择:

  1. 只解析与当前编辑的 JS 文件相关的 OC 头文件
  2. 一次性把所有文件都解析好,再进行筛选

若要用方案1,只解析与当前编辑的 JS 文件相关的文件,则需要知道 JS 文件引用到了哪些 OC 文件,需要像 OC 代码那样有 #import 其他文件的规则,而 JSPatch 的规则是调用 OC 代码时不需要 import OC 文件,只需要通过 require(‘className') 接口引入类,所以线索只有 require() 里的类名,而在还没解析时是不清楚类名和 OC 文件的对应关系的,无法知道当前 JS 文件依赖了哪些 OC 头文件,所以这里只能选择一次性把所有 OC 头文件都解析好。

JPObjcIndex 默认扫描了 Foundation 和 UIKit 这两个 framework 里的所有头文件,以及当前项目里的所有 OC 头文件,在 JPObjcIndex 里以 className 为 key 进行缓存,对外提供通过 className 去取这些类相应 completionItem 的接口。

JPJSIndex

对于 JS 文件,为了简单起见,同样采用了一次性解析全部文件的方式。JPJSIndex 做了以下这些事:

  1. 解析所有 JS 文件,生成一个个对应的 JPJSFile 对象,缓存起来。
  2. 取出每个 JPJSFile 里解析的 require() 以及 defineClass() 的 className,去 JPObjcIndex 取这些 Class 对应的 completionItems。
  3. 取出每个 JPJSFile 解析好的 method completionItems。
  4. 取出每个 JPJSFile 解析好的 keyword completionItems。
  5. 本地 keyword.plist 定义了 JSPatch 常用的一些自动补全关键字,例如 defineClass, CGRect 等,在这里取出这些数据并生成 completionItems。
  6. 把 2-5 步里的 completionItems 分成两种类型,keyword 类型和 method 类型,缓存起来并返回给 XCode。
  7. 当有 JS 文件保存时,重新对这个文件生成 JPJSFile 对象,并重做 2-6 步。

第6步分出来的两种类型应用于两种场景,method 类型会在 JS 输入 . 要进行方法调用时出现,这个类型里所有的 completionItem 都是方法,包括 OC 头文件定义的方法以及 JS 里解析的方法。keyword 类型则是其他的像类名/语句关键字等这些非方法,在平常输入中出现。

在没有 JS 文件保存时,用户编辑 JS 代码每一次输入走到补全逻辑时,JPJSIndex 都是直接返回内存里已组装好的 completionItems 列表,没有其他操作,提高操作性能。第7步虽然在有文件保存时重新做了 2-6 步对数据进行重新组装,但这个过程不涉及文件解析,只需要取内存里解析好的数据进行组装,并且文件保存不会那么频繁,所以性能上没有太大问题。

整个流程就是这样,实际上很简单,总结起来就是解析所有 OC 头文件,解析所有 JS 文件,组装并缓存好 completionItem 返回。

不足

做这个项目的想法是先用最简单的方式快速做出来,满足80%的需求,导致会有一些不足,例如

  1. 没有做 JS 语法解析,没有做细致的筛选规则,粗暴地全部提示。
  2. 没有补全 include 的其他 JS 文件里的全局变量。
  3. defineClass 里写定义方法时,若要覆盖 OC 原有方法,没有方法名补全(因为方法名只有在 . 后才有补全)
  4. 没有加上除了 Foundation 和 UIKit 以外的 framework。

欢迎一起改进 JSPatchX,完善这些不足~