JavaScriptCore引擎深度解析2—API篇

前言

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

在JavaScriptCore.h文件中可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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的头文件

1
2
3
4
5
@interface JSVirtualMachine : NSObject
- (instancetype)init;
- (void)addManagedReference:(id)object withOwner:(id)owner;
- (void)removeManagedReference:(id)object withOwner:(id)owner;
@end

接下来看下JSVirtualMachine的mm文件

1
2
3
4
5
6
@implementation JSVirtualMachine {
JSContextGroupRef m_group;// JSContext集合
NSMapTable *m_contextCache;
NSMapTable *m_externalObjectGraph;
NSMapTable *m_externalRememberedSet;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (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

1
2
3
4
5
6
7
// 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对象的内存管理,为了看的更为清晰一点,先看下该方法如何被调用的。

1
2
3
4
5
6
7
8
9
- (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (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;

1
2
3
4
5
- (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
- (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中移除

1
2
3
4
5
6
7
8
9
10
11
12
13
- (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语言之间的一座桥梁。

1
2
3
4
5
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初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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对应着一个全局对象

1
2
3
4
- (JSValue *)globalObject
{
return [JSValue valueWithJSValueRef:JSContextGetGlobalObject(m_context) inContext:self];
}

1
2
3
4
5
6
7
8
9
10
11
12
13
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)));
}

执行脚本

1
2
3
4
5
6
7
8
9
- (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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异常

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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集合,它在添加元素的时候,会增加元素的引用计数,导致元素不会被释放

1
2
3
4
5
6
7
ProtectCountSet m_protectedValues;

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

类型转换

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

1
2
3
4
5
6
7
8
9
10
11
12
  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

1
2
3
4
5
// 将OC对象转换为JSValue
+ (JSValue *)valueWithObject:(id)value inContext:(JSContext *)context
{
return [JSValue valueWithJSValueRef:objectToValue(context, value) inContext:context];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}
1
2
3
4
5
+ (JSValue *)valueWithJSValueRef:(JSValueRef)value inContext:(JSContext *)context
{
// context对JSValueRef进行了打包,所以JSValue离不开JSContext
return [context wrapperForJSObject:value];
}

JS->OC

1
2
3
4
5
6
7
8
9
10
11
- (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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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对象,这种关系将会导致循环引用,因而可能造成内存泄漏。
1
2
3
4
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。

1
2
3
4
5
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。

初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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的实例类及其实例方法,类方法和属性。

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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代码中可以这样调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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的一个成员而存在的:

1
JSWrapperMap *m_wrapperMap;

JSWrapperMap

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@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的生成如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (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信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
- (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,我们的神经是不是应该敏感一下?

1
2
3
4
5
Protocol *getJSExportProtocol()
{
static Protocol *protocol = objc_getProtocol("JSExport");
return protocol;
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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

1
2
3
4
5
6
7
8
9
10
11
12
13
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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);
}
1
2
3
4
5
6
7
8
9
10
11
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 国际 转载请保留原文链接及作者。