JavaScriptCore框架

在WWDC 2013上,苹果公司推出了JavaScriptCore框架,如果感兴趣的朋友可以查看WWDC 2013: Integrating JavaScript into Native Apps。这是一个基于JavaScript的框架,它完美的以面向对象的方式实现js和oc的交互。

JavaScriptCore涉及到几个核心类:

  • JSContext:JavaScript的执行环境;
  • JSValue:JSValue则可以说是JavaScript和Object-C之间互换的桥梁;
  • JSManagedValue:JSValue的内存管理器;
  • JSVirtualMachine:JSVirtualMachine为JavaScript的运行提供了底层资源;
  • JSExport:协议,oc和js的通信协议。

JavaScriptCore让OC和JS自动完成交互。由于js是单线程的,JavaScriptCore的操作也是线程安全的。

开发过程中需要引入的库:#import <JavaScriptCore/JavaScriptCore.h>

1 JSContext

JSContext是JavaScript的执行环境,所有的JavaScript的执行都是基于这个环境。JSContext同时也管理JavaScript对象的生命周期,每一个JSValue都和JSContext强引用关联。

JSContext通过- (JSValue *)evaluateScript:(NSString *)script;执行JavaScript方法,并返回一个JSValue对象。

// 初始化
JSContext *context = [[JSContext alloc] init];
context = [JSContext currentContext];

2 JSValue

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

下列是Objective-C类型和JavaScript类型的对应关系图。

Objective-C类型JavaScript类型
nilundefined
NSNullnull
NSStringstring
NSNumbernumber, boolean
NSDictionaryObject object
NSArrayArray object
NSDateDate object
NSBlock (1)Function object (1)
id (2)Wrapper object (2)
Class (3)Constructor object (3)

通过Objective-C创建JSValue对象,可以使用如下常用方法。

+ valueWithBool:(BOOL)value inContext:(JSContext *)context;
+ valueWithDouble:(double)value inContext:(JSContext *)context;
+ valueWithInt32:(int32_t)value inContext:(JSContext *)context;
+ valueWithUInt32:(uint32_t)value inContext:(JSContext *)context;
+ valueWithNullInContext:(JSContext *)context;
+ valueWithUndefinedInContext:(JSContext *)context;

+ valueWithNewObjectInContext:(JSContext *)context;
+ valueWithNewArrayInContext:(JSContext *)context;
+ valueWithNewRegularExpressionFromPattern:(NSString *)pattern flags:(NSString *)flags inContext:(JSContext *)context;
+ valueWithNewErrorFromMessage:(NSString *)message inContext:(JSContext *)context;

你还可以使用通配方法创建。

+ valueWithObject:(id)value inContext:(JSContext *)context;

如果你想访问JSValue的值,可以使用如下常用方法。

- (BOOL)toBool;
- (double)toDouble;
- (int32_t)toInt32;
- (uint32_t)toUInt32;
- (NSNumber *)toNumber;
- (NSString *)toString;

- (NSDate *)toDate;
- (NSArray *)toArray;
- (NSDictionary *)toDictionary;

- (id)toObject;
- (id)toObjectOfClass:(Class)expectedClass;

3 JSExport

JavaScript可以脱离prototype继承完全用JSON来定义对象,但是Objective-C编程里可不能脱离类和继承写代码。所以JavaScriptCore就提供了JSExport作为两种语言的互通协议。JSExport中没有约定任何的方法,连可选的(@optional)都没有,但是所有继承了该协议(@protocol)的协议(注意不是Objective-C的类(@interface))中定义的方法,都可以在JSContext中被使用。

简单点说就是你定义的类要想在JSContext中被使用,就像下面这样写。

@protocol MyClassJavaScriptMethods <JSExport>
- (void)foo;
@end

@interface MyClass : NSObject <MyClassJavaScriptMethods>
- (void)foo;
- (void)bar;
@end

4 基本类型转换

下面是一些基本类型转换操作,你可以使用XCTestCase做这些测试。

#import <XCTest/XCTest.h>
#import <JavaScriptCore/JavaScriptCore.h>

@interface JavaScriptCoreTests : XCTestCase

@property (nonatomic, strong) JSContext *context;///< JavaScript运行环境

@end

@implementation JavaScriptCoreTests

