基于JavaScriptCore的OC与JS互相调用

最近在公司的项目中,有这么一个需求:将OC程序中基于服务器的功能提取出来,写成JS脚本的形式存储于服务器,这样当服务器更新时,只需要更新服务端的JS脚本,OC本地只需要下载并执行JS脚本,便完成了服务器与客户端的更新,而不必发布新的客户端程序。

这里面就涉及一个问题,JS脚本与OC语言的相互调用。

通过查找资料,发现IOS中早已集成了JavaScriptCore专门用于OC与JS之间的调用,但是网上的例子多是基于WebView加载URLRequest的方法获得JS运行环境再作进一步操作的。

这里就将直接通过JavaScriptCore实现JS与OC互相调用的实现方式记录如下:

基本概念

JavaScriptCore

JavaScriptCore是Apple提供的一个实现OC与JS交互的库,要使用它,需要在工程设置中添加库引用:

ProjectSetting->General->Linked Frameworks and Libraries

输入并选择JavaScriptCore。

JS运行环境

在这里我们说的是在IOS APP中实现JS与OC的相互调用,IOS为OC提供了其可以运行的环境,JS并不能直接运行在这个环境中,因此JavaScriptCore为JS提供了虚拟运行环境

JSVirtualMachine 就是JS虚拟机,在IOS环境中为JS提供了一个得以运行的虚拟环境,这有点类似于Java的虚拟机。


OC调用JS

在OC中调用JS,是在一个叫做JSContext上下文中进行的,而JSContext则要在JSVirtualMachine中创建。

JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
同一个 JSVirtualMachine中可以创建多个上下文。


OC在JS中定义/读取变量

在OC中的绝大多数数据类型均能够与JS相互对应,表格如下

//   Objective-C type  |   JavaScript type
// --------------------+---------------------
//         nil         |     undefined
//        NSNull       |        null
//       NSString      |       string
//       NSNumber      |   number, boolean
//     NSDictionary    |   Object object
//       NSArray       |    Array object
//        NSDate       |     Date object
//       NSBlock       |   Function object 
//          id         |   Wrapper object 
//        Class        | Constructor object

