iOS充电日记--JavaScriptCore

摘要:最近我们客户端想实现代码高亮,由于显示网页不是用的UIWebView而是用的DTCoreText,这就需要将代码片段进行加工,那就考虑prettify.js这个文件了,如何将OC的NSString传到js里并且返回处理后的结果

##JavaScriptCore简介

OS X Mavericks 和 iOS 7 引入了 JavaScriptCore 库,它把 WebKit 的 JavaScript 引擎用 Objective-C 封装,提供了简单,快速以及安全的方式接入世界上最流行的语言。 而且JavaScriptCore是开源的哦,如果感兴趣,大家可以到这里下载源码

##核心类介绍 先来看看<JavaScriptCore/JavaScriptCore.h>里面都有什么吧

#ifndef JavaScriptCore_h
#define JavaScriptCore_h

#include <JavaScriptCore/JavaScript.h>
#include <JavaScriptCore/JSStringRefCF.h>

#if defined(__OBJC__) && JSC_OBJC_API_ENABLED

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

#endif

#endif /* JavaScriptCore_h */

里面主要有JSContext、JSValue、JSManagedValue、JSVirtualMachine、JSExport。而我们常用的只有前两个类。JSContext 是运行 JavaScript 代码的环境。一个 JSContext 是一个全局环境的实例,如果你写过一个在浏览器内运行的 JavaScript,JSContext 类似于 window。创建一个 JSContext 后,可以很容易地运行 JavaScript 代码来创建变量,做计算,甚至定义方法:

//第一个简单的例子
JSContext *context = [[JSContext alloc] init];
JSValue *jsVal = [context evaluateScript:@"21+7"];
int iVal = [jsVal toInt32];
NSLog(@"JSValue: %@, int: %d", jsVal, iVal);

任何出自 JSContext 的值都被包裹在一个 JSValue 对象中。像 JavaScript 这样的动态语言需要一个动态类型,所以 JSValue 包装了每一个可能的 JavaScript 值:字符串和数字;数组、对象和方法;甚至错误和特殊的 JavaScript 值诸如 null 和 undefined。

JSValue 包括一系列方法用于访问其可能的值以保证有正确的 Foundation 类型,包括:

<pre>
@textblock
   Objective-C type  |   JavaScript type
 --------------------+---------------------
         nil         |     undefined
        NSNull       |        null
       NSString      |       string
       NSNumber      |   number, boolean
     NSDictionary    |   Object object
       NSArray       |    Array object
        NSDate       |     Date object
       NSBlock (1)   |   Function object (1)
          id (2)     |   Wrapper object (2)
        Class (3)    | Constructor object (3)
@/textblock
</pre>

###下标值 对 JSContext 和 JSValue 实例使用下标的方式我们可以很容易地访问我们之前创建的 context 的任何值。JSContext 需要一个字符串下标,而 JSValue 允许使用字符串或整数标来得到里面的对象和数组:

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"var arr = [21, 7 , 'iderzheng.com'];"];
JSValue *jsArr = context[@"arr"]; // Get array from JSContext

NSLog(@"JS Array: %@; Length: %@", jsArr, jsArr[@"length"]);
jsArr[1] = @"blog"; // Use JSValue as array
jsArr[7] = @7;

NSLog(@"JS Array: %@; Length: %d", jsArr, [jsArr[@"length"] toInt32]);

NSArray *nsArr = [jsArr toArray];
NSLog(@"NSArray: %@", nsArr);

你也许也有疑问他们是怎么做到的,我们可以看看JSValue的头文件可以发现

/*!
@category
@discussion Instances of JSValue implement the following methods in order to enable
support for subscript access by key and index, for example:

@textblock
JSValue *objectA, *objectB;
JSValue *v1 = object[@"X"]; // Get value for property "X" from 'object'.
JSValue *v2 = object[42];   // Get value for index 42 from 'object'.
object[@"Y"] = v1;          // Assign 'v1' to property "Y" of 'object'.
object[101] = v2;           // Assign 'v2' to index 101 of 'object'.
@/textblock

An object key passed as a subscript will be converted to a JavaScript value,
and then the value converted to a string used as a property name.
*/
@interface JSValue (SubscriptSupport)

