OC与js的交互 - javascriptCore

前言

鉴于很多小伙伴最近老是问我关于js与OC交互的问题,原本打算先写完CoreAnimation的我,决定先吧这块知识点整理出来,毕竟核心动画的知识的确比较多,不是那么容易写完。

OC与js的交互方式

OC与js的交互方式有好几种,不借助第三方框架的情况下,我们有两个选择,webkit和javascriptCore。

方法一:通过webkit进行OC与js的交互

没认识JavaScriptCore之前,如果想在OC中使用JavaScript代码,一般都是在webview(webview内置webkit引擎,解析JavaScript代码)

主要方法就是在webView的代理中执行:
stringByEvaluatingJavaScriptFromString:@"JS的方法名"这个方法,即可在webView中调用js的函数
同时,通过代理方法webView:shouldStartLoadWithRequest: navigationType:监听由js在内部定义的类似重定向的消息,并在OC中执行相关的代码

代码如下:

#import "ViewController.h"
@interface ViewController ()<UIWebViewDelegate>
@property (nonatomic, strong)UIWebView * webView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    self.title = @"Web Test";
    [self.view addSubview:self.webView];
}

-(UIWebView *)webView{
    if (!_webView) {
        _webView = [[UIWebView alloc] initWithFrame:[UIScreen mainScreen].bounds];
        _webView.delegate = self;
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@".html"];
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:filePath]];
        [_webView loadRequest:request];
    }
    return _webView;
}

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{

//      判断js是否需要OC调用相关的方法.
    if ([request.URL.absoluteString hasSuffix:@"clickLoginBtn"]) {
//      向js传递数据
        [_webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"loginCallBack('I am id from app','I am token from app')"]];
//      从js获取数据
        NSString *webData = [_webView stringByEvaluatingJavaScriptFromString:@"returnData();"];
        NSLog(@"%@",webData);

        [_webView stopLoading];
    }
    return YES;
}

js的实现如下:

<!DOCTYPE HTML>
<html>

    <head>

        <script>
            function loginCallBack(id,token){
                var x=document.getElementById("logindata");
                alert(id)
                alert(token)
            }
            </script>

    </head>

    <body>
        <h2 id="logindata" align= center>Clict To Transmit Data</h2>
        <button id="hello" onclick="buttonClick()" >login</button>
        <script >
            function buttonClick()
            {
         //webview重定向
                document.location = "clickLoginBtn"
            }  
            function returnData(){
                return document.getElementById("logindata").script
            }
        </script>
    </body>
</html>

这种方式可以解决大多数不需要太过复杂的OC与js交互的需求,但对于比较复杂,或者OC与js交互频繁的情况下,更推荐大家使用方法二。

方法二:通过javascriptCore进行OC与js的交互

javascriptCore简介

这是javascriptCore的头文件
这里写图片描述
在头文件中,我们可以看到,javascriptCore中有5个类,分别是

  • JSVirtualMachine
    JSVirtualMachine顾名思义,是javaScript的虚拟机,是为JSContext提供运行资源。

  • JSManagedValue
    主要是作为一个引用桥接,将 JSValue 转为 JSManagedValue 类型后,可以添加到 JSVirtualMachine 对象中,这样能够保证你在使用过程中 JSValue 对象不会被释放掉,当你不再需要该 JSValue 对象后,从 JSVirtualMachine 中移除该 JSManagedValue 对象,JSValue 对象就会被释放并置空。

  • JSContext
    JSVirtualMachine为JavaScript的运行提供了底层资源,JSContext就为其提供着运行环境。通过- (JSValue )evaluateScript:(NSString )script;方法就可以执行一段JavaScript脚本,并且如果其中有方法、变量等信息都会被存储在其中以便在需要的时候使用。而JSContext的创建都是基于JSVirtualMachine:- (id)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine;,如果是使用- (id)init;进行初始化,那么在其内部会自动创建一个新的JSVirtualMachine对象然后调用前边的初始化方法。

  • JSValue
    JSValue则可以说是JavaScript和Object-C之间互换的桥梁,它提供了多种方法可以方便地把JavaScript数据类型转换成Objective-C,或者是转换过去。

  • JSExport
    JSExport是一个协议,让JSContext运行环境中的JavaScript 可以识别该协议中定义的实例方法、类方法、属性等,让objective-c/swift与JavaScript能够自动交互;

