滴滴跨端框架 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 侧就能调用这些函数了。

参考资料

相关推荐
小小小小宇3 分钟前
前端领域 30 个值得安装的 Agent Skills
前端
喵了几个咪7 分钟前
基于 Next.js 的 Headless CMS 前端架构:技术解析与二次开发导引
前端·javascript·架构
星栈8 分钟前
Makepad 不只是画界面:事件、状态和组件通信,到底怎么写
前端·rust
dsyyyyy11019 分钟前
只用HTML和CSS实现换一换效果
前端·css·html
小江的记录本17 分钟前
【Spring全家桶】Spring AI核心原理、大模型集成、Prompt工程、RAG实现、AI Agent开发(附《思维导图》+《面试高频考点清单》)
java·人工智能·spring boot·后端·spring·面试·prompt
EMA20 分钟前
Agent开发中数据分析图表工具分层暴露方案总结
面试
青山Coding24 分钟前
Cesium应用(七):地形开挖的实现思路
前端·cesium
风骏时光牛马26 分钟前
Verilog常见问题及代码易错点梳理
前端
用户21816970493027 分钟前
swift (一) var let 字符串 int double 元组 数组[] 字典[:] 可选类型 if while for 函数func 可选类型?
前端
牛油果子哥q29 分钟前
【C++封装】C++封装思想与访问权限终极精讲:public/private/protected权限解析、类封装设计、继承权限变化、工程私有化规范与面试坑点
c++·面试