iOS中动态化方案

于2017年初总结当前移动平台上的动态化方案,现已过时

2017-01-06 | 阅读

动态化方案,我将其分为几类:

  • Hybird型 :使用浏览器引擎解析脚本,并在webview中构建界面。 APP中大量内容使用web页面实现,APP作为一个浏览器,并提供一些接口以扩展页面的功能。所以这里的方案,一般都是一些供JS调用的库,以扩展浏览器功能,常见的有phonegap ,cordova 等, 而我们之前的5+runtime和现在的jsbridge都属于这种方案。 对于推崇Web技术的人,总是在说现在web技术体验较差是因为网络和设备性能的问题,等以后网速变好了,设备性能提升了,体验就会好很多, 那发展到今天,这个体验问题解决了没有呢?没有,即使发展到今天手机性能已上天,但 web 做出来的东西体验仍然跟客户端有差别 , 现在都2017年了,我们还要等多久才能体验到无比流畅的Web呢?
  • Native UI型 : 使用单独的引擎来解析脚本,但是不受制于webview,而是自行构建Native,以获取原生的体验。 为了优化Web性能,即抛开拥有很多历史问题,性能达到瓶颈的webkit引擎,而创建一个针对原生客户端优化的,不需要兼容繁杂的 web 标准。基于这个思路,常见的方案有 :React NativeWeex, 两者使用Web技术来编写APP界面与逻辑。 而类似的方案还有一个LuaView,但是后者的不同之处,在于其是使用Lua语言,且不是web开发方式。 Native UI型,就是用脚本语言,以一种类似构建Web的方式,来创建一个个定义好的原生控件。
  • 脚本化Native型 :即通过脚本语言来编写Native代码,脚本语言拥有与Native相似或相同的功能,能够创建类、方法和互相调用,达到与直接编写Native代码相似的效果。
    • 基于JS语言的动态化方案 : 基于JavaScritpCore来解析动态脚本,通过OC的动态性以调用所有OC函数和C函数,即通过JS脚本实现所有原生的功能。常见的有 :JSPatchDynamicCocoa
    • 其他脚本语言动态化 : 基于其他脚本语言的动态化方案 : 不是使用JS语言作为脚本,而是使用其他语言,并在APP中引入该脚本语言的解析引擎,而构建出来的动态化方案,常见的有: OCSWax .

    这里我们将JS和其他脚本语言分离开来进行讨论,因为在Apple Develope Program License Agreement 中的第3.3.2条,也就是是如下 :

    此条声明中指出,禁止下发脚本,可执行代码和解释器,但是如果这些内容是在WebKit或者JavaScriptCore中执行的,那是可以接受的。 所以基于JS和其他脚本语言的动态化,其最大区别就在于这里,一个符合Apple的条款,一个不符合。但是现在的情况是Apple对于这些违规,睁一只眼闭一只眼,不是很苛刻,所以我们可以无视这条规则,使用脚本语言来为APP添加动态性,但是需要了解这条规则。(但是现在已经明确,这种方案已被苹果禁止)

Hybird型 vs Native UI型

不同之处在于,Hybird型一般使用浏览器构建页面,即视图是Web中的元素,最多存在一些插件与Natvie进行交互。而Native UI型,将界面布局不再由浏览器构建,而是通过自己的引擎去构建Native控件,以组成一个Native APP。而这些Native UI型的动态化方案,一般会考虑提高开发效率,而修改或提供一些前端框架,如RN使用ReactWeex使用Vue

Native UI型 VS 脚本化Native型

Native UI型与脚本化Native型的不同,在于构建APP的内容不同,前者相当于提供了许多插件来创建原生组件,为脚本语言提供了一套完整创建界面的方式,更专注于界面的实现,其引擎是一个翻译工具,将创建web界面的语言转换为创建Native控件。而后者不在乎界面,而更专注于语言的翻译,将脚本语言翻译成原生语言,即让JS脚本可以在APP中像原生代码一样去执行,去响应,即Native脚本化是提供一个翻译引擎,来将脚本语言像OC代码一样执行,而界面的创建与响应都是脚本中实现的。

