JavaScriptCore 框架详细解析(二) —— JS与OC通信

版本记录

版本号 时间
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 

然后,我们先看一下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代码如下:


    

这里是第一种方式



// 在项目的控制器中实现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 
#import 

@protocol JSExportProtocol 

//求两个数的和

- (NSInteger)add:(NSInteger)a b:(NSInteger)b;

@property (nonatomic, assign) NSInteger sum;

@end
//自定义对象并遵守上面的协议

2. JSExportObj.h
#import 
#import "JSExportProtocol.h"

@interface JSExportObj : NSObject 

@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 
#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)"];

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

后记

未完,待续~~~

JavaScriptCore 框架详细解析(二) —— JS与OC通信_第1张图片

你可能感兴趣的:(JavaScriptCore 框架详细解析(二) —— JS与OC通信)