• 关于iOS热修复(HotPatch)技术的使用总结


    苹果做了非常多的努力来建造和维持一个健康并且干净的应用环境。其中对现在的现状起到很大作用的部分就是苹果APP STORE,它是被一个十分周密的对所有提交的应用进行检查的审批程序所保护的。尽管这个程序是被设计为用来保护IOS用户并且确保应用程序符合苹果的安全性和完整性的要求,体验过这个流程的开发者可能会觉得它太复杂了并且花费了大量的时间。发布一个新的release版本或者发布一个已经存在的APP的补丁也要遵守这个流程,这对于一个想要给一个影响现有APP用户的重要bug或者安全漏洞打补丁的开发者来说就会非常困扰。

    开发者社区已经在寻找相应的替代方案,并且取得了一些进展。这一系列的解决方案现在提供更有效率的IOS APP开发体验,让APP开发者去更新他们的代码只要他们认为是合适的,并且立即把补丁部署到用户的设备上去。尽管这些技术提供更自由的开发体验,它们并不符合苹果试图去维持的相同的安全规范。更糟糕的是,这些理念可能会成为苹果APP STORE坚固“城墙”的阿喀琉斯之踵。

    在这篇文章中,FireEye手机安全研究员将会分析采用这些可替代方案的IOS APP的安全风险,并且试图阻止IOS APP生态环境做出意外的安全妥协。

    有关这方面在最近了解和学习中我注意到两种解决方案,一种是常用的-JSPatch,还有一种是iOS滴滴的动态化方案-DynamicCocoa

    一、JSPATCH

    有关JSPatch方面的说明就不多说了,想了解的朋友可以百谷必就能找出一大把,我在此贴个我觉得比较详细的传送门:

    http://www.tuicool.com/articles/IRr2Ura

    本来是乌云上的原贴 不过乌云事件相比大家也了解了。。。

    JSPatch的使用接入方式也简单粗暴,两句代码完成接入。。而且引擎也是小的很。

    1.接入JSPatch官方服务器的方式:

        [JSPatch startWithAppKey:@"0a78sd6f7dsad7f658"];

        [JSPatch sync];

    这个APP KEY和正常SDK一样,需要去官方申请

    然后在官方服务器上上传补丁即可

    2.本地测试时接入工程中的方式:

    [JSPatch testScriptInBundle];

    注意要把工程中的JS文件名称改成main.js

    这样:

    然后就没有然后了

    3.接入项目中相关服务器的方式:

    // 开启引擎

    [JPEngine startEngine];

    // 传入相关的JS字符串

    [JPEngine evaluateScript:script];

    同样的向服务器发起一次请求,然后检测两次文件的MD5值或者是其他值,来判断是否更新包,其中加密过程由前端和后台人员进行制定

    分隔----------------------------------------------------------

    在公司体系、人员和制度比较完善的环境下一般会做一个Manager类来专门进行处理这个流程
    比如:

    1.因为工程需要,将检测补丁写在只在程序启动时:

    2.因需要写在每次程序跳转到前台时:

    有关JSPatch的相关使用方法可以参考这篇文章:

    http://www.cnblogs.com/jiangshengkai/p/5784422.html

    以上是JSPatch的一些说明

    分隔---------------------------------

    二、DynamicCocoa

    动态化一直是 App 开发梦寐以求的能力,而在 iOS 环境下,Apple 禁止了在 Main Bundle 外加载和执行的自己的动态库,所以像 Android 一样下发原生代码的方案被堵死。

    后来像 React Native、Weex 这样的基于 Web 标准的跨端方案出现,各大公司都有对其进行尝试,但对于滴滴现状,也许并不适合:

    滴滴 App 强交互、以地图为主体、端特异性高;

    客户端人员充足,跨技术栈学习和开发有较大成本;

    大量固化 Native 代码,重写成本高。

    所以我们思考,能不能做一套保持 iOS 原生技术栈、不重写代码就神奇的拥有动态化能力的方案呢?

    于是,我们设计和实现了一个具有里程碑意义的 iOS 专属动态化方案:DynamicCocoa

    ---------孙源

    DynamicCocoa初识

    DynamicCocoa可以让现有的Objective-C代码转换生成中间代码(JS),下发后动态执行,相比其他动态化方案,优势在于:

    • 使用原生技术栈:使用者完全不用接触到JS或任何中间代码,保持原生的Objective-C开发、调试方式不变;

    • 无需重写已有代码:已有native模块能很方便的变成动态化插件;

    • 语法支持完备性高:支持绝大多数日常开发中用到的语法,不用担心这不支持那不支持;

    • 支持HotPatch:改完bug后直接从源码打出patch,一站式解决动态化和热修复需求。

    不论是动态化还是HotPatch,我们都能让开发者“Write Cocoa,Run Dynamically”。

    ff577fae869247068dd2853a6b65afdb_th.png

    语法支持

    DynamicCocoa能支持绝大部分日常使用的Objective-C/C语法,挑几个特殊的:

    • 完整的Class定义:interface、category、classextension、method、property,最重要的是支持完备的ivar定义,保持和native完全一致的实例内存结构;

    • ARC:可以正确处理strong、weak、unsafe_unretained等对象的引用计数,对象的ivar也可以正确的释放;

    • C函数:支持C函数的定义与C函数的调用、内联函数的调用;

    • 可变参数:支持C与OC的可变参数方法的调用,如NSLog;

    • struct:支持任意结构体的使用,无需额外处理;

    • block:支持创建和调用任意参数类型的block;

    • 其他OC特性:如@selector、@protocol、@encode、for..in等;

    • 其他C特性:支持使用宏、static变量、全局变量,取地址等。

    举个例子,你可以放心的使用下面的写法,并能被正确的动态执行:

    a67a4ced370444a0bba4a05f2fca2b3e_th.jpeg

    资源支持

    一个功能模块,除了代码外,资源也是必不可少的,DynamicCocoa的动态bundle支持:

    • xib和storyboard;

    • xcassets;

    • 不放在xcassets里的图片资源;

    • 其他资源文件。

    对于习惯于使用IB来开发UI的人来说,这将是一个很好的开发体验。

    工具链支持

    我们使用Ruby开发了一套命令行工具(类比为xcodebuild),大幅简化了配置开发环境、OC代码转换、资源处理、打包的复杂度,它可以:

    • 解析XcodeProject:读取工程编译选项,保持和native编译参数一致;

    • 增量编译:缓存JS转换结果,只重新转换修改过的文件,大幅提高build速度;

    • 链接:分析类依赖,将多个JS按依赖顺序合并,提高文件读取速度;

    • 资源编译:编译用到的xib、storyboard和xcassets;

    • 打包:将JS、资源等打包成bundle。

    对于开发者来说,就像pod命令一样,所有操作都可以通过这个命令完成。

    动态插件开发流程

    首先App中需要集成DynamicCocoaEngineSDK,用来执行下发的bundle开发到发布的流程如下图所示:

    fe86cf549e4647be83b2f4547622dd15_th.jpeg

    当然,DynamicCocoa只提供命令行工具和EngineSDK,可以完成本地打包、运行和测试,而线上发布后台、服务端、CDN等需要自行解决。

    在滴滴内部,我们构建了开发、Review、线上回归测试、灰度、发布、回滚、统计的闭环系统,以服务的形式给内部接入。

    HotPatch过程

    HotPatch本质上是方法粒度上的动态化,所以在整个框架搭建起来后,HotPatch也不难实现,使用DynamicCocoa做热修复的最大优势是开发者依然只对源码负责,修改完bug后,打个patch包,修复成功后把源码改动直接push到代码仓库就行了。

    假设我们发现了下面的bug:

    706b4b9ace7e42e980b91de252b079f9_th.png

    然后在Native进行修复并自测:

    36.jpeg

    自测完成后,在这个方法后面添加一个神奇的Annotation:

    37.jpeg

    使用命令行工具在patch模式下进行打包,就能把所有标记了的method提取出来,分别转换成JS表示,打到一起进行发布。

    除了修改一个方法外,patch模式还支持:

    • 调用原方法;

    • 新增一个方法;

    • 新增一个property来辅助修复bug;

    • 新增一个Class。

    最后,开发者可以安心的把修改后的代码(甚至可以保留Annotation)gitpush,完成热修复工作。

    打开黑箱

    66.png

    就像Objective-C是由Clang编译器和Objective-CRuntime共同实现一样,DynamicCocoa也是由对应的两部分构成:

    • 在Clang的基础上,实现了一个OC源码到JS代码的转换器;

    • 实现OC-JS互调引擎的DynamicCocoaSDK。

    我们知道,Clang-LLVM的标准编译流程是从源代码经过预处理、词法解析、语法解析生成语法树,CodeGen生成LLVM-IR,进入编译器后端进行优化和汇编,最终生成目标文件(Mach-O)。

    14.png

    而我们既希望Clang帮助完成源码处理的步骤,又希望生成结果是JS表示形式,于是在Clang生成抽象语法树(AST)后,我们进行接管,实现了一个OC2JSCodeGen,遍历各个特定语法节点输出JS表示:

    15.png

    由于转换器和Clang前端标准编译流程相同,所以只要native代码能build,转换器就能build,这也是DynamicCocoa能让动态包和native保持严格一致的先决条件。

    注:转换器是基于Clang开发的独立命令行工具,它的使用并不会对原有的Xcode工程产生任何影响。

    另一部分是要集成进App的DynamicCocoaSDK,它的职责是为JS中间代码提供Runtime环境,实现OC-JS的互调引擎,能够加载动态bundle,提供便捷的API,整体架构如下:

    16.png

    其中一些有趣的点:

    • 底层使用libffi来处理各个架构下的callingconventions,实现caller调用栈的构建和callee调用栈的解析,用于实现OC/C函数调用、动态imp、block等。

    • 由于JS的弱类型,数值变量在做计算时很容易丢失类型信息,比如inta=1/2;在OC中表示整除,结果为0,但进入JS就都会按照double计算,结果为0.5,造成了不一致。所以DynamicCocoa接管了JS中的类型信息,强转或运算符都需要特殊处理。

    • 为了实现block,我们构造了和nativeblock一致的内存结构,不论是JS创建的block还是native传进JS的block,都可以无差别的调用。

    • 虽然runtime提供了动态创建OCClass的API,但只能创建MRC的Class,导致ARC下ivar并不会乖乖释放,我们深入到Class和实例真实内存结构中,给动态创建的类增加了ARC能力,并按照Non-FragileABI模拟真实ivar内存布局和ivarlayout编码,如果你重写了dealloc方法,DynamicCocoa甚至能够像native一样自动调用super。

    DynamicCocoa带来的改变

    DynamicCocoa动态化技术给App开发带来了很大的想象空间:

    • 低成本的动态化:无需额外学习,无需重写代码,可以快速的将已有模块动态化;

    • 协作方式:对于大团队,发布版本不必再彼此牵制;

    • 功能快速迭代:无需经过审核和AppStore发版,像HTML5一样随发随上;

    • App瘦身:Native只需要留好插件入口,实现由网络下发,减少App体积;

    • ABTest:不必局限于Native埋进去的AB功能Test,发版后能动态下发各种Test。

    相比跨端方案,也带来了一个新思路:iOS和Android都保留Native开发模式,用各自的方式将Native代码直接动态化,保持各平台的差异性。

    Q&A

    与JSPatch有什么区别?

    两者思路上都是实现JS和OC的互调:DynamicCocoa的重点是动态化能力,优势在于完全不用写JS和更多的语法特性支持;对于HotPatch来说JSPatch是更加小巧、轻量的解决方案。

    这套框架在滴滴App有上线使用么?

    有,在滴滴App已经上线并使用了好几个版本,如滴滴小巴、专车接送机都有过10k级别的动态化模块上线。

    动态包运行的性能是否有很大下降?

    动态JS代码的运行要经过频繁的JSCore和OC间的切换,性能相比Native必定会有损耗,但经过优化,现在已经达到了无感知的程度:在我们的实际使用中,若不在页面上添加特定标志,开发者和QA都无法分辨出当前页面运行的是native还是动态包…后续会有详细的性能分析和大家分享。

    动态包大小如何?

    与资源大小和Native源码量有很大关系,不考虑资源的情况下,量级大概在10000行代码100KB的动态包。

    是否支持多线程?

    现在简单的支持GCD来处理多线程,可以使用dispatch_async将一个block放到另一个queue中执行。

    如何定位动态包的Crash?

    动态JS代码运行在JSCore中,并没有直接获取调用栈的方式,我们提供了stacktrace功能,将最近调用栈中每个JS到OC/C的互调都记录下来,在发生Crash时便可以取出来作为附加信息随Crash日志上报给统计平台,方便问题的定位。

    会不会过不了苹果审核?

    市面上很多动态化、HotPatch方案都基于JS的下发,运行在原生JSCore上,相信只要不在审核期间下发动态功能,Apple是不太会拒绝的。

    有没有可能支持Swift直接动态化?

    相比OC,Swift的动态化和HotPatch更加有难度,但我们已经有了可行的方案,是可以做到的,只是对于当前滴滴的现状(绝大多数都在用OC开发),紧急程度并不高,后面再考虑支持。

    是否有开源计划?

    有,我们正在积极的准备相关事项,于2017年初考虑开源。

    分隔-------------------------------以上转自:http://www.cocoachina.com/ios/20161220/18400.html

     

    相比,JSPatch在补丁方面以JavaScript为主,DynamicCocoa以原生代码为主,已经属于使用阶段,但并未开源,或者说是未普及;

    JSPatch需要开发人员需要有一定的JavaScript基础,DynamicCocoa在此之上做了相应处理,调用原生OC和Swift语言并且打上标签就可以使用;

    我个人不介意了解和使用这两种技术,毕竟JavaScript使用还是很广泛,而且Swift的发展有点向进军服务器的趋势,而且C#也不甘落后的做出了桥接iOS的SDK

    (参考我上篇文章:http://www.cnblogs.com/axclogo/p/6179150.html

    以后指不定C#也会推出相应的热修复功能也不一定。

    总结:

    市场行情在不断着发展着新需求,可见出开发也不断着用各种方法适配着新需求,在开发界只有不断进步才会不被淘汰。

     

    Once a new technology starts rolling, if you’re not part of the steamroller, you’re part of the road.

    -------(Stewart Brand)

     

    一旦一种新技术开始滚动碾压道路,如果你不能成为压路机的一部分,那么你就只能成为道路的一部分

    -------Stewart Brand

     

     

     

  • 相关阅读:
    解决“ 故障模块名称: clr.dll ”
    关于阿里云专有网络搭建FTP服务器的深坑
    电脑异常断电,IDEA崩溃
    Winform 出现“Win已停止工作”解决方法
    C# WinForm控件、自定义控件整理(大全)
    cmd获取管理员权限等
    检测笔记本电池状态
    单片机
    常用工具、焊接技术
    元器件
  • 原文地址:https://www.cnblogs.com/axclogo/p/6208229.html
Copyright © 2020-2023  润新知