iOS 开发 Object-C和JavaScript交互详解之OC与JS交互在JavaScriptCore中使用

本文详细介绍了JavaScriptCore框架在iOS开发中如何实现Objective-C与JavaScript的交互,包括JSContext、JSValue、JSManagedValue等核心概念的使用,以及在Web容器中的应用,提供OC调用JS和JS调用OC的方法示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaScriptCore简介

JSCore全称为 JavaScriptCore,是苹果公司在iOS中加入的一个新的framework。该framework为OC与JS代码相互操作的提供了极大的便利。该工程默认是没有导入工程中的,需要我们手动添加。

这里写图片描述

添加完成后,我们可以看到JavaScriptCore.h中包含以下5个主要的文件。

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"

JSContext

  • JSContext代表一个JavaScript的执行环境的一个实例。所有JavaScript执行都是在上下文内进行。
  • JSContext还用于管理对象的生命周期内JavaScript的虚拟机(API文档原文翻译)。作为上下文很多地方我们的都有可能使用过,比如CoreData,CoreGraphics等等,那么上下文对象到底是什么呢?你可以理解为是一个两者的连接桥梁,如图所示.
    这里写图片描述

JSValue

JSValue:是OC和JavaScript值相互转化的桥梁。他提供了很多方法把OC和JavaScript的数据类型进行相互转化。JSValue的主要作用就是用来接收JSContext执行后的返回结果,当然了,JSValue可以是js的任意类型,例如,js中的变量,对象以及函数. 其一一对应关系如下表所示:
这里写图片描述

JSManagedValue

JSManagedValue:JSValue的包装类。JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。

如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。

JSVirtualMachine

JSVirtualMachine: JS运行的虚拟机。可以支持并行的JavaScript执行,管理JavaScript和OC转换中的内存管理。

JSExport

JSExport:一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。

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

基本类型转换

JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
NSLog(@"JSValue: %@, int: %d", jsVal, iVal);
 
//Output:
//  JSValue: 28, int: 28

还可以存一个JavaScript变量在JSContext中,然后通过下标来获取出来。而对于Array或者Object类型,JSValue也可以通过下标直接取值和赋值。

NSArray *nsArr = [jsArr toArray];
NSLog(@"NSArray: %@", nsArr);
 
//Output:
//  JS Array: 21,7,iderzheng.com    Length: 3
//  JS Array: 21,blog,iderzheng.com,,,,,7    Length: 8
//  NSArray: (
//   21,
//   blog,
//   "iderzheng.com",
//   "<null>",
//   "<null>",
//   "<null>",
//   "<null>",
//   7
//   )

通过输出结果很容易看出代码成功把数据从Objective-C赋到了JavaScript数组上,而且JSValue是遵循JavaScript的数组特性:无下标越位,自动延展数组大小。并且通过JSValue还可以获取JavaScript对象上的属性,比如例子中通过"length"就获取到了JavaScript数组的长度。在转成NSArray的时候,所有的信息也都正确转换了过去。

方法的转换

各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。比如在前端开发中常用的log方法,虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟

JSContext *context = [[JSContext alloc] init];
context[@"log"] = ^() {
    NSLog(@"+++++++Begin Log+++++++");
 
    NSArray *args = [JSContext currentArguments];
    for (JSValue *jsVal in args) {
        NSLog(@"%@", jsVal);
    }
 
    JSValue *this = [JSContext currentThis];
    NSLog(@"this: %@",this);
    NSLog(@"-------End Log-------");
};
 
[context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
 
//Output:
//  +++++++Begin Log+++++++
//  ider
//  7,21
//  [object Object]
//  this: [object GlobalObject]
//  -------End Log-------

通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表(+ (NSArray *)currentArguments)和当前调用该方法的对象(+ (JSValue *)currentThis)。上边的例子中对于"this"输出的内容是GlobalObject,这也是JSContext对象方法- (JSValue *)globalObject;所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。

Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;方法可以反过来将参数传进去来调用方法。

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"function add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func:  %@", add);
 
JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
NSLog(@"Sum:  %d",[sum toInt32]);
//OutPut:
//  Func:  function add(a, b) { return a + b; }
//  Sum:  28

JSValue还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContext的globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue对象调用。

异常处理

Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler,它接受的是^(JSContext *context, JSValue *exceptionValue)形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
};

我们也可以给exceptionHandler赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:

JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
    NSLog(@"%@", exception);
    con.exception = exception;
};
 
[context evaluateScript:@"ider.zheng = 21"];
 
//Output:
//  ReferenceError: Can't find variable: ider

OC中调用JS方法

案例一:我在js中定义了一个函数add(a,b),我们需要在OC中进行调用。

-(void)ObjectCCallJavaScriptCore {
    self.context = [[JSContext alloc] init];

    NSString *js = @"function add(a,b) {return a+b}";
    
    [self.context evaluateScript:js];
    JSValue *addJS = self.context[@"add"];

    JSValue *sum = [addJS callWithArguments:@[@(10),@(17)]];
    NSInteger intSum = [sum toInt32];
    NSLog(@"intSum: %zi",intSum);
}

