Hummer 是滴滴开源的跨端结构,目前是滴滴货运司机端重要的开发三方库

一、加载导出类、办法和特点(在 OC 侧准备好被 JS 调用的类、办法和特点)

  1. 加载一切「现已导出的类

    什么叫「导出的类 Exported Class」?指的是那些在 Native 侧现已写好了,TypeScript 代码可以运用的类。导出的办法,导出的特点同理。导出的类在Mach-O 文件中

导出一个类
#define HM_EXPORT_CLASS(jsClass, objcClass) \
__attribute__((used, section("__DATA, hm_export_class"))) \
static const HMExportStruct __hm_export_class_##jsClass##__ = {#jsClass, #objcClass};
举个例子
HM_EXPORT_CLASS(Loading, HMActivityIndicatorView)

存储的时分,用到了__attribute__((section(“name”))) ,这个编译特点,改变了数据的存储特性。也便是说经过魔法一般的代码,咱们得以在编译链接的时分就把对应的结构体(也便是 HMExportStruct)写到可履行文件 Mach-O 中,在后续的初始化代码中能读出来。

ARM 文档在这里,有爱好就点去看看吧。

used 参数是告知编译器:不论我用不用都不需要优化掉这个函数。这里有一篇文章讲了怎样运用这个编译特点

于是咱们的疑惑得以解开了:

举个栗子给观众朋友们整明白点:

咱们在 .m 文件中写下 HM_EXPORT_CLASS(Loading, HMActivityIndicatorView), 就足以让编译器把对应的 hm_export_class_Loding 结构体写到 Mach-O 文件中,然后在startEngine 的时分就能获取到对应的导出类信息(一组字符串罢了)。

读取的时分运用到dladdr 函数,获取到 DL_info,然后依据 info获取共享目标的基地址(我猜测的Mach-O 文件的基地址),然后经过getsectbynamefromheader_64函数,获取Mach-O 文件的段数据。

const struct section_64 *section = getsectbynamefromheader_64((void *) mach_header, "__DATA", "hm_export_class");
// 注意看这里的 hm_export_class 字符串,跟存储类信息时分的字符串对应上了

这时,就成功获取到了一个 HMExportClass 目标了(这是个单例目标)

@interface HMExportClass : NSObject
@property (nonatomic, nullable, copy) NSString *className;
@property (nonatomic, nullable, copy) NSString *jsClass;

那什么叫加载呢?便是把这些Native 类起一个objc 的类名,再起一个 js 类名,然后放入一个 dictionary 中,这样既可以经过 objc 类名获取到这个类,也能经过 js 类名获取到这个类。一类,两名。就像给我家的仆人起一个中文名玛丽,起一个英文名 Mary,那既可以经过「玛丽」呼喊她,也可以经过「Mary」呼喊她。

  1. 加载导出类对应的导出的办法和特点

    导出的办法没放 Mach-O 文件里,而是界说了一个类办法

#define HM_EXPORT_PROPERTY(jsProp, getter, setter) \
+ (HMExportProperty *)__hm_export_property_##jsProp##__ { \
    HMExportProperty *exportProperty = [[HMExportProperty alloc] init]; \
    exportProperty.jsFieldName = @#jsProp; \
    exportProperty.propertyGetterSelector = @selector(getter); \
    exportProperty.propertySetterSelector = @selector(setter); \
\
    return exportProperty; \
}
#define HM_EXPORT_CLASS_METHOD(jsMethod, sel) \
+ (HMExportMethod *)__hm_export_method_class_##jsMethod##__ { \
    HMExportMethod *exportMethod = [[HMExportMethod alloc] init]; \
    exportMethod.jsFieldName = @#jsMethod; \
    exportMethod.selector = @selector(sel); \
\
    return exportMethod; \
}

所以直接经过class_copyMethodList 函数就能获取到一切办法和特点了,然后存到这个HMExportClass 目标的classMethodPropertyList 和 instanceMethodPropertyList

二、初始化 JSContext,创立 JS 履行的上下文,注册 C 函数到 JS履行上下文中

为了履行 JS 代码,咱们需要一个 JSContext

在 Hummer 结构中,便是HMJSContext,抛开别的事情不看,实际履行 JS 代码的是HMJSContext的一个特点 HMJSCExecutor 的 contextRef 特点,它是 C 言语的类型,表明 JS 代码履行的上下文。详细类型是 JSGlobalContextRef

它跟 OC 言语中的JSContext可以互相转化。

所以,这一步最重要的,其实便是创立这个 context,给它的大局目标注册 C 函数,好让 js 可以调用这些C 函数,然后实现通信。

// 创立 JS 履行上下文
_contextRef = JSGlobalContextCreateInGroup(virtualMachineRef, NULL);
// 「经过 JS 履行上下文」履行JS 代码
JSValueRef result = JSEvaluateScript(self.contextRef, scriptRef, NULL, sourceRef, 1, &exception);

这一步还需要注册一些 C 函数给 JS 侧调用,详细代码是:

以 hummerCall 为例
JSObjectRef inlineHummerCallFunction = JSObjectMakeFunctionWithCallback(_contextRef, **NULL**, &hummerCall);
JSObjectSetProperty(_contextRef, globalThis, hummerCallString, inlineHummerCallFunction, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, &exception);

原理是经过 JSObjectMakeFunctionWithCallbackJSObjectSetProperty 函数,把 C 函数 hummerCall 注册给了 JSContext 中的 globalThis 目标。JS 侧就能调用这些函数了。

参考资料

  • __attribute__((section(“name”))) 文档
  • JSGlobalContextRef