iOS热修复方案可行性研究以及Aspects修复方案的实践

前言:

伴随着企业的快速发展,承载着移动互联网业务的App的更新迭代要求越来越高。尤其是在中国,App的迭代速度很快,有时App需要紧急发版来处理线上业务和技术等问题。由于Apple审核制度的限制,在Apple提速了审核速度的情况下,大多数App审核往往需要1到2天。但是这仍旧无法满足App快速更新的需求。所以,想不通过发版,解决线上问题的方案一直为iOS端技术研发人员所青睐。

但是随着公开版本的JSPatch被Apple拒绝。热更新这个概念似乎冷了下来。但是App更新与审核速度的矛盾从没有解决。分析、构思,探索和找寻解决这个问题的合理方案。

理论可行性分析:

查阅了几种热更新的方案,JSPatch、Aspects、Stringer、TTPatch。分别对其实现原理、上架审核现状以及文档和资料完备性等方面进行分析。

拒绝JSPatch:

关于Apple拒绝JSPath事件的分析。

Apple对于App的开发倾向于Native。但是拒绝JSPatch并不是因为其使用了JS动态下发代码,而是因为这种方式存在着安全漏洞。可能被第三方篡改造成隐患。正式因为这样,所以在市面上滴滴出行App仍旧存在热更新。从一些资料上看到,滴滴的方案是从技术层面上对于下发的JS代码做了安全性校验。使得滴滴的App能够识别这些JS是由滴滴下发的。从而避免了Apple拒绝JSPatch热更新的安全漏洞。所以,重要的是安全性。而不是热更新的手段。

关于Aspects方案:

Aspects的原理与消息转发机制密切相关。Aspects主要是利用了forwardInvocation进行转发,Aspects其实利用和kvo类似的原理,通过动态创建子类的方式,把对应的对象isa指针指向创建的子类,然后把子类的forwardInvocation的IMP替成__ASPECTS_ARE_BEING_CALLED__,假设要hook的方法名XX,在子类中添加一个Aspects_XX的方法,然后将Aspects_XX的IMP指向原来的XX方法的IMP,这样方便后面调用原始的方法,再把要hook的方法XX的IMP指向_objc_msgForward,这样就进入了消息转发流程,而forwardInvocation的IMP被替换成了__ASPECTS_ARE_BEING_CALLED__,这样就会进入__ASPECTS_ARE_BEING_CALLED__进行拦截处理,这样整个流程大概结束。

关于Stringer方案:

虽然Stringer声称速度快又好,但是怎奈查看github库,当前开发版本号是0.3.0,可以认为是还不够成熟。并且其公开文档、资料极少。Demo也不完备。这给实际应用带来了麻烦。

综合分析:

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

综合查阅以上几个热更新方案,遴选出Aspects方案。原因是,此方案可以实现需要的代码修复功能,而且Aspects库与iOS库相关。这可以作为通过审核的有力依据。

This is stable and used in hundreds of apps since it’s part of PSPDFKit,

an iOS PDF framework that ships with apps like Dropbox or Evernote.

综上如果你是企业账号那么方案就是JSPatch 1.7.2版本加自己管理补丁,因为他这个平台超过1万会收费.但是人家js转OC的代码转换器都有了开发成本很低的。

如果是需要上架的App,并且团队的后台安全性没有得到充分保证的情况下,Aspects是稳妥的方案。

Aspects深入分析:

理论分析--面向切面(AOP)编程:

通过预编译和运行期动态代理实现给程序动态统一添加功能的一种技术。可以实现不修改原始类的实现无入侵式改变应用行为,相对来讲,实现简单,易于维护。可以在需要的某一个类或实例中添加一些我们自己的实现,只针对某个切面进行Hook操作,这个就是面向切面的概念(AOP,aspect-oriented programming),框架Aspects是 AOP 编程的框架。

AOP是采用使用运行时hook方法实现的原理(iOS的消息转发、swzziling)。

Aspects可以做什么:

AOP在开发中是一个非常重要的思想,我们希望将需求分离到非业务逻辑的方法中,尽可能的不影响业务逻辑的代码。主要的应用场景大概有以下几种:

参数校验:网络请求前的参数校验,返回数据的格式校验等等;

无痕埋点:统一处理埋点,降低代码耦合度;

页面统计:帮助统计页面访问量;

事务处理:拦截指定事件,添加触发事件;

异常处理:发生异常时使用面向切面的方式进行处理;

热修复:AOP可以让我们在某方法执行前后或者直接替换为另一段代码,我们可以根据这个思路,实现bug修复.

Aspects使用注意事项:

会有额外的系统开销,要避免高频调用。hook方法不能修改 @"retain", @"release", @"autorelease", @"forwardInvocation:"。hook类中的dealloc方法只能AspectPositionBefore。Aspects会产生额外开销,性能不高,不建议高频调用。

