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/

posted @ 2015-09-30 10:09  Mr.林的博客  阅读(1023)  评论(0编辑  收藏  举报