JavaScriptCore 框架详细解析(二) —— JS与O
版本记录
版本号 | 时间 |
---|---|
V1.0 | 2017.10.13 |
前言
JavaScriptCore
是用来评估应用程序中的JavaScript
程序,并支持应用程序的JavaScript脚本编写。接下来这几篇我们就详细的解析一下JavaScriptCore框架的使用情况。感兴趣的可以看上面写的那篇。
1. JavaScriptCore 框架详细解析(一) —— 基本概要
JS和OC通信
其实学习JS很多情况下就是需要JS和OC之间进行通信。
1. OC调用JavaScript
OC调用JavaScript有两种方法。
使用方法stringByEvaluatingJavaScriptFromString
下面还是直接看代码
NSString *jsStr = [NSString stringWithFormat:@"showAlert('%@')",@"JS中alert弹出的message"];
[_webView stringByEvaluatingJavaScriptFromString:jsStr];
注意:该方法会同步返回一个字符串,因此是一个同步方法,可能会阻塞UI。
stringByEvaluatingJavaScriptFromString
是一个同步的方法,使用它执行JS方法时,如果JS 方法比较耗的时候,会造成界面卡顿。尤其是js 弹出alert 的时候。alert 也会阻塞界面,等待用户响应,而stringByEvaluatingJavaScriptFromString
又会等待js执行完毕返回。这就造成了死锁。官方推荐使用WKWebView的evaluateJavaScript:completionHandler:
代替这个方法。
其实我们也有另外一种方式,自定义一个延迟执行alert 的方法来防止阻塞,然后我们调用自定义的alert 方法。同理,耗时较长的js 方法也可以放到setTimeout
中。
function asyncAlert(content) {
setTimeout(function(){
alert(content);
},1);
}
使用框架JavaScriptCore
首先需要引入头文件。
#import <JavaScriptCore/JavaScriptCore.h>
然后,我们先看一下OC调用JavaScript的代码。
- (void)viewDidLoad
{
[super viewDidLoad];
//OC调用JS代码
self.context = [[JSContext alloc] init];
NSString *jsStr = @"function add(a,b) {return a*b}";
[self.context evaluateScript:jsStr];
JSValue *value = [self.context[@"add"] callWithArguments:@[@10, @20]];
NSInteger result = [value toInt32];
NSLog(@"result = %ld", result);
}
下面我们就看一下输出结果
2017-10-15 19:06:03.903213+0800 JJJSOC_demo[1067:32206] result = 200
2. JavaScript 调用OC
JavaScript
调用OC代码有两种方法。
使用拦截假的URL请求的方式
第一种方式是用JS发起一个假的URL请求,然后利用UIWebView
的代理方法拦截这次请求,然后再做相应的处理。下面给一个例子,该例子由Haley_Wong提供,下面我们就看一下。
// 一个简单的HTML网页和一个btn点击事件用来与原生OC交互,HTML代码如下:
<html>
<header>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script type="text/javascript">
function showAlert(message){
alert(message);
}
function loadURL(url) {
var iFrame;
iFrame = document.createElement("iframe");
iFrame.setAttribute("src", url);
iFrame.setAttribute("style", "display:none;");
iFrame.setAttribute("height", "0px");
iFrame.setAttribute("width", "0px");
iFrame.setAttribute("frameborder", "0");
document.body.appendChild(iFrame);
// 发起请求后这个 iFrame 就没用了,所以把它从 dom 上移除掉
iFrame.parentNode.removeChild(iFrame);
iFrame = null;
}
function firstClick() {
loadURL("firstClick://shareClick?title=分享的标题&content=分享的内容&url=链接地址&imagePath=图片地址");
}
</script>
</header>
<body>
<h2> 这里是第一种方式 </h2>
<br/>
<br/>
<button type="button" onclick="firstClick()">Click Me!</button>
</body>
</html>
// 在项目的控制器中实现UIWebView的代理方法:
#pragma mark - UIWebViewDelegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL * url = [request URL];
if ([[url scheme] isEqualToString:@"firstclick"]) {
NSArray *params =[url.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
for (NSString *paramStr in params) {
NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
NSString *decodeValue = [dicArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[tempDic setObject:decodeValue forKey:dicArray[0]];
}
}
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"方式一" message:@"这是OC原生的弹出窗" delegate:self cancelButtonTitle:@"收到" otherButtonTitles:nil];
[alertView show];
NSLog(@"tempDic:%@",tempDic);
return NO;
}
return YES;
}
- JS中的firstClick,在拦截到的url scheme全都被转化为小写。
- html中需要设置编码,否则中文参数可能会出现编码问题。
- JS用打开一个iFrame的方式替代直接用document.location的方式,以避免多次请求,被替换覆盖的问题。
早期的JS与原生交互的开源库很多都是用得这种方式来实现的,例如:PhoneGap、WebViewJavascriptBridge。关于这种方式调用OC方法,唐巧早期有篇文章有过介绍:关于UIWebView和PhoneGap的总结。
使用JavaScriptCore框架
第二种就是使用ios7.0出现的JavaScriptCore
框架,JavaScriptCore
调用OC代码有两种形式,一种是block
,另外一种就是JSExport
协议。
block实现
我们先看一下block实现JavaScript
调用OC代码。
//JS调用OC代码
- (void)jsCallOC
{
self.context = [[JSContext alloc] init];
self.context[@"add"] = ^(NSInteger a, NSInteger b){
NSLog(@"%@",@(a * b));
};
[self.context evaluateScript:@"add(10,20)"];
}
下面我们看一下输出结果
2017-10-15 19:28:32.859407+0800 JJJSOC_demo[1272:53289] 200
我们定义一个block,然后保存到context里面,其实就是转换成了JS的function。然后我们直接执行这个function,调用的就是我们的block里面的内容了。
JSExport 协议
下面我们就爱一下利用JSExport 协议实现JavaScript 调用OC代码的示例。
//先定义了一个协议
1. JSExportProtocol.h
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>
@protocol JSExportProtocol <JSExport>
//求两个数的和
- (NSInteger)add:(NSInteger)a b:(NSInteger)b;
@property (nonatomic, assign) NSInteger sum;
@end
//自定义对象并遵守上面的协议
2. JSExportObj.h
#import <Foundation/Foundation.h>
#import "JSExportProtocol.h"
@interface JSExportObj : NSObject <JSExportProtocol>
@end
3. JSExportObj.m
#import "JSExportObj.h"
@implementation JSExportObj
@synthesize sum = _sum;
#pragma mark - JSExportProtocol
- (NSInteger)add:(NSInteger)a b:(NSInteger)b
{
return a + b;
}
#pragma mark - Setter && Getter
- (void)setSum:(NSInteger)sum
{
_sum = sum;
}
@end
4. ViewController.m
#import "ViewController.h"
#import <JavaScriptCore/JavaScriptCore.h>
#import "JSExportObj.h"
@interface ViewController ()
@property (nonatomic, strong) JSExportObj *obj;
@property (nonatomic, strong) JSContext *context;
@end
@implementation ViewController
#pragma mark - Override Base Function
- (void)viewDidLoad
{
[super viewDidLoad];
self.context = [[JSContext alloc] init];
self.obj = [[JSExportObj alloc] init];
//异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception = %@", exception);
};
//将对象obj添加到上下文中
self.context[@"OCObj"] = self.obj;
//JS调用Obj方法,并将结果赋值给Obj的sum属性
[self.context evaluateScript:@"OCObj.sum = OCObj.addB(10, 20)"];
NSLog(@"%ld", self.obj.sum);
}
@end
下面我们看输出结果
2017-10-15 22:14:18.582288+0800 JJJSOC_demo2[2838:171289] 30
这里大家还要注意:- (NSInteger)add:(NSInteger)a b:(NSInteger)b;
是OC中的方法,而js中方法却是为addB(10, 20)
,可以通过JSExportAs
这个宏转换成JS的函数名字。
所以上面有两处函数可以做如下替换:
//求两个数的和
//- (NSInteger)add:(NSInteger)a b:(NSInteger)b;
//用宏转换下,将JS函数名字指定为add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a b:(NSInteger)b);
// [self.context evaluateScript:@"OCObj.sum = OCObj.addB(10, 20)"];
//调用
[self.context evaluateScript:@"OCObj.sum = OCObj.add(10,20)"];
运行一下,你可以得到一样的结果。
后记
未完,待续~~~