zoukankan      html  css  js  c++  java
  • iOS.ReactNative-2-bridge-and-react-native-app-execution

    Bridge and React Native App Execution

    基于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   [_javaScriptExecutor injectJSONText: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

  • 相关阅读:
    鼠标放在图片上出现提示
    NSIS调用dll
    IIS7 CMD命令
    NSIS检测
    NSIS修改文件夹访问权限
    NSIS——检测IIS是否安装及版本
    NSIS——检测SQL Server安装版本
    NSIS使用技巧集合
    提供修复界面的NSIS安装包
    NSIS MUI教程
  • 原文地址:https://www.cnblogs.com/cwgk/p/4746666.html
Copyright © 2011-2022 走看看