JavaScriptCore学习总结

总览

在iOS的开发过程中,除了使用oc和swift语言开发原生应用, iOS还支持使用js与原生进行交互,比如热修复应用JSPatch就是使用js代码进行原生代码的替换。代码之所以可以动态替换,是因为oc的runtime机制,js代码之所以可以跟被iOS识别,则是因为JavaScriptCore。所以,总的来说,JavaScriptCore的主要作用是建立js与iOS原生之间的交互。 既然是交互,那就肯定有js调用原生和原生调用js方法,以下以oc为例,学习这两种调用方式。 首先附上DEMO地址和JavaScriptCore的源码地址

网上已经有很多文章提到了JavaScriptCore暴露的几个类,我也要把这几个类再贴一下,因为用到的确实就这几个类。。。

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

Objective-C -> JavaScript

想要调用js方法,那首先得有个方法。先建立一个js文件,取名factorial.js。里面填入下面内容:

var factorial = function(n) {
    if (n < 0)
        return ;
    if (n === 0)
        return 1;
    return n * factorial(n - 1)
};

然后写个方法来调它:

JSContext *context = [[JSContext alloc] init];
context = [[JSContext alloc] init];

NSString *path = [[NSBundle mainBundle] pathForResource:@"factorial" ofType:@"js"];
NSString *factorialScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
NSLog(@"factorialScript : %@",factorialScript);

[context evaluateScript:factorialScript];
JSValue *function = context[@"factorial"];
NSLog(@"function: %@",function);

JSValue *result = [function callWithArguments:@[@5]];
NSLog(@"factorial(5) = %d", [result toInt32]);

上面的代码中出现了上面几个.h文件中的一些概念:JSContext和JSValue。JSContext是js的运行环境。主要作用是执行js代码和注册oc方法接口。JSValue是JSContext的返回结果,它是oc和js之间数据通信的桥梁。在实现上JSValue指向一个js的值。每一个JSValue都归属于某个JSContext。JSContext通过调用evaluateScript:方法来执行一段js脚本。之后,即可以通过[]的方式来获得js中的方法名。再利用callWithArguments:方法就可以执行js方法并返回一个JSValue类型的值。

JavaScript -> Objective-C

js调用oc代码有两种方式,一种是通过block,一种是通过oc类实现JSExport协议。
先来看block:

context[@"useBlock"] = ^(NSString *word) {
        NSLog(@"useBlock : %@",word);
    };

相应的,要在js中写调用此方法的代码:

var useBlockInJS = function(word) {
    
    return useBlock(word);
};

方法写完后,在oc中执行这个js方法:

JSValue *useBlockInJS = context[@"useBlockInJS"];
[useBlockInJS callWithArguments:@[@"useBlockInJS"]];

整个过程很简单,首先利用context注册了一个block方法,然后在js中就可以直接调用这个方法。但是在使用block的时候需要注意一些问题,比如不要直接在block使用context和JSValue的值,因为这样很容易循环引用。例如以下的情况:

//由于block强引用context,context 又强引用block,所以循环引用
context[@"useBlock"] = ^(NSString *word) {
        NSLog(@"useBlock : %@ in context : %@",word,context);
    };

//由于context强引用block,block强引用useBlockInJS,useBlockInJS也强引用context,所以形成引用三角,循环引用
context[@"useBlock"] = ^(NSString *word) {
        NSLog(@"useBlock : %@ and the function is : %@",word, [useBlockInJS toObject]);
    };


下面介绍oc类实现JSExport协议的方式:
这是一个实现了JSExport的protocol,里面我们定义了一些property和method。

@protocol MyPointExports <JSExport>

@property double x;
@property double y;

- (NSString *)description;

+ (MyPoint *)makePointWithX:(double)x y:(double)y;

@end

js里面的实现代码为:

var euclideanDistance = function(p1,p2) {
    var xDelta = p2.x - p1.x;
    var yDelta = p2.y - p1.y;
    return Math.sqrt(xDelta * xDelta + yDelta * yDelta);
};

