JSPatch原理解析(一)

本篇開始以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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末倚搬,一起剝皮案震驚了整個(gè)濱河市冶共,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌每界,老刑警劉巖捅僵,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異眨层,居然都是意外死亡庙楚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門趴樱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馒闷,“玉大人,你說我怎么就攤上這事叁征∧烧耍” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵捺疼,是天一觀的道長(zhǎng)塞祈。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么议薪? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任尤蛮,我火速辦了婚禮,結(jié)果婚禮上斯议,老公的妹妹穿的比我還像新娘产捞。我一直安慰自己,他們只是感情好哼御,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布坯临。 她就那樣靜靜地躺著,像睡著了一般恋昼。 火紅的嫁衣襯著肌膚如雪看靠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天液肌,我揣著相機(jī)與錄音挟炬,去河邊找鬼。 笑死嗦哆,一個(gè)胖子當(dāng)著我的面吹牛谤祖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播老速,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼粥喜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了橘券?” 一聲冷哼從身側(cè)響起额湘,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎旁舰,沒想到半個(gè)月后缩挑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鬓梅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了谨湘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绽快。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖紧阔,靈堂內(nèi)的尸體忽然破棺而出坊罢,到底是詐尸還是另有隱情,我是刑警寧澤擅耽,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布活孩,位于F島的核電站,受9級(jí)特大地震影響乖仇,放射性物質(zhì)發(fā)生泄漏憾儒。R本人自食惡果不足惜询兴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望起趾。 院中可真熱鬧诗舰,春花似錦、人聲如沸训裆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽边琉。三九已至属百,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間变姨,已是汗流浹背族扰。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留钳恕,地道東北人别伏。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像忧额,于是被迫代替她去往敵國(guó)和親厘肮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載:原文鏈接 http://blog.cnbang.net/tech/2808/ JSPatch以小巧的體積做到...
    made_China閱讀 390評(píng)論 0 0
  • http://blog.cnbang.net/tech/2808/ JSPatch實(shí)現(xiàn)原理詳解 注:本文較早撰寫睦番,...
    hypercode閱讀 1,185評(píng)論 0 1
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,090評(píng)論 1 32
  • 2017年10月30日晚上20:52 姑娘有點(diǎn)感冒类茂,自己把藥找出來,我一樣一袋喂她吃下托嚣。吃完她說:“媽媽巩检,沒飽”。...
    薄荷氣球閱讀 314評(píng)論 0 0
  • 睡大炕的在國(guó)內(nèi)大概也就只有東北人和膠東人吧示启。我們膠東的大炕一般由黃土壘成兢哭,位于正屋中間,橫跨整個(gè)屋子夫嗓,將兩面墻連在...
    曉妮臆空間閱讀 1,363評(píng)論 4 1