UIWebView 和 H5 交互
由于自己没有做过这方面的交互,自己写的 DEMO 也一直是使用代理方法的方式去完成交互。
OC -> H5 使用简单的stringByEvaluatingJavaScriptFromString
H5 -> OC 使用- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
JavaScriptCore 和 WebView 进行交互
现在很多的 App 基本都是 Hybrid 。所以,一定会用到 H5 和 OC 交互。
我之前虽然也明白 OC 和 H5 交互的原理,但一直没有研究过框架的使用。
感觉虽然懂原理,但不会用框架,有点说不过去。
于是,今天就研究一下 JavaScriptCore 下的 UIWebView 和 OC 的交互。
什么是 JavaScriptCore ?
JavaScriptCore 表示的是一个用户解析 JS 的引擎。类似于 Chrome 中的 V8 。
只不过 JavaScriptCore 是用于 Safari 浏览器的。
而 JavaScriptCore框架 则是继承了 JavaScriptCore ,在苹果 iOS 7 引入的,该框架可以让 Objective-C 和 JavaScript 代码交互变的更加简单。
注意:初学者,一般的有点前端经验的,很容易迷惑的一点的。这里描述的仅仅是 Objective-C 和 JavaScript 之间的交互。不包括 HTML。也不是用 request 捕获的方式交互。
JavaScriptCore 框架本质上是基于 WebKit 中以 c/c++ 实现的 JavaScript 引擎的一个面向 OC 的面向对象框架的封装。
JavaScriptCore 的使用方式。
首先要导入 #import <JavaScriptCore/JavaScriptCore.h>
。
在这个框架里面,我们看到了几个头文件。
```
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
```
-
JSContext : 一个可以独立于浏览器的 JS 执行环境。我们可以在向 JSContext 这个 JS 引擎执行环境里面注入 JS 变量、对象、函数。
// 1. JS 执行的上下文 JSContext *context = [[JSContext alloc] init]; // 2. 等于 var boolValue = false; JSValue *boolValue = [JSValue valueWithBool:NO inContext:context]; // boolValue = @(YES); NSLog(@"%@",[boolValue toBool] ? @"true" : @"false"); // false // 2. 等于 var doubleValue = 2.22; JSValue *doubleValue = [JSValue valueWithDouble:2.22 inContext:context]; NSLog(@"%lf",[doubleValue toDouble]); // 3. 等于 var intValue = 33; JSValue *intValue = [JSValue valueWithInt32:33 inContext:context]; NSLog(@"%zd",[intValue toInt32]); // intValue = @(3333); NSLog(@"%zd",[intValue toInt32]); // 4. 等于 var nullValue = null; JSValue *nullValue = [JSValue valueWithNullInContext:context]; NSLog(@"%@",nullValue); // 5. 等于 var undefinedValue = undefined; JSValue *undefinedValue = [JSValue valueWithUndefinedInContext:context]; NSLog(@"%@",undefinedValue); // 6. 等于 var obj = {name : "张三","age" : 22}; JSValue *objectValue = [JSValue valueWithObject:@{@"name" : @"zhangsan",@"age" : @22} inContext:context]; NSLog(@"%@",objectValue.toDictionary); // 7. 等于 var array = []; JSValue *arrayValue = [JSValue valueWithNewArrayInContext:context]; arrayValue[0] = @"this is the first array element."; NSLog(@"%@",arrayValue.toArray); // 8. 等于 var obj = {}; JSValue *objValue = [JSValue valueWithNewObjectInContext:context]; objValue[@"name"] = @"guoqingsong"; NSLog(@"%@",objValue.toDictionary);
-
JSValue 望文生义:表示的就是在 JSContext 中的 JS 变量 OC端的引用。毕竟是两门完全不同的语言。所以存在两种语言之间的数据转换关系。
下面的表格就是 OC 和 JS 之间的数据转换关系。
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
啥意思?
因为框架的主要目的,就是 OC 和 JavaScript 之间进行交互,交互无非就是两端的数据进行交互。
如果 OC 这边是一个 NSDictionary 对象,那么到了JS那边就是一个 Object。
如果 JS 那边是一个 Object 对象,那么到了 OC 这边就是一个 NSDictionary。
关于后面3个,我还没有研究到。先把这两个搞明白在说。
测试场景
我有一个本地的 HTML 文件。
<html>
<head>
<title></title>
</head>
<body>
<!-- 获取标签元素 -->
<label id="label">这是 HTML 的 Label 标签的字符串</label>
</body>
</html>
<script type="text/javascript">
// 获取 JS 变量
var name = "这是 JavaScript 的变量";
// 获取 JS 对象
var obj = {
name : "张三",
age : 22,
address : "湖北省武汉市"
};
// 获取 JS 函数
function showAlert(info) {
window.alert(info);
}
</script>
创建一个 UIWebView 去加载这个本地的 HTML 文件。
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"demo.html" ofType:nil];
NSString *html = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
[_webView loadHTMLString:html baseURL:nil];
已经知道了 JSContext 是一个可以独立于 UIWebView 本身的 JS 执行环境。但光光是 JSContext 自己在那玩,就没有意思了。开发 Hybrid 的主要目的是为了 WebView 和 OC 之间的交互。所以,我们需要WebView 的 JS 执行环境拿到手才行。
如何才能拿到 WebView 的 JS 执行环境呢?
- (void)webViewDidFinishLoad:(UIWebView *)webView {
// 页面加载完成
NSLog(@"%@",@"页面加载完成");
// 拿到了 WebView 的 JS 执行环境。
_context = [_webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
_context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"%@",exception);
};
}
在浏览器加载完毕 HTML 之后,通过 keyPath 的方式定位于
documentView.webView.mainFrame.javaScriptContext
。 即可拿到 WebView 的 JS 执行环境。
至于为什么?我不清楚。希望有知道原因的告知一二。
到目前为止,我们已经拿到了 WebView 基于 JavaScriptCore 的 JS 执行环境上下文。
现在,我们可以通过上下文,调用 JS 方法,也可以往 JS 上下文中注入 JS 变量、方法、对象等。
从 OC 到 JS,往 JS 环境中注入变量、方法、对象。
// 往 JS 环境中注入变量
_context[@"varA"] = @"这是 OC 往 JS 环境中注入的 JS 变量";
// 往 JS 环境中注入对象
_context[@"varObj"] = @{
@"name" : @"zhangsan",
@"age" : @22,
@"address" : @"hubeiwuhan"
};
// 往 JS 环境中注入JS函数
_context[@"jsFunc"] = ^(NSString *name) {
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"window.alert('%@')",name]];
};
_context[@"jsFunc"] = @"function(name){alert(name)}";
注意:一开始,我是这么往 JS 环境中注入 JS 函数的,发现并不好使。OC 往 JS 注入函数,还是得用 Block 的方式。
现在开始测试,往 JS 中注入的数据是否好使。
测试注入的变量
[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"alert('%@')",_context[@"varA"]]];
运行结果:
测试注入的对象:
[webView stringByEvaluatingJavaScriptFromString:@"alert(varObj.name + ' -- ' + varObj.age + ' -- ' + varObj.address )"];
测试注入的 JS 函数
[webView stringByEvaluatingJavaScriptFromString:@"jsFunc('bigMad')"];
目前从 OC 往 JS 环境中注入的变量、对象、函数都可以正常使用。
大致总结:我们往 JS 环境中注入的这些变量对象,都是全局的。等于是在 JS 编辑器中,声明了这些变量。
从 WebView 到 OC
已经在 UIWebView 中定义了
标签元素
<!-- 获取标签元素 -->
<label id="label">这是 HTML 的 Label 标签的字符串</label>
JS变量
// 获取 JS 变量
var name = "这是 JavaScript 的变量";
JS对象
// 获取 JS 对象
var obj = {
name : "张三",
age : 22,
address : "湖北省武汉市"
};
JS 函数
// 获取 JS 函数
function showAlert(info) {
window.alert(info);
}
由于 JSContext 本身就是 UIWebView 的浏览器的 JS 执行环境了。
所以,在它内部已经包含了这些数据。
如何获取呢?
使用 _context[key]
的方式。
获取 HTML 元素
// 从 HTML 获取数据
NSString *strValue = [webView stringByEvaluatingJavaScriptFromString:@"document.getElementById('label').innerText"];
NSLog(@"HTML 元素信息 %@",strValue);
运行结果:
2018-03-10 14:24:35.913 混合开发之-JavaScriptCore框架[39443:8105010] HTML 元素信息 这是 HTML 的 Label 标签的字符串
获取 JS 变量
// 从 JS 获取变量
JSValue *value = _context[@"name"];
NSLog(@"JS 变量 %@",value.toString);
运行结果:
2018-03-10 14:24:35.914 混合开发之-JavaScriptCore框架[39443:8105010] JS 变量 这是 JavaScript 的变量
获得 JS 对象
// 从 JS 获取对象
JSValue *obj = _context[@"obj"];
NSDictionary *dict = obj.toDictionary;
NSLog(@"JS对象 %@",dict);
运行结果:
2018-03-10 14:33:31.913 混合开发之-JavaScriptCore框架[39492:8132863] JS对象 {
name = 张三,
age = 22,
address = 湖北省武汉市,
}
获取 JS 函数并调用
// 获取 JS 方法
JSValue *func = _context[@"showAlert"];
[func callWithArguments:@[@"heheda"]];
运行结果:
大致总结:由于 JSContext 本质上就是 WebView 的 JS 执行环境了,我们可以把 JSContext 想想成浏览器中的 global 对象,说白了可以理解成为 window 对象。
上述的从 _context[key] 就等价于在浏览器中写 window.key 的方式。
最后说明一点个人的理解:
1.JSContext 和 H5 之间的交互,仅仅只是 JS 环境和 OC 的交互。不包括 HTML。也不是 request 捕获的那种方式。
当 JSContext 拿到 UIWebView 的 JS执行环境后,可以简单的把 JSContext 理解成浏览器里的全局 global 对象,也就是 window。
我们通过 JSContext 对象就想在 JS 中使用 window 对象来访问或者修改 JS 里的一些变量。