深入理解React Native核心原理(React Native的桥接((2)

这非常直观,只需要从指定的任何提供程序中加载源代码,通常在开发过程中从打包程序中加载源代码,在生产环境中从磁盘加载。

执行JavaScript代码

一旦所有事情准备就绪,我们可以在JS虚拟机中加载应用的源代码,复制代码,解析并执行它。在第一次执行时需要注册所有CommonJS模块并且需要入口文件。

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, &jsError); JSStringRelease(jsURL); JSStringRelease(execJSString);

JavaScript中的Modules

在JS侧我们现在可以通过react-native的NativeModules拿到前面的JSON配置信息构成的module:

深入理解React Native核心原理(React Native的桥接(

它运行的方式是当你调用一个方法的时候它被放到一个队列,包括module的名称,method的名称以及所有的参数,在JavsScript执行的最后这个队列会给原生模块执行。

调用周期

现在如果我们用上面的代码调用module,它将会是这个样子的:

深入理解React Native核心原理(React Native的桥接(

调用必须从native开始,native调用JS(这张图只是截取了JS运行的某个时刻),在执行过程中,因为JS调用NativeModules的方法,它把这个调用入队,因为这个调用必须在原生那边执行。当JS执行完后,原生模块遍历入队的所有调用,然后当它执行这些调用后,通过桥接进行回调(一个原生模块可以通过_bridge实例来调用enqueueJSCall:args:),来再次回调JS。

(如果您一直在关注该项目,过去也有来自native-> JS的调用队列,该调用队列会在每个vSYNC上分派,但为了缩短启动时间已将其删除)

参数类型

native到JS的调用很容易,参数被NSArray传递,我们将其编码为JSON数据,但是对于JS对native的调用,我们需要native的类型,为此我们检查基本类型(ints,floats,chars...)但是就像上边提及那样,对于任何对象(结构),运行时我们不会从NSMthodSignature获得足够的信息,所以我们把类型保存为字符串。

我们使用正则表达式从method签名中提取类型,并使用RCTConvert类来实际转换对象,默认情况下它为每种类型都提供了方法,并且尝试将JSON输入转换为所需要的类型。

除非是一个struct,否则我们使用objc_msgSend动态调用该方法,因为arm64上没有objc_msgSend_stret的版本,因此我们使用NSInvocation。

转换完所有参数后,我们将使用另一个NSInvocation来调用目标module和method。

例子:

// 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: require('NativeModules').MyModule.method(['a', 1], { x: 0, y: 0, width: 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 ], // method IDs @[ // arguments @[ @[@"a", @1], @{ @"x": @0, @"y": @0, @"width": @200, @"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, @[@"a", @1]) call[args][3] = NSInvocation(RCTConvert, CGRect, @{ @"x": @0, ... }) call()

线程

正如以上提及那样,每个module默认都有一个GCD队列,除非它通过实现-methodQueue方法或将methodQueue属性与有效队列合并来指定要在哪个队列运行。ViewManagers*是例外(扩展了RCTViewManager),将默认使用Shadow Queue,而特殊目标RCTJSThread仅是一个占位符,因为它是线程而不是队列。

(其实View Managers不是真正的例外,因为基类显式的将Shadow Queue指定为目标队列了)

当前线程规则如下:

-init和-setBridge:保证在主线程执行

所有export的方法保证在目标队列执行

如果你实现了RCTInvalidating协议,则还可以确保在目标队列上调用了invalidate

无法保证在哪个线程调用-dealloc

当接收到JS的一批调用时,这些调用会按目标队列进行分组,并行调用:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wsdgdx.html