滴滴跨端框架 Hummer 的启动流程

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

一、加载导出类、方法和属性(在 OC 侧准备好被 JS 调用的类、方法和属性)

  1. 加载所有「已经导出的类

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

objc 复制代码
导出一个类
#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 文件的段数据。

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

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

less 复制代码
@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 文件里,而是定义了一个类方法

objc 复制代码
#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 函数,从而实现通信。

objc 复制代码
// 创建 JS 执行上下文
_contextRef = JSGlobalContextCreateInGroup(virtualMachineRef, NULL);

// 「通过 JS 执行上下文」执行JS 代码
JSValueRef result = JSEvaluateScript(self.contextRef, scriptRef, NULL, sourceRef, 1, &exception);

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

objc 复制代码
以 hummerCall 为例

JSObjectRef inlineHummerCallFunction = JSObjectMakeFunctionWithCallback(_contextRef, **NULL**, &hummerCall);

JSObjectSetProperty(_contextRef, globalThis, hummerCallString, inlineHummerCallFunction, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, &exception);

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

参考资料

相关推荐
Mintopia2 分钟前
计算机图形学环境贴图(Environment Mapping)教学指南
前端·javascript·计算机图形学
码农之王4 分钟前
(二)TypeScript前置编译配置
前端·后端·typescript
spmcor5 分钟前
css 之 Flexbox 的一生
前端·css
Mintopia9 分钟前
Three.js 高级纹理(Advanced Textures):超越基础,打造沉浸式 3D 世界
前端·javascript·three.js
玄玄子9 分钟前
JS Promise
前端·javascript·程序员
GIS之路20 分钟前
OpenLayers 获取地图状态
前端·javascript·html
FogLetter37 分钟前
深入理解Flex布局:grow、shrink和basis的计算艺术
前端·css
remember_me37 分钟前
前端打印实现-全网最简单实现方法
前端·javascript·react.js
前端小巷子40 分钟前
IndexedDB:浏览器端的强大数据库
前端·javascript·面试
Whbbit199940 分钟前
如何使用 Vue Router 的类型化路由
前端·vue.js