iOS.ReactNative-2-bridge-and-react-native-app-execution iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule

基于0.18.1

Async batched bridge used to communicate with the JavaScript application.

分析Objective-C和JavaScript的通信机制。

Bridge承担以下工作(或者提供接口):

A: 执行JavaScript代码

1 - (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args;

B: 管理"bridge module"

1 - (id)moduleForName:(NSString *)moduleName;
2 - (id)moduleForClass:(Class)moduleClass;

C: 创建 JavaScript 执行器

1 - (void)initModules
2 {
3     ......
4     _javaScriptExecutor = [self moduleForClass:self.executorClass];

1. 寻找起点-RCTRootView

在React-Native Based的工程中, 我们看到在AppDelegate.m文件中有以下代码:

1   RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
2                                                       moduleName:@"AwesomeProject"
3                                                    launchOptions:launchOptions];
4 
5   self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
6   UIViewController *rootViewController = [[UIViewController alloc] init];
7   rootViewController.view = rootView;

之所以可以使用JS来进行iOS App开发,RCTRootView类是可以进行探索其原因的起点。

2. RCTBridge

接下来浏览类RCTRootView源码,RCTRootView是UIView的子类,并且很简单。其中有一个属性:

1 @property (nonatomic, strong, readonly) RCTBridge *bridge;

通读类RCTBridge的代码,只有很少的200~300行。但是发现类RCTBatchedBridge继承自类RCTBridge。

类RCTBatchedBridge是Bridge模块的一个私有类,只被在类RCTBridge中被使用。这样设计使得接口和繁复的实现

分离。

3. RCTBatchedBridge

TODO: Rewrite this section to match the modification in version 0.18.1

观察类RCTBatchedBridge的initiailizer,发现对接口的'initJS'的调用。在这里终于和JS '发生关系'。

 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
 {
      // 省略多行代码
      // ...... 
      // ......
      /**
       * Initialize and register bridge modules *before* adding the display link
       * so we don't have threading issues
       */
       [self registerModules];  // A

      /**
       * Start the application script
       */
       [self initJS];  // B       }
     return self;
}

在查看initJS方法的代码之前,我们先来关注比较重要的方法 registerModules。

3.0 Module是什么东西?

Module 在React Native中实际上是 可以被 JavaScript 代码调用的模块, 实现了接口RCTBridgeModule的类。

Module 包含有 Native类型, JavaScript源码类型。

A): Native类型:

由Objective-C来实现相应的功能,并将接口提供给JavaScript代码调用。

B): JavaScript源码类型:

也就是用JavaScript写的React Native App。这个类型的Module由 RCTSourceCode类 来代表。

3.1 registerModules 方法

TODO: Rewrite this section to match the modification in version 0.18.1

 1 - (void)registerModules
 2 {
 3   RCTAssertMainThread();
 4 
 5   // Register passed-in module instances
 6   NSMutableDictionary *preregisteredModules = [[NSMutableDictionary alloc] init];
 7   for (id<RCTBridgeModule> module in self.moduleProvider ? self.moduleProvider() : nil) {  // A
 8     preregisteredModules[RCTBridgeModuleNameForClass([module class])] = module;
 9   }
10 
11   // Instantiate modules
12   _moduleDataByID = [[NSMutableArray alloc] init];
13   NSMutableDictionary *modulesByName = [preregisteredModules mutableCopy];    
14   for (Class moduleClass in RCTGetModuleClasses()) {   // B
15      NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
16 
17      // Check if module instance has already been registered for this name
18      id<RCTBridgeModule> module = modulesByName[moduleName];
19 
20      if (module) {
21        // Preregistered instances takes precedence, no questions asked
22        if (!preregisteredModules[moduleName]) {
23          // It's OK to have a name collision as long as the second instance is nil
24          RCTAssert([[moduleClass alloc] init] == nil,
25                    @"Attempted to register RCTBridgeModule class %@ for the name "
26                    "'%@', but name was already registered by class %@", moduleClass,
27                    moduleName, [modulesByName[moduleName] class]);
28        }
29        if ([module class] != moduleClass) {
30          RCTLogInfo(@"RCTBridgeModule of class %@ with name '%@' was encountered "
31                     "in the project, but name was already registered by class %@."
32                     "That's fine if it's intentional - just letting you know.",
33                     moduleClass, moduleName, [modulesByName[moduleName] class]);
34        }
35      } else {
36        // Module name hasn't been used before, so go ahead and instantiate
37        module = [[moduleClass alloc] init];
38      }
39      if (module) {
40        modulesByName[moduleName] = module;
41      }
42   }
43 
44   // Store modules
45   _modulesByName = [[RCTModuleMap alloc] initWithDictionary:modulesByName];
46 
47   /**
48    * The executor is a bridge module, wait for it to be created and set it before
49    * any other module has access to the bridge
50    */
51   _javaScriptExecutor = _modulesByName[RCTBridgeModuleNameForClass(self.executorClass)]; // C
52   RCTLatestExecutor = _javaScriptExecutor;
53 
54   [_javaScriptExecutor setUp];
55 
56   // Set bridge
57   for (id<RCTBridgeModule> module in _modulesByName.allValues) {  // D
58     if ([module respondsToSelector:@selector(setBridge:)]) {
59       module.bridge = self;
60     }
61 
62     RCTModuleData *moduleData = [[RCTModuleData alloc] initWithExecutor:_javaScriptExecutor
63                                                                   uid:@(_moduleDataByID.count)
64                                                              instance:module];
65     [_moduleDataByID addObject:moduleData];
66 
67     if ([module conformsToProtocol:@protocol(RCTFrameUpdateObserver)]) {
68       [_frameUpdateObservers addObject:moduleData];
69     }
70   }
71   // E
72   [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidCreateNativeModules
73                                                       object:self];
74 }

