JavaScriptCore & UIWebView 交互一 JSContext & JSValue

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 & V8

JavaScriptCore框架 则是继承了 JavaScriptCore ,在苹果 iOS 7 引入的,该框架可以让 Objective-C 和 JavaScript 代码交互变的更加简单。

注意:初学者,一般的有点前端经验的,很容易迷惑的一点的。这里描述的仅仅是 Objective-CJavaScript 之间的交互。不包括 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"
```
  1. 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);
    
  2. 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 执行环境。
至于为什么?我不清楚。希望有知道原因的告知一二。

拿到 UIWebView 的 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"]]];

运行结果:

image.png

测试注入的对象:

    [webView stringByEvaluatingJavaScriptFromString:@"alert(varObj.name + ' -- ' + varObj.age + ' -- ' + varObj.address )"];
image.png

测试注入的 JS 函数

[webView stringByEvaluatingJavaScriptFromString:@"jsFunc('bigMad')"];
image.png

目前从 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"]];

运行结果:

image.png

大致总结:由于 JSContext 本质上就是 WebView 的 JS 执行环境了,我们可以把 JSContext 想想成浏览器中的 global 对象,说白了可以理解成为 window 对象。

上述的从 _context[key] 就等价于在浏览器中写 window.key 的方式。


最后说明一点个人的理解:

1.JSContext 和 H5 之间的交互,仅仅只是 JS 环境和 OC 的交互。不包括 HTML。也不是 request 捕获的那种方式。

  1. 当 JSContext 拿到 UIWebView 的 JS执行环境后,可以简单的把 JSContext 理解成浏览器里的全局 global 对象,也就是 window。

  2. 我们通过 JSContext 对象就想在 JS 中使用 window 对象来访问或者修改 JS 里的一些变量。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343