React Native

React Native 是从web前端开发框架React延生出来的解决方案,由facebook于2015年发布,着力于提高多平台开发的开发效率,目标是Learn once, write anywhere , 即拥有跨平台的能力,但是具体实施时还是要对平台做一定的适配。React Native同时支持iOSAndroid.

React Native通过JSOC的通信,让界面使用原生组件渲染,以在使开发者通过JS脚本编写程序,来创建基于原生组件的界面,使开发出来的功能拥有原生APP的性能和体验。

React Native提高的开发效率,在于可以使用大量的前端框架,如React ,Redux 等。React Native使用了HTML+CSS的方式去布局UI,所以布局方面比较方便,也方便快平台开发。 React是一种组件化的开发框架,所以在React Native中有大量的社区开发的组件可供复用,拥有一套完整的生态体系。

Weex

由阿里出品的跨平台开发框架,其野心就比RN要大一点,声称自己能Write Once Run Everywhere ,同时能保证Native Speed in Native Platform。 所以,weex同时支持了iOSAndroidweb,并能使用同一套代码运行在不同的平台上。 在原生环境下,使用与React Native相同的方式,通过JS与原生的通信,让界面使用原生组件去渲染,以创建基于原生组件的界面,得到原生的性能体验。

WeexRN最大的不同,就在于支持的平台,与跨平台的态度上,weex追求三端一致,且使用Vue.jsWeex使用的前端框架是vue.js,而RN中使用的是React ,所以有人也戏称应该将Weex改名为vue-native . vue是一个更加轻量级的前端框架,所以性能也优于React.

Weex的开源发展,在2016年6月正式开源,在12月正式捐赠给Apache,虽然现在受到的关注程度比React Native要低,但是我们相信随着开源社区的发展潜力,Weex也会发展的更好,更加稳定而强大。

LuaView

LuaView是聚划算无线技术团队的开源项目,其初衷是为了提高移动端应用动态化能力,同时提供比H5更好地用户体验。LuaView支持AndroidiOS,其与前两者的最大的区别,在于RNWeex都是基于Web的开发框架来构建的,而LuaView是一个使用Lua语言编写的,没有前端开发框架供使用,而使用偏向与原生开发的方式去编写创建界面。但是其还是属于Web型开发方案,虽然其能够完全用LuaOC的转换去构建界面,编写逻辑,但是其选择了一种保守的思路,专注于界面的创建,通过大量封装的界面控件来组成界面,构建APP。所以我们将这个方案归类于第二种。

原理上,LuaView通过luaJluaC引擎来解析Lua脚本,利用OC的动态性来实现调用与界面处理。 与其他方案最大的优点在于引擎,不再受制于JS脚本的单线程。

LuaView项目,当前拥有不到两千个Star, 所以偏小众的一个方案,主要使用者是 聚划算客户端,手机淘宝和天猫中得聚划算模块,以及俪人购客户端。

Native UI型

Native UI型的动态化方案,通过构建一个引擎,来解析脚本,构建界面, 所以他们都是基于界面的开发方案,原理都是创建一个View,堆积一个界面,连接各种跳转,组成一个APP。

而这种方案,一般不能做到一套代码,多个平台,因为各个平台有自己的特性,只有贴近平台,才能制作出性能更好且效果更好的界面。 而这就要求开发者需要去了解当前所在平台的一些特性, 如在iOS上进行RN的开发,就必须要学习iOS开发的知识,不能说只会RN就可以了。而RN本身也是一套方案,带有reactweb开发的一套知识要学习,RN的学习成本太高,且必须贴近原生开发。

