zoukankan      html  css  js  c++  java
  • 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

     第二种方式: 待更新!

  • 相关阅读:
    《求医不如求己》1,2,3全集下载(附人体穴位图)
    Lambda 表达式(C# 编程指南)
    .NET生成静态页面的方案总结
    匿名函数(C# 编程指南)
    NameValueCollection详解
    sql数据库压缩的全面解析
    人的一生
    抓取博客园列表数据
    相对论的时空观
    C#中搜索关键词高亮显示
  • 原文地址:https://www.cnblogs.com/dianming/p/7160534.html
Copyright © 2011-2022 走看看