JavaScriptCore引擎深度解析2—API篇

前言

相对而言,这篇文章最贴近日常的iOS开发了,应该是我们最熟悉,同时又有点陌生的一篇了,这里陌生的原因是会加入一些对源码的分析。

在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 */

JavaScriptCore一共向我们暴露了这几个主要的头文件,先看一张全局关系图

接下来逐个阐述,阅读的时候,可能需要回翻,因为有些就好像鸡生蛋、蛋生鸡的哲学问题,无法分清楚先后…

JSVirtualMachine

一个JSVirtualMachine的实例就是一个完整独立的JavaScript的执行环境,为JavaScript的执行提供底层资源。

这个类主要用来做两件事情:

  • 实现JavaScript的并发执行
  • JavaScript和Objective-C桥接对象的内存管理

每一个JSContext对象都归属于一个JSVirtualMachine虚拟机。每个虚拟机可以包含多个不同的上下文,并允许在这些不同的上下文之间传值(JSValue对象)。

然而,每个虚拟机都是完整且独立的,有其独立的堆空间和垃圾回收器,GC无法处理别的虚拟机堆中的对象,因此不能把一个虚拟机中创建的值传给另一个虚拟机。

接下来来窥探一下源码,先看下JSVirtualMachine的头文件

@interface JSVirtualMachine : NSObject
- (instancetype)init;
- (void)addManagedReference:(id)object withOwner:(id)owner;
- (void)removeManagedReference:(id)object withOwner:(id)owner;
@end

接下来看下JSVirtualMachine的mm文件

@implementation JSVirtualMachine {
    JSContextGroupRef m_group;// JSContext集合
    NSMapTable *m_contextCache;
    NSMapTable *m_externalObjectGraph;
    NSMapTable *m_externalRememberedSet;
}
- (instancetype)init
{
    JSContextGroupRef group = JSContextGroupCreate();
    self = [self initWithContextGroupRef:group];
    // The extra JSContextGroupRetain is balanced here.
    JSContextGroupRelease(group);
    return self;
}

// 主要逻辑在`initWithContextGroupRef:`方法中,这里做了精简
- (instancetype)initWithContextGroupRef:(JSContextGroupRef)group
{
    self = [super init];

    // Retain操作
    m_group = JSContextGroupRetain(group);

    // 实际上这3个属性都是NSMapTable哈希表
    m_contextCache = [[NSMapTable alloc] initWith...];
    m_externalObjectGraph = [[NSMapTable alloc] initWith...];
    m_externalRememberedSet = [[NSMapTable alloc] initWith...;

    // 建立JSContextGroupRef和JSVirtualMachine的映射
    [JSVMWrapperCache addWrapper:self forJSContextGroupRef:group];

    return self;
}

倒数第二行代码的映射,实际上也是放到一个全局的NSMapTable表中,JSContextGroupRef为key,JSVirtualMachine为value,这样给定一个JSContextGroupRef,就可以比较方便地定位到其所属的JSVirtualMachine

// globalWrapperCache = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:valueOptions capacity:0];

+ (void)addWrapper:(JSVirtualMachine *)wrapper forJSContextGroupRef:(JSContextGroupRef)group
{
    std::lock_guard<StaticLock> lock(wrapperCacheMutex);
    NSMapInsert(globalWrapperCache, group, wrapper);
}

addManagedReference

实际上,我们更想关注的是它的两个方法,因为这两个方法涉及到了JS对象和OC对象的内存管理,为了看的更为清晰一点,先看下该方法如何被调用的。

- (void)testDemo
{
    JSContext *context = [[JSContext alloc] init];
    JSValue *message = [JSValue valueWithObject:@"hello" inContext:context];
    JSCollection *collection = [[JSCollection alloc] init];
    JSValue *jsCollection = [JSValue valueWithObject:collection inContext:context];
    JSManagedValue *weakCollection = [JSManagedValue managedValueWithValue:jsCollection andOwner:rootObject];
    [context.virtualMachine addManagedReference:weakCollection withOwner:message];
}

可以看到addManagedReference的第一个参数是一个JSManagedValue对象,第二个参数是一个JSValue

- (void)addManagedReference:(id)object withOwner:(id)owner
{    
    // 增加owner的引用计数
    if ([object isKindOfClass:[JSManagedValue class]])
        [object didAddOwner:owner];

    // unwrap对象,请参考getInternalObjcObject方法
    object = getInternalObjcObject(object);
    owner = getInternalObjcObject(owner);

    if (!object || !owner) return;

    JSC::JSLockHolder locker(toJS(m_group));
    if ([self isOldExternalObject:owner] && ![self isOldExternalObject:object])
        [self addExternalRememberedObject:owner];

    // m_externalObjectGraph存放ownedObjects
    // ownedObjects存放object的引用计数
    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];
    }

    size_t count = reinterpret_cast<size_t>(NSMapGet(ownedObjects, object));
    NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(count + 1));
}

