美文网首页
JavaScriptCore学习总结

JavaScriptCore学习总结

作者: byn | 来源:发表于2016-12-26 19:18 被阅读97次

    总览

    在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)

    相关文章

      网友评论

          本文标题:JavaScriptCore学习总结

          本文链接:https://www.haomeiwen.com/subject/ymajvttx.html