JSPatch是一個(gè)可以在線修復(fù)bug的輕量級(jí)框架大脉,項(xiàng)目中嵌入這個(gè)框架可以讓你的app具有熱更新的能力揪胃。你可以通過框架提供的一個(gè)平臺(tái)术裸,在線分發(fā)修復(fù)bug的js代碼归园,平臺(tái)的地址是http://jspatch.com/ 枯跑,平臺(tái)適合中小型 APP (日活<5w) 使用惨驶,對(duì)于用戶量大的 APP,建議自行搭建后臺(tái)使用敛助。這個(gè)框架剛剛誕生我就開始關(guān)注粗卜,目前在github上面已經(jīng)有了三千多的star,非常值得一讀辜腺,它在github上面的地址是
https://github.com/bang590/JSPatch 休建。另外,框架的作者已經(jīng)寫了一篇詳盡的實(shí)現(xiàn)原理以及相關(guān)的說明文檔评疗,是輔助分析源碼的最佳工具测砂,詳見
https://github.com/bang590/JSPatch/wiki/JSPatch-%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86%E8%AF%A6%E8%A7%A3
*幾點(diǎn)說明:
??1、框架的核心部分僅僅只有兩個(gè)文件:JPEngine.m和JSPatch.js,非常的精巧百匆,讓人驚嘆;
??2砌些、通過閱讀作者的實(shí)現(xiàn)原理blog(下文簡(jiǎn)稱原理詳解blog),可以發(fā)現(xiàn)在研發(fā)框架的過程中需要實(shí)際解決的問題非常多加匈,各種精妙的設(shè)計(jì)存璃、奇思妙想也非常讓人佩服;
??3雕拼、原理詳解blog全面的介紹了設(shè)計(jì)的想法纵东,解決問題中的思路和采取的方法,但是啥寇,僅僅閱讀了blog偎球,而不看源碼是遠(yuǎn)遠(yuǎn)不夠的,框架對(duì)js高階特性和OC Runtime的運(yùn)用和配合辑甜,如神來之筆衰絮,非常值得學(xué)習(xí)和研究,本文僅僅作為一個(gè)通過原理詳解blog的引導(dǎo)磷醋,來學(xué)習(xí)源碼的一個(gè)筆記猫牡。
??4、這里通過兩條主線邓线,分別是修復(fù)過程和調(diào)用過程淌友,把整個(gè)處理的過程連貫的串起來煌恢,順便進(jìn)行方法的解析。
我們從實(shí)際的應(yīng)用出發(fā)震庭,分為三個(gè)部分對(duì)源碼進(jìn)行解讀症虑。當(dāng)我們引入了框架,準(zhǔn)備好了修復(fù)bug的js文件归薛,并且按照說明文檔調(diào)用了相關(guān)接口之后谍憔,就完成了整個(gè)流程。
??那么我們需要關(guān)心的就是主籍,如何準(zhǔn)備js文件习贫,調(diào)用了相關(guān)接口之后,到底發(fā)生了什么事情千元。如何就具有了替換OC代碼的能力了呢苫昌?
??接下來我們將使用框架附帶的demo來進(jìn)行分析,demo的地址也就是上面提到的框架的鏈接幸海。</br>
??下載下來demo運(yùn)行可以發(fā)現(xiàn)祟身,app的首頁就是一個(gè)簡(jiǎn)單的視圖控制器(JPViewController),上面只有一個(gè)按鈕物独,如下圖所示:
??并且對(duì)應(yīng)著一個(gè)空方法(handleBtn)袜硫。
//JPViewController.m
- (void)handleBtn:(id)sender
{
}
我們的修復(fù)js代碼文件(demo.js)就是實(shí)現(xiàn)這個(gè)空方法,創(chuàng)建一個(gè)全新的tableViewController挡篓,并且給tableView配置代理和數(shù)據(jù)源婉陷,設(shè)置點(diǎn)擊事件,然后調(diào)用navigation 的push方法把這個(gè)控制器push出來,如下圖所示:
而這一切只需要在app啟動(dòng)時(shí)候調(diào)用開啟JSPatch引擎官研。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
...
}
第一部分 源碼解析###
備注:
修復(fù)已存在的方法秽澳,和創(chuàng)建新的類以及方法的過程都是一樣的,調(diào)用的過程中如果發(fā)現(xiàn)沒有類和方法戏羽,就會(huì)創(chuàng)建新的担神。我們這邊對(duì)這個(gè)流程分析的時(shí)候,只分析handleBtn方法的修復(fù)始花,不會(huì)介紹創(chuàng)建新的控制器和方法的過程妄讯,因?yàn)榱鞒潭际且粯拥摹?/p>
現(xiàn)在我們來開始分析其內(nèi)部的實(shí)現(xiàn)機(jī)制,首先看一下修復(fù)文件:demo.js文件的代碼,這里截取一部分代碼衙荐,就是上文說的handleBtn的具體實(shí)現(xiàn)捞挥。如下
defineClass('JPViewController', {
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
})
看到這里浮创,可能有很多疑問忧吟,handleBtn的方法實(shí)現(xiàn)完全是OC的對(duì)象和風(fēng)格,但是又是js的語法斩披,而且溜族,點(diǎn)擊button是如何調(diào)用到這個(gè)js函數(shù)的呢讹俊?defineClass又做了哪些事情?那么我們就從startEngine開啟引擎開始解析煌抒。</br>
修復(fù)過程第一步:JPEngine startEngine的調(diào)用####
我們?nèi)タ匆幌逻@個(gè)方法的具體實(shí)現(xiàn)仍劈,根據(jù)JSContext對(duì)象可以看到j(luò)s和native交互的核心引擎實(shí)際上是蘋果官方提供的JavaScriptCore框架。這個(gè)框架很簡(jiǎn)單寡壮,但是非常強(qiáng)大贩疙,在網(wǎng)上搜索一個(gè)博客,就能掌握它的用法况既,這里不再細(xì)說这溅,但是掌握這個(gè)框架的基本使用才能繼續(xù)分析JSPatch框架。這里先認(rèn)為你已經(jīng)掌握了該技能棒仍,接著往下說悲靴,我們知道JSContext代表了js的執(zhí)行環(huán)境,我們可以通過該對(duì)象莫其,為js的上下文注入全局的對(duì)象或者方法癞尚,下面舉個(gè)例子:在startEngine中有這樣一句調(diào)用
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
向js注入了全局的_OC_defineClass方法,其具體實(shí)現(xiàn)對(duì)應(yīng)著native的block乱陡,這就是JavaScriptCore的強(qiáng)大之處浇揩。這樣一來,我們?cè)趯慾s代碼的時(shí)候憨颠,就可以調(diào)用_OC_defineClass這個(gè)方法临燃,如下所示:我們?cè)趈sPatch.js 中聲明了一個(gè)全局方法defineClass,它的內(nèi)部實(shí)現(xiàn)就是調(diào)用了_OC_defineClass方法
global.defineClass = function(declaration, instMethods, clsMethods) {
var newInstMethods = {}, newClsMethods = {}
_formatDefineMethods(instMethods, newInstMethods)
_formatDefineMethods(clsMethods, newClsMethods)
var ret = _OC_defineClass(declaration, newInstMethods, newClsMethods)
return require(ret["cls"])
}
在看一看上面一段demo.js的代碼:
defineClass('JPViewController'....)烙心。這樣以來膜廊,我們便發(fā)現(xiàn)了如何把OC的實(shí)現(xiàn)注入到j(luò)s的秘密,defineClass方法就是我們使用js修復(fù)類的唯一方法入口淫茵,而它實(shí)際調(diào)用的是OC的代碼(運(yùn)用runtime技術(shù)爪瓜,后文在講)。接下來看看defineClass方法是如何使用runtime技術(shù)進(jìn)行類的修復(fù)匙瘪。</br>
修復(fù)過程第二步:global.defineClass方法解析####
讓我們來分析一下上一段代碼铆铆,defineClass方法接收的參數(shù)是1、類名字符串丹喻,2薄货、類的實(shí)例方法和類方法列表(都是js對(duì)象的形式,參見demo.js)碍论,這個(gè)對(duì)象的屬性是方法名谅猾,值是重寫方法的具體實(shí)現(xiàn)函數(shù)。defineClass方法會(huì)首先分別對(duì)這兩個(gè)對(duì)象調(diào)用_formatDefineMethods方法。
??來看一下_formatDefineMethods對(duì)這兩個(gè)對(duì)象做了什么,_formatDefineMethods方法接收的參數(shù)是一個(gè)方法列表js對(duì)象税娜,加一個(gè)新的js空對(duì)象
var _formatDefineMethods = function(methods, newMethods) {
for (var methodName in methods) {
(function(){
var originMethod = methods[methodName]
newMethods[methodName] = [originMethod.length, function() {
var args = _formatOCToJS(Array.prototype.slice.call(arguments))
var lastSelf = global.self
var ret;
try {
global.self = args[0]
args.splice(0,1)
ret = originMethod.apply(originMethod, args)
global.self = lastSelf
} catch(e) {
_OC_catch(e.message, e.stack)
}
return ret
}]
})()
}
}
可以發(fā)現(xiàn)坐搔,具體實(shí)現(xiàn)是遍歷方法列表對(duì)象的屬性(方法名),然后往js空對(duì)象中添加相同的屬性敬矩,它的值對(duì)應(yīng)的是一個(gè)數(shù)組概行,數(shù)組的第一個(gè)值是方法名對(duì)應(yīng)實(shí)現(xiàn)函數(shù)的參數(shù)個(gè)數(shù),第二個(gè)值是一個(gè)函數(shù)(也就是方法的具體實(shí)現(xiàn))弧岳。
??_formatDefineMethods作用凳忙,簡(jiǎn)單的說,它把defineClass中傳遞過來的js對(duì)象進(jìn)行了修改:
原來的形式是:
{
handleBtn:function(){...}
}
修改之后是:
{
handleBtn: [argCount,function (){...新的實(shí)現(xiàn)}]
}
那么為什么要傳遞參數(shù)個(gè)數(shù)禽炬?為什么要修改方法實(shí)現(xiàn)呢消略???
??傳遞參數(shù)個(gè)數(shù)的目的是,runtime在修復(fù)類的時(shí)候瞎抛,無法直接解析原始的js實(shí)現(xiàn)函數(shù)艺演,那么就不知道參數(shù)的個(gè)數(shù),特別是在創(chuàng)建新的方法的時(shí)候桐臊,需要根據(jù)參數(shù)個(gè)數(shù)生成方法簽名胎撤,所以只能在js端拿到j(luò)s函數(shù)的參數(shù)個(gè)數(shù),傳遞到OC端断凶。
??分析修改方法實(shí)現(xiàn)伤提,就要看一下是如何修改的。
??1认烁、首先會(huì)把參數(shù)轉(zhuǎn)換成js對(duì)象肿男;
(具體轉(zhuǎn)換的原因可見原理詳解blog 的<4.對(duì)象持有/轉(zhuǎn)換> ,我們這里不再重復(fù))
2却嗡、self的處理舶沛,在js修復(fù)代碼中我們可以像在OC中一樣使用self;
(參見原理詳解blog的 6.self關(guān)鍵字)
3窗价、args.splice(0,1)刪除前兩個(gè)參數(shù):
??在OC中進(jìn)行消息轉(zhuǎn)發(fā)的時(shí)候如庭,前兩個(gè)參數(shù)是self和selector,我們?cè)趯?shí)際調(diào)用js的具體實(shí)現(xiàn)的時(shí)候撼港,需要把這兩個(gè)參數(shù)刪除坪它。
最后,回到defineClass方法帝牡,在調(diào)用_formatDefineMethods完畢之后往毡,拿著要重寫的類名和經(jīng)過處理過的js對(duì)象,來調(diào)用_OC_defineClass靶溜,對(duì)應(yīng)著OC端的block方法开瞭。
修復(fù)過程第三步 _OC_defineClass方法實(shí)現(xiàn)####
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
在JPEngine中,定了一個(gè)名為defineClass的函數(shù),這個(gè)函數(shù)對(duì)類進(jìn)行真正的重寫操作惩阶。我們知道runtime重寫一個(gè)方法,需要幾個(gè)最基本的參數(shù):類名扣汪、selector断楷、方法實(shí)現(xiàn)(IMP)、方法簽名崭别,defineClass做的就是把這些信息提取出來冬筒,當(dāng)然流程會(huì)更復(fù)雜一點(diǎn):
??1、首先是對(duì)類名進(jìn)行解析茅主,把協(xié)議名舞痰、類名、父類名都解析出來诀姚。如果類不存在响牛,那么創(chuàng)建并注冊(cè)該類。</br>
(對(duì)協(xié)議的處理可參加 原理詳解blog 的 4.新增方法 i.方案 ii.Protocol)
2赫段、然后分別對(duì)實(shí)例方法和類方法進(jìn)行處理呀打,上面一節(jié)中,我們知道js函數(shù)_formatDefineMethods處理返回的是js對(duì)象糯笙,傳遞到OC這邊會(huì)被JavaScriptCore轉(zhuǎn)換為JSValue對(duì)象贬丛,可以對(duì)該對(duì)象直接調(diào)用toDictionary把js對(duì)象轉(zhuǎn)換成OC的字典。這樣我們就可以取到方法名给涕、參數(shù)個(gè)數(shù)豺憔、具體實(shí)現(xiàn)。
JSValue
可以說是JavaScript和Object-C之間互換的橋梁够庙,它提供了多種方法可以方便地把JavaScript數(shù)據(jù)類型轉(zhuǎn)換成Objective-C恭应,或者是轉(zhuǎn)換過去。其一一對(duì)應(yīng)方式可見下表:(引用自 http://www.cnblogs.com/ider/p/introduction-to-ios7-javascriptcore-framework.html)
3耘眨、遍歷字典的key暮屡,即方法名,根據(jù)方法名取出的值還是JSValue對(duì)象毅桃,不過它代表的是數(shù)組褒纲,第一個(gè)值是參數(shù)的個(gè)數(shù),第二個(gè)值是函數(shù)的實(shí)現(xiàn)钥飞。</br>
4莺掠、方法名的處理:這塊涉及到方法名的格式要求和處理,例如读宙,在js中的tableView_numberOfRowsInSection彻秆,下劃線需要被替換成':'。
這一塊可以參見原理詳解blog的 細(xì)節(jié) 3.‘_’的處理。
對(duì)于格式的說明可見 <defineClass使用文檔>
https://github.com/bang590/JSPatch/wiki/defineClass%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
<defineProtocol使用文檔>
https://github.com/bang590/JSPatch/wiki/defineProtocol%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3
最后拿著處理好的方法名和具體實(shí)現(xiàn)等調(diào)用overrideMethod函數(shù)唇兑。
修復(fù)過程第四步 overrideMethod函數(shù)####
static void overrideMethod(Class cls, NSString *selectorName,
JSValue *function, BOOL isClassMethod, const char *typeDescription)
1酒朵、把selector對(duì)應(yīng)的具體實(shí)現(xiàn)使用class_replaceMethod替換成_objc_msgForward,我們知道這個(gè)對(duì)應(yīng)著消息轉(zhuǎn)發(fā)機(jī)制扎附。例如蔫耽,本文的例子中點(diǎn)擊button是不會(huì)執(zhí)行空的handleBtn方法的,而是直接走消息轉(zhuǎn)發(fā)的路徑留夜。
??2匙铡、把forwardInvocation的具體實(shí)現(xiàn)替換成JPForwardInvocation(下文分析)。
??3碍粥、向class添加名為ORIGforwardInvocation的方法逃顶,實(shí)現(xiàn)是原始的forwardInvocation的IMP雁社。
這一步的目的是如果原來的類已經(jīng)實(shí)現(xiàn)了自定義的消息轉(zhuǎn)發(fā)棍郎,重寫了forwardInvocation方法阅签。那么我們?cè)谏厦嬉徊街幸呀?jīng)修改了forwardInvocation對(duì)應(yīng)的方法實(shí)現(xiàn)怎么辦?保存舊的forwardInvocation實(shí)現(xiàn)的意義就是枕面,會(huì)在新的實(shí)現(xiàn)中判斷蜂厅,如果當(dāng)前的selector不是被js修改重寫過的,就執(zhí)行舊的實(shí)現(xiàn)膊畴。
具體可見 原理詳解blog 3.ForwardInvocation實(shí)現(xiàn)
4掘猿、向class添加名為ORIG+selector,對(duì)應(yīng)原始selector的IMP唇跨。JS 可以通過這個(gè)方法調(diào)用到原來的實(shí)現(xiàn)稠通。
??5、向class添加名為_JP + selector买猖,對(duì)應(yīng)js重寫的函數(shù)實(shí)現(xiàn)改橘。
為什么要把selector加上_JP呢?
實(shí)際上_JP是一個(gè)標(biāo)識(shí)的字符,我們?cè)趈s中重寫的方法實(shí)現(xiàn)玉控,傳遞到OC這邊飞主,還是JSValue的對(duì)象。我們存儲(chǔ)了一份全局的字典對(duì)象高诺,字典的key是classname碌识,值還是一個(gè)字典,key是_JP開頭的selector name虱而,value就是JSValue類型的js函數(shù)筏餐。
當(dāng)進(jìn)行消息轉(zhuǎn)發(fā)時(shí),我們根據(jù)經(jīng)過_JP標(biāo)示的selector和clsName牡拇,在這個(gè)全局字典中就可以找到對(duì)應(yīng)js函數(shù)的JSValue對(duì)象魁瞪。
而如果想執(zhí)行這個(gè)js函數(shù)的話穆律,只需要對(duì)JSValue對(duì)象調(diào)用callWithArgument方法就可以了。
到這里导俘,我們終于可以開始分析handleBtn的點(diǎn)擊處理了峦耘。
調(diào)用過程第一步 JPForwardInvocation函數(shù)####
這一步是,OC調(diào)用JS重寫的方法旅薄。
OC:handleBtn空實(shí)現(xiàn)--->js:handleBtn function
我們知道辅髓,經(jīng)過上一步的處理,selector對(duì)應(yīng)的實(shí)現(xiàn)是objc_msgForward赋秀,即走到了消息轉(zhuǎn)發(fā)的環(huán)節(jié)利朵。當(dāng)我們點(diǎn)擊button律想,調(diào)用handleBtn的時(shí)候猎莲,函數(shù)調(diào)用的參數(shù)會(huì)被封裝到NSInvocation對(duì)象,走到forwardInvocation方法技即。而我們上一步中把forwardInvocation方法的實(shí)現(xiàn)替換成了JPForwardInvocation著洼,那么我們來看一下這個(gè)函數(shù)是如何執(zhí)行具體的函數(shù)實(shí)現(xiàn)的。
1而叼、把selector前面加上 _JP,構(gòu)成的新的selector身笤,正好是上一步中我們添加的新方法,如果class無法識(shí)別葵陵,說明這個(gè)不是我們重寫的方法液荸,那么走原來的消息轉(zhuǎn)發(fā),上一節(jié)中介紹過了脱篙。
2娇钱、把self和其他的參數(shù)都轉(zhuǎn)換稱js對(duì)象,我們知道js端重寫的函數(shù)绊困,傳遞過來是JSValue類型文搂,這里對(duì)應(yīng)著js函數(shù),我們可以對(duì)其調(diào)用callWithArgument方法秤朗,所以參數(shù)也要是js對(duì)象煤蹭,把js對(duì)象參數(shù)傳遞過去,執(zhí)行函數(shù)取视。上文提到過硝皂,類型的轉(zhuǎn)換在原理詳解blog中有介紹。
??實(shí)際上這個(gè)方法的細(xì)節(jié)是非常多的作谭,根據(jù)方法簽名吧彪,取出每個(gè)參數(shù)的類型,進(jìn)行參數(shù)的封裝丢早、對(duì)于結(jié)構(gòu)體的處理等等姨裸。
講到這里秧倾,就完成了函數(shù)調(diào)用環(huán)節(jié),那么下面再來分析一下傀缩,js代碼如何執(zhí)行的那先,實(shí)際上作者的原理篇開篇就介紹了實(shí)現(xiàn)思路和方法,非常清晰赡艰,還是拿handleBtn舉例:
建議查看原理詳解blog 第一部分 方法調(diào)用售淡,作者講解了為何會(huì)采取下面這種解決方案的原因和處理過程
handleBtn: function(sender) {
var tableViewCtrl = JPTableViewController.alloc().init()
self.navigationController().pushViewController_animated(tableViewCtrl, YES)
}
在最開始startEngine的時(shí)候,會(huì)把這些js代碼統(tǒng)一進(jìn)行正則表達(dá)式的匹配替換慷垮,也就是把所有的函數(shù)都替換成對(duì)名為__C的函數(shù)的調(diào)用揖闸,也就是JPTableViewController.__C('alloc')().__C('init')(),而作者給 JS 對(duì)象基類 Object 的 prototype 加上 __C 成員,具體的實(shí)現(xiàn)是調(diào)用了_methodFunc函數(shù):
Object.prototype.__c = function(methodName) {
if (!this.__obj && !this.__clsName) return this[methodName].bind(this);
var self = this
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName, args, self.__isSuper)
}
}
**JPTableViewController.**C('alloc')().__C('init')()
就成了:(require('JPTableViewController')會(huì)生成一個(gè)全局的js對(duì)象料身,這里用clsObj代替)
1汤纸、_methodFunc(clsObj,'JPTableViewController','alloc',false)
返回一個(gè)JPTableViewController的對(duì)象,這里用jpObj代替,接著調(diào)用init初始化方法
2芹血、_methodFunc(jpObj,'JPTableViewController','init',false)
這樣就完成了JPTableViewController的alloc和init調(diào)用
參見源碼我們可以知道_methodFunc函數(shù)會(huì)調(diào)用_OC_call函數(shù)贮泞,而在startEngine的一開始,我們就為JSContext注入了_OC_call函數(shù)幔烛,具體實(shí)現(xiàn)是一個(gè)調(diào)用了OC的callSelector的block:
context[@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) {
return callSelector(nil, selectorName, arguments, obj, isSuper);
};
調(diào)用過程第二步 callSelector函數(shù)####
這一步是js重寫方法中啃擦,調(diào)用OC的對(duì)象方法等
JS:JPTableViewController.alloc().init()實(shí)際是通過callSelector調(diào)用OC的方法
1、把js對(duì)象和js參數(shù)轉(zhuǎn)換為OC對(duì)象饿悬;
2令蛉、判斷是否調(diào)用的是父類的方法,如果是狡恬,就走父類的方法實(shí)現(xiàn)珠叔;
3、把參數(shù)等信息封裝成NSInvocation對(duì)象傲宜,并執(zhí)行运杭,然后返回結(jié)果;
具體的實(shí)現(xiàn)細(xì)節(jié)包括對(duì)methodSignature的字符處理函卒,根據(jù)這些字符對(duì)js對(duì)象進(jìn)行處理和轉(zhuǎn)換辆憔,還有對(duì)結(jié)構(gòu)體對(duì)支持等。
??本文對(duì)這一塊介紹的比較簡(jiǎn)單报嵌,實(shí)際上虱咧,通過作者的原理詳解,可以發(fā)現(xiàn)這里面包含很多的設(shè)計(jì)思路锚国,可以說是一個(gè)基于這些設(shè)計(jì)思路一個(gè)集大成的方法實(shí)現(xiàn)腕巡。涉及到:
消息轉(zhuǎn)發(fā)的處理
formatOCToJS等的用意
overrideMethod函數(shù)的應(yīng)用
內(nèi)存的處理
nil對(duì)象的處理
JPBoxing
結(jié)構(gòu)體處理等等,
如果基于前面的分析血筑、作者的原理詳解和對(duì)源碼的實(shí)際分析绘沉,理解這些應(yīng)該不是問題煎楣。
??第一部分對(duì)于js修復(fù)的過程以及調(diào)用的詳細(xì)環(huán)節(jié)進(jìn)行了分析,接下來講一下框架的延伸部分车伞。
第二部分 JPExtension###
我們知道使用JSPatch修復(fù)代碼择懂,在寫js的修復(fù)代碼時(shí),終究是對(duì)callSelector函數(shù)的調(diào)用另玖,那么對(duì)于非對(duì)象的函數(shù)調(diào)用怎么辦呢困曙?實(shí)際上js對(duì)代碼的修復(fù)能力取決于我們給它擴(kuò)展的能力。例如Core Graphic的一些C的函數(shù)UIGraphicsGetCurrentContext等是無法支持的谦去,那么JSPatch給我們提供了支持?jǐn)U展的解決方案慷丽。
例如JPUIGraphics
context[@"UIGraphicsGetCurrentContext"] = ^id() {
CGContextRef c = UIGraphicsGetCurrentContext();
return [self formatPointerOCToJS:c];
};
我們看到這個(gè)調(diào)用給js的執(zhí)行環(huán)境注入了全局的方法,可以讓我們?cè)趈s中使用UIGraphicsGetCurrentContext鳄哭。
還有JPUIGeometry中添加UIEdgeInsets結(jié)構(gòu)體的支持等要糊。
框架現(xiàn)在也有了相當(dāng)一部分的這種擴(kuò)展,主要是UIKit和CoreGraphics框架的窃诉。我們也可以根據(jù)自己的需要杨耙,添加擴(kuò)展赤套,向github上的項(xiàng)目提交pull request飘痛。
第三部分 使用###
作者提供了在線OC代碼翻譯成js的工具
??(http://bang590.github.io/JSPatchConvertor/)
??作者還有一篇文章提供了調(diào)試這些js代碼的方法
??https://github.com/bang590/JSPatch/wiki/JS-%E6%96%AD%E7%82%B9%E8%B0%83%E8%AF%95,
??我們可以調(diào)試這些修復(fù)的js代碼容握,非常的實(shí)用宣脉。
??翻譯工具進(jìn)行的一些轉(zhuǎn)換還是需要手動(dòng)調(diào)整,例如在OC中如果屬性和方法有下劃線剔氏,例如promote_type塑猖,那么js中要修改為promote__type,否則就會(huì)被轉(zhuǎn)換成promote:type:的格式谈跛,還有不能寫CGRectMake羊苟,要修改成{x:,y:,width:,height:}的形式,還有項(xiàng)目中原有的一些宏定義也不能使用感憾,除非你是用擴(kuò)展蜡励,把這些都注入到JSContext中去。實(shí)際上阻桅,這樣的規(guī)則并不多凉倚,寫一個(gè)修復(fù)方法差不多就能掌握了嫂沉,有了調(diào)試工具稽寒,迅速的解決問題還是比較簡(jiǎn)單的。