OC读取JS定义的变量,其形式类似于读取字典中的值,采用‘变量名-值’的形式。返回类型均为JSValue,使用时需要进一步的转换,

    JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
    JSValue *aValue = context[@"a"];
 NSLog(@"The value is %.f", [aValue toDouble]

类似,OC获取JS中定义的函数

JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
 JSValue *squareFunction = context[@"square"];
JSValue *result = [squareFunction callWithArguments:@[context[@"5"]]];

这里JS函数返回类型仍然是JSValue,通过

[squareFunction callWithArguments:@[@5]]
调用函数,函数参数通过数组的形式传入,多参数函数可在数组中依次添加函数参数。


OC向JS中添加变量/对象

OC向JS中添加变量,对象有两种方式,

一种直接类似于向子典中添加数据:

context[@"getFileExtension"] = ^(NSString *filePath)
    {
        NSLog(@"Get file extension is %@", filePath);
        return filePath;
    };

context[@"a"] = @5;

这里的“数据”,既可以是数字,字符串,也可以是由block所定义
的函数。这样,在JS的上下文中,便有了a变量,与getFileExtension的function。


将OC中的类导出到JS中

上面说所的OC获取JS中的变量,都是按值传递的,因此对于一个变量的修改,并不会影响到它的副本。

但是,通过JavaScriptCore,你也可以将OC中的类导出到JS中,这样JS与OC运行环境便可以同时拥有同一个类对象,对于这个对象的修改,JS与OC环境均会体现出其修改。

要将OC对象导入到JS中,

OC类需要遵守JSExport协议,有趣的是JSExport协议并没有规定任何内容,

@protocol JSExport
@end

你需要自已写一个新的协议来继承这个协议,并在该新协议中,添加你想导出的类的变量声明。

@protocol ThingJSExports<JSExport>
@property(nonatomic, copy) NSString *name;
@end
@interface Thing :NSObject<ThingJSExports>
@property(nonatomic, copy) NSString *name;
@property(nonatomic) NSInteger number;
@end

@implementation Thing

-(NSString *) description{
    return [NSString stringWithFormat:@"%@ : %ld", self.name, (long)self.number];
}
@end

将thing对象导入到JSContext

    JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
    Thing *thing = [[Thing alloc] init];
    thing.name = @"John";
    thing.number = 13;
    
    context[@"thing"] = thing;
    NSLog(@"Thing is %@, In JS, thing is %@", thing, context[@"thing"]);
    
    thing.name = @"Tom"; thing.number = 1;
    NSLog(@"After OC thing change, thing is %@, thing in JS is %@", thing, context[@"thing"]);
    
    [context evaluateScript:@"thing.name='Jack';thing.number=6"];
    NSLog(@"After JS thing change, thing is %@, thing in JS is %@", thing, context[@"thing"]);
    
    [context evaluateScript:@"thing.number=52"];
   
     NSLog(@"Change number in JS, now thing in OC is %@, thing in JS is %@", thing, context[@"thing"]);
    thing.number = 34;
     NSLog(@"Change number in OC, now thing in OC is %@, thing in JS is %@", thing, context[@"thing"]);

得到输出结果:

2016-02-24 23:32:35.997 OCJsTest[1784:61910] Thing is John : 13, In JS, thing is John : 13
2016-02-24 23:32:35.998 OCJsTest[1784:61910] After OC thing change, thing is Tom : 1, thing in JS is Tom : 1
2016-02-24 23:32:35.998 OCJsTest[1784:61910] After JS thing change, thing is Jack : 1, thing in JS is Jack : 1
2016-02-24 23:32:35.998 OCJsTest[1784:61910] Change number in JS, now thing in OC is Jack : 1, thing in JS is Jack : 1
2016-02-24 23:32:35.999 OCJsTest[1784:61910] Change number in OC, now thing in OC is Jack : 34, thing in JS is Jack : 34

可以发现,我们这里仅仅将name数据导出到了JS中,这JS能够读写name属性,而对于number,JS中所做的修改是无效的。


JS调用OC函数

重点来了,我们有时候可能想写一个JS脚本,在该脚本中,调用OC的函数,该如何实现呢?我们可以利用上面介绍的OC在JS中定义方法的实现方式,通过向JS中定义block函数,在JS脚本中调用相对应得函数名,就能够实现JS调用OC代码。


例如

JSContext *context = [[JSContext alloc] initWithVirtualMachine:[[JSVirtualMachine alloc] init]];
    NSURL *jsFileURL = [[NSBundle mainBundle] URLForResource:@"NXFileDispatch" withExtension:@"js"];

    NSString *jsString = [NSString stringWithContentsOfURL:jsFileURL encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"The js content is %@", jsString);
    [context evaluateScript:jsString];
    context[@"openFileInNXAPP"] = ^(NSInteger fileType)
    {
        [self openFileInNXAPP:fileType];
    };
<p style="margin-top: 0px; margin-bottom: 0px; font-size: 14px; line-height: normal; font-family: Menlo;">  [context[@"nxOpenFileDispatch"] callWithArguments:@[@"abc dert124"]];</p><div><span style="font-variant-ligatures: no-common-ligatures; color: #ffffff">
</span></div>

JS脚本可以这么写

var nxOpenFileDispatch = function(filePath){
    openFileInNXAPP(12);
}

脚本中定义了变量nxOpenFileDispatch为一个JS函数,其中调用了另一个function叫openFileInNXAPP,但该function却并没有在JS中定义,该定义我们写在了OC的block中,并且JS与OC还可以互相传递参数(注意这里必须是系统类型参数而非自定义类型)。这样就实现了JS调用OC函数的方法。


参考文档

https://www.bignerdranch.com/blog/javascriptcore-and-ios-7/




  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值