didAddOwner

可以看到,方法的一开始逻辑didAddOwner,就是为JSValue增加引用计数,并存放到m_owners中,m_ownersJSManagedValue的一个属性:NSMapTable *m_owners;

- (void)didAddOwner:(id)owner
{
    size_t count = reinterpret_cast<size_t>(NSMapGet(m_owners, owner));
    NSMapInsert(m_owners, owner, reinterpret_cast<void*>(count + 1));
}

getInternalObjcObject

接下来的getInternalObjcObject的代码如下,用一个字来解释:unwrap

static id getInternalObjcObject(id object)
{
    if ([object isKindOfClass:[JSManagedValue class]]) {
        // static_cast编译时类型检查,必须人工保证
        JSValue* value = [static_cast<JSManagedValue *>(object) value];
        id temp = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]);
        if (temp)
            return temp;
        return object;
    }

    if ([object isKindOfClass:[JSValue class]]) {
        JSValue *value = static_cast<JSValue *>(object);
        object = tryUnwrapObjcObject([value.context JSGlobalContextRef], [value JSValueRef]);
    }

    return object;
}

removeManagedReference

- (void)removeManagedReference:(id)object withOwner:(id)owner
{
    // JSManageredValue减少owner引用计数
    if ([object isKindOfClass:[JSManagedValue class]])
        [object didRemoveOwner:owner];

    // unwrap
    object = getInternalObjcObject(object);
    owner = getInternalObjcObject(owner);

    if (!object || !owner) return;

    JSC::JSLockHolder locker(toJS(m_group));

    // 查找owner对应的ownedObjects
    NSMapTable *ownedObjects = [m_externalObjectGraph objectForKey:owner];
    if (!ownedObjects) return;

    // 从ownedObjects查找object对应的引用计数,并减一
    size_t count = reinterpret_cast<size_t>(NSMapGet(ownedObjects, object));
    if (count > 1) {
        NSMapInsert(ownedObjects, object, reinterpret_cast<void*>(count - 1));
        return;
    }

    if (count == 1)
        NSMapRemove(ownedObjects, object);

    if (![ownedObjects count]) {
        [m_externalObjectGraph removeObjectForKey:owner];
        [m_externalRememberedSet removeObjectForKey:owner];
    }
}

didRemoveOwner

didRemoveOwnerdidAddOwner的逆过程,减少引用计数,如果减少之后为0,就将owner从m_owners中移除

- (void)didRemoveOwner:(id)owner
{
    size_t count = reinterpret_cast<size_t>(NSMapGet(m_owners, owner));

    if (!count) return;

    if (count == 1) {
        NSMapRemove(m_owners, owner);
        return;
    }

    NSMapInsert(m_owners, owner, reinterpret_cast<void*>(count - 1));
}

是不是已经感觉凌乱了?我们先看一下图:

实际上到这里,我们也没讲明白,JSVirturlMachine和JSManagedValue是怎么做到有条件强持有的,并且没有循环引用。

以下是我的一点推理:
我们知道Objective-C的内存管理是ARC,而JavaScript的是GC,我们这里已经研究到JavaScriptCore的源码层面,GC的实现也必然在这里。不管怎样,一个JavaScript的对象要想真正的释放,对应到JavaScriptCore源码层级,必然对应到某一个C++对象的析构函数的调用,否则这个GC就无法实现。有了这个前提,上面的代码就派上用处了:当我们使用JSManagedValue对象JMV去包裹了一个JSValue对象JV的时候(JMV对象并没有强持有JV对象,而是弱引用),只要JMV对象的生命周期还在,我们就能在m_externalObjectGraph寻找到至少一个JV对象对应的JMV对象,那么就可以在GC回收内存的时候告诉它,不要释放JV对象,尽管该JV对象的引用计数已经可能为0,可能已经成为孤岛。这样,只要JSValue的内存块没有被释放掉,它就可以一直被我们访问。如果到了某一个时刻,我们在m_externalObjectGraph找不到任何JV对应的JMV对象,GC就可以把JV的内存收回了。并且在这整个过程中,没有产生循环引用。

实际上这部分内容在scanExternalObjectGraph方法中可以看到一些端倪,感兴趣的童鞋可以看一下。

JSContext

一个JSContext对象代表一个JavaScript执行环境。在native代码中,使用JSContext去执行JS代码,访问JS中定义或者计算的值,并使JavaScript可以访问native的对象、方法、函数,它就好像是OC和JS语言之间的一座桥梁。

SValue *value = [context evaluateScript:@"var a = 1+2*3;"];