- (void)setUp {
    [super setUp];
    self.context = [[JSContext alloc] init];
    // self.context = [JSContext currentContext];

}

- (void)tearDown {
    [super tearDown];
}

- (void)testExample {
    // 计算2+2
    JSValue *result = [self.context evaluateScript:@"2 + 2"];
    NSLog(@"2 + 2 = %d", [result toInt32]);
    // 数组操作
    [self.context evaluateScript:@"var array = [\"阳君\", 937447974 ];"];
    JSValue *jsArray = self.context[@"array"];// 下标获取值
    // 判断是否为数组
    if (![jsArray isArray]) {
        return;
    }
    NSLog(@"JS Array: %@; Length: %@", jsArray, jsArray[@"length"]);
    jsArray[1] = @"blog"; // 修改
    jsArray[7] = @YES; // 数组越界时自动添加
    NSLog(@"JS Array: %@; Length: %d", jsArray, [jsArray[@"length"] toInt32]);
    // 转换为array
    NSArray *nsArr = [jsArray toArray];
    NSLog(@"NSArray: %@", nsArr);
}

@end

以下关于JavaScriptCore的测试为节约篇幅只显示核心代码,不在显示整个文件的代码。

5 函数

你还可以在JSContext中添加函数,这里我们通过jS实现一个递归算法计算1*2*3*....*n

#pragma mark 函数测试
- (void)testFunctions {
    /* js代码
     var factorial = function(n) {
        if (n < 0)
            return;
        if (n === 0)
            return 1;
        return n * factorial(n - 1);
     };
     */
    // 计算1*2*3....*n
    NSMutableString *factorialScript = [NSMutableString stringWithCapacity:10];
    [factorialScript appendString:@"var factorial = function(n) { "];
    [factorialScript appendString:@"if (n < 0) return; "];
    [factorialScript appendString:@"if (n === 0) return 1; "];
    [factorialScript appendString:@"return n * factorial(n - 1); };"];
    [_context evaluateScript:factorialScript];
    JSValue *function = _context[@"factorial"]; // 提取函数
    JSValue *result = [function callWithArguments:@[@5]];
    NSLog(@"factorial(5) = %d", [result toInt32]);
}

6 Blocks

各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。

如果你不懂什么是Blocks,可以阅读我的博文《Blocks Programming

这里我们通过Blocks实现如下需求:

  1. 创建一个类user,其中包含name和qq属性。
  2. 修改属性name的值,即user.name = newName;
#pragma mark Blocks测试
- (void)testBlocks {

    // 设计一个user类,其中有name和qq属性
    self.context[@"user"] = ^{
        // 如果想使用JSContext,必须使用[JSContext currentContext],避免循环引用
        JSValue *object = [JSValue valueWithNewObjectInContext:[JSContext currentContext]];
        // 属性传入
        [object setValue:@"阳君" forProperty:@"name"];
        // 通过下标传入
        object[@"qq"] = @"937447974";
        return object;
    };
    JSValue *user = [self.context evaluateScript:@"user()"];
    NSLog(@"name:%@, qq:%@", user[@"name"], [user valueForProperty:@"qq"]);

    // 修改name值
    self.context[@"setName"] = ^(JSValue *user, JSValue *name) {
        // 也可以这样获得传入参数
        NSArray *args = [JSContext currentArguments];
        NSLog(@"currentArguments:%@", args);
        user[@"name"] = name;
    };
    JSValue *setName = self.context[@"setName"];
    [setName callWithArguments:[NSArray arrayWithObjects:user, @"YangJun",nil]];
    NSLog(@"name:%@", user[@"name"]);

}

其中有几个值得注意的地方:

  1. 在Blocks内,想获得JSContext时,需用[JSContext currentContext]获得,详见创建user类的实现;
  2. 在Block内不要直接使用其外部定义的JSValue,应该将其当做参数传入到Block中,否则会造成循环引用使得内存无法被正确释放,详见修改name值的实现。

7 异常处理

Objective-C和Swift的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。

时时刻刻检查JSContext对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext对象设置exceptionHandler。点击查看其需要的blocks格式如下。

@property (copy) void(^exceptionHandler)(JSContext *context, JSValue *exception);

现在来做一个js出错的测试。