通过OC执行js方法或调取js属性。

下面通过JSContext的evaluateScript方法模拟执行js代码,写一些简单的demo。
例子:

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var arr = [1, 2, 'This is js string'];var sum = function(a, b) { return a+b;}"];
JSValue *jsArray = context[@"arr"];
JSValue *jsSum = context[@"sum"];

JSValue *jsSumResult = [jsSum callWithArguments:[NSArray arrayWithObjects:@12, @33, nil]];

示例代码里的,JSValue *jsArray,jsArray对应着javaScript中的一个 array对象:arr。所以我们可以对jsArray进行一些操作,从而操作javaScript 中的 “arr”。

例如:
jsArray[0];//1
jsArray[2];//This is js string
jsArray[1] = 49;//修改arr 的第二个元素。
jsArray[@”length”];//结果是3,调用js arr对象的方法。

又比如我们示例代码里的,jsSum,对应着sum function,因此我们可以通过操作jsSum从而调取javaScript中的 “sum function”。

JSValue *jsSumResult = [jsSum callWithArguments:[NSArray arrayWithObjects:@12, @33, nil]];

如同这个例子,我们可以方便的通过JSValue对象的 callWithArguments:方法来直接调取 js 的 function。js function的多参数,在OC中,由NSArray组装而成。

通过js执行OC方法或调取OC属性。

有两种方式可以方便的通过js 调用 OC:
  1. Block 用来调用方法。
    我们有一个OC方法,提供给js调用

    - (NSInteger)sumWithA:(NSInteger)a B:(NSInteger)b C:(NSInteger)c
    {
    return a + b + c;
    }
    
    - (void)jsToOcFunction
    {
    //需要写的代码
    JSContext *context = [[JSContext alloc] init];
    context[@"sumNums"] = ^(NSInteger a, NSInteger b, NSInteger c) {
        return [self sumWithA:a B:b C:c];
    };
    //模拟执行js代码
    JSValue *sum = [context evaluateScript:@"sumNums(7, 56, 22)"];
    NSLog(@"sum  %@", sum);//sum  85
    }

    Block方式需要特别注意的是:

    1. 不论在任何情况下,不要在Block中直接使用外面的JSValue对象, 而应该把JSValue当做参数来传进Block中。
    2. 不论在任何情况下,不要在Block中直接使用外面的JSContext对象, 而应该使用 [JSContext currentContext]获取。
  2. JSExport protocol 用来调用对象。
    使用rumtime 为一个系统控件UIButton增加JSExport protocol

    @protocol UIButtonExport <JSExport>
    - (void)setTitle:(NSString *)title forState:(UIControlState)state;
    @end
    
    - (void)changeTitle
    {
    class_addProtocol([UIButton class], @protocol(UIButtonExport));
    
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    [button setTitle:@"你好 OC" forState:UIControlStateNormal];
    button.frame = CGRectMake(100, 100, 100, 100);
    [self.view addSubview:button];
    
    JSContext *context = [[JSContext alloc] init];
    context[@"button"] = button;
    [context evaluateScript:@"button.setTitleForState('你好 js', 0)"];
    }

    通过runtime的方式增加JSExport protocol之外,还可以通过category的方式,比如:

//UIButton+js.h
#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol UIButtonExport <JSExport>
@property (nonatomic,assign) int index;
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
@end

@interface UIButton (js) <UIButtonExport>
@property (nonatomic,assign) int index;
- (void)setTitle:(NSString *)title forState:(UIControlState)state;
@end

