JavaScriptCore详解

本博客主要分以下几个方面来介绍iOS中的JavaScriptCore

  • JavaScriptCore简介
  • JavaScriptCore中主要的类
  • JSContext
  • JSValue
  • JSExport
  • JSManagedValue
  • JSVirtualMachine
  • Native Code 和 JS 之间的互相调用
  • Native Code 与UIWebView中的JS交互
  • Native Code 与JS文件直接交互

JavaScriptCore简介

JavaScriptCore背景

  • iOS中的JavaScriptCore.framework其实只是基于webkit(Safari的浏览器引擎)中以C/C++实现的JavaScriptCore的一个包装,在iOS7中,Apple将其作为一个标准库供开发者使用

JavaScriptCore主要功能

  1. JavaScriptCore主要是对JS进行解析和提供执行环境。代码是开源的,JavaScriptCore源码
  2. JavaScriptCore可以让我们脱离webview直接运行我们的js
  3. JavaScriptCore提供一种动态局部升级和更新的逻辑,大大提高应用的可扩展性
  4. 对手机内嵌web模式的新尝试点,即通过Native+JS file的方式取代webview的方式

JavaScriptCore中主要的类

  1. JSContext --- 在OC中创建JavaScript运行的上下文环境

    - (instancetype)init; // 创建JSContext对象,获得JavaScript运行的上下文环境
    
    // 在特定的对象空间上创建JSContext对象,获得JavaScript运行的上下文环境
    - (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;
    
    // 运行一段js代码,输出结果为JSValue类型
    - (JSValue *)evaluateScript:(NSString *)script;
    
    // iOS 8.0以后可以调用此方法
    - (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL NS_AVAILABLE(10_10, 8_0);
    
    // 获取当前正在运行的JavaScript上下文环境
    + (JSContext *)currentContext;
    
    // 返回结果当前执行的js函数 function () { [native code] } ,iOS 8.0以后可以调用此方法
     + (JSValue *)currentCallee NS_AVAILABLE(10_10, 8_0);
    
     // 返回结果当前方法的调用者[object Window]
     + (JSValue *)currentThis;
    
     // 返回结果为当前被调用方法的参数
     + (NSArray *)currentArguments;
    
     // js的全局变量 [object Window]
     @property (readonly, strong) JSValue *globalObject;
    
  2. JSValue --- JavaScript中的变量和方法,可以转成OC数据类型,每个JSValue都和JSContext相关联并且强引用context

         @textblock
        Objective-C type  |   JavaScript type
      --------------------+---------------------
              nil         |     undefined
             NSNull       |        null
            NSString      |       string
            NSNumber      |   number, boolean
          NSDictionary    |   Object object
            NSArray       |    Array object
             NSDate       |     Date object
            NSBlock (1)   |   Function object (1)
               id (2)     |   Wrapper object (2)
             Class (3)    | Constructor object (3)
         @/textblock
     // 在context创建BOOL的JS变量
     + (JSValue *)valueWithBool:(BOOL)value inContext:(JSContext *)context;
    
     // 将JS变量转换成OC中的BOOL类型
     - (BOOL)toBool;
    
     // 修改JS对象的属性的值
     - (void)setValue:(id)value forProperty:(NSString *)property;
    
     // JS中是否有这个对象
     @property (readonly) BOOL isUndefined;
    
     // 比较两个JS对象是否相等
     - (BOOL)isEqualToObject:(id)value;
    
     // 调用者JSValue对象为JS中的方法名称,arguments为参数,调用JS中Window直接调用的方法
     - (JSValue *)callWithArguments:(NSArray *)arguments;
    
    // 调用者JSValue对象为JS中的全局对象名称,method为全局对象的方法名称,arguments为参数
     - (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
    
    // JS中的结构体类型转换为OC
     + (JSValue *)valueWithPoint:(CGPoint)point inContext:(JSContext *)context;
    
  3. JSExport --- JS调用OC中的方法和属性写在继承自JSExport的协议当中,OC对象实现自定义的协议

    // textFunction -- JS方法
    // - (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number -- OC方法
    JSExportAs (textFunction,- (void) ocTestFunction:(NSNumber *)value sec:(NSNumber *)number);
    
  4. JSManagedValue --- JS和OC对象的内存管理辅助对象,主要用来保存JSValue对象,解决OC对象中存储js的值,导致的循环引用问题

    JSManagedValue *_jsManagedValue = [JSManagedValue managedValueWithValue:jsValue];
    [_context.virtualMachine addManagedReference:_jsManagedValue];
    

    JSManagedValue本身只弱引用js值,需要调用JSVirtualMachine的addManagedReference:withOwner:把它添加到JSVirtualMachine中,这样如果JavaScript能够找到该JSValue的Objective-C owner,该JSValue的引用就不会被释放。

  5. JSVirtualMachine --- JS运行的虚拟机,有独立的堆空间和垃圾回收机制,运行在不同虚拟机环境的JSContext可以通过此类通信。

Native Code 和 JS 之间的互相调用(以UIWebView中的JS为例)

  1. JS中,点击事件直接调用方法方式JS和Native代码的互调如下:
  • 1.1 JS代码如下

    <html>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
    <body>
      <script type="text/javascript">
      var nativeCallJS = function(parameter) {
       alert (parameter);
      };
      </script>
      <button type="button" onclick = "jsCallNative('jsParameter')" style="width:100%; height:30px;"/>调用OC代码</button>
    </body>
    </html>
    
    
  • 1.2 OC代码如下

      - (void)__jsLogic
     {
         self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception){
             NSLog(@"JS代码执行中的异常信息%@", exception);
         };
         self.jsContext = [self valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
         self.jsContext[@"jsCallNative"] = ^(NSString *paramer){
             JSValue *currentThis = [JSContext currentThis];
             JSValue *currentCallee = [JSContext currentCallee];
             NSArray *currentParamers = [JSContext currentArguments];
             dispatch_async(dispatch_get_main_queue(), ^{
                   /**
                    *  js调起OC代码,代码在子线程,更新OC中的UI,需要回到主线程
                    */
             });
             NSLog(@"JS paramer is %@",paramer);
             NSLog(@"currentThis is %@",[currentThis toString]);
             NSLog(@"currentCallee is %@",[currentCallee toString]);
             NSLog(@"currentParamers is %@",currentParamers);
         };
         JSValue *jsMethod = self.jsContext[@"nativeCallJS"];
         [jsMethod callWithArguments:@[@"nativeCallJS"]];
     }
    
  • 1.3 OC运行结果,弹出HTML中的alert提示

    2016-08-05 17:57:08.974 LeWebViewPro[38150:3082770] JS paramer is jsParameter
    2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentThis is [object Window]
    2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentCallee is function () {
    [native code]
    }
    2016-08-05 17:57:08.975 LeWebViewPro[38150:3082770] currentParamers is (
    jsParameter
    )
    
  1. JS中,点击事件等事件通过调用全局对象的方法调用方法,JS和Native代码的互调如下:
    • 2.1 JS代码如下

      <html>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta content="width=device-width,initial-scale=1,user-scalable=no" name="viewport">
      <body>
        <script type="text/javascript">
        globalObject = new Object();
        globalObject.name = 100;
        globalObject.nativeCallJS = function (parameter) {
         alert (parameter);
        };
        </script>
        <button type="button" onclick = "globalObject.jsCallNative('jsParameter')" style="width:100%; height:30px;"/>调用OC代码</button>
      </body>
      </html>
      
      
    • 2.2 OC代码如下

    • 2.2.1 JSManager 代码,负责执行JS中的方法

      #import <JavaScriptCore/JavaScriptCore.h>
      #import <Foundation/Foundation.h>
      @protocol LeJSExport <JSExport>
      JSExportAs (jsCallNative,- (void) jsCallNative:(NSString *)jsParameter);
      @end
      
      @interface JSManager : NSObject<LeJSExport>
      
      @end
      -----.M文件-----
      #import "JSManager.h"
      @implementation JSManager
      - (void)jsCallNative:(NSString *)jsParameter
      {
         JSValue *currentThis = [JSContext currentThis];
         JSValue *currentCallee = [JSContext currentCallee];
         NSArray *currentParamers = [JSContext currentArguments];
         dispatch_async(dispatch_get_main_queue(), ^{
             /**
              *  js调起OC代码,代码在子线程,更新OC中的UI,需要回到主线程
              */
         });
         NSLog(@"JS paramer is %@",jsParameter);
         NSLog(@"currentThis is %@",[currentThis toString]);
         NSLog(@"currentCallee is %@",[currentCallee toString]);
         NSLog(@"currentParamers is %@",currentParamers);
      }
      @end
      
    • 2.2.2 包含UIWebView类的代码,负责调起JS中的方法

      - (void)nativeCallJS
      {
          self.jsManager = [[JSManager alloc] init];
          self.jsContext = [self valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
          self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exception){
              NSLog(@"JS代码执行中的异常信息%@", exception);
          };
          self.jsContext[@"globalObject"] = self.jsManager;
          //1.OC方法调起JS
          JSValue *varibleStyle = self.jsContext[@"globalObject"];
          [varibleStyle invokeMethod:@"nativeCallJS" withArguments:@[@100]];
      
          //2.OC脚本调起JS
          NSString *jsScript = [NSString stringWithFormat:@"globalObject.nativeCallJS('%@')",@100];
          [self.jsContext evaluateScript:jsScript];
      }
      
    • 2.3 OC运行结果,弹出HTML中的alert提示

      2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] JS paramer is jsParameter
      2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] currentThis is [object JSManager]
      2016-08-07 10:30:11.444 LeWebViewPro[46674:3253852] currentCallee is function () {
          [native code]
      }
      2016-08-07 10:30:11.445 LeWebViewPro[46674:3253852] currentParamers is (
          jsParameter
      )
      

JavaScriptCore和UIWebView的使用的注意事项

  1. OC在与UIWebView中的JS交互的逻辑是,先获取UIWebView中的JS的执行环境

    self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
  2. 获取UIWebView中的JS的执行环境的时机,一般在webViewDidFinishLoad时获取,获取不到的情况下,需改在其他方法中获取

    shouldStartLoadWithRequest:Sent before a web view begins loading a frame
    webViewDidStartLoad:Sent after a web view starts loading a frame.
    webViewDidFinishLoad:Sent after a web view finishes loading a frame
    
  3. 线程问题

    • JavaScriptCore中提供的API都是线程安全的,一个JSVirtualMachine在一个线程中,它可以包含多个JSContext,而且相互之间可以传值,为了确保线程安全,这些context在运行的时候会采用锁,可以认为是串行执行。
    • JS调用OC的回调方法,是在子线程,所以需要更新OC中的UI的话,需要切换到主线程
  4. 内存问题

    • oc中使用ARC方式管理内存(基于引用计数),但JavaScriptCore中使用的是垃圾回收方式,其中所有的引用都是强引用,但是我们不必担心其循环引用,js的垃圾回收能够打破这些强引用,有些情况需要考虑如下

    • js调起OC回调的block中获取JSConetxt容易循环引用

      self.jsContext[@"jsCallNative"] = ^(NSString *paramer){
        // 会引起循环引用
        JSValue *value1 =  [JSValue valueWithNewObjectInContext:
                            self.jsContext];
        // 不会引起循环引用
        JSValue *value =  [JSValue valueWithNewObjectInContext:
                           [JSContext currentContext]];
      
      };
      
    
    * JavaScriptCore中所有的引用都是强引用,所以在OC中需要存储JS中的值的时候,需要注意
    >在oc中为了打破循环引用我们采用weak的方式,不过在JavaScriptCore中我们采用内存管理辅助对象JSManagedValue的方式,它能帮助引用技术和垃圾回收这两种内存管理机制之间进行正确的转换
    
    

JavaScriptCore单独使用

  1. js代码如下test.js
globalObject = new Object();
globalObject.name = 100;
globalObject.nativeCallJS = function (parameter) {
    alert (parameter);
};
  1. OC读取JS文件,并相互通信
NSString *jsPath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"js"];
    NSString *jsContent = [[NSString alloc] initWithContentsOfFile:jsPath encoding:NSUTF8StringEncoding error:nil];

    JSContext *jsContext = [[JSContext alloc] init];
    //捕获运行js脚本的错误信息
    jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
    //js脚本添加到当前的js执行环境中
    [jsContext evaluateScript:jsContent];
    self.jsManager = [[JSManager alloc] init];
    jsContext[@"globalObject"] = self.jsManager;
    ...
    ...
  1. jS与OC的交互与通过UIWebView相同

相关博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容