• iOS开发日记28-详解JavaScriptCore


    今天博主有一个OC与JS的交互的需求,遇到了一些困难点,在此和大家分享,希望能够共同进步.

    上一篇博文与大家分享了如何使用webview与JS进行交互,但是随着新的API的开放,我们不仅仅需要在webview中与JS进行交互,也需要在很多其他的框架中与JS进行交互,有两种方法提供给大家:

    1.苹果在7.0之后新开放得API JavaScriptCore框架,让我们能够更简便的使用OC与JS进行交互.

    2.使用第三方框架WebViewJavascriptBridge

    今天就和大家总结一下JavaScriptCore框架的使用.

    JavaScriptCore中的类

    在项目中引入JavaScriptCore后,链到头文件中,除了大段的Copyright注释可以看到里面只要引入了5个文件,每个文件里都定义跟文件名对应的类:

    • JSContext
    • JSValue
    • JSManagedValue
    • JSVirtualMachine
    • JSExport

    JavaScriptCore_Head

    虽说代码中的注释介绍的也比较详细了,但是如同一图顶万言,对程序员来说代码更有说服力。本文就先来说说这些类相对比较好理解但又很常用的JSContext和JSValue以及它们方法的使用方式和效果。

    JSContext和JSValue

    JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境,通过- (JSValue *)evaluateScript:(NSString *)script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。

    JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。其一一对应方式可见下表:

     
     
    Objective-CJavaScriptJSValue ConvertJSValue Constructor
    nil undefined   valueWithUndefinedInContext
    NSNull null   valueWithNullInContext:
    NSString string toString  
    NSNumber number, boolean toNumber
    toBool
    toDouble
    toInt32
    toUInt32
    valueWithBool:inContext:
    valueWithDouble:inContext:
    valueWithInt32:inContext:
    valueWithUInt32:inContext:
    NSDictionary Object object toDictionary valueWithNewObjectInContext:
    NSArray Array object toArray valueWithNewArrayInContext:
    NSDate Date object toDate  
    NSBlock Function object    
    id Wrapper object toObject
    toObjectOfClass:
    valueWithObject:inContext:
    Class Constructor object    

    基本类型转换

    先看个简单的例子:

    复制代码
    1     JSContext *context = [[JSContext alloc] init];
    2     JSValue *jsVal = [context evaluateScript:@"21+7"];
    3     int iVal = [jsVal toInt32];
    4     NSLog(@"JSValue: %@, int: %d", jsVal, iVal);
    5      
    6     //Output:
    7     // JSValue: 28, int: 28
    复制代码

    很简单吧,还可以存一个JavaScript变量在JSContext中,然后通过下标来获取出来。而对于Array或者Object类型,JSValue也可以通过下标直接取值和赋值。

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     [context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];
     3     JSValue *jsArr = context[@"arr"]; // Get array from JSContext
     4      
     5     NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]);
     6     jsArr[1] = @"blog"; // Use JSValue as array
     7     jsArr[7] = @7;
     8      
     9     NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);
    10      
    11     NSArray *nsArr = [jsArr toArray];
    12     NSLog(@"NSArray: %@", nsArr);
    13      
    14     //Output:
    15     // JS Array: 21,7,iderzheng.com Length: 3
    16     // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 8
    17     // NSArray: (
    18     // 21,
    19     // blog,
    20     // "iderzheng.com",
    21     // "<null>",
    22     // "<null>",
    23     // "<null>",
    24     // "<null>",
    25     // 7
    26     // )
    复制代码

    通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过JSValue还可以获取JavaScript对象上的属性,比如例子中通过"length"就获取到了JavaScript数组的长度。在转成NSArray的时候,所有的信息也都正确转换了过去。

    方法的转换

    各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的log方法,虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     context[@"log"] = ^() {
     3     NSLog(@"+++++++Begin Log+++++++");
     4      
     5     NSArray *args = [JSContext currentArguments];
     6     for (JSValue *jsVal in args) {
     7     NSLog(@"%@", jsVal);
     8     }
     9      
    10     JSValue *this = [JSContext currentThis];
    11     NSLog(@"this: %@",this);
    12     NSLog(@"-------End Log-------");
    13     };
    14      
    15     [context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
    16      
    17     //Output:
    18     // +++++++Begin Log+++++++
    19     // ider
    20     // 7,21
    21     // [object Object]
    22     // this: [object GlobalObject]
    23     // -------End Log-------
    复制代码

    通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表(+ (JSContext *)currentContext;)和当前调用该方法的对象(+ (JSValue *)currentThis)。对于"this",输出的内容是GlobalObject,这也是JSContext对象方法- (JSValue *)globalObject;所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。

    Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法。

    复制代码
     1     JSContext *context = [[JSContext alloc] init];
     2     [context evaluateScript:@"function add(a, b) { return a + b; }"];
     3     JSValue *add = context[@"add"];
     4     NSLog(@"Func: %@", add);
     5      
     6     JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
     7     NSLog(@"Sum: %d",[sum toInt32]);
     8     //OutPut:
     9     // Func: function add(a, b) { return a + b; }
    10     // Sum: 28
    复制代码

    JSValue还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContextglobalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue对象调用.

    下面贴上代码,我们还是以webview作为示例,各位看官自行深究.

    首先我们添加协议

     @interface ViewController ()<UIWebViewDelegate>  

    实现代理方法

    #pragma mark --webViewDelegate  

    -(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType  

    {  

        //网页加载之前会调用此方法  

        //retrun YES 表示正常加载网页 返回NO 将停止网页加载  

    return YES;  

    }  

    -(void)webViewDidStartLoad:(UIWebView *)webView  

    {  

        //开始加载网页调用此方法  

    }  

    -(void)webViewDidFinishLoad:(UIWebView *)webView  

    {  

           //网页加载完成调用此方法  

    }  

    -(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error  

    {  

         //网页加载失败 调用此方法  

    }  

    每个方法是什么时候调用都在注释里面

    1.我们先尝试用oc调用一下js方法

     -(void)webViewDidFinishLoad:(UIWebView *)webView  

    {  

          //网页加载完成调用此方法  

          //首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)  

          JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  

          NSString *alertJS=@"alert('test js OC')"; //准备执行的js代码  

          [context evaluateScript:alertJS];//通过oc方法调用js的alert

    }

    2.下来我们使用js调用iOS

    js调用iOS分两种情况

    一,js里面直接调用方法

    二,js里面通过对象调用方法

    首先我们看第一种,直接调用方法。

    其中用到了iOS的block

     -(void)webViewDidFinishLoad:(UIWebView *)webView  

    {  

          //网页加载完成调用此方法  

          //iOS调用js  

          //首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)  

          JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  

          //js调用iOS  

          //第一种情况  

          //其中test1就是js的方法名称,赋给是一个block 里面是iOS代码  

          //此方法最终将打印出所有接收到的参数,js参数是不固定的 我们测试一下就知道  

          context[@"test1"] = ^() {  

          NSArray *args = [JSContext currentArguments];   

          for (id obj in args) {  

                NSLog(@"%@",obj);  

            }  

        };  

        //此处我们没有写后台(但是前面我们已经知道iOS是可以调用js的,我们模拟一下)  

        //首先准备一下js代码,来调用js的函数test1 然后执行  

        //一个参数  

        NSString *jsFunctStr=@"test1('参数1')";  

        [context evaluateScript:jsFunctStr];  

        //二个参数  

        NSString *jsFunctStr1=@"test1('参数a','参数b')";  

        [context evaluateScript:jsFunctStr1];  

    }  

    以上代码中 给test1赋值的block要是执行了那么结果就是对的,js调用了iOS

    我们看结果

    我们看到参数被打印出来了,而且一个参数和两个参数都可以,也符合js参数个数不限制

    下来我们看第二种情况 就是js 中是通过一个对象来调用方法的。

    此处稍微复杂一点我们需要使用到

    JSExport

    凡事添加了JSExport协议的协议,所规定的方法,变量等 就会对js开放,我们可以通过js调用到

    此处有点绕。我们直接看代码

    首先创建一个类 继承NSObject 并且规定一个协议

       #import <Foundation/Foundation.h>  

       #import <JavaScriptCore/JavaScriptCore.h>   

       //首先创建一个实现了JSExport协议的协议  

       @protocol TestJSObjectProtocol <JSExport>  

       //此处我们测试几种参数的情况  

    -(void)TestNOParameter;  

    -(void)TestOneParameter:(NSString *)message;  

    -(void)TestTowParameter:(NSString *)message1 SecondParameter:(NSString *)message2;  

    @end  

    //让我们创建的类实现上边的协议  

    @interface TestJSObject : NSObject<TestJSObjectProtocol>  

    @end

    类的实现

        #import "TestJSObject.h"    

        @implementation TestJSObject  

         //一下方法都是只是打了个log 等会看log 以及参数能对上就说明js调用了此处的iOS 原生方法  

    -(void)TestNOParameter  

    {  

        NSLog(@"this is ios TestNOParameter");  

    }  

    -(void)TestOneParameter:(NSString *)message  

    {  

        NSLog(@"this is ios TestOneParameter=%@",message);  

    }  

    -(void)TestTowParameter:(NSString *)message1 SecondParameter:(NSString *)message2  

    {  

       NSLog(@"this is ios TestTowParameter=%@  Second=%@",message1,message2);  

    }  

    @end

     

    下面我们在weibview加载完成之后调用测试一下

    -(void)webViewDidFinishLoad:(UIWebView *)webView  

    {  

          //网页加载完成调用此方法  

          //首先创建JSContext 对象(此处通过当前webView的键获取到jscontext)  

          JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];  

          //第二种情况,js是通过对象调用的,我们假设js里面有一个对象 testobject 在调用方法  

          //首先创建我们新建类的对象,将他赋值给js的对象  

          TestJSObject *testJO=[TestJSObject new];  

          context[@"testobject"]=testJO;  

          //同样我们也用刚才的方式模拟一下js调用方法  

          NSString *jsStr1=@"testobject.TestNOParameter()";  

          [context evaluateScript:jsStr1];  

          NSString *jsStr2=@"testobject.TestOneParameter('参数1')";  

          [context evaluateScript:jsStr2];  

          NSString *jsStr3=@"testobject.TestTowParameterSecondParameter('参数A','参数B')";  

          [context evaluateScript:jsStr3];  

    }  

    结果如下:

    TestJSOC[7038:258939] this is ios TestNOParameter

    TestJSOC[7038:258939] this is ios TestOneParameter=参数1

    TestJSOC[7038:258939] this is ios TestTwoParameter=参数A Second=参数B

    好了 我们看到了结果 三个方法都调用了 

    ok两种方式都完成了

    http://nshipster.cn/javascriptcore/

    http://www.webryan.net/2013/10/about-ios7-javascriptcore-framework/

  • 相关阅读:
    当年的笔记_apache配置虚拟主机
    sqlserver 调优(三)
    bat命令自用其(一)
    Always On主辅延迟相关描述
    sqlserver中常用的windows命令行的操作
    mysql复制以及一主多从等常见集群概述
    sqlserver 调优(二)
    sqlserver 获取实例上用户数据库的数据字典
    sqlserver事务隔离小结
    mysql 慢查询的小结
  • 原文地址:https://www.cnblogs.com/Twisted-Fate/p/4848387.html
Copyright © 2020-2023  润新知