今天博主有一個OC與JS的交互的需求,遇到了一些困難點(diǎn),在此和大家分享,希望能夠共同進(jìn)步.
上一篇博文與大家分享了如何使用webview與JS進(jìn)行交互,但是隨著新的API的開放,我們不僅僅需要在webview中與JS進(jìn)行交互,也需要在很多其他的框架中與JS進(jìn)行交互,有兩種方法提供給大家:
今天就和大家總結(jié)一下JavaScriptCore框架的使用.
JavaScriptCore中的類
在項(xiàng)目中引入JavaScriptCore后,鏈到頭文件中,除了大段的Copyright注釋可以看到里面只要引入了5個文件,每個文件里都定義跟文件名對應(yīng)的類:
- JSContext
- JSValue
- JSManagedValue
- JSVirtualMachine
- JSExport

雖說代碼中的注釋介紹的也比較詳細(xì)了,但是如同一圖頂萬言,對程序員來說代碼更有說服力。本文就先來說說這些類相對比較好理解但又很常用的JSContext和JSValue以及它們方法的使用方式和效果。
JSContext和JSValue
JSVirtualMachine為JavaScript的運(yùn)行提供了底層資源,JSContext就為其提供著運(yùn)行環(huán)境,通過- (JSValue *)evaluateScript:(NSString *)script;方法就可以執(zhí)行一段JavaScript腳本,并且如果其中有方法、變量等信息都會被存儲在其中以便在需要的時候使用。而JSContext的創(chuàng)建都是基于JSVirtualMachine:- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;進(jìn)行初始化,那么在其內(nèi)部會自動創(chuàng)建一個新的JSVirtualMachine對象然后調(diào)用前邊的初始化方法。
JSValue則可以說是JavaScript和Object-C之間互換的橋梁,它提供了多種方法可以方便地把JavaScript數(shù)據(jù)類型轉(zhuǎn)換成Objective-C,或者是轉(zhuǎn)換過去。其一一對應(yīng)方式可見下表:
| Objective-C | JavaScript | JSValue Convert | JSValue 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 | | |
基本類型轉(zhuǎn)換
先看個簡單的例子:
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中,然后通過下標(biāo)來獲取出來。而對于Array或者Object類型,JSValue也可以通過下標(biāo)直接取值和賦值。
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: 316 // JS Array: 21,blog,iderzheng.com,,,,,7 Length: 817 // NSArray: (18 // 21,19 // blog,20 // "iderzheng.com",21 // "<null>",22 // "<null>",23 // "<null>",24 // "<null>",25 // 726 // )
通過輸出結(jié)果很容易看出代碼成功把數(shù)據(jù)從Objective-C賦到了JavaScript數(shù)組上,而且JSValue是遵循JavaScript的數(shù)組特性:無下標(biāo)越位,自動延展數(shù)組大小。并且通過JSValue還可以獲取JavaScript對象上的屬性,比如例子中通過"length"就獲取到了JavaScript數(shù)組的長度。在轉(zhuǎn)成NSArray的時候,所有的信息也都正確轉(zhuǎn)換了過去。
方法的轉(zhuǎn)換
各種數(shù)據(jù)類型可以轉(zhuǎn)換,Objective-C的Block也可以傳入JSContext中當(dāng)做JavaScript的方法使用。比如在前端開發(fā)中常用的log方法,雖然JavaScritpCore沒有自帶(畢竟不是在網(wǎng)頁上運(yùn)行的,自然不會有window、document、console這些類了),仍然可以定義一個Block方法來調(diào)用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 // ider20 // 7,2121 // [object Object]22 // this: [object GlobalObject]23 // -------End Log-------通過Block成功的在JavaScript調(diào)用方法回到了Objective-C,而且依然遵循JavaScript方法的各種特點(diǎn),比如方法參數(shù)不固定。也因?yàn)檫@樣,JSContext提供了類方法來獲取參數(shù)列表(+ (JSContext *)currentContext;)和當(dāng)前調(diào)用該方法的對象(+ (JSValue *)currentThis)。對于"this",輸出的內(nèi)容是GlobalObject,這也是JSContext對象方法- (JSValue *)globalObject;所返回的內(nèi)容。因?yàn)槲覀冎涝贘avaScript里,所有全局變量和方法其實(shí)都是一個全局變量的屬性,在瀏覽器中是window,在JavaScriptCore是什么就不得而知了。
Block可以傳入JSContext作方法,但是JSValue沒有toBlock方法來把JavaScript方法變成Block在Objetive-C中使用。畢竟Block的參數(shù)個數(shù)和類型已經(jīng)返回類型都是固定的。雖然不能把方法提取出來,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反過來將參數(shù)傳進(jìn)去來調(diào)用方法。
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: 28JSValue還提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;讓我們可以直接簡單地調(diào)用對象上的方法。只是如果定義的方法是全局函數(shù),那么很顯然應(yīng)該在JSContext的globalObject對象上調(diào)用該方法;如果是某JavaScript對象上的方法,就應(yīng)該用相應(yīng)的JSValue對象調(diào)用.
下面貼上代碼,我們還是以webview作為示例,各位看官自行深究.
首先我們添加協(xié)議
@interface ViewController ()<UIWebViewDelegate>
實(shí)現(xiàn)代理方法
#PRagma mark --webViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
-(void)webViewDidStartLoad:(UIWebView *)webView
{
}
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
}
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
}
每個方法是什么時候調(diào)用都在注釋里面
1.我們先嘗試用oc調(diào)用一下js方法
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *alertJS=@"alert('test js OC')";
下來我們使用js調(diào)用iOS
js調(diào)用iOS分兩種情況
一,js里面直接調(diào)用方法
二,js里面通過對象調(diào)用方法
首先我們看第一種,直接調(diào)用方法。
其中用到了iOS的block
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"test1"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};
NSString *jsFunctStr=@"test1('參數(shù)1')";
[context evaluateScript:jsFunctStr];
NSString *jsFunctStr1=@"test1('參數(shù)a','參數(shù)b')";
[context evaluateScript:jsFunctStr1];
}
以上代碼中 給test1賦值的block要是執(zhí)行了那么結(jié)果就是對的,js調(diào)用了iOS
我們看結(jié)果

