iOS下JS与OC互相调用(四)--JavaScriptCore

1、简要介绍JavaScriptCore

JavaScriptCore是一个iOS 7 新添加的框架,使用前需要先导入JavaScriptCore.framework
然后我们在JavaScriptCore.h中可以看到,该框架主要的类就只有五个:

1.1 JSVirtualMachine 
JSVirtualMachine看名字直译是JS 虚拟机,也就是说JavaScript是在一个虚拟的环境中执行,而JSVirtualMachine为其执行提供底层资源。

1.2 JSContext
JSContext是为JavaScript的执行提供运行环境,所有的JavaScript的执行都必须在JSContext环境中。JSContext也管理JSVirtualMachine中对象的生命周期。每一个JSValue对象都要强引用关联一个JSContext。当与某JSContext对象关联的所有JSValue释放后,JSContext也会被释放。
通常情况下我们一般都这样创建JSContext:
//  通过webView的获取JSContext。
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
1.3 JSValue
JSValue都是通过JSContext返回或者创建的,并没有构造方法。JSValue包含了每一个JavaScript类型的值,通过JSValue可以将Objective-C中的类型转换为JavaScript中的类型,也可以将JavaScript中的类型转换为Objective-C中的类型。
OC、JSValue、JavaScript的类型对应关系表:
 
1.4 JSManagedValue
JSManagedValue主要用途是解决JSValue对象在Objective-C 堆上的安全引用问题。把JSValue 保存进Objective-C 堆对象中是不正确的,这很容易引发循环引用,而导致JSContext不能释放。 这个类主要是将JSValue对象转换为JSManagedValue的API,而且也不常用,就不做具体介绍了。
1.5 JSExport
JSExport是一个协议类,但是该协议并没有任何属性和方法。 怎么使用呢? 我们可以自定义一个协议类,继承自JSExport。无论我们在JSExport里声明的属性,实例方法还是类方法,继承的协议都会自动的提供给任何 JavaScript 代码。 So,我们只需要在自定义的协议类中,添加上属性和方法就可以了。

JS调用OC分两种情况

一,js里面直接调用方法(Block方式)

二,js里面通过对象调用方法(JSExport协议方式)

先说第一种吧:

创建UIWebView:

self.webView = [[UIWebView alloc] initWithFrame:self.view.frame];
    self.webView.delegate = self;
    NSURL *htmlURL = [[NSBundle mainBundle] URLForResource:@"index.html" withExtension:nil];
//    NSURL *htmlURL = [NSURL URLWithString:@"http://www.baidu.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:htmlURL];

    // 如果不想要webView 的回弹效果
    self.webView.scrollView.bounces = NO;
    // UIWebView 滚动的比较慢,这里设置为正常速度
    self.webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
    [self.webView loadRequest:request];
    [self.view addSubview:self.webView];

HTML的内容也大致一样,不过JS的调用有些区别,更简单了。

function shareClick() {
    share('测试分享的标题','测试分享的内容','url=http://www.baidu.com');
}

function shareResult(channel_id,share_channel,share_url) {
    var content = channel_id+","+share_channel+","+share_url;
    asyncAlert(content);
    document.getElementById("returnValue").value = content;
}

function locationClick() {
    getLocation();
}

function setLocation(location) {
    asyncAlert(location);
    document.getElementById("returnValue").value = location;
}
添加JS要调用的原生OC方法
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSLog(@"webViewDidFinishLoad");

    [self addCustomActions];
}

将所有要添加的功能方法,集中到一个方法addCustomActions中,便于维护

#pragma mark - private method
- (void)addCustomActions
{
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    [self addScanWithContext:context];

    [self addLocationWithContext:context];

    [self addSetBGColorWithContext:context];

    [self addShareWithContext:context];

    [self addPayActionWithContext:context];

    [self addShakeActionWithContext:context];

    [self addGoBackWithContext:context];
}

然后每一个小功能独立开来,这样修改和解决Bug的时候能够快速定位到某个功能

- (void)addShareWithContext:(JSContext *)context
{
    __weak typeof(self) weakSelf = self;
    context[@"share"] = ^() {
        NSArray *args = [JSContext currentArguments];

        if (args.count < 3) {
            return ;
        }
//        这个地方取到的值是jsvalue,需要转化一下
        NSString *title = [args[0] toString];
        NSString *content = [args[1] toString];
        NSString *url = [args[2] toString];
        // 在这里执行分享的操作...

        // 将分享结果返回给js
        NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
        [[JSContext currentContext] evaluateScript:jsStr];
    };
}

 