var midpoint = function(p1, p2) {
    log("p1.x = " + p1.x + " p1.y =" + p1.y);
    log("p2.x = " + p2.x + " p2.y =" + p2.y);
    var xDelta = (p2.x - p1.x) / 2;
    var yDelta = (p2.y - p1.y) / 2;
    return MyPoint.makePointWithXY(p1.x + xDelta, p1.y + yDelta);
};

这是类的使用代码,MyPoint的实现代码有兴趣请看Demo,里面只是几句简单的method实现代码。

    MyPoint *point1 = [[MyPoint alloc] initWithX:10.0 y:50.0];
    MyPoint *point2 = [[MyPoint alloc] initWithX:13.0 y:53.0];
    
    JSValue *euclideanDistanceFunction = context[@"euclideanDistance"];
    JSValue *euclideanDistanceResult = [euclideanDistanceFunction callWithArguments:@[point1,point2]];
    NSLog(@"euclideanDistanceResult : %f",[euclideanDistanceResult toDouble]);
    //如果想在js中使用整个类,只需要把类赋给context,如下:
    context[@"MyPoint"] = [MyPoint class];
    
    JSValue *midpointFunction = context[@"midpoint"];
    JSValue *jsResult = [midpointFunction callWithArguments:@[point1, point2]];
    MyPoint *midpoint = [jsResult toObject];
    NSLog(@"midpoint's x: %f, midpoint's y: %f",midpoint.x,midpoint.y);

通过[MyPoint class]把类赋给context,这样在protocol中的方法都可以在js中使用,整个过程也是很简单也很明了的。

截止目前,前面给出的5个.h文件只用了3个,还剩两个一直没提:JSVirtualMachine和JSManagedValue。JSVirtualMachine提供的是JS运行的虚拟机。每个JSVirtualMachine都有独立的堆空间和垃圾回收机制。JSManagedValue的作用是辅助管理js和oc对象。之所以存在JSManagedValue,是因为js内存管理采用的是垃圾回收机制,而oc采用的是引用计数。如果两方互相引用,很容易造成循环引用。具体可以看例子:

@interface MyButton : UIButton

@property (nonatomic,strong) JSContext *context;
@property (nonatomic, strong) JSManagedValue *onClickHandler;
    //@property (nonatomic, strong) JSValue *onClickHandler;

- (void)configureOnClickHandler:(JSValue *)onClickHandler;
@end

@implementation MyButton

- (void)configureOnClickHandler:(JSValue *)onClickHandler {
    _onClickHandler = onClickHandler;
}

//使用代码
JSValue *clickHandlerFunction = context[@"ClickHandler"];
MyButton *button = [[MyButton alloc] init];
[button configureOnClickHandler:[clickHandlerFunction callWithArguments:@[button,^{
    NSLog(@"clicked");
   }]]];

js端代码为:

function ClickHandler(button, callback) {
    this.button = button;
    this.button.onClickHandler = this;
    this.handleEvent = callback;
    
    callback();
}

在这个例子中,MyButton持有一个JSValue类型的值onClickHandler,而ClickHandler也持有MyButton的引用,如下图所示:

retainCycles.png

双方都是强引用,所以造成循环引用。想要解除循环引用,就需要用到NSManagedValue,如下所示:

@interface MyButton : UIButton

@property (nonatomic,strong) JSContext *context;
@property (nonatomic, strong) JSManagedValue *onClickHandler;
    //@property (nonatomic, strong) JSValue *onClickHandler;

- (void)configureOnClickHandler:(JSValue *)onClickHandler;

@end

@implementation MyButton

- (void)configureOnClickHandler:(JSValue *)onClickHandler {
        //_onClickHandler = onClickHandler;
    _onClickHandler = [JSManagedValue managedValueWithValue:onClickHandler];
    [_context.virtualMachine addManagedReference:_onClickHandler withOwner:self];
    
    NSLog(@"context virtual machine in MyButton: %@",_context.virtualMachine);
}

@end

将上面的JSValue改为JSManagedValue并将其添加到virtualMachine中, 此时再看下引用情况:

garbagecollectedreference.png

