跟進hybrid混合項目開發已有一段時間了,一直都想著手總結一下js與OC的交互關系,但又感覺一直都還沒有摸透,總感覺還差點什么...
下面總結下最近學到的js與OC之間的交互:
簡單來說js與OC交互就是通過一些方法使得js可以調用OC中的方法,亦或者OC可以調用js中的方法,使得兩者可以互傳參數,進行各自處理;下面介紹各自兩種方法:
一、OC調用js方法,OC傳參數給js:
1.*- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray **)arguments;
這個方法可以讓我們可以直接在OC上簡單地調用JS上的方法。只是如果定義的方法是全局函數,那么很顯然應該在JSContext的glocalObject對象上調用該方法;如果是某JavaScript對象上的方法,就應該用相應的JSValue對象調用。
舉個例子,在js上有方法:
function buttonClicke()
{
testobject.TestNOParameter();
}
那么方法“buttonClicke()”則是全局函數;
“TestNOParameter()”則是JavaScript對象上的方法;
//比如:buttonClicke是全局函數,所以用globalObject調用,從OC傳遞arguments回到web:
[[context globalObject] invokeMethod: buttonClicke withArguments:arguments];
[[context globalObject] invokeMethod:@"CallBack" withArguments:@[@"你好世界",@"a",@"b",@"c"]];
//比如:TestNOParameter是JavaScript對象上的方法,所以用相應的testobject調用,從OC傳遞arguments回到web:
`?????但這個方法我不知道怎么寫,望知道的朋友告知一下////////`
`百度上搜到的全是這段:
JSValue還提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
讓我們可以直接簡單地調用對象上的方法。只是如果定義的方法是全局函數,那么很顯然應該在JSContext的globalObject對象上調用該方法;
如果是某JavaScript對象上的方法,就應該用相應的JSValue對象調用.`
但是我比較笨,不知道所謂的"應該用相應的JSValue對象調用"應該怎么寫,請教大家;
2.*- (JSValue *)evaluateScript:(NSString **)script;
這個方法可以簡單地在OC上調用js的方法腳本;
比如:
//通過oc方法調用js的alert
[context evaluateScript:@"alert('test js OC')";];
再如js中有方法如下:
function buttonClicke()
{
testobject.alert(value);
}
OC中可以這樣調用:前面是先“對象.方法=”,后面再是“方法(回傳參數)”;
[context evaluateScript:[NSString stringWithFormat:@"testobject.alert = alert('%@');",@"回傳"]];
二、js調用OC方法,js傳參數給OC:
1.使用block 直接調用
如果js有方法:
function buttonClicke()
{
Tank("參數");
}
則OC中可以直接使用block響應js的這個Tank方法:
context[@"Tank"] = ^() {
NSArray *arguments = [JSContext currentArguments]; //傳過來的是一個數組
NSString *string = arguments[0];
};
//拿到參數后就可以干嘛干嘛了
//或者不用傳參數,js那邊點擊了這個方法,OC這邊響應進入到block里,想干嘛就在這里干嘛就是了。
或者js有方法
function clicke()
{
alert(value);
}
OC中可以直接執行并返回值給js用:
context[@"alert"] = ^() {
return @"回傳參數";
};
2.使用JSExport 對象調用
在OC中凡事添加了JSExport協議的協議,所規定的方法、變量等就會對js開放,就可以通過js調用到;
通過JSExport協議的重載宏,可以告訴js調用什么方法時,會執行OC端的什么方法;
如js方法為:testobject.TestOneParameter('參數1');
//testobject 是 js 對象,TestOneParameter是方法名,后面為參數,方法名可隨意起,沒有規范,并不像有些博客上寫的要與協議方法拼起來的名字一樣,沒有必要的。
* 首先創建一個類 繼承NSObject并遵循 規定好的這個協議,這個協議遵循JSExport協議:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
//首先創建一個實現了JSExport協議的協議
@protocol TestJSObjectProtocol <JSExport>
//使用JSExport重載宏JSExportAs來聲明方法:
//點進JSExportAs里面看API,可以發現其規則:前面是js的方法如:TestOneParameter;
后面是OC里的這個協議里的方法,(注意后面一定要有一個參數,即使js那邊的沒有參數,這邊也要這樣寫,大不了接收到的參數為null,這個規則在JSExportAs里寫有,note那里);
這一句的意思是告訴js:當執行js端的TestOneParameter方法時(當然,這個方法可以隨意其他名字),會調用OC端的“ -(void)TestNOParameter:(id)args);”這個方法,也叫注冊
JSExportAs(TestOneParameter, -(void)TestNOParameter:(id)args);
@end
//然后在創建的類遵循上邊的協議
@interface EXWebViewBridge : NSObject<TestJSObjectProtocol>
@end
* 然后去.m里實現或者在其他遵循這個協議的類里實現
-(void)TestNOParameter:(id)args
{
NSLog(@"this is ios TestNOParameter = %@",args);
}
* 將這個類創建一個新對象賦給JS的對象
js是通過對象調用的,我們假設js里面有一個對象 testobject 在調用方法
context[@"testobject"]=[EXWebViewBridge new];
//這樣就可以了,當JS那邊在調用一個方法時,
//如:testobject.TestOneParameter('參數1'),就會來到OC里的協議里使用OC的對象代替JS的對象 并調用對應的協議方法去實現,并傳參數過來。
三、下面是代碼部分
1,index.html文件
<!--// Created by Tank on 16-10-18.-->
<!--// Copyright (c) 2016年 Tank. All rights reserved.-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="">
<meta name="viewport" content="width=device-width; initial-scale=1.0">
<script type="text/javascript" src="index.js"></script>
</head>
<!-- js調用OC 使用block -->
<button id="halle" onclick="buttonClicka()"> Tank</button>
<button id="halle" onclick="buttonClickb()"> 直接reture</button>
<!-- 使用JSExport 對象調用方法 -->
<button id="halle" onclick="buttonClicke()"> 無參</button>
<button id="hallf" onclick="buttonClickf()"> 一參</button>
<button id="hallg" onclick="buttonClickg()"> 兩參</button>
<button id="hallg" onclick="buttonClickh()"> 多參</button>
<button id="hallg" onclick="buttonClicki()"> doFoo</button>
</body>
</html>
2,index.js文件
///使用Block js調用OC
function buttonClicka()
{
Tank("參數");
}
function buttonClickb()
{
value = getReture();
alert(value);
}
///使用使用JSExport 對象調用方法 js調用OC
//js這邊沒有參數
function buttonClicke()
{
testobject.TestNOParameter();
}
function buttonClickf()
{
testobject.TestOneParameter("這是一個參數!")
}
function buttonClickg()
{
testobject.TestOneParameterSecondParameter("這是第一個參數","這是第二個參數!");
}
//多個參數
function buttonClickh()
{
testobject.testMoreParameters("一個","兩個","三個","四個");
}
//doFoo
function buttonClicki()
{
testobject.doFoo("one","two");
}
function callBack()
{
//alert(value);
testobject.alert(value);
}
3,創建一個新類TestJSObject.h
//
// TestJSObject.h
// TestJSandOC
//
// Created by Tank on 16/9/20.
// Copyright ? 2016年 Tank. All rights reserved.
//
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol TestJSObjectProtocol <JSExport>
///Tank:使用這種必須得要帶一個參數以上,可以點JSExportAs進里面看;這里說的一個參數是OC這邊的方法,即使js那邊沒有參數傳過來,這里的方法也要帶一個參數,實現方法那邊也要有一個參數,這樣才不會報錯,如下面這個,js那邊沒有參數,oc這邊還是要有一個參數來接收的。
JSExportAs(TestNOParameter, -(void)TestNOParameter:(id)args);
///js那邊有一個參數,Oc這邊也要有一個參數接收
JSExportAs(TestOneParameter, -(void)TestOneParameter:(id)args);
///js那邊有兩個參數,OC這邊也要有兩個參數接收,當然也可以用一個數組去接收它所有的參數。注意方法名的寫法,可以規范成如下,也可以隨意,如上所說:
JSExportAs(TestOneParameterSecondParameter, -(void)TestTowParameter:(NSString *)message1 SecondParameter:(NSString *)message2);
///多個參數,用數組接收
JSExportAs(testMoreParameters, -(void)testMorePara:(id)args);
///再如下,告訴雙方,當js調用doFoo 時,OC就調用對應的方法“- (void)doFoo:(id)foo withBar:(id)bar);”;
///js傳過來的參數會一一對應到OC里的,如果不夠或無則顯示為null;
JSExportAs(doFoo, - (void)doFoo:(id)foo withBar:(id)bar);
@end
@interface TestJSObject : NSObject<TestJSObjectProtocol>
///這里不需要寫什么,只要讓這個類遵循上面所規定好的那個協議就好。
@end
4,這個新類的實現TestJSObject.m
//
// TestJSObject.m
// TestJSandOC
//
// Created by Tank on 16/9/20.
// Copyright ? 2016年 Tank. All rights reserved.
//
#import "TestJSObject.h"
@implementation TestJSObject
///js那邊沒有參數,這邊也要用一個來接收,接收回來的值為null
-(void)TestNOParameter:(id)message
{
NSLog(@"this is ios TestNOParameter = %@",message);
}
-(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);
}
///ja傳來多個參數,OC使用數組接收
-(void)testMorePara:(id)args
{
NSArray *arguments = [JSContext currentArguments];
NSLog(@"arguments = %@",arguments);
for (JSValue *content in arguments) {
NSLog(@"參數分別是 = %@",content);
}
NSString *stringOne = [[arguments objectAtIndex:2] toString];
NSLog(@"stringOne = 這是第%@參數",stringOne);
}
- (void)doFoo:(id)foo withBar:(id)bar
{
NSLog(@"doFoo = %@,withBar = %@",foo,bar);
}
@end
5,ViewController.m
創建一個webView,并LoadRequest剛才創建的index.html;
在webView的代理方法:webViewDidFinishLoad 下執行:
self.myContext = [self.myWebView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
///使用對象JSExport
TestJSObject *testJS=[[TestJSObject alloc] init];
///把OC對象 賦給 js對象
self.myContext[@"testobject"]=testJS;
//使用Block
self.myContext[@"Tank"] = ^(){
NSArray *arguments = [JSContext currentArguments];
for (NSString *args in arguments) {
NSLog(@"args = %@",args);
}
//或者
NSString *string = [[arguments objectAtIndex:0] toString];
};
self.myContext[@"getReture"] = ^(){
return @"直接返回一個值給js";
};
///OC調用JS的兩個方法:
///直接調用,或者將方法名與參數分開調用
[context evaluateScript:@"alert('test js OC')"]; //如果有變量可以使用stringWithFormat
[[context globalObject] invokeMethod:@"alert" withArguments:@[@"test js OC"]];