可以看到,如果想要在js中调用OC 的类或者对象的方法,需要将方法在JSExport protocol中声明。

当然上面这些只是一些演练,我们在实际操作中会有些不同。

javascriptCore 实战

在最近的斑马王国2.0的app中,支付中有优惠券的使用,而优惠券是放在H5进行实现的,有个简单的OC与js的交互。
当用户点击app中的优惠券时,打开优惠券的网页,在优惠券页面中点击对应的优惠券,则将对应的优惠券选中,传回app并跳转回支付页面。

在这次的开发中,我使用了block的方式进行了实现。实现过程中,我发现JSContext的这个上下文似乎应该从页面获取,经过打印webView的相关信息,发现我们可以通过这样的方式获取javascriptContext:
[webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

下面是在优惠券的h5页面声明OC方法的代码:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSLog(@"加载成功");

    if (_jsContext == nil) {
        // 1.
        _jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

        // 2. 关联打印异常
        _jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
            context.exception = exceptionValue;
            DDLogVerbose(@"异常信息:%@", exceptionValue);
        };

        //声明js调用的OC方法
        __weak ZBWebViewController *weakSelf = self;
        //选择优惠券代码
        _jsContext[@"selectCoupons"] = ^(NSString *couponsName, NSString *couponsCode, int selected) {
            return [weakSelf selectCoupons:couponsName couponsCode:couponsCode selected:selected];
        };
        //跳转立即体验代码
        _jsContext[@"gotoExperience"] = ^(){
            return [weakSelf gotoExperience];
        };
    }
}

我在网页加载成功的时候,来获取这个准确的JSContext,同时声明了我们将要在JS中调用的OC方法,并进行了异常信息的打印。

下面是OC中执行的优惠券选择代码:

/**
 *  选择优惠券
 *
 *  @param couponsName 优惠券名称
 *  @param excCode     优惠券码
 *  @param selected    是否选中优惠券
 */
- (void)selectCoupons:(NSString *)couponsName couponsCode:(NSString *)excCode selected:(int)selected
{
    NSLog(@"-- 选择优惠券 ---couponsName:%@---disCode:%@---selected:%d",couponsName,excCode,selected);
    \\执行跳转并传值。
}

而在JS中,我们只需要在选中优惠券的时候,selectCoupons(couponsName,excCode,select);这样调用即可。

总结

通过javascriptCore,我们可以随意的在JS与OC中进行切换。
1、在OC/swift里,所有JavaScript代码都可以在JavaScript运行环境(JSContext)中通过evaluateScript运行;

2、在OC/swift里,所有JavaScript中的方法、对象、属性都可以通过objectForKeyedSubscript(类似字典的方式,context.objectForKeyedSubscript("Person") == context[@"Person"],上面的演示都用的是后者)来取得,取得所有对象均为JSValue类型

3、通过objectForKeyedSubscript取得的JavaScript中的对象,都遵循该对象在JavaScript中有的所有特性,如数组的长度,无数组越界,自动延展的特性

4、通过objectForKeyedSubscript取得的JavaScript中的方法,均可以通过callWithArguments传入参数调用JavaScript中的方法并返回正确的结果(类型仍然为JSValue,可以通过toObject的方式转化为对应的OC对象)

5、补充一点:除了通过objectForKeyedSubscript取得JavaScript对象外,我们也可以通过 setObjectForKeyedSubscript的方式给JavaScript中传递类型或对象(传递类型:context[@”Book”] = [Book class];传递对象:context[@”book”] = book),传递完成的对象或者类型,可以在JavaScript中直接使用,类似button.setTitleForState('你好 js', 0)

6、我们可以通过继承JSExport的中自定义协议(@protocol)(定义的属性、方法)的方式,让给任意类型添加可以在JavaScript中访问到的属性、方法,方便JS直接进行点操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值