什么是热修复?首先我们先来还原一下场景:
客服MM:用户反馈生产版本出现crash问题了,很严重没法用,怎么办,怎么办?
程序猿GG:测试组确认问题后,我们修复Bug,测试验证完成,打包发布。
客服MM:用户需要多久能下载新包呢?
程序猿GG:至少3天。
客服MM:额,这个.....
这个时候大家都会想,如果能够马上修复线上版本bug该多好啊。想法没毛病,热修复技术就是在这种场景下发展并普及的。
热修复是一种App客户端快速修复bug方法,通过后台下发代码,可以在App不发布新版本的情况下修复bug。在2015年的时候国内开始流行起来,在2017年3月被苹果的一封警告信推上了风口浪尖。
【基本原理】
后台下发代码(Patch包),App加载patch包,通过patch里的数据分析,查找到有问题的Method方法进行替换。替换是APP运行时直接调用新Method’方法执行,从而避免了bug的影响。
(图一)
【IOS热修复】
15年项目上在选择方案时,基于当时市面上比较流行的2种方案JSPatch和WaxPatch进行了对比。主要从性能、功能、维护成本等方面综合考虑。因为lua语言没有JS语言普及程度高,而且WaxPatch框架现在已经停止维护了,而JSPatch框架还在持续不断的更新,因此IOS热修复方案我们选择JSPatch。
JSPatch的设计原理是利用了OC语言的动态特性,动态修改类的方法达到修复bug的目的。在介绍如何进行类方法替换之前,我们先来熟悉几个名词解释:Class、SEL、IMP,有助于我们理解方法替换。
Class在OC中是一个objc_class的结构体指针,定义如下:
图2
我们知道OC中每个类都有一个isa的指针,指向类对象的类【元类metaclass】。通过isa指针能找到对象所属的类,类结构体里保存着所有的信息,如父类、类名、类成员变量、类方法、协议链表等。类方法都存储在objc_method_list链表中,我们从图2中可以看到链表中存储的objc_method对象,包括SEL(selector)方法选择器,表示为方法的名称;char*存储的是参数类型,包括方法的返回值类型和入参类型;IMP(implement)是一个函数指针,存储方法具体实现代码块的地址,可以像普通C函数调用一样使用IMP。
一、IOS方法替换原理
OC上所有方法的调用/类的生成都通过 Objective-C Runtime 在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。
二、JSPARCH和OC交互原理
回过头来再来看看JSPatch是如何进行工作的。JSPatch主要语言是JS脚本,利用IOS系统提供的JavaScriptCore.framework库执行JS脚本,可以调用在JSContext中预先定义的方法。
1.DEFINECLASS方法
我们使用热修复就是为了要替换有Bug的方法,所以JS脚本中首先要定义好需要替换方法的类名、方法名,还有具体的方法实现过程。在OC端加载JSPatch的时候,会进行JPEngine的初始化,在初始化函数中有定义_OC_defineClass桥接函数。当js端执行_OC_defineClass方法时,就会触发OC的桥接函数调用。
▪ defineClass方法中能获得类名、方法名(如果有实现协议也能获得)。
▪通过NSClassFromString获得类对象,然后根据类对象和方法名获取到IMP。
▪同时返回{className:class}值给JS端。
2.OVERRIDEMETOD方法
OC从JS端脚本中获取到待替换的方法后,会判断app中是否有实现该方法,如果实现了就会执行overrideMethod函数。
▪overrideMethod把原始方法的IMP设置为_objc_msgForward,如果方法的返回值为特殊的struct类型(如:CGRect/CGRange)则设置为_objc_msgForward_stret,方法的调用会走消息转发流程。
▪添加新方法ORIG+方法名指向原始实现的IMP,保存原始方法。
▪添加JPForwardInvocation指向新的自定义IMP。
▪添加ORIGForwardInvocation指向原始的IMP。
3.JPFORWARDINVOCATION方法
上面提到了我们用JPForwardInvocation替换了原生的消息转发。替换的目的,是利用了IOS消息转发机制来实现一个通用的IMP,任意方法任意参数都可以通过这个IMP中转,拿到方法的所有参数回调JS的实现。在forwardInvocation中会有一个NSInvocation对象,保存了方法调用的所有参数值。
▪JPForwardInvocation读取传入的NSInvocation对象所有参数。
▪根据实际参数类型转换成OC类型。
▪把参数组装成一个数组返回给JS端。
▪如果转发的方法和JS脚本方法不匹配,调用ORIGForwardInvocation走原有的流程。
4._METHODFUNC()函数
JS脚本所有的方法调用都通过__c()函数,根据当前对象类型判断进行不同操作,_methodFunc()就是把相关信息转给OC,OC用Runtime接口调用相应方法的核心。
_OC_callI负责调用实例方法,_OC_callC负责调用类方法。
通过这两个函数调用,完成了JS端替换方法实现中调用OC对象或方法的过程。
5.CALLSELECTOR方法
JS端调用OC方法时,有时会出现JS类型和OC类型不一致的问题,通过此方法进行参数类型、返回值类型的转换和处理。
▪J将JS封装的对象进行拆分,得到OC对象。
▪J根据类名和方法名得到对应的Class对象和SEL。
▪J根据返回值类型,封装OC对象{className:cls,obj:obj}返回JS端
三、JSPATCH案例
本文介绍了热修复的基本原理、IOS热修复的原理与方法,并进行了实战案例演示。我们会继续跟新关于ANDROID热修复的内容,请继续关注!