前言
最近项目开发中用到了OC与JS交互方面的知识,以前也用过UIWebView JS与OC交互方面的,使用的苹果在iOS7开放的javascriptCore框架,使用起来挺方便快捷,javascriptCore源码是开放的,有兴趣的可以去了解一下。
自从iOS8,苹果就UIWebView性能不好,推出了WKWebView,以及github上评分很高的WebViewJavascriptBridge里面最新版本也最WKWebView做了兼容。
实现的方式
所以我总结的方式,分UIWebview、WKWebView、以及通用版本的第三方WebViewJavascriptBridge,进行实现。
- UIWebView中JS与OC交互
- WKWebView中JS与OC交互(只能iOS8及之后的版本)
- WebViewJavascriptBridge对UIWebView与WKWebView做了同意处理。
UIWebView与JS交互
与JS交互需要了解的5个类,JavaScriptCore框架包含的5个类
-
JSContext
- JSContext是JavaScript的运行环境,他主要作用是执行JS代码和注册OC方法接口,相当于HTML中< JavaScript ></JavaScript >之间的内容。
-
JSValue
- JSValue是JSContext的返回结果,他对数据类型进行了封装,并且为JS和OC的数据类型之间的转换提供了方法。
-
JSManagedValue
- JSManagedValue是JSValue的封装,用它可以解决JS和原生代码之间循环引用的问题
-
JSVirtualMachine
- JSVirtualMachine 是管理JS运行时和管理JS暴露的OC对象的内存。
-
JSExport
- JSExport是一个协议,通过实现它可以把一个OC对象暴漏给JS,这样JS就可以调用这个对象暴露的方法
使用
- 首先要是设置<UIWebViewDelegate>代理,代理实现的方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
//在加载网页之前,是否加载网页。YES加载 NO 不加载
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
//开始加载网页
}
- (void)webViewDidFinishLoad:(UIWebView *)webView {
//webveiw加载完成
}
- 初始化UIWebview
//JavaScriptCore导入这个类
#import <JavaScriptCore/JavaScriptCore.h>
//这个是加载URL
// [self.webview loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"https://www.baidu.com"]]];
//加载本地的html
NSString *path = [[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"test.html"];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]];
self.webview.delegate = self;
[self.webview loadRequest:request];
- 在webview在家完成时,获取标题JSContext
self.title = [webView stringByEvaluatingJavaScriptFromString:@"document.title"];
//JSContext是JavaScript的运行环境,建立js与oc的上下文,相当于打通oc与js交互的桥梁
JSContext *context = [self.webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
//oc调用js方法
[self OCToJS:context];
//js调用OC的方法
[self JSCallOC:context];
//通过JSExport可以调用OC对象的方法
[self JSuSEJSExportToOC:context];
- 先展示一下test.html的<script></script>代码
<script type="text/javascript">
//responseData数据就是OC传来的,字典、数组、字符串都会相应的转成js的数据类型
function getUserIdFromOC(responseData) {
//传过来的参数js做处理
log(responseData.userId);
}
//多个参数
function getParamsFromOC(responseData1,responseData2) {
//传过来的参数js做处理
log(responseData1.userId + responseData2);
}
</script>
- OC调用JS方法
- (void)OCToJS:(JSContext *)context {
//相当于获取JS的getUserIdFromOC方法
JSValue *result = context[@"getUserIdFromOC"];
NSDictionary *dict = @{@"userId": @"123456"};
//传的参数是数组。数组里面可以放字典、数组、字符串等
[result callWithArguments:@[dict]];
//执行异常的报错
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"exception -- %@",exception);
};
}
可以打印一下 result,如下结果:
//打印出来是JS的方法,callWithArguments相当于调用了getUserIdFromOC把值传进去
getUserIdFromOC(responseData) {
log(responseData.userId);
}
运行一下执行结果和前端配合一下查看有没有值,或者使用 alert(responseData.userId);自行弹出看看有没有值如下图:
![](https://img.haomeiwen.com/i15936146/e29bf566c679089d.png)
JS调用OC方法
- 直接调用OC方法
在test.html的<body></body>中添加几个button,代码如下:
<br/>
<input type="button" value="给OC传多个参数"
onclick="getParamsFromJS('张三','18');" />
<br/>
<input type="button" value="给OC传数组"
onclick="getArrayFromJS(['11111','22222']);" />
<br/>
<input type="button" value="给OC传字典"
onclick="getDictFromJS({'one':'1','two':'2'});" />
<br/>
<input type="button" value="调用oc然后回到js的方法"
onclick="getDataJSAndReturnJS('111');" />
<br/>
执行如下方法:
- (void)JSCallOC:(JSContext *)context {
//js方法传几个参数jm,你就接受几个参数,参数类型和OC相对应
context[@"getParamsFromJS"] = ^(NSString *str,NSString * str2){
// __strong typeof(self) strongSelf = self;
NSLog(@"context -- %@ context -- %@",str,str2);
};
//JS传输组
context[@"getArrayFromJS"] = ^(NSArray *array) {
NSLog(@"array -- %@",array);
};
//JS传字典
context[@"getDictFromJS"] = ^(NSDictionary *dict) {
NSLog(@"dict - %@",dict);
};
//js调用oc oc再调用js里方法
//避免循环引用
__block typeof(context) weakContext = context;
context[@"getDataJSAndReturnJS"] = ^(NSDictionary *dict) {
NSLog(@"dict - %@",dict);
//可以再把js传来的值传到js方法中
[weakContext[@"getUserIdFromOC"] callWithArguments:@[dict]];
};
}
执行结果如图:
![](https://img.haomeiwen.com/i15936146/cc1b28613f2a411e.png)
分别点击每个按钮,查看一下结果:
str -- 张三 str2 -- 18
array -- (
11111,
22222
)
dict - {
one = 1;
two = 2;
}
dict - {
userId = 123456;
- 使用JSExport将OC对象暴漏给JS
JSExport是JavaScriptCore框架里的一个协议。如果一个协议遵守了JSExport,那么该协议的方法会对JS开放,允许JS直接调用。
//只要遵守NativeJSExport协议,
//该协议的方法会对JS开放,允许JS直接调用
@protocol NativeJSExport <JSExport>
//多个参数的话,参数方法名拼接起来是js调用的方法名
//testMethodWithParam1Param2就是js的方法
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2;
- (void)test:(NSInteger)number Method:(NSString*)str;
- (void)testArray:(NSArray*)array;
- (void)testStr:(NSString*)string;
- (void)testDict:(NSDictionary*)dict;
@end
使用JSExport来让js调用oc方法
- (void)JSUseJSExportCallOC:(JSContext *)context {
//JSExport协议关联native的方法,要在webView的delegate里面添加
/*
会有循环引用问题,导致self的-dealloc方法不被执行。
因为JS中没有弱引用,所以__weak在这里不起作用。
一般来说,可以使用单独的类来处理OC JSExport协议的相关方法,以解决此问题
*/
// context[@"Native"] = self;
//所以要实现JSExport,单独写一个类,遵守NativeJSExport
context[@"Native"] = [Native new];
// 打印异常
context.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
NSLog(@"%@", exceptionValue);
};
}
为了让大家能更好理解JSExport,把Native这个对象代码写下来:
Native.h的源码:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol NativeJSExport <JSExport>
//多个参数的话,参数名字拼接起来是js调用的方法名
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2;
//testMethod就是js的方法名
- (void)test:(NSInteger)number Method:(NSString*)str;
- (void)testArray:(NSArray*)array;
- (void)testStr:(NSString*)string;
- (void)testDict:(NSDictionary*)dict;
@end
//让这个Navtive对象遵守该协议
//只要遵守NativeJSExport协议,
//该协议的方法会对JS开放,允许JS直接调用
@interface Native : NSObject<NativeJSExport>
@end
Native.m
#pragma mark -- JSExport 协议 --
- (void)testArray:(NSArray*)array {
NSLog(@"array -- %@",array);
}
- (void)testStr:(NSString*)string {
NSLog(@"str -- %@",string);
}
- (void)testDict:(NSDictionary*)dict {
NSLog(@"dict -- %@",dict);
}
//数组的类型会通过JSValue把JS数据类型转换成OC
- (void)testMethodWithParam1:(NSString *)str1 Param2:(NSString*)str2 {
NSLog(@"str1 - %@ str2 - %@",str1,str2);
}
- (void)test:(NSInteger)number Method:(NSString*)str {
NSLog(@"number --%ld, str -- %@",(long)number,str);
}
@end
执行结果:
str1 - 张三 str2 - 18
number --18, str -- 22222
array -- (
11111,
22222
)
str -- 测试
dict -- {
one = 1;
two = 2;
}
总结
OC调用JS
- 通过JSContxt获取JS的代码
- context[@"js方法名"]获取返回给JSValue
- 通过JSValue的 callWithArguments:把参数传进去执行JS方法
JS调用OC
-
block形式
- 如:context[@"js方法"] = ^(id str,id str2)
- 参数可以是多个,数据类型可以是NSArray、NSdictionary、NSString、NSInter等
-
JSExport形式
- JSExport是JavaScriptCore框架里的一个协议。如果一个协议遵守了JSExport,那么该协议的方法会对JS开放,允许JS直接调用。
- 创建一个对象,改对象遵守JSExport协议
- 调用context[@"Native"] = [对象 new];
- Native就是将JS中Native.方法名同意交给遵守该JSExport协议的对象去做。
网友评论