- (JSValue *)objectForKeyedSubscript:(id)key;
- (JSValue *)objectAtIndexedSubscript:(NSUInteger)index;
- (void)setObject:(id)object forKeyedSubscript:(NSObject <NSCopying> *)key;
- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index;

@end

这里是用了非正式协议,这里我也写了测试代码

//
//  JSCTest.h
//  JavaScriptCoreTest
//
//  Created by 张如泉 on 15/11/6.
//  Copyright © 2015年 quange. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface JSCTest : NSObject

- (id)objectAtIndexedSubscript:(NSUInteger)index;
- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index;
@end

//
//  JSCTest.m
//  JavaScriptCoreTest
//
//  Created by 张如泉 on 15/11/6.
//  Copyright © 2015年 quange. All rights reserved.
//

#import "JSCTest.h"

@implementation JSCTest
- (id)objectAtIndexedSubscript:(NSUInteger)index
{
return @(110);
}
- (void)setObject:(id)object atIndexedSubscript:(NSUInteger)index
{
return;
}
@end

JSCTest *test = [[JSCTest alloc] init];
NSLog(@"测试下标取值%@",test[0]);

###调用方法 那么 如何调用一个在js写好的函数 ,并且传参数呢 JSValue 包装了一个 JavaScript 函数,我们可以从 Objective-C / Swift 代码中使用 Foundation 类型作为参数来直接调用该函数。再次,JavaScriptCore 很轻松的处理了这个桥接:

NSString *path = [[NSBundle mainBundle]pathForResource:@"test"ofType:@"js"];
NSString *testScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:testScript];
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"JS Error: %@", exception);
};
JSValue *function = context[@"factorial"];
JSValue *result = [function callWithArguments:@[@(10)]];
NSLog(@"10的阶乘factorial(10) = %d", [result toInt32]);

在这里用context.exceptionHandler可以来监测js中发生的错误

###实战

那么 我们要怎样实现我们真实的目的 通过prettify.js来高亮我们的代码呢 我一开始是这样写的

//
NSString *path = [[NSBundle mainBundle]pathForResource:@"test"ofType:@"js"];
NSString *testScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
JSContext *context = [[JSContext alloc] init];
[context evaluateScript:testScript];
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"JS Error: %@", exception);
};
JSValue *function = context[@"prettyPrintOne"];
JSValue *result = [function callWithArguments:@[@"main \n{\n printf(); \n}",@"cpp",@(1)]];
NSLog(@"代码转化结果 = %@", [result toString]);

结果js报错了 JS Error: ReferenceError: Can’t find variable: document。

于是我去google,才发现,js里面如果有window document 等,但是JSContext通过alloc实例化,js引擎中根本没有这些,可以先实例化一个UIWebView,软后JSContext 通过[web valueForKeyPath:@”documentView.webView.mainFrame.javaScriptContext”]来获取就没问题了

修改后的代码为

NSString *path = [[NSBundle mainBundle]pathForResource:@"prettify"ofType:@"js"];
NSString *testScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
UIWebView * web = [[UIWebView alloc] init];
JSContext *context = [web valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
[context evaluateScript:testScript];
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"JS Error: %@", exception);
};
JSValue *function = context[@"prettyPrintOne"];
JSValue *result = [function callWithArguments:@[@"main \n{\n printf(); \n}",@"cpp",@(1)]];
//JSValue *result = [context evaluateScript:@"prettyPrintOne('main','cpp',1)"];
NSLog(@"factorial(10) = %@", [result toString]);

控制台输出

高亮后的结果为 = <ol class="linenums"><li value="1" class="L0"><span class="pln">main </span></li><li class="L1"><span class="pun">{</span></li><li class="L2"><span class="pln"> printf</span><span class="pun">();</span><span class="pln"> </span></li><li class="L3"><span class="pun">}</span></li></ol>

哇塞 很酷 有没有 这里是所有代码的例子

Written on November 5, 2015