本著追本溯源的思想(以及面試官的要求……),結合JSPatch作者對JSPatch原理的解釋,說一說個人對JSPatch原理的理解。
1.基于蘋果的JavascriptCore框架
JavascriptCore框架是實現JS和OC互相交互的框架,使用這個框架,你可以在OC里面調用JS代碼,也可以在JS中調用OC的代碼。這個框架是JSPatch實現的基礎。關于JavascriptCore框架,可以查看這篇文章。
2.基礎原理是OC的動態語言特性
使用JSPatch來進行熱修復,在很多情況下我們都是通過替換方法來實現的,在OC中,利用runtime可以很容易做到這一點。
3.實現方法調用
當使用require
方法引入一個類的時候,JSPatch實際做的是在全局作用域的某個全局對象里,創建一個對象:
{
__clsName: "UIView"
}
當使用這個類,調用類方法的時候,就會去全局對象里取這個對象。現在這個對象還沒有方法,而在JS中調用某個對象沒有的方法會拋出異常。這里JSPatch作者的解決方法是給Object對象定義一個__c()
方法,而在OC那邊,JSEngine實際使用JavascriptCore框架執行JS代碼之前,將所有的方法調用改為調用__c()
方法,這個方法內部做的就是將方法名參數等信息傳到OC,去OC里通過runtime執行,這樣一來就不會崩潰了。
除了這個問題以外,JSPatch還對OC回傳的對象做了包裝,通過_obj
這個key把他包裝:
{__obj: [OC Object 對象指針]}
通過判斷對象是否有 __obj 屬性得知這個對象是否表示 OC 對象指針,在 __c 函數里若判斷到調用者有 __obj 屬性,取出這個屬性,跟調用的實例方法一起傳回給 OC,就完成了實例方法的調用。
4.方法替換
JSPatch內部的方法替換是通過動態方法解析實現的,用的是forwardInvocation方法,因為可以從NSInvocation里面取得原方法的所有參數(通過va_list取得類結構體的方法列表獲取參數在arm64下不可用)。
JSPatch是在應用的didFinishLaunchWithOptions
里執行的,所以方法替換也是在這里實現,它會將原方法的IMP指向_objc_msgForward
(這個方法會執行forwardInvocation),新添加一個方法指向原方法的IMP。當原方法被調用的時候,會直接走forwardInvocation,在這里可以直接調用JS中的替換方法,至于JS的方法調用看上一部分就行了。