NSLog(@"a = %@", [context objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", [context.globalObject objectForKeyedSubscript:@"a"]);
NSLog(@"a = %@", context[@"a"]);

JSContext初始化

- (instancetype)initWithVirtualMachine:(JSVirtualMachine *)virtualMachine
{
    self = [super init];

    // 可以看到,JSContext对JSVirtualMachine做了强持有
    m_virtualMachine = [virtualMachine retain];

    // 从virtualMachine获取group,并在group中创建m_context
    m_context = JSGlobalContextCreateInGroup(getGroupFromVirtualMachine(virtualMachine), 0);

    // 创建m_wrapperMap集合,用来存放JSValue对象
    m_wrapperMap = [[JSWrapperMap alloc] initWithContext:self];

    // 将自己添加到virtualMachine中的m_context字典中,一个JSVirtualMachine可以对应多个JSContext
    [m_virtualMachine addContext:self forGlobalContextRef:m_context];

    return self;
}

全局对象

肤浅地看下全局对象(因为这个方法深究下去,非常复杂),简而言之,一个JSContext对应着一个全局对象

- (JSValue *)globalObject
{
    return [JSValue valueWithJSValueRef:JSContextGetGlobalObject(m_context) inContext:self];
}
JSObjectRef JSContextGetGlobalObject(JSContextRef ctx)
{
    if (!ctx) {return 0;}

    // 获取当前脚本执行状态
    ExecState* exec = toJS(ctx);

    // 上锁
    JSLockHolder locker(exec);

    // 这里就相当复杂了,后续篇章可能会涉及到
    return toRef(jsCast<JSObject*>(exec->lexicalGlobalObject()->methodTable()->toThis(exec->lexicalGlobalObject(), exec, NotStrictMode)));
}

执行脚本

- (JSValue *)evaluateScript:(NSString *)script withSourceURL:(NSURL *)sourceURL
{
    JSStringRef scriptJS = JSStringCreateWithCFString((CFStringRef)script);
    JSStringRef sourceURLJS = sourceURL ? JSStringCreateWithCFString((CFStringRef)[sourceURL absoluteString]) : nullptr;
    JSValueRef result = JSEvaluateScript(m_context, scriptJS, nullptr, sourceURLJS, 0, NULL);
    if (sourceURLJS) JSStringRelease(sourceURLJS);
    JSStringRelease(scriptJS);
    return [JSValue valueWithJSValueRef:result inContext:self];
}

可以看到核心逻辑在JSEvaluateScript

JSValueRef JSEvaluateScript(JSContextRef ctx, JSStringRef script, JSObjectRef thisObject, JSStringRef sourceURL, int startingLineNumber, JSValueRef* exception)
{
    // 再次碰到了ExecState,当前脚本执行状态
    ExecState* exec = toJS(ctx);
    JSLockHolder locker(exec);

    JSObject* jsThisObject = toJS(thisObject);

    startingLineNumber = std::max(1, startingLineNumber);

    // evaluate sets "this" to the global object if it is NULL
    JSGlobalObject* globalObject = exec->vmEntryGlobalObject();

    // 加载源码
    SourceCode source = makeSource(script->string(), sourceURL ? sourceURL->string() : String(), TextPosition(OrdinalNumber::fromOneBasedInt(startingLineNumber), OrdinalNumber::first()));

    // 执行JS代码,这里是我们要分析的核心入口
    JSValue returnValue = profiledEvaluate(globalObject->globalExec(), ProfilingReason::API, source, jsThisObject, evaluationException);

    // 转换为Ref形式
    if (returnValue) return toRef(exec, returnValue);

    return toRef(exec, jsUndefined());
}

JSValue

一个JSValue实例就是一个JavaScript值的引用。使用JSValue类在JavaScript和native代码之间转换一些基本类型的数据(比如数值和字符串)。你也可以使用这个类去创建包装了自定义类的native对象的JavaScript对象,或者创建由native方法或者block实现的JavaScript函数。

每个JSValue实例都来源于一个代表JavaScript执行环境JSContext的对象,这个执行环境就包含了这个JSValue对应的值。每个JSValue对象都持有其JSContext对象的强引用,只要有任何一个与特定JSContext关联的JSValue被持有(retain),这个JSContext就会一直存活。通过调用JSValue的实例方法返回的其他的JSValue对象都属于与最始的JSValue相同的JSContext。

每个JSValue都通过其JSContext间接关联了一个特定的代表执行资源基础的JSVirtualMachine对象。你只能将一个JSValue对象传给由相同虚拟机管理(host)的JSValue或者JSContext的实例方法。如果尝试把一个虚拟机的JSValue传给另一个虚拟机,将会触发一个Objective-C异常

初始化

- (JSValue *)initWithValue:(JSValueRef)value inContext:(JSContext *)context
{
    if (!value || !context) return nil;

    self = [super init];

    // 注意:强持有context
    _context = [context retain];

    // 将value值保存下来,但是并没有强持有
    m_value = value;

    // 这里相当于GC计数
    JSValueProtect([_context JSGlobalContextRef], m_value);

    return self;
}

倒数第二句相当于GC做一个强引用,该方法一直追溯下去,发现是一个ProtectCountSet集合,它在添加元素的时候,会增加元素的引用计数,导致元素不会被释放

ProtectCountSet m_protectedValues;

void Heap::protect(JSValue k)
{
    if (!k.isCell()) return;
    m_protectedValues.add(k.asCell());
}

类型转换

OC对象和JS对象或者值的转换对应图如下:

   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)

OC->JS

// 将OC对象转换为JSValue
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
{
    return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
}
JSValueRef objectToValue(JSContext *context, id object)
{
    JSGlobalContextRef contextRef = [context JSGlobalContextRef];

    // 如果是简单的对象
    ObjcContainerConvertor::Task task = objectToValueWithoutCopy(context, object);
    if (task.type == ContainerNone) return task.js;

    // 如果是一个集合类的对象,里面可能包含别的简单对象或者集合
    JSC::JSLockHolder locker(toJS(contextRef));
    ObjcContainerConvertor convertor(context);
    convertor.add(task);

    do {
        ObjcContainerConvertor::Task current = convertor.take();
        JSObjectRef js = JSValueToObject(contextRef, current.js, 0);

        if (current.type == ContainerArray) {
            // 如果是数组
            NSArray *array = (NSArray *)current.objc;
            NSUInteger count = [array count];
            for (NSUInteger index = 0; index < count; ++index)
                JSObjectSetPropertyAtIndex(contextRef, js, index, convertor.convert([array objectAtIndex:index]), 0);
        } 
        else
        {
            // 如果是字典
            NSDictionary *dictionary = (NSDictionary *)current.objc;
            for (id key in [dictionary keyEnumerator]) {
                if ([key isKindOfClass:[NSString class]]) {
                    JSStringRef propertyName = JSStringCreateWithCFString((CFStringRef)key);
                    JSObjectSetProperty(contextRef, js, propertyName, convertor.convert([dictionary objectForKey:key]), 0, 0);
                    JSStringRelease(propertyName);
                }
            }
        }
    } while (!convertor.isWorkListEmpty());

    return task.js;
}
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
{
    // context对JSValueRef进行了打包,所以JSValue离不开JSContext
    return [context wrapperForJSObject:value];
}

JS->OC

- (id)toObject
{
    return valueToObject(_context, m_value);
}

id valueToObject(JSContext *context, JSValueRef value)
{
    JSContainerConvertor::Task result = valueToObjectWithoutCopy([context JSGlobalContextRef], value);
    if (result.type == ContainerNone)  return result.objc;
    return containerValueToObject([context JSGlobalContextRef], result);
}
static id containerValueToObject(JSGlobalContextRef context, JSContainerConvertor::Task task)
{
    JSC::JSLockHolder locker(toJS(context));
    JSContainerConvertor convertor(context);
    convertor.add(task);

    do {
        JSContainerConvertor::Task current = convertor.take();
        JSObjectRef js = JSValueToObject(context, current.js, 0);

        if (current.type == ContainerArray) {
            ASSERT([current.objc isKindOfClass:[NSMutableArray class]]);
            NSMutableArray *array = (NSMutableArray *)current.objc;

            JSStringRef lengthString = JSStringCreateWithUTF8CString("length");
            unsigned length = JSC::toUInt32(JSValueToNumber(context, JSObjectGetProperty(context, js, lengthString, 0), 0));
            JSStringRelease(lengthString);

            for (unsigned i = 0; i < length; ++i) {
                id objc = convertor.convert(JSObjectGetPropertyAtIndex(context, js, i, 0));
                [array addObject:objc ? objc : [NSNull null]];
            }
        } else {
            NSMutableDictionary *dictionary = (NSMutableDictionary *)current.objc;
            JSC::JSLockHolder locker(toJS(context));

            JSPropertyNameArrayRef propertyNameArray = JSObjectCopyPropertyNames(context, js);
            size_t length = JSPropertyNameArrayGetCount(propertyNameArray);

            for (size_t i = 0; i < length; ++i) {
                JSStringRef propertyName = JSPropertyNameArrayGetNameAtIndex(propertyNameArray, i);
                if (id objc = convertor.convert(JSObjectGetProperty(context, js, propertyName, 0)))
                    dictionary[[(NSString *)JSStringCopyCFString(kCFAllocatorDefault, propertyName) autorelease]] = objc;
            }

            JSPropertyNameArrayRelease(propertyNameArray);
        }

    } while (!convertor.isWorkListEmpty());

    return task.objc;
}

辅助类JSContainerConvertor

class JSContainerConvertor {
public:
    struct Task {
        JSValueRef js;
        id objc;
        ConversionType type;
    };

    JSContainerConvertor(JSGlobalContextRef context): m_context(context){}

    id convert(JSValueRef property);
    void add(Task);
    Task take();
    bool isWorkListEmpty() const { return !m_worklist.size(); }

private:
    JSGlobalContextRef m_context;
    HashMap<JSValueRef, id> m_objectMap;
    Vector<Task> m_worklist;
    Vector<JSC::Strong<JSC::Unknown>> m_jsValues;
};

JSManagedValue

Objective-C 用的是ARC,不能自动解决循环引用问题,需要程序员手动处理,而JavaScript 用的是GC,所有的引用都是强引用,但是垃圾回收器会解决循环引用问题,JavaScriptCore 也一样,一般来说,大多数时候不需要我们去手动管理内存,但是有些情况需要注意:

  • 不要在在一个导出到JavaScript的native对象中持有JSValue对象。因为每个JSValue对象都包含了一个JSContext对象,这种关系将会导致循环引用,因而可能造成内存泄漏。
JSValue *value = [JSValue valueWithObject:@"test" inContext:context];
context[@"block"] = ^(){
    NSLog(@"%@", value);
};

通常我们使用weak来修饰block内需要使用的外部引用以避免循环引用,由于JSValue对应的JS对象内存由虚拟机进行管理并负责回收,这种方法不能准确地控制block内的引用JSValue的生命周期,可能在block内需要使用JSValue的时候,其已经被虚拟机回收。

因为JSValue的引用计数为0,所以早早就被释放了,不能达到我们的预期。

Apple引入了有条件的强引用:conditional retain,而对应的类就叫JSManagedValue
一个JSManagedValue对象包含了一个JSValue对象,“有条件地持有(conditional retain)”的特性使其可以自动管理内存。
最基本的用法就是用来在导入到JavaScript的native对象中存储JSValue。

JSValue *value = [JSValue valueWithObject:@"test" inContext:context];
JSManagedValue *managedValue = [JSManagedValue managedValueWithValue:value     andOwner:self];
context[@"block"] = ^(){
    NSLog(@"%@", [managedValue value]);
};
  • 所谓“有条件地持有(conditional retain)”,是指在以下两种情况任何一个满足的情况下保证其管理的JSValue被持有:可以通过JavaScript的对象图找到该JSValue。可以通过native对象图找到该JSManagedValue。使用addManagedReference:withOwner:方法可向虚拟机记录该关系反之,如果以上条件都不满足,JSManagedValue对象就会将其value置为nil并释放该JSValue。

初始化

@implementation JSManagedValue {
    JSC::Weak<JSC::JSGlobalObject> m_globalObject;
    RefPtr<JSC::JSLock> m_lock;
    WeakValueRef m_weakValue;//注意这里的m_weakValue
    NSMapTable *m_owners;
}


+ (JSManagedValue *)managedValueWithValue:(JSValue *)value andOwner:(id)owner
{
    // 这里的JSManagedValue并没有对JSValue进行强引用
    JSManagedValue *managedValue = [[self alloc] initWithValue:value];

    // context对应的virtualMachine对value进行了强持有
    [value.context.virtualMachine addManagedReference:managedValue withOwner:owner];
    return [managedValue 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);

    m_lock = &exec->vm().apiLock();

    NSPointerFunctionsOptions weakIDOptions = NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPersonality;
    NSPointerFunctionsOptions integerOptions = NSPointerFunctionsOpaqueMemory | NSPointerFunctionsIntegerPersonality;
    m_owners = [[NSMapTable alloc] initWithKeyOptions:weakIDOptions valueOptions:integerOptions capacity:1];

    // 可以看出JSManagedValue并没有对JSValue进行强引用
    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)dealloc
{
    JSVirtualMachine *virtualMachine = [[[self value] context] virtualMachine];
    if (virtualMachine) {
        NSMapTable *copy = [m_owners copy];
        for (id owner in [copy keyEnumerator]) {
            size_t count = reinterpret_cast<size_t>(NSMapGet(m_owners, owner));
            while (count--)
                [virtualMachine removeManagedReference:self withOwner:owner];
        }
        [copy release];
    }

    [self disconnectValue];
    [m_owners release];
    [super dealloc];
}

这部分的内容可以和JSVirtualMachine结合看,可能有助于理解,因为这二者的联系比较紧密

JSExport

JSExport协议提供了一种声明式的方法去向JavaScript代码导出Objective-C的实例类及其实例方法,类方法和属性。

使用示例

@class MySize;

@protocol MySizeExports <JSExport>
@property double w;
@property double h;
- (NSString *)description;
- (instancetype)initWithW:(double)w h:(double)h;
+ (MySize *)makeSizeWithW:(double)w h:(double)h;
@end

@interface MySize : NSObject <MySizeExports>
// 这两个方法不在MySizeExports协议中,所以在js代码中是不可见的
- (void)instanceTest; 
+ (void)classTest;
@end

在js代码中可以这样调用:

// Objective-C initializers can be called with constructor syntax. 
var size = MySize(1, 2); 

// Objective-C properties become fields. 
size.w; 

size.h = 10; 

// Objective-C instance methods become functions. 
size.description(); 

// Objective-C class methods become functions on the constructor object. 
var q = MySize.makeSizeWithWH(0, 0);

JSExport实现原理

对于一个class实现的每个协议,如果这个协议继承了JSExport协议,JavaScriptCore就将这个协议的方法和属性列表导出给JavaScript。

  • 对于每一个导出的实例方法,JavaScriptCore都会在prototype中创建一个对应的方法;
  • 对于么一个导出的实例属性,JavaScriptCore都会在prototype中创建一个对应一个存取器属性;
  • 对于每一个导出的类方法,JavaScriptCore会在constructor对象中创建一个对应的JavaScript function.

说起来,JavaScript一直是让人很费解的一门语言,它是动态的,本身没有类的概念,尽管ES6引入了关键字class,但是这种语法糖仍然改变不了JavaScript是基于原型的这一事实,掌握原型和原型链的本质是JavaScript进阶的非常重要一环。

引用一章非常经典的原型链和构造函数的关系图

但是这里我们不打算细究JavaScript的原型链,感兴趣的同学可以参考这里:
js基础篇——原型与原型链的详细理解
javascript的原型与原型链

我们来看下JavaScriptCoreJSExport实现原理,在分析之前,得做一些铺垫,从JSWrapperMap说起,而后者正是作为JSContext的一个成员而存在的:

JSWrapperMap *m_wrapperMap;

JSWrapperMap

JSWrapperMap,顾名思义,这是一个包装容器,它的初始化方法中有一个参数JSContext,看来他本身也是离不开JSContext而存在的。

@interface JSWrapperMap : NSObject
- (id)initWithContext:(JSContext *)context;
- (JSValue *)jsWrapperForObject:(id)object;
- (JSValue *)objcWrapperForJSValueRef:(JSValueRef)value;
@end

@implementation JSWrapperMap {
    JSContext *m_context;

    // Class -> JSObjCClassInfo
    NSMutableDictionary *m_classMap;

    // OC对象 -> JSObject
    std::unique_ptr<JSC::WeakGCMap<id, JSC::JSObject>> m_cachedJSWrappers;

    // 
    NSMapTable *m_cachedObjCWrappers;
}

对于一个OC对象,其jsWrapper的生成如下:

- (JSValue *)jsWrapperForObject:(id)object
{
    // 先从m_cachedJSWrappers容器中查找
    JSC::JSObject* jsWrapper = m_cachedJSWrappers->get(object);

    // 如果找到,封装为JSValue返回
    if (jsWrapper)
        return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];


    if (class_isMetaClass(object_getClass(object)))
        // 如果是object的类是元类,即object本身是一个Class,构建原型对象
        jsWrapper = [[self classInfoForClass:(Class)object] constructor];
    else {
        // 如果object是对象,先生成JSObjCClassInfo类信息,然后包装object对象
        JSObjCClassInfo* classInfo = [self classInfoForClass:[object class]];
        jsWrapper = [classInfo wrapperForObject:object];
    }

    // 设置key-value
    m_cachedJSWrappers->set(object, jsWrapper);

    // 将生成的jsWrapper,封装为JSValue返回
    return [JSValue valueWithJSValueRef:toRef(jsWrapper) inContext:m_context];
}