这种方案无法实现完整的动态化,因为其中组件是基于Native的实现,也就是对于一个组件,必须在线上代码中有这个组件的Native代码,才存在之后动态添加该组件的可能。

JSPatch

JSPatch是一个小的JS解析引擎,在运行时运行JS脚本,以达到OC源码的效果。利用OC的动态性去添加类,添加方法,替换方法,以达到自己的目的。JSPatch最开始推出时,专注于修复线上BUG,但所搭建的JS与OC交互的引擎,赋予了JS脚本编写业务的能力,我们可以使用JSPatch去开发一个完整的APP,通过下发JS脚本实现动态更新所有模块的目的。

JSPatch的主要原理就是利用JavaScriptCore去执行JS脚本,利用OC语言的动态性,来在运行时添加代码,替换代码。同时利用libffi来调用C函数。所以JSPatch能够基本满足所有业务的开发需求,实现只使用JS脚本就能编写业务,编写APP。

JSPatch提供了一个简单的语言转换器,将Obejctive-C的代码翻译成JSPatch使用的JS脚本,但是由于其简单,所以不能做到100%转换成功。而JSPatch本身语法比较简单,开发方式接近OC开发,所以即使手写JS脚本,也是不会有太高的学习成本的。

JSPatch由腾讯开发人员个人维护,在Github上现在有8000多个Star,社区活跃度和当前发展情况都是很成功的一个项目,而使用者也很多,国内几乎所有大型APP都在使用JSPatch

虽然JSPatch有动态化更新APP的能力,但是暂时没有大型项目采用其去构建APP,所以完全使用JSPatch构建APP,可能会有一些坑的存在,且性能上是否能达到要求,以及JS单线程的问题如何解决,这些是JSPatch的问题所在。

DynamicCocoa

DynamicCocoa是滴滴推出的动态化方案,是一款能够保持iOS原生技术栈,不重写代码就能拥有动态化能力的方案。其原理是,在Clang的基础上实现一个OC源码到JS代码的转换器,然后将源码编译成JS脚本进行下发和执行,同时APP中再存在一个与JSPatch类似的解析引擎,以实现OC-JS互调。

DynamicCocoa宣称的很多特性,都很漂亮:

  • 基本支持所有OC和C的语法
  • 支持各种资源,支持xib和storyboard
  • 开发了命令行工具来处理配置、资源处理和打包等工作
  • 支持patch模式,(比较弱,通过标记函数来提取diff)
  • 优化了JS的类型处理,避免OC与JS相同的代码获取不同的结果
  • 优化block的调用,是js创建的block和oc传入的block效果相同。

是为了动态化而开发出来的方案,而JSPatch是为了热修复而设计的方案,两者目标的不同导致项目的复杂度也不同。JSPatch只是专注于OC的动态性来替换函数,而DynamicCocoa花费了更多地精力用于优化这个复杂流程中的每一步。所以上面一些特性,就是JSPatch中的缺点,而DynamicCocoa花费时间优化的地方。如为了实现一个准确的转换器,DynamicCocoa深入研究clang-llvm,以通过定制clang来实现输出中间JS代码。

DynamicCocoa声称已经在滴滴中使用了几个版本,且是10K级别以上代码的模块,并将在2017年初进行开源。如果其能达到所声称的目标,那绝对是iOS中最好的动态化方案之一。

个人最看好这个方案,个人对于这个方案可能存在的问题,在于调试是否方便以及是否存在JS单线程的问题,但是方案是否能如期完全开源,以及之后维护是否靠谱,是否会被Apple禁用,都是不确定的问题。

OCS

OCS是腾讯qq开源的动态化方案,OCS定义了一套精确描述OC语义的字节码指令集OCScript(虽然有Script,但不是脚本语言),开发了一个全自动编译器,实现了一个高性能的虚拟机以及一个可以与底层无缝对接的桥接器。OCSDynamicCocoa更加激进,直接自行创建了一个新的脚本语言,自建运行时与桥接,不再使用JS和JSCore,不再受制于JS的单线程。