#pragma mark 异常捕获
- (void)testError {
    // 通过exceptionHandler捕获js错误
    self.context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
        NSLog(@"%@", exception);
        con.exception = exception;
    };
    [self.context evaluateScript:@"阳君-937447974"];
}

8 JSExport协议

上面我们使用Blocks实现了类的创建和属性的赋值。下面我们使用JSExport来创建可维护User类,其实现的需求如下。

  1. 创建一个类user,其中包含name和qq属性。
  2. 修改属性name的值,即user.name = newName;

首先我们根据JSExport协议制作User类,下面是核心代码。

User.h

//
//  User.h
//  UIWebView
//
//  Created by yangjun on 15/11/5.
//  Copyright © 2015年 六月. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

/** JavaScript可调的协议*/
@protocol UserProtocol <JSExport>

@property (nonatomic, copy) NSString *name; ///< 姓名
@property (nonatomic, copy) NSString *qq;   ///< qq号码

- (NSString *)description; // 打印信息

@end

/** 用户*/
@interface User : NSObject <UserProtocol>

@end

这里定义了UserProtocol协议,其实现JSExport协议。在这个协议中,我们声明了需要暴露给js的属性和方法。

接下来声明了一个User类,然后让它实现UserProtocol即可。由于我们在协议中定义了相关属性和方法,在User类中只需实现方法,对于属性需要使用@synthesize通知编译器实现getter/setter方法。下面是User.m的核心代码

//
//  User.m
//  UIWebView
//
//  Created by yangjun on 15/11/5.
//  Copyright © 2015年 六月. All rights reserved.
//

#import "User.h"

@implementation User

// 由于属性在代理里面,需要通知编译器实现getter/setter方法
@synthesize name,qq;

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

@end

使用XCTestCase测试。

- (void)testJSExport {
    User *user = [[User alloc] init];
    self.context[@"user"] = user; // 对象绑定
    // 修改值
    user.name = @"阳君";
    [self.context evaluateScript:@"user.qq = 937447974"];
    // 打印
    NSLog(@"oc:%@", user); //由于继承NSObJect,会自动调用description方法
    JSValue *description = [self.context evaluateScript:@"user.description()"];
    NSLog(@"js:%@", description);
}

 


其他

参考资料

WWDC 2013: Integrating JavaScript into Native Apps

iOS与JS交互实战篇(ObjC版)

iOS7新JavaScriptCore框架入门介绍

文档修改记录

时间描述
2015-11-5关于JavaScriptCore的相关介绍
2015-11-6增加相关文章的链接。

版权所有

CSDN:http://blog.csdn.net/y550918116j

GitHub:https://github.com/937447974/Blog

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
iOS中的JavaScriptCore是一个框架,它提供了将JavaScript代码嵌入到iOS应用程序中的功能。它是基于WebKit引擎的一部分,可以让开发者在iOS应用中执行JavaScript代码,并且可以实现JavaScript与Objective-C之间的相互调用。 JavaScriptCore框架提供了以下主要功能: 1. 执行JavaScript代码:可以使用JavaScriptCore框架来执行JavaScript代码,包括解析和执行JavaScript脚本文件。 2. JavaScript与Objective-C的相互调用:可以在Objective-C代码中调用JavaScript函数,并且可以在JavaScript代码中调用Objective-C方法。这种相互调用可以实现iOS应用与JavaScript之间的数据交互和功能扩展。 3. JavaScript对象与Objective-C对象的转换:JavaScriptCore框架提供了将JavaScript对象转换为Objective-C对象的功能,以及将Objective-C对象转换为JavaScript对象的功能。这样可以方便地在JavaScript和Objective-C之间传递数据。 4. JavaScript上下文管理:可以创建和管理多个JavaScript上下文,每个上下文都有自己的全局对象和执行环境。这样可以实现多个独立的JavaScript环境,避免不同模块之间的命名冲突。 5. JavaScript异常处理:JavaScriptCore框架提供了处理JavaScript异常的机制,可以捕获和处理JavaScript代码中的异常情况。 使用JavaScriptCore框架,开发者可以将JavaScript代码嵌入到iOS应用中,实现一些动态的功能和交互效果。比如,在一个网页浏览器应用中,可以使用JavaScriptCore框架来执行网页中的JavaScript代码,并且可以在Objective-C代码中调用JavaScript函数,实现与网页的交互。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值