classInfoForClass

下来看下怎么通过一个Class生成JSObjCClassInfo信息

- (JSObjCClassInfo*)classInfoForClass:(Class)cls
{
    if (!cls)
        return nil;

    // 如果已经有缓存好的JSObjCClassInfo信息,直接返回
    if (JSObjCClassInfo* classInfo = (JSObjCClassInfo*)m_classMap[cls])
        return classInfo;


    // 跳过一些以下划线开头的内部中间类,直接获取其父类信息
    if ('_' == *class_getName(cls))
        return m_classMap[cls] = [self classInfoForClass:class_getSuperclass(cls)];

    // 通过cls类信息生成JSObjCClassInfo信息,并存入m_classMap
    return m_classMap[cls] = [[[JSObjCClassInfo alloc] initWithContext:m_context forClass:cls] autorelease];
}

JSObjCClassInfo

@interface JSObjCClassInfo : NSObject {
    JSContext *m_context;
    Class m_class;
    bool m_block;
    JSClassRef m_classRef;
    JSC::Weak<JSC::JSObject> m_prototype;
    JSC::Weak<JSC::JSObject> m_constructor;
}

- (id)initWithContext:(JSContext *)context forClass:(Class)cls;
- (JSC::JSObject *)wrapperForObject:(id)object;
- (JSC::JSObject *)constructor;
- (JSC::JSObject *)prototype;

