本系列文章作为学习RN期间的总结
流程梳理
首先,无论是JS端还是OC端都会持有一个bridge负责对来自OC端或者JS端ModuleId、MedhodId的映射,再做消息处理。现附上一张来自JSPath作者Bang的图。
JS调用某个方法向OC发送消息,方法首先会被分解成ModuleId、MethodId和参数,发送到OCBridge。那么OCBridge是如何接受的呢?
在第一次初始化ReactNative的时候模块信息会被初始化,包括模块名称、方法名。在注册模块的时候我们会用到RCT_EXPORT_MODULE();
这个宏,宏展开
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
方法1:如果自定义了模块的名称就使用自定义的名称而不是原类的名称
方法2:在+(void)load方法里对类进行一次注册,注册代码如下
void RCTRegisterModule(Class moduleClass)
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
RCTModuleClasses = [NSMutableArray new];
});
RCTAssert([moduleClass conformsToProtocol:@protocol(RCTBridgeModule)],
@"%@ does not conform to the RCTBridgeModule protocol",
moduleClass);
// Register module
[RCTModuleClasses addObject:moduleClass];
}
如果当前类遵循协议RCTBridgeModule
,就把它放到一个静态变量数组RCTModuleClasses
中。所以启动之后,所有RN需要的模块都会被动态载入。
这时候还有一个重要的东西需要处理就是callback,JSBridge会把callback缓存起来,仅仅把callbackId传递到OCBridge。
这时候OCBridge拿到了传过来的ModuleId、MethodId、callbackId,再通过OC端的配置表分别取出模块和方法。模块会被转化为RCTModuleData
对象,方法都会被转化为一个RCTModuleMethod
对象,看一下这个对象
{
Class _moduleClass;
const RCTMethodInfo *_methodInfo;
NSString *_JSMethodName;
SEL _selector;
NSInvocation *_invocation;
NSArray<RCTArgumentBlock> *_argumentBlocks;
}
发现了_moduleClass
、_argumentBlocks
、_invocation
、_selector
这时候就很容易想到消息转发的最后一个阶段,将消息的目标、selector、参数封装到NSInvocation中进行消息转发。
刚才上面讲到模块的注册,那么方法是如何筛选的呢?注意下面的3个宏
#define RCT_EXPORT_METHOD(method) \
RCT_REMAP_METHOD(, method)
#define RCT_REMAP_METHOD(js_name, method) \
_RCT_EXTERN_REMAP_METHOD(js_name, method, NO) \
- (void)method;
#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+ (const RCTMethodInfo *)RCT_CONCAT(__rct_export__, RCT_CONCAT(js_name, RCT_CONCAT(__LINE__, __COUNTER__))) { \
static RCTMethodInfo config = {#js_name, #method, is_blocking_synchronous_method}; \
return &config; \
}
在我们具体定义方法的时候并没有-(void)
字符串前缀,因为在预编译的时候,宏会帮我们加上这段字符(看第二个宏)。
第三个宏主要实现了字符串的拼接,(__LINE__, __COUNTER__)
的意思是为当前的宏生成一个唯一的tag(行数+一个数字),再和前面的js_name
连接,最后再和__rct_export__
连接,大概像这样
-(void)__rct_export__8976699MethodName{
//
}
其实COUNTER是一个预定义的宏,这个值在编译过程中将从0开始计数,每次被调用时加1。
所有JSBridge传过来的参数都是NSNumber
,OCBridge会把它们转化成基本类型和字符串。上面在提到JSBridge传参数的时候会带一个callbackId,这时候OCBridge会根据这个Id生成一个callback,保存在内存中。
这时候OC执行方法的调用,执行block。将参数和callbackId传到JSBridge,JSBridge根据Id找到之前缓存的JSCallback,进行调用。到这里就完成了个闭环。
JS是怎么调用OC的呢?
其实并不是JS调用OC,而是JS将消息放到一个消息队列里面MessageQueue,等OC来取,这里面还有很多问题值得讨论多长时间取一次;不来取怎么办。有待我们继续研究
为什么OC和JS可以实现这种框架?
要实现这个机制需要语言有动态反射的特性,即可以通过类/方法名字符串找到对应的类/方法进行调用,没有这特性就做不了。