OC与js的交互 - javascriptCore

来源:互联网 发布:传奇数据库下载 编辑:程序博客网 时间:2024/05/17 17:46

前言

鉴于很多小伙伴最近老是问我关于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
原创粉丝点击