初识JavaScriptCore
背景
由于近半年来的技术主要围绕着Cordova,ReactNative等跨平台技术,一方面为了深入理解其中的运行原理,一方面也是因为对大红大紫的前端技术越来越感兴趣,于是对iOS系统上的JavaScript相关的技术进行了一些更深入的研究
JavaScriptCore中的基本概念
JSVirtualMachine
JS虚拟运行环境,它有两个作用:一是提供了JS的多线程运行环境,为了并发执行JS,你需要提供多个JSVirtualMachine,二是负责管理JS和OC或者Swift之间桥接的对象(也就是内存管理)。不同JSVirtualMachine中的JSValue对象不能被互相传递(参考图一)。
JSContext
执行JS代码的环境,通过Objective-C或者Swift代码运行JS代码,不但能接收从JS中计算的值,而且提供了从原生对象,方法,函数对JS的调用能力。一个JSVirtualMachine中可以有多个JSContext,不同JSContext中的JSValue对象可以互相传递(参考图一)。
JSValue
JSValue可以表示任何的原始数据类型,它既可以用来转换基本数值(如number,string),也可以用来创建js对象,用于包装native代码中的class,method,block对象(参考图二)。每一个JSValue对象关联着一个JSContext,并且强引用持有JSContext。
JSManagedValue
JSManagedValue封装了JSValue对象,通过“条件持有”的方式提供自动内存管理。注意:不要在native对象中持有non-managed的JSValue对象,否则会造成循环引用。
JSExport
JSExport是一个协议,实现该协议可以将Objective-C类中的方法,属性导出成JS代码。
图一:对象传递规则,图片来自https://www.raywenderlich.com/124075/javascriptcore-tutorial
图二:JavaScript和Native之间的对象转换规则
Native | JavaScript |
---|---|
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) |
基本用法
Objective-C调用Javascript
func objcToJs(_ number1: Int, _ number2: Int) -> Int32 {
// 声明一个JSContext对象
guard let ctx = JSContext() else { return 0}
// 定义一个js函数,这里的js代码也可以从js文件中读取
let js = "function add(a, b) { return a + b }"
_ = ctx.evaluateScript(js)
// 获取add函数对象
let jsValue = ctx.objectForKeyedSubscript("add")
// 调用函数add()
guard let result = jsValue?.call(withArguments: [number1, number2]).toInt32() else { return 0 }
return result
}
Javascript调用Objective-C
func jsToObjc() {
// 声明一个JSContext对象
guard let ctx = JSContext() else { return }
// 定义一个兼容OC Block的swift闭包
let addfunction: @convention(block) (_ title: String) -> Void = { (title) in
print("called from " + title)
}
let callNativeBlock = unsafeBitCast(addfunction, to: AnyObject.self)
// 将Native的方法设置到Context中,
ctx.setObject(callNativeBlock, forKeyedSubscript: "callNativeBlock" as NSCopying & NSObjectProtocol);
//执行js代码,调用callNativeBlock方法
ctx.evaluateScript("callNativeBlock('js')")
}
JSExport的使用
- 首先定义支持JSExport的协议,表明哪些属性和方法支持导出到JavaScript
// 声明MovieJSExports协议
@objc protocol MovieJSExports: JSExport {
var title: String {get set}
var imageUrl: String{get set}
static func movieWith(title: String, imageUrl: String) -> Movie
}
class Movie: NSObject, MovieJSExports {
dynamic var title: String
dynamic var imageUrl: String
init(title: String, imageUrl: String) {
self.title = title
self.imageUrl = imageUrl
}
class func movieWith(title: String, imageUrl: String) -> Movie {
return Movie(title: title, imageUrl: imageUrl)
}
}
- 调用
func jsExportExample() {
// 声明一个JSContext对象
guard let ctx = JSContext() else { return }
// 将原生的Movie类添加到JSContext中
ctx.setObject(Movie.self, forKeyedSubscript: "Movie" as (NSCopying & NSObjectProtocol)!)
// 声明js方法mapToMovie(),调用原生的Movie的类方法
let js = "function mapToMovie(title, imageUrl) { return Movie.movieWithTitleImageUrl(title, imageUrl) }"
_ = ctx.evaluateScript(js)
let jsValue = ctx.objectForKeyedSubscript("mapToMovie")
// 调用js方法mapToMovie(),并传入参数
guard let movie = jsValue?.call(withArguments: ["电影名", "http://www.xxoo.com"]).toObject() as? Movie else {return}
// 获取经JSContext转换后的moview对象
print(movie.title, movie.imageUrl)
}
小技巧
由于js中的console.log()在没法在xcode中打印信息,在调用js的时候调试参数很不方便,于是可以定义一个Native版的console.log(),就可以直接在js中打印信息了。
let consoleLog: @convention(block) (String) -> Void = { message in
print("console.log: " + message)
}
\\ 调用
let consoleLogBlock = unsafeBitCast(consoleLog, to: AnyObject.self)
ctx.setObject(consoleLogBlock, forKeyedSubscript: "consoleLog" as NSCopying & NSObjectProtocol)
ctx.evaluateScript("consoleLog('helloworld')")
总结
在对JavaScriptCore进一步研究之后,对这个神奇的框架有了更多更深的认识:
- JavaScriptCore组件在整个Webkit框架中独立存在(除开JavaScriptCore,还有负责Dom渲染,CSS解析等组件),在之前,我们如果要做到Native和JS之间的交互,大多只能通过WebView中提供的方法来处理,这样最大的问题就是必须依赖WebView,然而JavaScriptCore的出现,让JS的运行环境完全独立出来,进而催生了ReactNative、JSPatch这些优秀开源框架的诞生。
Comments