本篇開始以JSPatch給的demo代碼為例按流程講解JSPatch的實(shí)現(xiàn)原理
首先在iOS的demo中在appdelegate開始看和JSPatch有關(guān)的是這幾句代碼
[JPEngine startEngine];
NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
[JPEngine evaluateScript:script];
首先啟動(dòng)了JP引擎之后加載了本地的一個(gè)demo的js文件之后讓JP引擎執(zhí)行了這個(gè)js腳本文件咧欣,其實(shí)在這里那個(gè)啟動(dòng)JP引擎的代碼其實(shí)是多余的夭织,可能bang大神在更新JSPatch以后沒有對(duì)這個(gè)demo進(jìn)行更新艇炎,去查看startEngine方法的注釋就可以發(fā)現(xiàn)這個(gè)方法被廢棄了,在需要執(zhí)行本地JS腳本的時(shí)候回自動(dòng)調(diào)用這個(gè)方法啟動(dòng)引擎,在追溯evaluateScript:script方法的調(diào)用流程的時(shí)候也會(huì)發(fā)現(xiàn)有這么一串代碼尤勋,已經(jīng)自動(dòng)執(zhí)行了啟動(dòng)引擎的方法,所以我們只要著重研究evaluateScript這個(gè)方法就好了
一.OC中對(duì)JS文件的預(yù)處理
+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
{
if (!script || ![JSContext class]) {
_exceptionBlock(@"script is nil");
return nil;
}
[self startEngine];
...}
之后就根據(jù)OC的代碼可以看出來進(jìn)入了JPViewController里面有一個(gè)按鈕還有這個(gè)按鈕未實(shí)現(xiàn)的接收點(diǎn)擊事件的方法handleBtn,把項(xiàng)目跑起來以后會(huì)發(fā)現(xiàn)點(diǎn)擊按鈕會(huì)跳到一個(gè)tableview里面吮蛹,明明handleBtn沒有實(shí)現(xiàn),那它是怎么跳轉(zhuǎn)的呢拌屏?答案就在evaluateScript這里潮针,evaluateScript這個(gè)方法主要做了幾件事:
1.首先調(diào)用了startEngine方法
1.1初始化JSContext及一些列變量
+ (void)startEngine
{
if (![JSContext class] || _context) {
return;
}
JSContext *context = [[JSContext alloc] init];
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
_nilObj = [[NSObject alloc] init];
_JSMethodSignatureLock = [[NSLock alloc] init];
_JSMethodForwardCallLock = [[NSRecursiveLock alloc] init];
_registeredStruct = [[NSMutableDictionary alloc] init];
_currInvokeSuperClsName = [[NSMutableDictionary alloc] init];
1.2.創(chuàng)建JS將要調(diào)用的OC方法
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
return defineClass(classDeclaration, instanceMethods, classMethods);
};
...
1.3.如果是iphone的話還要檢測(cè)內(nèi)存警告
#if TARGET_OS_IPHONE
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
1.4.執(zhí)行JSPatch.js代碼
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"JSPatch" ofType:@"js"];
if (!path) _exceptionBlock(@"can't find JSPatch.js");
NSString *jsCore = [[NSString alloc] initWithData:[[NSFileManager defaultManager] contentsAtPath:path] encoding:NSUTF8StringEncoding];
if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
[_context evaluateScript:jsCore withSourceURL:[NSURL URLWithString:@"JSPatch.js"]];
} else {
[_context evaluateScript:jsCore];
}
那么對(duì)于這JSPatch.js和demo.js的本質(zhì)關(guān)系就相當(dāng)于在一個(gè)網(wǎng)頁中先引入了JSPatch.js之后引入了demo.js
2.修改要執(zhí)行的JS文件
_evaluateScript后半段代碼是這樣的
if (!_regex) {
_regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
}
NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{\n%@\n}catch(e){_OC_catch(e.message, e.stack)}})();", [_regex stringByReplacingMatchesInString:script options:0 range:NSMakeRange(0, script.length) withTemplate:_replaceStr]];
@try {
if ([_context respondsToSelector:@selector(evaluateScript:withSourceURL:)]) {
NSLog(@"%@",[_context evaluateScript:formatedScript withSourceURL:resourceURL]);
return [_context evaluateScript:formatedScript withSourceURL:resourceURL];
} else {
return [_context evaluateScript:formatedScript];
}
}
@catch (NSException *exception) {
_exceptionBlock([NSString stringWithFormat:@"%@", exception]);
}
這段代碼用了一個(gè)正則表達(dá)式去把JS中寫的所有.方法調(diào)用都改成了.__c(方法名),這樣就與把所有的方法調(diào)用都變成了同一個(gè)方法__C然后再通過這個(gè)方法進(jìn)行轉(zhuǎn)發(fā)倚喂,然后給整個(gè)的js代碼包裹了一個(gè)trycatch用于接受錯(cuò)誤信息并在xcode打印出來
二.JSPatch.js中做的預(yù)定義
JSPatch在剛引入的時(shí)候只做了一個(gè)一件事每篷,其它的都是定義方法但是沒有實(shí)際調(diào)用,那些方法都是留給介入JSPatch開發(fā)者來調(diào)用的端圈,而在了解JSPatch過程中的一個(gè)難點(diǎn)就是理解它是如何維護(hù)oc中的類和js中模擬的oc類兩套操作的
那些留給開發(fā)者調(diào)用的代碼我們?nèi)蘸笥龅搅嗽僬f焦读,先看看JSPatch一引入做了什么事
var _ocCls = {};
var _jsCls = {};
首先在文件開頭聲明了兩個(gè)數(shù)組,一個(gè)存儲(chǔ)的是oc的class另一個(gè)是存儲(chǔ)的js的class舱权,實(shí)際在后期可以得知這個(gè)數(shù)組有三層分別是_jsCls[類名][類方法還是實(shí)例方法][方法名]矗晃,就是用這個(gè)數(shù)組實(shí)現(xiàn)了邏輯上的類
var _customMethods = {
__c: function(methodName) {... },
super: function() {...},
performSelectorInOC: function() {...},
performSelector: function() {...}
}
可以很清晰的看出來,定義了一個(gè)js對(duì)象宴倍,然后后面的for循環(huán)將這個(gè)對(duì)象里面的方法挨個(gè)綁定在js的object原型上张症,有點(diǎn)類似使用catagory給NSObject添加方法仓技,重點(diǎn)就在這幾個(gè)方法中的__C上面,js里面沒有iOS的消息轉(zhuǎn)發(fā)機(jī)制俗他,但是給js的object原型上綁定的這個(gè)__C實(shí)現(xiàn)了這個(gè)機(jī)制脖捻,至于如何把所有的js方法都轉(zhuǎn)發(fā)到__c上處理之后會(huì)講到
__c: function(methodName) {
var slf = this
if (slf instanceof Boolean) {
return function() {
return false
}
}
if (slf[methodName]) {
return slf[methodName].bind(slf);
}
if (!slf.__obj && !slf.__clsName) {
throw new Error(slf + '.' + methodName + ' is undefined')
}
if (slf.__isSuper && slf.__clsName) {
slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName);
}
var clsName = slf.__clsName
if (clsName && _ocCls[clsName]) {
var methodType = slf.__obj ? 'instMethods': 'clsMethods'
if (_ocCls[clsName][methodType][methodName]) {
slf.__isSuper = 0;
return _ocCls[clsName][methodType][methodName].bind(slf)
}
}
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
}
},
__c方法:首先取到這個(gè)js對(duì)象調(diào)用的方法名,然后做了一系列判斷以進(jìn)行方法轉(zhuǎn)發(fā)
a.去查這個(gè)js對(duì)象有沒有這個(gè)方法,該對(duì)象有該方法名的方法兆衅,則直接將該方法綁定到 即將調(diào)用的方法上
b.如果沒有就判斷一下這個(gè)對(duì)象存不存在地沮,因?yàn)槿绻钦{(diào)用被類的方法那就一定會(huì)使用defineClass,defineClass會(huì)初始化該Class涯保,如果是調(diào)用其它類的對(duì)象也會(huì)用到require()來引入該類诉濒,所以如果沒有初始化該對(duì)象,說明這個(gè)對(duì)象不存在直接報(bào)錯(cuò)沒有這個(gè)方法定義
c.如果對(duì)象存在但本類又沒有這個(gè)方法的話判斷一下__isSuper是否要去找父類方法比如如果調(diào)用時(shí)self.method就不需要夕春,如果是super.method就需要從父類去找這個(gè)方法(消息轉(zhuǎn)發(fā))
d.如果需要就調(diào)用_OC_superClsName方法讓oc在運(yùn)行時(shí)中找一下該類的父類的類名如果有這個(gè)類名而且這個(gè)方法還在模擬oc類中的方法表ocCls中注冊(cè)了未荒,就從occls的方發(fā)表中找到這個(gè)方法去綁定到該調(diào)用上
e.最后如果都沒找到就說明這個(gè)方法不是在js里面寫的,然后調(diào)用_methodFunc去oc里面找這個(gè)方法及志,_methodFunc方法很簡(jiǎn)單片排,這個(gè)方法將js中寫的方法名處理成oc中的真實(shí)方法名然后將參數(shù)傳過去,最后將調(diào)用oc方法后的返回值格式化成js的對(duì)象傳回來,這樣就完成了消息轉(zhuǎn)發(fā)速侈。
super方法:在__C方法中使用的isSuper和oc中的概念一樣率寡,super和self都是指向的本類輸出[self class]和[super class]結(jié)果相同,只是super有一個(gè)標(biāo)志表明了要求父類尋找方法以下綁定的super方法也證實(shí)了這點(diǎn)返回了一個(gè)模擬指針與slf的唯一不同點(diǎn)是把isSuper標(biāo)志改成了1
super: function() {
var slf = this
if (slf.__obj) {
slf.__obj.__realClsName = slf.__realClsName;
}
return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
},
performSelector方法:與__C方法結(jié)尾的查找oc運(yùn)行時(shí)方法同理就不贅述了
performSelectorInOC方法:描述見這里
https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3