美文网首页
JSPatch中JS与OC间的通信

JSPatch中JS与OC间的通信

作者: 秦砖 | 来源:发表于2017-03-05 15:49 被阅读103次

    JSPatch是目前iOS平台上比较主流的热更新方案之一。它主要使用了OC的runtime技术并通过JS语言来调用OC代码来实现代码的更新与修复。本篇主要就该方案中两种语言间的通信方案做下探索。

    通信机制

    两种语言间通信的机制是苹果官方提供的JavaScriptCore.framework框架,它可以让我们脱离Webview直接运行JS代码。其具体使用可以参考这篇文章。而JSPatch正是通过这个框架在两种语言间架起了一座相互来往的桥梁。整体架构可以用下图来表示:

    JSPatch架构

    代码流程

    [JPEngine startEngine];
    NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
    NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
    [JPEngine evaluateScript:script];
    

    这是一段标准的JSPatch调用代码,热更新的代码都位于demo.js文件中。首先调用startEngine将JSPatch的整个运行环境都调用起来,然后就可以使用evaluateScript将JS文件中的修复代码运行起来并通过runtime体系注入到OC代码实现中。下面让我们具体的跟踪下evaluateScript运行轨迹。

    + (JSValue *)_evaluateScript:(NSString *)script withSourceURL:(NSURL *)resourceURL
    {
      ...
      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:)]) {
          return [_context evaluateScript:formatedScript withSourceURL:resourceURL];
        } else {
          return [_context evaluateScript:formatedScript];
        }
      }
      ...
    }
    

    这里使用了正则表达式,具体作用是将demo.js中的js函数调用都替换为__c函数,这样做的具体原因是将简化JS侧注入的OC函数数量,大家也可移步到JSPatch作者的具体说明文章,里面有详尽的解说。下面来看__c的具体实现。

    var _customMethods = {
        __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)
          }
        }
    }
    
    for (var method in _customMethods) {
        if (_customMethods.hasOwnProperty(method)) {
          Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false, enumerable: false})
        }
      }
    

    在这个接口中解析出要调用的OC函数的具体信息,如类名、方法名、父类名、调用参数及调用的对象信息等。然后再_methodFunc中根据JSPatch函数全名的规则将JS函数名转换成OC函数名。再调用_OC_callI或者_OC_callC来实际执行函数内部的具体代码,这属于runtime中的内容了,将于下一章节介绍。

    最后,函数执行的结果经包装后返回到JS侧来,转换成JS可用的数据对象,执行转换的主体代码位于formatOCToJS接口中。

    相关文章

      网友评论

          本文标题:JSPatch中JS与OC间的通信

          本文链接:https://www.haomeiwen.com/subject/adljgttx.html