A): A部分用来注册外部传递进来的Module。由于RCTBridge创建RCTBatchedBridge对象时,传入的参数导致

属性 self.moduleProvider 的值为nil,故我们先跳过这部分,直接跳到B部分。

B): B部分的循环是将加载的ModuleClass进行注册,注册到成员变量 '_modulesByName'中。

(其中的 RCTGetModuleClasses()和 RCTBridgeModuleNameForClass()

参见 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule 中的说明)

C): 从'_modulesByName'中获取javaScriptExecutor,JS Executor是关键所在,JS Executor是执行JS代码的。

在 initJS 方法中会用到。

D): 为ModuleObject(模块对象/模块实例, 或者简称: 模块)设置bridge以及ModuleData(模块元数据),最后将实现接口RCTFrameUpdateObserver

的模块对象添加到'_frameUpdateObservers' 中。

E): 发送通知, NativeModules已创建完毕。TODO: 该通知的观察者做了哪些工作?

3.2 initJS 方法

TODO: Rewrite this section to match the modification in version 0.18.1

  1 - (void)initJS
  2 {
  3   RCTAssertMainThread();
  4 
  5   // Inject module data into JS context
  6   NSMutableDictionary *config = [[NSMutableDictionary alloc] init];  // A
  7   for (RCTModuleData *moduleData in _moduleDataByID) {
  8     config[moduleData.name] = moduleData.config;
  9   }
 10   NSString *configJSON = RCTJSONStringify(@{
 11     @"remoteModuleConfig": config,
 12   }, NULL);
 13   [_javaScriptExecutorinjectJSONText:configJSON
 14                   asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
 15                              callback:^(NSError *error) {
 16     if (error) {
 17       [[RCTRedBox sharedInstance] showError:error];
 18     }
 19   }];
 20 
 21   NSURL *bundleURL = _parentBridge.bundleURL;
 22   if (_javaScriptExecutor == nil) {
 23 
 24     /**
 25      * HACK (tadeu): If it failed to connect to the debugger, set loading to NO
 26      * so we can attempt to reload again.
 27      */
 28     _loading = NO;
 29 
 30   } else if (!bundleURL) {
 31 
 32     // Allow testing without a script
 33     dispatch_async(dispatch_get_main_queue(), ^{
 34       _loading = NO;
 35       [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
 36                                                           object:_parentBridge
 37                                                         userInfo:@{ @"bridge": self }];
 38     });
 39   } else {   40 
 41     RCTProfileBeginEvent();
 42     RCTPerformanceLoggerStart(RCTPLScriptDownload);
 43     RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; // B
 44     [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
 45       RCTPerformanceLoggerEnd(RCTPLScriptDownload);
 46       RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]);
 47 
 48       _loading = NO;
 49       if (!self.isValid) {
 50         return;
 51       }
 52 
 53       static BOOL shouldDismiss = NO;
 54       if (shouldDismiss) {
 55         [[RCTRedBox sharedInstance] dismiss];
 56       }
 57       static dispatch_once_t onceToken;
 58       dispatch_once(&onceToken, ^{
 59         shouldDismiss = YES;
 60       });
 61 
 62       RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
 63       sourceCodeModule.scriptURL = bundleURL;
 64       sourceCodeModule.scriptText = script;
 65       if (error) {
 66 
 67         NSArray *stack = [error userInfo][@"stack"];
 68         if (stack) {
 69           [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
 70                                              withStack:stack];
 71         } else {
 72           [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
 73                                            withDetails:[error localizedFailureReason]];
 74         }
 75 
 76         NSDictionary *userInfo = @{@"bridge": self, @"error": error};
 77         [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
 78                                                             object:_parentBridge
 79                                                           userInfo:userInfo];
 80 
 81       } else {
 82 
 83         [self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { // C
 84 
 85           if (loadError) {
 86             [[RCTRedBox sharedInstance] showError:loadError];
 87             return;
 88           }
 89 
 90           /**
 91            * Register the display link to start sending js calls after everything
 92            * is setup
 93            */
 94           NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];  
 95           [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];  // D
 96       // E
 97           [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
 98                                                               object:_parentBridge
 99                                                             userInfo:@{ @"bridge": self }];
100         }];
101       }
102     }];
103   }
104 }