注意: 1.JS要调用的原生OC方法,可以在viewDidLoad webView被创建后就添加好,但最好是在网址加载成功后再添加,以避免无法预料的乱入Bug。
2.block 中的执行环境是在子线程中。奇怪的是竟然可以更新部分UI,例如给view设置背景色,调用webView执行js等,但是弹出原生alertView就会在控制台报子线程操作UI的错误信息。
3.避免循环引用,因为block 会持有外部变量,而JSContext也会强引用它所有的变量,因此在block中调用self时,要用__weak 转一下。而且在block内不要使用外部的context 以及JSValue,都会导致循环引用。如果要使用context 可以使用[JSContext currentContext]。当然我们可以将JSContext 和JSValue当做block的参数传进去,这样就可以使用啦。
 
OC调用JS方法:

OC调用JS方法就有多种方式了。首先介绍使用JavaScriptCore框架的方式。
方式1 
使用JSContext的方法-evaluateScript,可以实现OC调用JS方法。
下面是一个调用JS中payResult方法的示例代码:

NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[[JSContext currentContext] evaluateScript:jsStr];

方式2 
使用JSValue的方法-callWithArguments,也可以实现OC调用JS方法。
下面这个示例代码依然是调用JS中的payResult:

JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

[context[@"payResult"] callWithArguments:@[@"支付弹窗"]];

当然,如果是在执行原生OC方法之后,想要在OC执行完操作后,将结果回调给JS时,可以这样写:

- (void)addPayActionWithContext:(JSContext *)context
{
    context[@"payAction"] = ^() {
        NSArray *args = [JSContext currentArguments];
        
        if (args.count < 4) {
            return ;
        }
        
        NSString *orderNo = [args[0] toString];
        NSString *channel = [args[1] toString];
        long long amount = [[args[2] toNumber] longLongValue];
        NSString *subject = [args[3] toString];
        
        // 支付操作
        NSLog(@"orderNo:%@---channel:%@---amount:%lld---subject:%@",orderNo,channel,amount,subject);
        
        // 将支付结果返回给js
//        NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
//        [[JSContext currentContext] evaluateScript:jsStr];
        //自动回调
        [[JSContext currentContext][@"payResult"] callWithArguments:@[@"支付成功"]];
    };
}
方式3      利用UIWebView的API:
NSString *jsStr = [NSString stringWithFormat:@"payResult('%@')",@"支付成功"];
[self.webView stringByEvaluatingJavaScriptFromString:jsStr];

第一种方式到此介绍完毕.

下面介绍第二种<JSExport>协议的方式:

首先我们新建一个类,JSNativeMethod:

.h 声明

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>


//首先创建一个实现了JSExport协议的协议
@protocol TestJSObjectProtocol <JSExport>

//此处我们测试几种参数的情况
- (NSString *)imgCallBack:(NSString *)url;

// 通过JSON传过来
- (void)callWithDict:(NSDictionary *)params;

@end

@interface JSNativeMethod : NSObject<TestJSObjectProtocol>

@end

.m实现

@implementation JSNativeMethod

- (NSString *)imgCallBack:(NSString *)url {
  NSLog(@"touch image %@",url);
  return @"iOS To H5";
}
- (void)callWithDict:(NSDictionary *)params {
  
  NSLog(@"%@",params);
  JSValue *jsFunc = self.jsContext[@"uploadimage"];
  [jsFunc callWithArguments:@[@{@"image":@"image upload success"}]];
}
@end

然后在WebView代理方法里:

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
  // 获取当前JS运行环境
  self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
  
  /  JSNativeMethod *call = [[JSNativeMethod alloc] init];
  //将JSNativeMethod封装到JavaScript函数Native()中
  self.jsContext[@"Native"] = call;
//JSContext 还有另外一个有用的招数:通过设置上下文的 exceptionHandler 属性,你可以观察和记录语法,类型以及运行时错误。 exceptionHandler 是一个接收一个 JSContext 引用和异常本身的回调处理
  self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
    context.exception = exceptionValue;
    NSLog(@"异常信息:%@", exceptionValue);
  };
}

利用这种方式:需要js那边同样需要Native:

//app端callback callWithDict()
function callWithDict(a) {
log(JSON.stringify(a));
window.Native.callWithDict(a);
}

OK,基本结束,这种方式demo还没整理好,只是在我之前项目做了部分修改,因涉及源码,demo暂时不上,以后补上.

 第一种方式:demo地址(Block形式):https://github.com/domanc/JS_OC_JavaScriptCore.git

 第二种方式: 待更新!

 

posted @ 2017-07-13 15:00  iOS_Doman  阅读(594)  评论(0编辑  收藏  举报