JSPatch原理解析(一)

7553 ワード

本編では、JSPatchからのデモコードを例にとって、JSPatchの実現原理を説明します.まずiOSのデモでappdelegateからJSPatchに関するコードを見始めました.
    [JPEngine startEngine];
    NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
    [JPEngine evaluateScript:script];
まずJPエンジンを起動した後、地元のデモのjsファイルをロードしてJPエンジンにこのjsスクリプトファイルを実行させました.実はここでは、JPエンジンを起動するコードは余分です.Bang大神はJSPatchを更新した後、このdemoを更新していないかもしれません.startEgine方法のコメントを見ると、この方法は破棄されていることが分かります.ローカルJSスクリプトを実行する必要がある時は自動的にこの方法でエンジンを起動します.evaluate Script:scriptメソッドの呼び出しプロセスを遡る時もこのようなコードが発見されます.エンジンを起動する方法は自動的に実行されていますので、evaluate Scriptという方法を重点的に研究すればいいです.
一.OCにおけるJSファイルの前処理
+ (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
{
    if (!script || ![JSContext class]) {
        _exceptionBlock(@"script is nil");
        return nil;
    }
    [self startEngine];
...}
その後、OCのコードによって、JPView Controllerの中に入ったボタンとこのボタンが実現されていないクリックイベントの受信方法handleBtnが見えます.プロジェクトを走ったら、ボタンを押したらtableviewの中に飛び込みます.handleBtnが実現していないのに、どうやってジャンプしますか?答えはevaluate Scriptここで、evaluate Scriptこの方法は主にいくつかのことをしました.
+ (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.JSを作成するために呼び出すOC方法
context[@"_OC_defineClass"] = ^(NSString *classDeclaration, JSValue *instanceMethods, JSValue *classMethods) {
        return defineClass(classDeclaration, instanceMethods, classMethods);
    };
...
1.3.iphoneの場合はメモリ警告も確認してください.
#if TARGET_OS_IPHONE
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleMemoryWarning) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
1.4.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];
    }
このJSPatch.jsとdemo.jsの本質的な関係については、一つのページにJSPatch.jsを導入した後、demo.jsを導入しました.実行するJSファイルを修正します.evaluate Scriptの後半のコードはこうです.
 if (!_regex) {
        _regex = [NSRegularExpression regularExpressionWithPattern:_regexStr options:0 error:nil];
    }
    NSString *formatedScript = [NSString stringWithFormat:@";(function(){try{
%@
}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]); }
このコードは正規表現を使って、JSに書いてあるすべてのものを呼び出す方法を変えました.c(メソッド名)は、すべてのメソッドを呼び出すことと同じ方法になります.Cはその後、この方法で転送し、jsコード全体にtrycatchを包んで、エラー情報を受け取り、xcodeで印刷します.
二.JSPatch.jsでの事前定義
JSPatchは導入時に一つのことしかしませんでした.他のは全部定義方法ですが、実際に呼び出されていません.これらの方法はすべてJSPatchの開発者に参加して呼び出させました.JSPatchを知る過程での一つの難点は、どのようにしてocの中のクラスとjsのシミュレーションのoc類を維持するかを理解することです.
開発者に呼び出されたコードは後で会ってから話します.まずJSPatchを紹介して何をしましたか?
  var _ocCls = {};
  var _jsCls = {};
まず文書の冒頭で二つの配列を宣言しました.一つはocのクラスです.もう一つはjsのクラスです.実際に後期にこの配列は三つの層がそれぞれ_であることが分かります.jsClas[クラス名][クラスの方法ですか?それとも実例の方法ですか?[メソッド名]は、この配列で論理的なクラスを実現しました.
  var _customMethods = {
    __c: function(methodName) {... },
    super: function() {...},
    performSelectorInOC: function() {...},
    performSelector: function() {...}
  }
はっきりと見えますが、jsオブジェクトを定義して、その後のforサイクルはこのオブジェクトの中の方法をjsのobjectプロトタイプに一つずつ結びつけています.catagoryを使ってNSObjectに追加する方法と似ています.このいくつかの方法の中の_u_u_0026 quot;に重点を置いています.Cの上には、jsにはiOSのメッセージ転送機構がありませんが、jsのobjectの原型に紐付けされているこの__u u uCはこのメカニズムを実現しました.どのようにしてすべてのjs方法を__u uに転送しますか?c上で処理したら話します.
    __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方法:まず、このjsオブジェクトから呼び出された方法名を取って、方法の転送aを行うために一連の判断をしました.このjsオブジェクトがこの方法があるかどうかを調べてください.このオブジェクトに方法名がある方法があれば、直ちに呼び出される方法にこの方法を結びつけます.
b.もしこのオブジェクトが存在しないと判断したら、被クラスを呼び出す方法であれば必ずdefineClassを使います.defineClassはこのクラスを初期化します.他のクラスのオブジェクトを呼び出してもrequire()を使ってこのクラスを導入します.このオブジェクトを初期化しないと、このオブジェクトが存在しないということです.直接エラーがないということです.この方法の定義はありません.
c.対象が存在するが、本類にはこの方法がない場合は、__u uを判断する.isSuperが親の方法を探していますか?例えば、呼び出し時self.methodはいらないです.super.methodなら、親からこの方法を探してください.
d.必要であれば_を呼び出します.OC_superClas Name方法は、このクラスの親のクラス名がある場合は、この方法はまだアナログのocクラスに登録されています.occlsの発表からこの呼び出しに結び付けられます.
e.最後に見つけられなかったら、この方法はjsに書かれていないと説明して、_を呼び出します.methodFunはocの中でこの方法を探しに行きます.methodFun方法は簡単で、この方法はjsに書いてあるメソッド名をocの本当の方法名に処理してパラメータを過去に送り、最後にocメソッドを呼び出した後の戻り値をjsのオブジェクトにフォーマットして戻して、メッセージ転送を完了します.
superメソッド:___C方法で使用されるisSuperとocの概念は同じで、superとselfは指した本類出力[self class]と[super class]の結果は同じです.superは父類の探し方以下の結合を要求するsuper方法を示しています.これは一つのアナログポインタとslafの唯一の違いに戻りました.isper 1を示しました.
    super: function() {
      var slf = this
      if (slf.__obj) {
        slf.__obj.__realClsName = slf.__realClsName;
      }
      return {__obj: slf.__obj, __clsName: slf.__clsName, __isSuper: 1}
    },
perform Selector方法:_u u uとCメソッドの最後の検索oc動作時の方法は同じです.perform SelectorInOC方法を説明します.ここで説明します.https://github.com/bang590/JSPatch/wiki/performSelectorInOC-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3