在这篇文章之前我们假设你已经相识了React Native的基本常识,我们会重点存眷当native和JavaScript举办信息交换时的内部运行道理。
主线程在开始之前,我们需要知道在React Native中有三个主要的线程:
shadow queue:认真机关事情
main thread:UIKit 在这个线程事情(译者注:UI Manager线程,可以当作主线程,主要认真页面交互和控件绘制的逻辑)
JavaScript thread:运行JS代码的线程
别的,一般环境下每个native模块都有本身的GCD行列,除非有非凡说明(后头会表明)
*shadow queue其实更像一个GCD行列而不是线程
Native模块假如你还不知道怎么建设一个Native模块,我推荐你去阅读一下文档
这是一个native模块Person的例子,它既受JavaScript的挪用,也可以挪用JavaScript
@interface Person : NSObject <RCTBridgeModule> @end @implementation Logger RCT_EXPORT_MODULE() RCT_EXPORT_METHOD(greet:(NSString *)name) { NSLog(@"Hi, %@!", name); [_bridge.eventDispatcher sendAppEventWithName:@"greeted" body:@{ @"name": name }]; } @end
我们重点存眷RCT_EXPORT_MODULE和RCT_EXPORT_METHOD这两个宏,它们扩展成什么,它们的脚色是什么,它们是如何运行的。
RCT_EXPORT_MODULE([js_name])正如这个要领的名字那样,它export出你的module,可是在这个特定的上下文中export是什么意思呢,它意味着桥接知道你的模块。
它的界说实际上很是简朴:
#define RCT_EXPORT_MODULE(js_name) \ RCT_EXTERN void RCTRegisterModule(Class); \ + (NSString \*)moduleName { return @#js_name; } \ + (void)load { RCTRegisterModule(self); }
它做了以下事情:
首先声明RCTRegisterModule为外部函数,意味着这个函数的实现对付编译器不行见,可是在链接阶段可用
声明一个要领moduleName,返回可选的宏参数js_name,这样这个模块在JS中具有和Objective-C中纷歧样的类名
声明一个load要领(当app加载到内存中后,每个类的load要领城市被挪用),load要领挪用RCTRegisterModule,然后桥接才知道这个袒暴露来的模块
RCT_EXPORT_METHOD(method)这个宏更有趣,它没有在你的method中增加任何对象,除了声明指定的要领外,它还建设了一个新要领。新要领如下所示:
+ (NSArray *)__rct_export__120 { return @[ @"", @"log:(NSString *)message" ]; }
它是通过将前缀(__rct_export__)和可选的js_name(本例子为空)和声明的行号以及__COUNTER__宏组成。
这个要领的目标是返回一个包括可选js_name和method签名的数组,这个js_name的浸染是制止要领定名斗嘴。
Runtime这整个配置仅仅是为了给桥接提供信息,让它可以找到export出来的所有对象,modules和methods,可是这些都是在加载的时候产生的,此刻我们来看看运行的时候是怎么利用的。
这是桥接初始化时的依赖干系图:
初始化模块RCTRegisterModule所做的事就是把类推进数组,这样在实例化一个新的桥接的时候就能找到这个类。桥接遍历数组中的所有模块,为每个模块建设一个实例,在桥接何处存储一个实例的引用,同时给这个模块实例一个桥接的引用(所以我们能双方都相互挪用),然后查抄这个模块实例是否有指定要在哪个行列运行,不然给它一个新行列,与其他模块分隔:
NSMutableDictionary *modulesByName; // = ... for (Class moduleClass in RCTGetModuleClasses()) { // ... module = [moduleClass new]; if ([module respondsToSelector:@selector(setBridge:)]){ module.bridge = self; modulesByName[moduleName] = module; // ... }
设置模块一旦我们有了这些modules,在靠山线程中,我们列出每个module的所有methods,然后挪用以__rct__export__开头的methods,我们获得一个method签名的字符串。这很重要因为我们此刻知道了参数的实际范例,在运行的时候我们只知道个中一个参数是id,可是通过这个途径我们可以知道这个id实际上是NSString *
unsigned int methodCount; Method *methods = class_copyMethodList(moduleClass, &methodCount); for (unsigned int i = 0; i < methodCount; i++) { Method method = methods[i]; SEL selector = method_getName(method); if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) { IMP imp = method_getImplementation(method); NSArray *entries = ((NSArray *(*)(id, SEL))imp)(_moduleClass, selector); //... [moduleMethods addObject:/* Object representing the method */]; } }
配置JavaScript执行器JS执行器有一个 -setUp 要领答允它做更巨大的事情,譬喻在靠山线程初始化JS代码,这同时节省了一些事情,因为只有活泼的执行器会接管 setUp 要领的挪用,而不是所有的执行器:
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL); _context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx];
注入JSON设置JSON设置仅包括我们的module,譬喻:
这个设置信息作为全局变量存储在JavaScript虚拟机,所以当JS何处的桥接初始化后它可以用这个信息来建设modules
加载JavaScript代码