OCS中称 前者存在一定的性能问题,在JS或者lua调用Native运行时进行消息发送时,会进行大量的字符串解析和数据转换,导致性能低下。而OCS从2015年下旬开始设计与实现,致力于将OCS脚本与Native原生开发保存一致,使开发体验,内存管理,线程管理和语义定义全部一致。OCS1.0版本于2016年8月在手Q成功上线,现在已经得到大规模代码转换和海量用户使用实战验证,解决了传统脚本动态化方案存在的弊端。

OCS与DynamicCocoa相似,也基于Clang构建了编译器,在编译时期将开发者编写的代码生成的AST转换成OCScript,该字节码由OCSVM进行解析,这个虚拟机不仅负责解析执行OCScript,还承担了内存管理和线程管理的任务。接口调用即OCSBridge,实现了对C和OC的调用和block的调用,block调用指OCS中的block和OC中的block都可以互相调用,OCSBridge是基于OCSABI来构建,而OCSABI是使用汇编实现的,确保了虚拟机与Native最底层进行直接高速通信。

OCS宣称自己的性能接近于原生代码,并有数据展示。现在也有几十业务,数万行代码使用OCS实现了动态化,减小了安装包大小,且崩溃率不断下降,低于 10万分之二。其有用性、稳定性、流程性都经过实战。

这个方案看起来很美好,但是暂时没有开源计划。

Wax

Wax是类似JSPatch的简单方案,是一个开源库,使用Lua脚本编写程序,利用OC的动态性来桥接LuaOC. 该方案之前由个人开发者开源,现在由阿里进行维护,当前有不到2000星,也算是一个小众的动态化方案。

Wax需要学习Lua的语法,使用Lua的方式来开发APP。Wax还有一个热修复对应的方案,称为WaxPatch,是在JSPatch出现之前,唯一的热修复方案,但现在已经年久失修。

WaxLuaView最大的不同,是对于脚本的使用方式,Wax相当于将Lua语言翻译成OC语言来执行,而LuaView则通过Lua语言来构建Native UI , 这也就是类web开发与脚本动态化的不同之处。

Wax的优点,是一个与JSPatch相似的小巧的引擎,且Lua语言简单易学习,且不会受制于JS的单线程。

脚本化Native型

总结一下脚本动态化的四个方案,JSPatchDynamicCocoa还算是在遵守Apple的规则,即使用webkitJSCore来解析JS脚本。而Wax使用Lua脚本,OCS则更加激进地使用自行研发的字节码指令集,别人好歹发的是脚本,OCS直接在下发指令了。但是从当前Apple的暧昧的态度来看,我们其实不必过于担心,Apple的目的是避免开发者通过下发可执行代码来做到跳过审核,发放一些不合规定的内容。但事实上,不用下发可执行代码,我们有很多方式能做到将审核期间APP与审核后不同,如通过后台开关来控制。所以Apple的目的已经难以实现了,过于苛求这条规则,也是没有太多意义的,之后Apple可能会放宽这个部分的审核。

DynamicCocoaOSC很像,都是通过编译工具,来直接将OC代码翻译成自己的语言,以使开发者可以完全无感知的继续开发APP,几乎没有学习成本。而在性能上OSC做得比DynamicCocoa更加极致化,OSC抛弃了JSCore,自行实现的高效虚拟机,在性能上自然强大。这样看来,与激进的OSC相比,DynamicCocoa还是有些保守谨慎的,但是在没有足够把握判断Apple的态度时,我觉得这份保守还是应该的。

WaxJSPatch都是老实的脚本动态化方案,编写脚本,构建APP,没有过多地去考虑直接翻译OC代码,毕竟一些OCC的语法,是很难用脚本语言实现的,而两者都是倾向于做一个简单小巧的引擎,所以更加轻量级。