@end

重点来了,JavaScript中很重要的两个概念:constructor和prototype

- (JSC::JSObject*)constructor
{
    JSC::JSObject* constructor = m_constructor.get();
    if (!constructor)
        constructor = [self allocateConstructorAndPrototype].first;
    ASSERT(!!constructor);
    return constructor;
}

- (JSC::JSObject*)prototype
{
    JSC::JSObject* prototype = m_prototype.get();
    if (!prototype)
        prototype = [self allocateConstructorAndPrototype].second;
    ASSERT(!!prototype);
    return prototype;
}

这两个方法都不约而同的调到了allocateConstructorAndPrototype:

allocateConstructorAndPrototype

- (ConstructorPrototypePair)allocateConstructorAndPrototype
{
    // 获取父类的JSObjCClassInfo信息
    JSObjCClassInfo* superClassInfo = [m_context.wrapperMap classInfoForClass:class_getSuperclass(m_class)];

    JSC::JSObject* jsPrototype = m_prototype.get();
    JSC::JSObject* jsConstructor = m_constructor.get();

    if (!superClassInfo) {
        // 如果父类为空,返回js中的根类Object的contructor和prototype
        JSContextRef cContext = [m_context JSGlobalContextRef];
        JSValue *constructor = m_context[@"Object"];
        if (!jsConstructor)
            jsConstructor = toJS(JSValueToObject(cContext, valueInternalValue(constructor), 0));

        if (!jsPrototype) {
            JSValue *prototype = constructor[@"prototype"];
            jsPrototype = toJS(JSValueToObject(cContext, valueInternalValue(prototype), 0));
        }
    } 
    else
    {
        // 获取该类名字
        const char* className = class_getName(m_class);

        // 生成 prototype/constructor 对.
        if (!jsPrototype)
            // 生成js对象的原型(重点方法)
            jsPrototype = objectWithCustomBrand(m_context, [NSString stringWithFormat:@"%sPrototype", className]);

        if (!jsConstructor)
            // 生成js对象的构造器(重点方法)
            jsConstructor = allocateConstructorForCustomClass(m_context, className, m_class);

        // 类型转换
        JSValue* prototype = [JSValue valueWithJSValueRef:toRef(jsPrototype) inContext:m_context];
        JSValue* constructor = [JSValue valueWithJSValueRef:toRef(jsConstructor) inContext:m_context];

        // 为JS对象定义constructor和prototype这两个属性
        putNonEnumerable(prototype, @"constructor", constructor);
        putNonEnumerable(constructor, @"prototype", prototype);

        // 遍历该类实现的协议方法,如果包含JSExport中的方法
        Protocol *exportProtocol = getJSExportProtocol();
        forEachProtocolImplementingProtocol(m_class, exportProtocol, ^(Protocol *protocol){
            // 拷贝属性和方法到JS对象的原型中
            copyPrototypeProperties(m_context, m_class, protocol, prototype);
            copyMethodsToObject(m_context, m_class, protocol, NO, constructor);
        });

        // 设置原型
        JSC::JSObject* superClassPrototype = [superClassInfo prototype];
        JSObjectSetPrototype([m_context JSGlobalContextRef], toRef(jsPrototype), toRef(superClassPrototype));
    }

    m_prototype = jsPrototype;
    m_constructor = jsConstructor;
    return ConstructorPrototypePair(jsConstructor, jsPrototype);
}

