Objective-C与JS交互

Author Avatar
XibHe 7月 13, 2016
  • 在其它设备中阅读本文章

更新说明

更新记录:

  • 2016 年 7 月,第一版。
  • 2017 年 8 月,增加OC调用JS方法相关介绍。

JavaScriptCore简介

JavaScriptCore是iOS7引入的新功能,使用JavaScriptCore后可以实现js代码与本地native代码进行相互调用。

要使用JavaScriptCore,首先我们需要引入它的头文件 #import <JavaScriptCore/JavaScriptCore.h>

这个头里面引入了几个重要的对象

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
  • JSContext是JavaScript的运行上下文,他主要作用是执行js代码和注册native方法接口
  • JSValue是JSContext执行后的返回结果,他可以是任何js类型(比如基本数据类型和函数类型,对象类型等),并且都有对象的方法转换为native对象。
  • JSManagedValue是JSValue的封装,用它可以解决js和原声代码之间循环引用的问题
  • JSVirtualMachine 管理JS运行时和管理js暴露的native对象的内存
  • JSExport是一个协议,通过实现它可以完成把一个native对象暴漏给js

具体的交互过程可以参见这篇博客https://imciel.com/2016/06/18/oc-js-communication/

OC与JS交互的方式

OC与js交互,主要涉及到两方面:

  • OC调用JS方法,将本地JS需要的值传递过去,供JS函数调用;
  • JS调用OC的native方法,将JS函数中的返回值传递给本地方法,执行相应操作;
    下面将针对以上两条交互方式,展开来说。

    OC调用JS方法

    通过UIWebView展示JS页面,在UIWebView的代理方法中通过执行stringByEvaluatingJavaScriptFromString方法将JS代码执行结果以字符串方式返回,
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *text = aControl.titleLabel.text;
    NSString *jsMethod = [NSString stringWithFormat:@"ocScrollToElementByName('%@')", text];
    [self.contentView stringByEvaluatingJavaScriptFromString:jsMethod];
}

也可以在当前加载webView页面类的

- (void)viewWillAppear:(BOOL)animated

方法中调用JS方法,执行相关操作,如:

传递参数

 // JS交互,传gid,刷新JS页面商品数量
    NSString *gid = [[NSUserDefaults standardUserDefaults] objectForKey:@"GoodInfoH5Gid"];
    if (gid && gid.length > 0) {
        NSString *jsMethod = [NSString stringWithFormat:@"updateGoodNum('%@')",gid];
        [_webView stringByEvaluatingJavaScriptFromString:jsMethod];
    }

局部刷新列表

// 局部刷新
[_webView stringByEvaluatingJavaScriptFromString:@"updatecartnumAndTotalPay()"];

JS调用OC的native方法

通过JavaScriptCore进行交互,需要在webView的加载完成的代理方法中设置交互上下文-JSContext,并将JS中的交互对象赋给当前类。下面将叙述如何使用JSExport设置引用名称来进行交互,使用JSExport引用名称空间后,对于调用了哪些JS方法就一目了然了。

JSExport引用名称空间交互设置

js那边统一使用一个名为jsObject的对象来调用js的方法进行传值或触发某一特定的事件。在 js 中定义一个方法:

<html>
<head>
<title>Demo</title>
<script type="text/javascript">
    function setContent(){

        jsObject.shopCartNumChanged(totalNum);
    }    
</script>
</head>
<body onload="javascript:setContent('ios is: ' + typeof ios)">
</body>
</html>

当点击 js 界面上的一个 “+” 号时,js 那边会查找 OC 代码通过JSContext注册的名为jsObject.shopCartNumChanged的调用方法。现在问题来了,在 OC 中该如何注册该方法呢?答案是使用语言穿梭机—JSExport协议。比如,我有一个 ShopCarViewController 的类。在.h中声明一个名为 MallJSExports 的协议。

// ShopCarViewController.h

#import <JavaScriptCore/JavaScriptCore.h>

@protocol MallJSExports <JSExport>
- (void)shopCartNumChanged:(NSString*)shopNum;  //购物车数量变化
- (void)orderGoBackToNative;                   //订单返回按钮
@end

@interface ShopCarViewController : MallViewController

@end

在.m中当然要声明并实现该协议的方法。

@interface ShopCarViewController()<UIWebViewDelegate,MallJSExports>{

}

@property (nonatomic, strong) UIWebView *webView;
@property (nonatomic, copy) NSString *shopNum;       //商品数量
@end

@implementation ShopCarViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.navigationItem.title = @"购物车";

    _webView = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, screenWidth, self.view.height-49)];
    [_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:_webUrl]]];
    _webView.delegate = self;
    _webView.detectsPhoneNumbers = NO;
    [self.view addSubview:_webView];
}

#pragma mark - 在webView加载完成的代理方法里设置JSContext
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    //JS上下文对象
    JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    context[@"jsObject"] = self;
}

#pragma mark - 购物车数量变化
- (void)shopCartNumChanged:(NSString *)shopNum
{
    CLog(@"jsString shopNum = %@",shopNum);
    [[NSNotificationCenter defaultCenter] postNotificationName:ShopCartNumIsChanged object:shopNum];
}

#pragma mark - 订单返回按钮
- (void)orderGoBackToNative
{
    [self.navigationController popViewControllerAnimated:YES];
}

这里要注意的是 context[@ “jsObject” ] = self 中的的key值是和服务器商量好的, 即, js 中定义的回调方法 jsObject.shopCartNumChanged( )相一致。

在这里对 JSExport 的使用只是简单的设置了一下命名空间,将下标方法暴露给js对象方便服务器调用。其实, JSExport 协议主要用途是把objc复杂对象转换成JSValue并暴露给js对象。 JSExport 作为两种语言的互通协议。 JSExport 中没有约定任何的方法,连可选的(@optional)都没有,但是所有继承了该协议(@protocol)的协议(注意不是Objective-C的类(@interface))中定义的方法,都可以在JSContext中被使用。

补充

关于使用WKWebView进行交互。调用JS函数:

[self.wkwebView evaluateJavaScript:@"refreshList()" completionHandler:^(id _Nullable rr, NSError * _Nullable error) {
 }];

参考资料

使用 JavaScriptCore 实现 JS和OC间的通信

JavaScriptCore框架在iOS7中的对象交互和管理

iOS JavaScriptCore使用

示例代码下载

Demo

–EOF–

若无特别说明,本站文章均为原创,转载请保留链接,谢谢