我們看到參數(shù)被打印出來了,而且一個參數(shù)和兩個參數(shù)都可以,也符合js參數(shù)個數(shù)不限制
下來我們看第二種情況 就是js 中是通過一個對象來調(diào)用方法的。
此處稍微復(fù)雜一點(diǎn)我們需要使用到
JSExport
凡事添加了JSExport協(xié)議的協(xié)議,所規(guī)定的方法,變量等 就會對js開放,我們可以通過js調(diào)用到
此處有點(diǎn)繞。我們直接看代碼
首先創(chuàng)建一個類 繼承NSObject 并且規(guī)定一個協(xié)議
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol TestJSObjectProtocol <JSExport>
-(void)TestNOParameter;
-(void)TestOneParameter:(NSString *)message;
-(void)TestTowParameter:(NSString *)message1 SecondParameter:(NSString *)message2;
@end
//讓我們創(chuàng)建的類實(shí)現(xiàn)上邊的協(xié)議
@interface TestJSObject : NSObject<TestJSObjectProtocol>
@end
類的實(shí)現(xiàn)
#import "TestJSObject.h"
@implementation TestJSObject
-(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加載完成之后調(diào)用測試一下
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
JSContext *context=[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
TestJSObject *testJO=[TestJSObject new];
context[@"testobject"]=testJO;
NSString *jsStr1=@"testobject.TestNOParameter()";
[context evaluateScript:jsStr1];
NSString *jsStr2=@"testobject.TestOneParameter('參數(shù)1')";
[context evaluateScript:jsStr2];
NSString *jsStr3=@"testobject.TestTowParameterSecondParameter('參數(shù)A','參數(shù)B')";
[context evaluateScript:jsStr3];
}
結(jié)果如下:
TestJSOC[7038:258939] this is ios TestNOParameter
TestJSOC[7038:258939] this is ios TestOneParameter=參數(shù)1
TestJSOC[7038:258939] this is ios TestTwoParameter=參數(shù)A Second=參數(shù)B
好了 我們看到了結(jié)果 三個方法都調(diào)用了
ok兩種方式都完成了
http://nshipster.cn/javascriptcore/
http://www.webryan.net/2013/10/about-ios7-javascriptcore-framework/