更新

这里看到了getJSExportProtocol,我们的神经是不是应该敏感一下?

Protocol *getJSExportProtocol()
{
    static Protocol *protocol = objc_getProtocol("JSExport");
    return protocol;
}

实际上它仅仅返回JSExport这个Protocol,关键的是接下来的遍历:

inline void forEachProtocolImplementingProtocol(Class cls, Protocol *target, void (^callback)(Protocol *))
{
    Vector<Protocol *> worklist;
    HashSet<void*> visited;

    // Initially fill the worklist with the Class's protocols.
    unsigned protocolsCount;
    Protocol ** protocols = class_copyProtocolList(cls, &protocolsCount);
    worklist.append(protocols, protocolsCount);
    free(protocols);

    while (!worklist.isEmpty()) {
        Protocol *protocol = worklist.last();
        worklist.removeLast();

        // Are we encountering this Protocol for the first time?
        if (!visited.add(protocol).isNewEntry)
            continue;

        // If it implements the protocol, make the callback.
        if (protocolImplementsProtocol(protocol, target))
            callback(protocol);

        // Add incorporated protocols to the worklist.
        protocols = protocol_copyProtocolList(protocol, &protocolsCount);
        worklist.append(protocols, protocolsCount);
        free(protocols);
    }
}

对于每个声明在JSExport里的属性和方法,classInfo会在prototype和constructor里面存入对应的property和method。之后我们就可以通过具体的methodName和PropertyName生成的setter和getter方法,来获取实际的SEL。最后就可以让JSExport中的方法和属性得到正确的访问。所以简单点讲,JSExport就是负责把这些方法打个标,以methodName为key,SEL为value,存入一个map(prototype和constructor本质上就是一个Map)中去,之后就可以通过methodName拿到对应的SEL进行调用。