至于这两行代码的作用,我们可以通过这两个方法的实现来大致看一下:

+ (JSManagedValue *)managedValueWithValue:(JSValue *)value
{
    return [[[self alloc] initWithValue:value] autorelease];
}

- (instancetype)initWithValue:(JSValue *)value
{
    self = [super init];
    if (!self)
        return nil;
    
    if (!value)
        return self;

    JSC::ExecState* exec = toJS([value.context JSGlobalContextRef]);
    JSC::JSGlobalObject* globalObject = exec->lexicalGlobalObject();
    JSC::Weak<JSC::JSGlobalObject> weak(globalObject, managedValueHandleOwner(), self);
    m_globalObject.swap(weak);

    JSC::JSValue jsValue = toJS(exec, [value JSValueRef]);
    if (jsValue.isObject())
        m_weakValue.setObject(JSC::jsCast<JSC::JSObject*>(jsValue.asCell()), self);
    else if (jsValue.isString())
        m_weakValue.setString(JSC::jsCast<JSC::JSString*>(jsValue.asCell()), self);
    else
        m_weakValue.setPrimitive(jsValue);
    return self;
}

- (void)addManagedReference:(id)object withOwner:(id)owner
{    
    object = getInternalObjcObject(object);
    owner = getInternalObjcObject(owner);
    
    if (!object || !owner)
        return;
    
    JSC::APIEntryShim shim(toJS(m_group));
    
    NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner];
    if (!ownedObjects) {
        NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
        NSPointerFunctionsOptions integerOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality;
        ownedObjects = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:integerOptions capacity:1];

        [m_externalObjectGraph setObject:ownedObjects forKey:owner];
        [ownedObjects release];
    }
    NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(reinterpret_cast<size_t>(NSMapGet(ownedObjects, object)) + 1));
}

通过以上方法的实现,我们大致可以看出,JSManagedValue是将JSValue的强引用转换为弱引用,应该类似于oc里面的Strong和weak的转化,后面添加到virtualMachine中,因为是为了virtualMachine的内存管理和与oc runtime引用计数功能相关。

关于线程相关

一个JSVirtualMachine可以拥有多个JSContext,同一个JSVirtualMachine内的多个JSContext可以共享值,
但是不同JSVirtualMachine之间不可以。原因在于每一个JSVirtualMachine都拥有自己独立的heap和garbage collection 。正如我们上面看到的源码:

object = getInternalObjcObject(object);
owner = getInternalObjcObject(owner);
    
if (!object || !owner)
    return;

addManagedReference:withOwner:方法在处理JSValue之前,要先检查是是否是一个context中的, 如果不是,则不能处理。

所有JavaScriptCore的API都是线程安全的,所以不同的线程,同一时间JSVirtualMachine只会在一个线程运行。如果你想要获取同步或者并发操作,需要使用多个JSVirtualMachine。

参考

JavaScriptCore 使用
iOS JavaScriptCore使用
IOS7开发~JavaScriptCore (一)
IOS7开发~JavaScriptCore (二)
WWDC 2013 - Session 615 (Integrating JavaScript into Native Apps)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • 本文由我们团队的 纠结伦 童鞋撰写。 写在前面 本篇文章是对我一次组内分享的整理,大部分图片都是直接从keynot...
    知识小集阅读 15,241评论 11 172
  • 注:本文copy自http://www.jianshu.com/p/ac534f508fb0,纯属当笔记使用。 概...
    BookKeeping阅读 731评论 1 3
  • 写在前面 本篇文章是对我一次组内分享的整理,大部分图片都是直接从keynote上截图下来的,本来有很多炫酷动效的,...
    等开会阅读 14,432评论 6 69
  • 一. JavaScriptCore 简介 1.1 JavaScriptCore 和 JavaScriptCore ...
    GShining阅读 857评论 0 0
  • ——匈奴后裔 日沉西斜阳若影,夕云残照满目彤; 清风徐来轻拂柳,叶映金莲洒河汀; 静坐晚钓鱼虾戏,信...
    匈奴后裔阅读 215评论 0 1