关于封装库的使用经验:

JS替换实例方法会覆盖原类和其分类的方法实现。注意有返回值和无返回值JS方法的灵活运用。注意无参数、有参数、多参数JS方法的调用。JS方法同样可以修改某一类中代理方法的实现。与一般方法相同。JS修改方法,可以获得上下文,参数,方法名。可以根据具体需要来进行逻辑处理。

部分JS代码:

fixInstanceMethodAfter('UBTestViewController', 'initViewLayout', function(instance, originInvocation, originArguments){

    //runVoidInstanceWithNoParamter(instance, 'showProgressForCommit');

    //runVoidInstanceWith1Paramter(instance, 'showText:','这是热更新测试');//替换某个方法,为该类的已有方法

    //下面是用JS语法设置OC的实例对象

    var coffeeVo = runClassWithNoParamter('UBCoffeeModel', 'new');

    runVoidInstanceWith1Paramter(coffeeVo, 'setBrand:','luckin');

    runVoidInstanceWith1Paramter(coffeeVo, 'setPrice:','16');

    runVoidInstanceWith1Paramter(instance, 'setCoffee:',coffeeVo);

    //runVoidInstanceWith2Paramter(instance, 'resetMyCoffee::',coffeeVo);

    console.log('这是热更新测试 替换方法验证##');

});

对于block可以参考如下代码。

//参数作为方法的参数,在JS中的调用(makeMyCoffeeComplete:的参数在原OC实现是一个block)

fixInstanceMethodReplace('UBTestViewController', 'makeMyCoffeeComplete:', function(instance, originInvocation, originArguments){

    if (originArguments[0]) {//取出参数,这里在对应的OC中,这个参数是block也就是function

        var callback = originArguments[0];

        callback(20);//block  作为参数,直接调用

    }

    console.log('这是热更新测试 替换block方法验证##' + originArguments[0]);

});

部分对Aspects的封装代码(借鉴了网上搜到的信息):

+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector obj1:(id)obj1 obj2:(id)obj2 {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

    [instance performSelector:NSSelectorFromString(selector) withObject:obj1 withObject:obj2];

#pragma clang diagnostic pop

}

+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector withObjects:(NSArray *)objects {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

    [[UBFreeFixManger sharedInstance] performSelector:NSSelectorFromString(selector) target:instance  withObjects:objects];

#pragma clang diagnostic pop

}

+ (void)_runVoidInstanceWithInstance:(id)instance selector:(NSString *)selector {

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Warc-performSelector-leaks"

    [instance performSelector:NSSelectorFromString(selector)];

#pragma clang diagnostic pop

}

特别的,基于Aspects的热修复,其目的是“热修复”,也仅仅是热修复。不建议通过这种方式随意修改一般功能逻辑。我想这也是基于Aspects的热修复能得到Apple认可的原因。

以上可行性分析很多资料借鉴和来源于网络,Aspects方案经过笔者仔细实践。Demo源码可以留言索取。

参考资料:

1、TTPatch【类JSPatch】<有成功案例、JS代码下发,转成原生代码>

《TTPatch使用》

https://www.jianshu.com/p/470a9e49b4f2

2、iOS中OC转Javascript的工具

https://blog.csdn.net/u013602835/article/details/52777461

3、Aspects<数百成功案例、JS代码下发,转成原生代码>

https://www.jianshu.com/p/2c93446d86bd

4、《iOS AOP框架Aspects实现原理》

https://www.jianshu.com/p/0d43db446c5b

5、《轻量级低风险 iOS 热更新方案》

https://mp.weixin.qq.com/s/2re_s3NmOvE9RXlbGQqGDA

6、《iOSHotFixDemo》

https://github.com/3KK3/iOSHotFixDemo/tree/master/iOSHotFixDemo

7、《OS 2020 热更新》

https://blog.csdn.net/u013712343/article/details/107932706

8、《《【iOS 教程】亮剑: Stinger到底能比Aspects快多少》

https://blog.csdn.net/weixin_47143210/article/details/105603880》

9、iOS使用Aspects做简单热修复原理

https://www.pianshen.com/article/9576238931/

10、动态交换方法实现

https://blog.csdn.net/WangErice/article/details/51211328

11、iOS AOP框架Aspects实现原理

https://www.jianshu.com/p/0d43db446c5b

12、Mac启动本地服务

https://www.jianshu.com/p/90d5fa728861

13、热更新方案

https://srxboys.github.io/2018/06/03/%E7%83%AD%E6%9B%B4%E6%96%B0%E6%96%B9%E6%A1%88/

你可能感兴趣的:(iOS热修复方案可行性研究以及Aspects修复方案的实践)