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 & UIWebView 交互一 JSContext & JSValue_第1张图片
JavaScriptCore & V8

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

注意:初学者,一般的有点前端经验的,很容易迷惑的一点的。这里描述的仅仅是 Objective-CJavaScript 之间的交互。不包括 HTML。也不是用 request 捕获的方式交互。

JavaScriptCore 框架本质上是基于 WebKit 中以 c/c++ 实现的 JavaScript 引擎的一个面向 OC 的面向对象框架的封装。


JavaScriptCore 的使用方式。

首先要导入 #import
在这个框架里面,我们看到了几个头文件。

```
#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 文件。



    


    
    




创建一个 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 执行环境。
至于为什么?我不清楚。希望有知道原因的告知一二。

JavaScriptCore & UIWebView 交互一 JSContext & JSValue_第2张图片
拿到 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"]]];

运行结果:

JavaScriptCore & UIWebView 交互一 JSContext & JSValue_第3张图片
image.png

测试注入的对象:

    [webView stringByEvaluatingJavaScriptFromString:@"alert(varObj.name + ' -- ' + varObj.age + ' -- ' + varObj.address )"];
JavaScriptCore & UIWebView 交互一 JSContext & JSValue_第4张图片
image.png

测试注入的 JS 函数

[webView stringByEvaluatingJavaScriptFromString:@"jsFunc('bigMad')"];
JavaScriptCore & UIWebView 交互一 JSContext & JSValue_第5张图片
image.png

目前从 OC 往 JS 环境中注入的变量、对象、函数都可以正常使用。
大致总结:我们往 JS 环境中注入的这些变量对象,都是全局的。等于是在 JS 编辑器中,声明了这些变量。


从 WebView 到 OC

已经在 UIWebView 中定义了

标签元素




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"]];

运行结果:

JavaScriptCore & UIWebView 交互一 JSContext & JSValue_第6张图片
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 里的一些变量。

你可能感兴趣的:(JavaScriptCore & UIWebView 交互一 JSContext & JSValue)