A): 将每个ModuleObject 元数据中的config 注册到 JS Executor中。(config 参见 5. RCTModuleData)

B): 拉取JS Bundler, 可以将JS Bundler看作JS代码的'包'。

C): 执行Application JS code。(参见 4. JS Executor)

D): 将'_jsDisplayLink'添加到runloop中。'_jsDisplayLink'周期性的触发的工作是什么?

E): 发送通知 RCTJavaScriptDidLoadNotification。该通知的观察者进行了哪些处理?

到此,焦点会集中到JS Executor上面,接下来进行JS Executor的代码阅读。

3.3 invalidate 方法

TODO: Rewrite this section to match the modification in version 0.18.1

3.4 React Native App源码的执行

3.4.1 执行步骤

1: RCTBatchedBridge类的init方法中调用start方法来启动React Native App

1 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
2 {
3     .......
4     
5     [self start]; // 1
6   }
7   return self;
8 }

2: 在start方法的最后,模块初始化完毕,并且(React Native App的)源码加载完毕后执行源码。

模块的初始化包含两个部分:

A) JavaScript 模块的初始化

B) Native 模块的初始化。方法 initModules 完成了 Native模块的初始化。

 1 - (void)start
 2 {
 3      ......
 4       // Synchronously initialize all native modules that cannot be loaded lazily
 5   [self initModules]; 
 6     
 7     ......
 8   dispatch_group_notify(initModulesAndLoadSource, dispatch_get_main_queue(), ^{
 9     RCTBatchedBridge *strongSelf = weakSelf;
10     if (sourceCode && strongSelf.loading) {
11       dispatch_async(bridgeQueue, ^{
12         [weakSelf executeSourceCode:sourceCode]; // 2
13       });
14     }
15   });

3: executeSourceCode方法调用方法enqueueApplicationScript来执行(React Native App的)JavaScript源码。

 1 - (void)executeSourceCode:(NSData *)sourceCode
 2 {
 3   ......
 4 
 5   RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
 6   sourceCodeModule.scriptURL = self.bundleURL;
 7   sourceCodeModule.scriptData = sourceCode;
 8 
 9   [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { // 3
10     ......
11 
12     // Register the display link to start sending js calls after everything is setup
13   NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
14     [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];
15 
16     // Perform the state update and notification on the main thread, so we can't run into
17     // timing issues with RCTRootView
18     dispatch_async(dispatch_get_main_queue(), ^{
19       [self didFinishLoading];
20       [[NSNotificationCenter defaultCenter]
21        postNotificationName:RCTJavaScriptDidLoadNotification
22        object:_parentBridge userInfo:@{@"bridge": self}];
23     });
24   }];
25 
26 }

4: 方法enqueueApplicationScript 最终依赖RCTJSCExecutor类型的实例 _javaScriptExecutor

来执行(React Native App的)JavaScript源码。

 1 - (void)enqueueApplicationScript:(NSData *)script
 2                              url:(NSURL *)url
 3                       onComplete:(RCTJavaScriptCompleteBlock)onComplete
 4 {
 5 
 6   [_javaScriptExecutor executeApplicationScript:script sourceURL:url onComplete:^(NSError *scriptLoadError) {  // 4
 7 
 8    .......
 9 
10     [_javaScriptExecutor flushedQueue:^(id json, NSError *error)
11      {
12 
13        [self handleBuffer:json batchEnded:YES];
14 
15        onComplete(error);
16      }];
17   }];
18 }

下一节来走读 类 RCTJSCExecutor 和 接口RCTJavaScriptExecutor。

4. JS Executor

TODO: Rewrite this section to match the modification in version 0.18.1

4.0 接口RCTJavaScriptExecutor

接口RCTJavaScriptExecutor定义了JS Executor需要实现的接口。在React中提供了两个JS Executor的实现,

在React/Executors Group中:RCTWebViewExecutor、RCTContextExecutor。

WebSocket中也有一个实现: RCTWebSocketExecutor。

下面是接口RCTJavaScriptExecutor的方法声明:

typedef void (^RCTJavaScriptCompleteBlock)(NSError *error);
typedef void (^RCTJavaScriptCallback)(id json, NSError *error);

/**
 * Abstracts away a JavaScript execution context - we may be running code in a
 * web view (for debugging purposes), or may be running code in a `JSContext`.
 */
@protocol RCTJavaScriptExecutor <RCTInvalidating, RCTBridgeModule>

/**
 * Used to set up the executor after the bridge has been fully initialized.
 * Do any expensive setup in this method instead of `-init`.
 */
- (void)setUp;

/**
 * Executes given method with arguments on JS thread and calls the given callback
 * with JSValue and JSContext as a result of the JS module call.
 */
- (void)executeJSCall:(NSString *)name
               method:(NSString *)method
            arguments:(NSArray *)arguments
             callback:(RCTJavaScriptCallback)onComplete;

/**
 * Runs an application script, and notifies of the script load being complete via `onComplete`.
 */
- (void)executeApplicationScript:(NSString *)script
                       sourceURL:(NSURL *)sourceURL
                      onComplete:(RCTJavaScriptCompleteBlock)onComplete;

// 将由script表示的JavaScript脚本代表的object以objectName注册为全局变量。 - (void)injectJSONText:(NSString *)script asGlobalObjectNamed:(NSString *)objectName callback:(RCTJavaScriptCompleteBlock)onComplete; /** * Enqueue a block to run in the executors JS thread. Fallback to `dispatch_async` * on the main queue if the executor doesn't own a thread. */ - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block; @optional /** * Special case for Timers + ContextExecutor - instead of the default * if jsthread then call else dispatch call on jsthread * ensure the call is made async on the jsthread */ - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block; @end

4.1 RCTJSCExecutor类

RCTJSCExecutor实现了接口RCTJavaScriptExecutor:

1 /**
2  * Uses a JavaScriptCore context as the execution engine.
3  */
4 @interface RCTJSCExecutor : NSObject <RCTJavaScriptExecutor>

5. RCTModuleData

RCTModuleData实例保存关于RCTBridgeModule实例的数据,这些数据包含: "bridge module"模块的类(1),

"bridge module"模块在Javascript中名字(2), "bridge module"模块导出到JavaScript中的 Method(3),

"bridge module"模块实例(4), the module method dispatch queue(5), "bridge module"模块的配置信息(6)。

1 @property (nonatomic, strong, readonly) Class moduleClass; // 1
2 @property (nonatomic, copy, readonly) NSString *name;  // 2
1 @property (nonatomic, copy, readonly) NSArray<id<RCTBridgeMethod>> *methods; // 3
2 
3 @property (nonatomic, strong, readonly) id<RCTBridgeModule> instance;  // 4
1 @property (nonatomic, strong, readonly) dispatch_queue_t methodQueue; // 5
2 
3 @property (nonatomic, copy, readonly) NSArray *config; // 6

RCTModuleData的方法instance 会创建"bridge module"模块实例:

 1 - (id<RCTBridgeModule>)instance
 2 {
 3   [_instanceLock lock];
 4   if (!_setupComplete) {
 5     if (!_instance) {
 6       _instance = [_moduleClass new];
 7     }
 8     // Bridge must be set before methodQueue is set up, as methodQueue
 9     // initialization requires it (View Managers get their queue by calling
10     // self.bridge.uiManager.methodQueue)
11     [self setBridgeForInstance];
12     [self setUpMethodQueue];
13     [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
14     _setupComplete = YES;
15   }
16   [_instanceLock unlock];
17   return _instance;
18 }

6. RCTJavaScriptLoader

从本地文件系统或者远程Server加载JavaScript。

1 + (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(RCTSourceLoadBlock)onComplete

该接口实现使用了 NSURLSessionDataTask, React Native需要 iOS 7.0+ 的系统

7. RCTSourceCode

RCTSourceCode抽象JavaScript源码数据, 包含属性:

scriptData 和 scriptURL
1 @interface RCTSourceCode : NSObject <RCTBridgeModule> // E
2 
3 @property (nonatomic, copy) NSData *scriptData;
4 @property (nonatomic, copy) NSURL *scriptURL;
5 
6 @end

E: 关于 RCTBridgeModule接口 参见 iOS.ReactNative-3-about-viewmanager-uimanager-and-bridgemodule


Reference

1. React-Native: RCTBridge.m/h