React-Native和iOS的橋接 如何實現

基於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文件中有以下代碼:

{
  NSURL *jsCodeLocation;

  jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"WaterApp"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  rootView.backgroundColor = [UIColor blackColor];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

之所以可以使用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. RCTCxxBridge.mm

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

觀察類 RCTCxxBridge.mm中的initiailizer,發現對接口的'initJS'的調用。在這裏終於和JS '發生關係'。

- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
  RCTAssertParam(bridge);

  if ((self = [super initWithDelegate:bridge.delegate
                            bundleURL:bridge.bundleURL
                       moduleProvider:bridge.moduleProvider
                        launchOptions:bridge.launchOptions])) {
    _parentBridge = bridge;
    _performanceLogger = [bridge performanceLogger];

    registerPerformanceLoggerHooks(_performanceLogger);

    RCTLogInfo(@"Initializing %@ (parent: %@, executor: %@)", self, bridge, [self executorClass]);

    /**
     * Set Initial State
     */
    _valid = YES;
    _loading = YES;
    _moduleRegistryCreated = NO;
    _pendingCalls = [NSMutableArray new];
    _displayLink = [RCTDisplayLink new];
    _moduleDataByName = [NSMutableDictionary new];
    _moduleClassesByID = [NSMutableArray new];
    _moduleDataByID = [NSMutableArray new];

    [RCTBridge setCurrentBridge:self];
  }
  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

- (void)initJS
{
  RCTAssertMainThread();

  // Inject module data into JS context
  NSMutableDictionary *config = [[NSMutableDictionary alloc] init];  // A
  for (RCTModuleData *moduleData in _moduleDataByID) {
    config[moduleData.name] = moduleData.config;
  }
  NSString *configJSON = RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
  [_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:^(NSError *error) {
    if (error) {
      [[RCTRedBox sharedInstance] showError:error];
    }
  }];

  NSURL *bundleURL = _parentBridge.bundleURL;
  if (_javaScriptExecutor == nil) {

    /**
     * HACK (tadeu): If it failed to connect to the debugger, set loading to NO
     * so we can attempt to reload again.
     */
    _loading = NO;

  } else if (!bundleURL) {

    // Allow testing without a script
    dispatch_async(dispatch_get_main_queue(), ^{
      _loading = NO;
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
                                                          object:_parentBridge
                                                        userInfo:@{ @"bridge": self }];
    });
  } else {   40 
    RCTProfileBeginEvent();
    RCTPerformanceLoggerStart(RCTPLScriptDownload);
    RCTJavaScriptLoader *loader = [[RCTJavaScriptLoader alloc] initWithBridge:self]; // B
    [loader loadBundleAtURL:bundleURL onComplete:^(NSError *error, NSString *script) {
      RCTPerformanceLoggerEnd(RCTPLScriptDownload);
      RCTProfileEndEvent(@"JavaScript download", @"init,download", @[]);

      _loading = NO;
      if (!self.isValid) {
        return;
      }

      static BOOL shouldDismiss = NO;
      if (shouldDismiss) {
        [[RCTRedBox sharedInstance] dismiss];
      }
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
        shouldDismiss = YES;
      });

      RCTSourceCode *sourceCodeModule = self.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
      sourceCodeModule.scriptURL = bundleURL;
      sourceCodeModule.scriptText = script;
      if (error) {

        NSArray *stack = [error userInfo][@"stack"];
        if (stack) {
          [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
                                             withStack:stack];
        } else {
          [[RCTRedBox sharedInstance] showErrorMessage:[error localizedDescription]
                                           withDetails:[error localizedFailureReason]];
        }

        NSDictionary *userInfo = @{@"bridge": self, @"error": error};
        [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidFailToLoadNotification
                                                            object:_parentBridge
                                                          userInfo:userInfo];

      } else {

        [self enqueueApplicationScript:script url:bundleURL onComplete:^(NSError *loadError) { // C

          if (loadError) {
            [[RCTRedBox sharedInstance] showError:loadError];
            return;
          }

          /**
           * Register the display link to start sending js calls after everything
           * is setup
           */
          NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTContextExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];  
          [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];  // D
      // E
          [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptDidLoadNotification
                                                              object:_parentBridge
                                                            userInfo:@{ @"bridge": self }];
        }];
      }
    }];
  }
}

 

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

 - (instancetype)initWithParentBridge:(RCTBridge *)bridge
 {
     .......
     
     [self start]; // 1
   }
   return self;
 }

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源碼。

- (void)executeSourceCode:(NSData *)sourceCode
{
  ......

  RCTSourceCode *sourceCodeModule = [self moduleForClass:[RCTSourceCode class]];
  sourceCodeModule.scriptURL = self.bundleURL;
  sourceCodeModule.scriptData = sourceCode;

  [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:^(NSError *loadError) { // 3
    ......

    // Register the display link to start sending js calls after everything is setup
  NSRunLoop *targetRunLoop = [_javaScriptExecutor isKindOfClass:[RCTJSCExecutor class]] ? [NSRunLoop currentRunLoop] : [NSRunLoop mainRunLoop];
    [_jsDisplayLink addToRunLoop:targetRunLoop forMode:NSRunLoopCommonModes];

    // Perform the state update and notification on the main thread, so we can't run into
    // timing issues with RCTRootView
    dispatch_async(dispatch_get_main_queue(), ^{
      [self didFinishLoading];
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:_parentBridge userInfo:@{@"bridge": self}];
    });
  }];

}

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 }

 

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)。

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

 RCTModuleData的方法instance 會創建"bridge module"模塊實例:

- (id<RCTBridgeModule>)instance
{
  [_instanceLock lock];
  if (!_setupComplete) {
    if (!_instance) {
      _instance = [_moduleClass new];
    }
    // Bridge must be set before methodQueue is set up, as methodQueue
    // initialization requires it (View Managers get their queue by calling
    // self.bridge.uiManager.methodQueue)
    [self setBridgeForInstance];
    [self setUpMethodQueue];
    [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
    _setupComplete = YES;
  }
  [_instanceLock unlock];
  return _instance;
}

6. RCTJavaScriptLoader

從本地文件系統或者遠程Server加載JavaScript。

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

該接口實現使用了 NSURLSessionDataTask, React Native需要 iOS 7.0+ 的系統

  

7. RCTSourceCode

RCTSourceCode抽象JavaScript源碼數據, 包含屬性:

scriptData 和 scriptURL


@interface RCTSourceCode : NSObject <RCTBridgeModule> // E

@property (nonatomic, copy) NSData *scriptData;
@property (nonatomic, copy) NSURL *scriptURL;
 
@end

 

 

本文mark下,重要的是思路,以備後面用的着就轉載過來了,版本不一樣,了,有些對不上了,可是思路還是那個

 轉載鏈接於:https://www.cnblogs.com/cwgk/p/4746666.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章