2.JS中调用OC方法

JS中调用OC有两种方法,第一种为block调用,第二种为JSExport protocol。
案例二:我们在OC中定义了一个如下方法,我们需要在JS中对它进行调用

-(NSInteger)add:(NSInteger)a and:(NSInteger)b {    
    return  a+b;
}

block调用

-(void)JSCallOC_block {
    self.context = [[JSContext alloc] init];

    __weak typeof(self) weakSelf = self;
    //add函数方法
    self.context[@"add"] = ^NSInteger(NSInteger a, NSInteger b){
        return [weakSelf add:a and:b];
    };
    //jsvaule给add函数传值
    JSValue *sum = [self.context evaluateScript:@"add(4,5)"];
    NSInteger intSum = [sum toInt32];
    NSLog(@"intSum: %zi",intSum);
}

JSExport protocol

第一步:定义一个遵守JSExport的AddJSExport协议。

@protocol AddJSExport //用宏转换下,将JS函数名字指定为add;
JSExportAs(add, - (NSInteger)add:(NSInteger)a and:(NSInteger)b);
@property (nonatomic, assign) NSInteger sum;
@end

第二步:新建一个对象AddJSExportObj,去实现以上协议。

AddJSExportObj.h
@interface AddJSExportObj : NSObject@property (nonatomic, assign) NSInteger sum;
@end
AddJSExportObj.m
@implementation AddJSExportObj
-(NSInteger)add:(NSInteger)a and:(NSInteger)b{
    return a+b;
}
@end

第三步:在VC中进行JS调用

-(void)JSCallOC_JSExport{
    self.context = [[JSContext alloc] init];

    //异常处理
    self.context.exceptionHandler = ^(JSContext *context, JSValue *exception){
        [JSContext currentContext].exception = exception;
        NSLog(@"exception:%@",exception);
    };

    self.addObj = [[AddJSExportObj alloc] init];

    self.context[@"OCAddObj"] = self.addObj;//js中的OCAddObj对象==>OC中的AddJSExportObj对象
    [self.context evaluateScript:@"OCAddObj.sum = OCAddObj.add(2,30)"];
    NSLog(@"%zi",self.addObj.sum);
}

JSCore在Web容器中的使用

在UIWebView中,我们可以在- (void)webViewDidFinishLoad:(UIWebView *)webView方法中,通过KVC的方式获取到当前容器的JSContent对象,通过该对象,我们就可以方便的进行hybrid操作。

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

案例演示:在html中调研OC代码中的分享功能和调用相机功能。

第一步:HelloWord.html代码如下:

function jsCallNative(){
    WBBridge.callCamera();
}
function jsCallNative2(){
    var shareInfo = "分享内容";
    var str = WBBridge.share(shareInfo);
    alert(str);
}<input type="button" onclick="jscallnative()" value="jscallnative" ><input type="button" onclick="jscallnative2()" value="jscallnative2" >
</input type="button" onclick="jscallnative2()" value="jscallnative2" ></input type="button" onclick="jscallnative()" value="jscallnative" >

第二步:实现一个遵守JSExport的协议WebViewJSExport

@protocol WebViewJSExport - (void)callCamera;
- (NSString*)share:(NSString*)shareString;
@end

第三步:当前VC需要实现WebViewJSExport

@interface ViewController ()<uiwebviewdelegate,webviewjsexport>@property (nonatomic, strong) JSContext *context;
@property (nonatomic, strong) UIWebView *webView;
@end
@implementation ViewController
-(void)initWebView{
    self.context = [[JSContext alloc] init];

    _webView = [[UIWebView alloc] initWithFrame:self.view.bounds];
    _webView.delegate = self;
    [self.view addSubview:_webView];

    NSURL *url = [[NSURL alloc] initWithString:@"http://localhost:8080/myDiary/HelloWorld.html"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [self.webView loadRequest:request];
}
- (void)webViewDidFinishLoad:(UIWebView *)webView{

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

    // 将本对象与 JS 中的 WBBridge 对象桥接在一起,在 JS 中 WBBridge 代表本对象
    [_context setObject:self forKeyedSubscript:@"WBBridge"];
    _context.exceptionHandler = ^(JSContext* context, JSValue* exceptionValue) {
        context.exception = exceptionValue;
        NSLog(@"异常信息:%@", exceptionValue);
    };
}
- (void)callCamera{
    NSLog(@"调用相机");
}

- (NSString*)share:(NSString*)shareString{
    NSLog(@"分享::::%@",shareString);
    return @"分享成功";
}
@end     </uiwebviewdelegate,webviewjsexport>

这样我们就可以在webView中调用我们native组建了,实现了一个简单的hybird功能。这就补充了在UIWebView实现hybird功能的方式。

参考:
JSCore的基本使用
JavaScript :浅谈 iOS 与 H5 的交互- JavaScriptCore 框架
iOS7新JavaScriptCore框架入门介绍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值