生成prototype

// Make an object that is in all ways a completely vanilla JavaScript object,
// other than that it has a native brand set that will be displayed by the default
// Object.prototype.toString conversion.
static JSC::JSObject *objectWithCustomBrand(JSContext *context, NSString *brand, Class cls = 0)
{
    JSClassDefinition definition;
    definition = kJSClassDefinitionEmpty;
    definition.className = [brand UTF8String];
    JSClassRef classRef = JSClassCreate(&definition);
    JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
    JSClassRelease(classRef);
    return result;
}

生成contructor

static JSC::JSObject* allocateConstructorForCustomClass(JSContext *context, const char* className, Class cls)
{
    if (!supportsInitMethodConstructors())
        return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);

    // For each protocol that the class implements, gather all of the init 
    // 对于class以及其父类遵守的每一个协议,收集其初始化方法到一个哈希表中
    __block HashMap<String, Protocol *> initTable;
    Protocol *exportProtocol = getJSExportProtocol();
    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
        forEachProtocolImplementingProtocol(currentClass, exportProtocol, ^(Protocol *protocol) {
            forEachMethodInProtocol(protocol, YES, YES, ^(SEL selector, const char*) {
                const char* name = sel_getName(selector);
                if (!isInitFamilyMethod(@(name)))return;                    
                initTable.set(name, protocol);
            });
        });
    }

    // 循环遍历class以及其父类的方法列表,如果能在哈希表中找到,将其进行标识
    for (Class currentClass = cls; currentClass; currentClass = class_getSuperclass(currentClass)) {
        __block unsigned numberOfInitsFound = 0;
        __block SEL initMethod = 0;
        __block Protocol *initProtocol = 0;
        __block const char* types = 0;
        forEachMethodInClass(currentClass, ^(Method method) {
            SEL selector = method_getName(method);
            const char* name = sel_getName(selector);
            auto iter = initTable.find(name);

            if (iter == initTable.end())
                return;

            numberOfInitsFound++;
            initMethod = selector;
            initProtocol = iter->value;
            types = method_getTypeEncoding(method);
        });

        if (!numberOfInitsFound)
            continue;

        if (numberOfInitsFound > 1) {
            break;
        }

        // 找到就返回
        JSObjectRef method = objCCallbackFunctionForInit(context, cls, initProtocol, initMethod, types);
        return toJS(method);
    }
    return constructorWithCustomBrand(context, [NSString stringWithFormat:@"%sConstructor", className], cls);
}
static JSC::JSObject *constructorWithCustomBrand(JSContext *context, NSString *brand, Class cls)
{
    JSClassDefinition definition;
    definition = kJSClassDefinitionEmpty;
    definition.className = [brand UTF8String];
    definition.hasInstance = constructorHasInstance;
    JSClassRef classRef = JSClassCreate(&definition);
    JSC::JSObject* result = makeWrapper([context JSGlobalContextRef], classRef, cls);
    JSClassRelease(classRef);
    return result;
}

总结

本篇是对JavaScriptCore的API的分析总结,由于源码过于庞大,为了避免掉进泥潭不能自拔,很多地方都是浅尝辄止,后续篇章将会逐步深入进行分析。
但是总的来说,JavaScriptCore隐藏了很多实现细节,API的接口也非常简洁

-------------本文结束 感谢您的阅读-------------

本文标题:JavaScriptCore引擎深度解析2—API篇

文章作者:lingyun

发布时间:2018年07月25日 - 00:07

最后更新:2018年10月09日 - 23:10

原始链接:https://tsuijunxi.github.io/2018/07/25/JavaScriptCore引擎深度解析-2-API篇/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。