iOS之webView与JS交互篇

JavaScriptCore 框架详细解析(二) —— JS与O

2017-10-15  本文已影响133人  刀客传奇

版本记录

版本号 时间
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与原生交互的开源库很多都是用得这种方式来实现的,例如: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)"];

运行一下,你可以得到一样的结果。

后记

未完,待续~~~

上一篇下一篇

猜你喜欢

热点阅读