REACT NATIVE模块桥接详解
在这篇文章中,我假设你已经掌握了React Native的基础知识,并且有兴趣了解JavaScript和本地通信的内部工作原理。 主线程在开始之前,我们首先要知道React Native中的3个重要的线程:
此外,如果没有特殊说明,每一个单独的本地模块都有自己的GCD队列。
本地模块如果你还不知道如何创建一个本地模块,我推荐你可以先看看官方文档。 下面是一个Person模块,实现了JS调用本地模块的交互过程。 @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([js_name]) 顾名思义,这个宏会导出你的模块,但在特定的环境中, #define RCT_EXPORT_MODULE(js_name) RCT_EXTERN void RCTRegisterModule(Class); + (NSString *)moduleName { return @#js_name; } + (void)load { RCTRegisterModule(self); } 这段代码做了哪些事情?
RCT_EXPORT_METHOD(method)这个宏更有意思,实际上他没有给你的方法添加任何东西,而是额外创建了一个新的方法。 这个新的方法看起来像下面的例子一样: + (NSArray *)__rct_export__120 { @[ @"", @"log:(NSString *)message" ]; } “这是什么玩意儿?”,你一定有这样的想法。 实际上,它是由前缀(rct_export),一个可选的 这个方法唯一的目的就是返回一个包含 注:如果你使用Category,这里依然可能存在两个同名的方法,虽然Xcode会有警告,但仍然会表现出异常。 运行时 整个组装过程提供了信息给 桥接初始化的依赖图: 初始化模块 NSMutableDictionary *modulesByName; // = ... for (Class moduleClass in RCTGetModuleClasses()) { // ... module = [moduleClass new]; if ([module respondsToSelector:@selector(setBridge:)]) { module.bridge = self; } modulesByName[moduleName] = module; // ... } 配置模块 一旦我们在后台线程运行了模块,就可以列出并且调用该模块的所有以 unsigned int methodCount; Method *methods = class_copyMethodList(moduleClass, &methodCount); for (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 ExecutorJavaScript Executors有一个 JSGlobalContextRef ctx JSGlobalContextCreate(NULL); _context = [[RCTJavaScriptContext alloc] initWithJSContext:ctx]; 注入JSON配置JSON配置仅仅包含自己模块的信息,如下: { "remoteModuleConfig": { "Logger": { "constants": { /* If we had exported constants... */ }, "moduleID": 1,85)">"methods": { "requestPermissions": { "type": "remote", "methodID"1 } } } } } 这个信息被作为全局变量储存在JavaScript虚拟机中,所以JS这边的 加载JavaScript代码 这个过程很直观,就是从指定的地方载入源码。通常,在开发期间从 执行JavaScript代码一切准备就绪,程序就可以加载JavaScriptCore虚拟机中的应用源码,拷贝、解析、执行代码。首次执行需要注册所有CommanJS模块,指明入口文件。 JSValueRef jsError = NULL; JSStringRef execJSString JSStringCreateWithCFString((__bridge CFStringRef)script); JSStringRef jsURL JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString); JSValueRef result JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0,204)">&jsError); JSStringRelease(jsURL); JSStringRelease(execJSString); JavaScript模块 模块从JSON配置中生成,在JavaScript中通过 var { NativeModules } require('react-native'); var { Person } = NativeModules; Person.greet('Tadeu'); 这种工作方式的流程是,当你调用一个方法,请求会被推进一个队列中,它包含了模块名称、方法名称和所有调用所需的参数。在JavaScript执行之后,该队列会被传回到本地去执行请求。 调用周期 如果我们用上面的代码去调用一个模块,它的流程如下: 调用从本地模块到JS。在执行期间,当调用 上图仅表示了JavaScript执行中期的流程 注:如果你关注过该项目,曾经有一个本地到JS的调用队列,它会被指派到每一个vSYNC,但为了加快启动的速度已经将这个功能移除。 参数类型 从本地到JS的调用是比较容易的,参数被传入一个 我们使用正则表达式从方法签名中提取类型,实际中还使用 除了结构体以外,我们使用 一旦我们转换了所有的参数,我们使用另一个 下面是一个例子: // If you had the following method in a given module,e.g. `MyModule` RCT_EXPORT_METHOD(methodWithArray:(NSArray *) size:(CGRect)size) {} // And called it from JS,like: 'NativeModules').MyModule.method(['a',73)">1], { x: : : 200, height: 100 }); // The JS queue sent to native would then look like the following: // ** Remember that it's a queue of calls,so all the fields are arrays ** @[ @[ @0 ], // module IDs 1 ],153)">// method IDs @[ // arguments @[ @[@"a", @"x": @"y": @"width": @"height": 100 } ] ] ]; // This would convert into the following calls (pseudo code) NSInvocation call call[args][0] GetModuleForId(0) call[args][1] GetMethodForId(1) call[args][2] obj_msgSend(RCTConvert, NSArray,73)">1]) call[args][3] NSInvocation(RCTConvert, CGRect, ... }) call() 线程 正如上面提到的,每个模块默认都会有自己的GCD队列,除非通过实现
shadow queue 作为目标队列。
当前线程的规则如下:
-setBridge 是为了保证在主线程中被调用。
如果从JS有批量的调用请求,请求将会被目标队列分组,并行地被调度。 // group `calls` by `queue` in `buckets` for (id queue in buckets) { dispatch_block_t block = ^{ NSOrderedSet *calls = [buckets objectForKey:queue]; for (NSNumber *indexObj in calls) { // Actually call } }; if (queue == RCTJSThread) { [_javaScriptExecutor executeBlockOnJavaScriptQueue:block]; } else if (queue) { dispatch_async(queue, block); } } 结尾 以上就是稍加深入地对 本文翻译自http://tadeuzagallo.com/blog/react-native-bridge/ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |