天天看點

iOS Principle:ReactNative

??‍? Github Demo

友善記憶:

  • React原理:一套可以用簡潔的文法高效繪制 DOM 的架構
  • React特點:
    • 簡潔:不單單指它的 HTML 和 CSS 文法,更因為可以單用 JavaScript 構造頁面
    • 高效:因為 React 獨創了 Virtual DOM 機制,兩大特征
      • 它存在于記憶體中的 JavaScript 對象,并且與 DOM 是對應關系
      • 使用高效的 DOM Diff 算法不需要對 DOM 進行重新繪制
  • React Native原理:通過 JS 對 OC 的 JavaScript Core 架構的互動來實作對原生的調用
    • rn 在 OC 和 JS 兩端都儲存了一份配置表,裡面标記了所有 OC 暴露給 JS 的子產品和方法 ,js對oc的調用通過block方式實作回調
    • AppDelegate初始化過程中建立bridge,内部通過setUp建立BatchedBridge來批量讀取 JS 對 OC 的方法調用并通過JavaScriptExecutor執行 JS 代碼
  • 建立 BatchedBridge 步驟
    • 讀取 JS 源碼:把 JSX 代碼轉成 JS 加載進記憶體中
    • 初始化子產品資訊:找到所有需要暴露給 JS 的類
    • 初始化 JS 代碼的執行器:即 RCTJSCExecutor 對象
    • 生成子產品清單并寫入 JS 端:接受 ModuleName 并且生成子產品資訊
    • 執行 JavaScript 源碼:通過RCTJSCExecutor執行代碼,寫入資訊
  • 互相調用方法:
    • OC調用JS:OC會通過executeBlockOnJavaScriptQueue方法在單獨的線程上運作 JS 代碼
      • 處理參數:_executeJSCall:(NSString *)method方法
      • 實際調用:sendAppEventWithName和body方法
    • JS調用OC:JS 會解析出方法的類、方法和方法參數并放入到 MessageQueue 中,等待 OC 調用或逾時發送
      • 使用RCT_EXPORT_METHOD 宏,用來注冊子產品表
      • JS中使用NativeModules.CryptoExport.注冊方法調用
  • rn 的更新機制:React 狀态機,不停地檢查确認更新
    • 文本元素:ReactDOMTextComponent 比較替換文本元素
    • 基本元素:updateComponent方法分屬性、節點替換基本元素
    • 自定義元素:_performComponentUpdate判斷-先解除安裝再安裝子節點
20180522更新:React Native 原了解析

準備工作,首先要有個解剖對象

從 HelloWord 看起,我們來分析RN的實作原理

import React, { Component } from 'react';
import { AppRegistry, Text } from 'react-native';
class HelloWorldApp extends Component {
  render() {
    return (
      <Text>Hello world!</Text>
    );
  }
}
// 注意,這裡用引号括起來的'HelloWorldApp'必須和你init建立的項目名一緻
AppRegistry.registerComponent('HelloWorldApp', () => HelloWorldApp);
複制代碼
           

可以建立一個新的項目

react-native init ProjectName
複制代碼
           

建立完成你可以手動打開項目,也可以在項目根目錄執行

// 啟動 iOS
react-native run-ios
// 啟動 Android
react-native run-android
複制代碼
           

準備工作完成了

React 原理探究

首先我們聊聊 React,我們注意到這條資料源代碼

return (
      <Text>Hello world!</Text>
);
複制代碼
           

“為什麼 JavaScript 代碼裡面出現了 HTML 的文法?”

React Native 把一組相關的 HTML 标簽,也就是 app 内的 UI 控件,封裝進一個元件(Component)中,這種文法被稱為 JSX,它是一種 JavaScript 文法拓展。

JSX 允許我們寫 HTML 标簽或 React 标簽,它們終将被轉換成原生的 JavaScript 并建立 DOM。

在 React 架構中,除了可以用 JavaScript 寫 HTML 以外,我們甚至可以寫 CSS。

總之 React 是一套可以用簡潔的文法高效繪制 DOM 的架構
  • 簡潔:不單單指它的 HTML 和 CSS 文法,更因為可以單用 JavaScript 構造頁面;
  • 高效:因為 React 獨創了 Virtual DOM 機制,Virtual DOM 有兩大特征,一它存在于記憶體中的 JavaScript 對象,并且與 DOM 是一一對應的關系;二使用高效的 DOM Diff 算法不需要對 DOM 進行重新繪制。

當然,React 并不是前端開發的全部。從之前的描述也能看出,它專注于 UI 部分,對應到 MVC 結構中就是 View 層。

要想實作完整的 MVC 架構,還需要 Model 和 Controller 的結構。在前端開發時,我們可以采用 Flux 和 Redux(基于Flux) 架構,它們并非架構(Library),而是和 MVC 一樣都是一種架構設計(Architecture)。

React Native 原理探究

談談 RN 的故事背景

而 React 在前端取得突破性成功以後,JavaScript 開始試圖一統三端。

他們利用了移動平台能夠運作 JavaScript (腳本語言)代碼的能力,并且發揮了 JavaScript 不僅僅可以傳遞配置資訊,還可以表達邏輯資訊的優點。

最終,一個基于 JavaScript,具備動态配置能力,面向前端開發者的移動端開發架構 —— React Native

談談 RN 的原理

即使使用了 React Native,我們依然需要 UIKit 等架構,調用的是 Objective-C 代碼,JavaScript 隻是提供了配置資訊和邏輯的處理結果。

而 JavaScript 是一種腳本語言,它不會經過編譯、連結等操作,而是在運作時才動态的進行詞法、文法分析,生成抽象文法樹(AST)和位元組碼,然後由解釋器負責執行或者使用 JIT 将位元組碼轉化為機器碼再執行。

蘋果提供了一個叫做 JavaScript Core 的架構,這是一個 JavaScript 引擎。整個流程由 JavaScript 引擎負責完成。

JSContext *context = [[JSContext alloc] init];  
JSValue *jsVal = [context evaluateScript:@"21+7"];  
int iVal = [jsVal toInt32];  
複制代碼
           

JavaScript 是一種單線程的語言,它不具備自運作的能力,是以總是被動調用。很多介紹 React Native 的文章都會提到 “JavaScript 線程” 的概念,實際上,它表示的是 Objective-C 建立了一個單獨的線程,這個線程隻用于執行 JavaScript 代碼,而且 JavaScript 代碼隻會在這個線程中執行。

下面将 JavaScript ? OC

由于 JavaScript Core 是一個面向 Objective-C 的架構,在 Objective-C 這一端,我們對 JavaScript 上下文知根知底,可以很容易的擷取到對象,方法等各種資訊,當然也包括調用 JavaScript 函數。

真正複雜的問題在于,JavaScript 不知道 Objective-C 有哪些方法可以調用。

React Native 解決這個問題的方案是在 Objective-C 和 JavaScript 兩端都儲存了一份配置表,裡面标記了所有 Objective-C 暴露給 JavaScript 的子產品和方法。

這樣,無論是哪一方調用另一方的方法,實際上傳遞的資料隻有

  • ModuleId 類
  • MethodId 方法
  • Arguments 方法參數

當 Objective-C 接收到這三個值後,就可以通過 runtime 唯一确定要調用的是哪個函數,然後調用這個函數。

對于 Objective-C 來說,執行完 JavaScript 代碼再執行 Objective-C 回調毫無難度,難點依然在于 JavaScript 代碼調用 Objective-C 之後,如何在 Objective-C 的代碼中,回調執行 JavaScript 代碼。

目前 React Native 的做法是:在 JavaScript 調用 Objective-C 代碼時,注冊要回調的 Block,并且把 Block Id 作為參數發送給 Objective-C,Objective-C 收到參數時會建立 Block,調用完 Objective-C 函數後就會執行這個剛剛建立的 Block。

Objective-C 會向 Block 中傳入參數和 Block Id,然後在 Block 内部調用 JavaScript 的方法,随後 JavaScript 查找到當時注冊的 Block 并執行。

簡單的表示就是:JS ? OC (Block ? JS)

繼續看項目-初始化

除了 index.js 中的 JavaScript 代碼,留給我們的還有 AppDelegate 中的入口方法:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  NSURL *jsCodeLocation;

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

  RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
                                                      moduleName:@"demo"
                                               initialProperties:nil
                                                   launchOptions:launchOptions];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:f green:f blue:f alpha:];

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

實際我們操作的視圖就是這個 RootView ,但是 RootView 是依托于 Bridge 對象,它是 Objective-C 與 JavaScript 互動的橋梁,後續的方法互動完全依賴于它,而整個初始化過程的最終目的其實也就是建立這個橋梁對象。

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
                       moduleName:(NSString *)moduleName
                initialProperties:(NSDictionary *)initialProperties
                    launchOptions:(NSDictionary *)launchOptions {
  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
                                            moduleProvider:nil
                                             launchOptions:launchOptions];

  return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
複制代碼
           

初始化方法的核心是 setUp 方法,而 setUp 方法的主要任務則是建立 BatchedBridge。

- (void)setUp {
  RCT_PROFILE_BEGIN_EVENT(, @"-[RCTBridge setUp]", nil);

  _performanceLogger = [RCTPerformanceLogger new];
  [_performanceLogger markStartForTag:RCTPLBridgeStartup];
  [_performanceLogger markStartForTag:RCTPLTTI];

  Class bridgeClass = self.bridgeClass;

  #if RCT_DEV
  RCTExecuteOnMainQueue(^{
    RCTRegisterReloadCommandListener(self);
  });
  #endif

  // Only update bundleURL from delegate if delegate bundleURL has changed
  NSURL *previousDelegateURL = _delegateBundleURL;
  _delegateBundleURL = [self.delegate sourceURLForBridge:self];
  if (_delegateBundleURL && ![_delegateBundleURL isEqual:previousDelegateURL]) {
    _bundleURL = _delegateBundleURL;
  }

  // Sanitize the bundle URL
  _bundleURL = [RCTConvert NSURL:_bundleURL.absoluteString];

  self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
  [self.batchedBridge start];

  RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
}
複制代碼
           

BatchedBridge 的作用是批量讀取 JavaScript 對 Objective-C 的方法調用,同時它内部持有一個 JavaScriptExecutor,顧名思義,這個對象用來執行 JavaScript 代碼。

建立 BatchedBridge 的關鍵是 start 方法,它可以分為五個步驟:

  • 讀取 JavaScript 源碼
  • 初始化子產品資訊
  • 初始化 JavaScript 代碼的執行器,即 RCTJSCExecutor 對象
  • 生成子產品清單并寫入 JavaScript 端
  • 執行 JavaScript 源碼

逐個分析上面每一步完成的操作:

1.讀取JavaScript源碼 這一部分的具體代碼實作沒有太大的讨論意義。我們隻要明白,JavaScript 的代碼是在 Objective-C 提供的環境下運作的,是以第一步就是把 JavaScript 加載進記憶體中,對于一個空的項目來說,所有的 JavaScript 代碼大約占用 1.5 Mb 的記憶體空間。

需要說明的是,在這一步中,JSX 代碼已經被轉化成原生的 JavaScript 代碼。

2.初始化子產品資訊 這一步在方法 initModulesWithDispatchGroup: 中實作,主要任務是找到所有需要暴露給 JavaScript 的類。

每一個需要暴露給 JavaScript 的類(也成為 Module,以下不作區分)都會标記一個宏:RCT_EXPORT_MODULE,這個宏的具體實作并不複雜

#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }
複制代碼
           

這樣,這個類在 load 方法中就會調用 RCTRegisterModule 方法注冊自己:

void RCTRegisterModule(Class moduleClass) {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
  });
  [RCTModuleClasses addObject:moduleClass];
}
複制代碼
           

是以,React Native 可以通過 RCTModuleClasses 拿到所有暴露給 JavaScript 的類。下一步操作是周遊這個數組,然後生成 RCTModuleData 對象:

for (Class moduleClass in RCTGetModuleClasses()) {
    RCTModuleData *moduleData = [[RCTModuleData alloc]initWithModuleClass:moduleClass                                                                      bridge:self];
    [moduleClassesByID addObject:moduleClass];
    [moduleDataByID addObject:moduleData];
}
複制代碼
           

可以想見,RCTModuleData 對象是子產品配置表的主要組成部分。如果把子產品配置表想象成一個數組,那麼每一個元素就是一個 RCTModuleData 對象。

這個對象儲存了 Module 的名字,常量等基本資訊,最重要的屬性是一個數組,儲存了所有需要暴露給 JavaScript 的方法。

暴露給 JavaScript 的方法需要用 RCT_EXPORT_METHOD 這個宏來标記,它的實作原理比較複雜,有興趣的讀者可以自行閱讀。簡單來說,它為函數名加上了 rct_export 字首,再通過 runtime 擷取類的函數清單,找出其中帶有指定字首的方法并放入數組中:

- (NSArray<id<RCTBridgeMethod>> *)methods{
    unsigned int methodCount;
    Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount); // 擷取方法清單
    for (unsigned int i = ; i < methodCount; i++) {
        RCTModuleMethod *moduleMethod = /* 建立 method */
        [_methods addObject:moduleMethod];
      }
    }
    return _methods;
}
複制代碼
           

是以 Objective-C 管理子產品配置表的邏輯是:Bridge 持有一個數組,數組中儲存了所有的子產品的 RCTModuleData 對象,RCTModuleData又儲存了類的方法、常亮、類名等資訊。隻要給定 ModuleId 和 MethodId 就可以唯一确定要調用的方法。

3.初始化JavaScript執行器(RCTJSCExecutor) 通過檢視源碼可以看到,初始化 JavaScript 執行器的時候,會調用

+ (instancetype)initializedExecutorWithContextProvider:(RCTJSContextProvider *)JSContextProvider 
applicationScript:(NSData *)applicationScript 
sourceURL:(NSURL *)sourceURL 
JSContext:(JSContext **)JSContext 
error:(NSError **)error;
複制代碼
           

傳回的 excuter 對象是已經被同步執行的

// 執行對應的方法
- (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args jsValueCallback:(RCTJavaScriptValueCallback)onComplete
{
  [self _callFunctionOnModule:module method:method arguments:args returnValue:NO unwrapResult:NO callback:onComplete];
}
複制代碼
           

這裡需要關注 nativeRequireModuleConfig 和 nativeFlushQueueImmediate 這兩個block。

在這兩個 block 中會通過 bridge 調用 oc 的方法。

[self executeBlockOnJavaScriptQueue:^{
    if (!self.valid) {
      return;
    }

    JSContext *context = nil;
    if (self->_jscWrapper) {
      RCTAssert(self->_context != nil, @"If wrapper was pre-initialized, context should be too");
      context = self->_context.context;
    } else {
      [self->_performanceLogger markStartForTag:RCTPLJSCWrapperOpenLibrary];
      self->_jscWrapper = RCTJSCWrapperCreate(self->_useCustomJSCLibrary);
      [self->_performanceLogger markStopForTag:RCTPLJSCWrapperOpenLibrary];

      RCTAssert(self->_context == nil, @"Didn't expect to set up twice");
      context = [self->_jscWrapper->JSContext new];
      self->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context onThread:self->_javaScriptThread];
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTJavaScriptContextCreatedNotification
                                                          object:context];

      configureCacheOnContext(context, self->_jscWrapper);
      installBasicSynchronousHooksOnContext(context);
    }

    __weak RCTJSCExecutor *weakSelf = self;

    context[@"nativeRequireModuleConfig"] = ^NSString *(NSString *moduleName) {
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return nil;
      }

      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeRequireModuleConfig", nil);
      NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
      NSString *result = config ? RCTJSONStringify(config, NULL) : nil;
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call,config", @{ @"moduleName": moduleName });
      return result;
    };

    context[@"nativeFlushQueueImmediate"] = ^(NSArray<NSArray *> *calls){
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid || !calls) {
        return;
      }

      RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"nativeFlushQueueImmediate", nil);
      [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
      RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"js_call", nil);
    };

#if RCT_PROFILE
    __weak RCTBridge *weakBridge = self->_bridge;
    context[@"nativeTraceBeginAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
      if (RCTProfileIsProfiling()) {
        [weakBridge.flowIDMapLock lock];
        int64_t newCookie = [_RCTProfileBeginFlowEvent() longLongValue];
        CFDictionarySetValue(weakBridge.flowIDMap, (const void *)cookie, (const void *)newCookie);
        [weakBridge.flowIDMapLock unlock];
      }
    };

    context[@"nativeTraceEndAsyncFlow"] = ^(__unused uint64_t tag, __unused NSString *name, int64_t cookie) {
      if (RCTProfileIsProfiling()) {
        [weakBridge.flowIDMapLock lock];
        int64_t newCookie = (int64_t)CFDictionaryGetValue(weakBridge.flowIDMap, (const void *)cookie);
        _RCTProfileEndFlowEvent(@(newCookie));
        CFDictionaryRemoveValue(weakBridge.flowIDMap, (const void *)cookie);
        [weakBridge.flowIDMapLock unlock];
      }
    };
#endif

#if RCT_DEV
    RCTInstallJSCProfiler(self->_bridge, context.JSGlobalContextRef);

    // Inject handler used by HMR
    context[@"nativeInjectHMRUpdate"] = ^(NSString *sourceCode, NSString *sourceCodeURL) {
      RCTJSCExecutor *strongSelf = weakSelf;
      if (!strongSelf.valid) {
        return;
      }

      RCTJSCWrapper *jscWrapper = strongSelf->_jscWrapper;
      JSStringRef execJSString = jscWrapper->JSStringCreateWithUTF8CString(sourceCode.UTF8String);
      JSStringRef jsURL = jscWrapper->JSStringCreateWithUTF8CString(sourceCodeURL.UTF8String);
      jscWrapper->JSEvaluateScript(strongSelf->_context.context.JSGlobalContextRef, execJSString, NULL, jsURL, , NULL);
      jscWrapper->JSStringRelease(jsURL);
      jscWrapper->JSStringRelease(execJSString);
    };
#endif
  }];
}
複制代碼
           

4.生成子產品配置表并寫入JavaScript端

複習一下 nativeRequireModuleConfig 這個 Block,它可以接受 ModuleName 并且生成詳細的子產品資訊,但在前文中我們沒有提到 JavaScript 是如何知道 Objective-C 要暴露哪些類的(目前隻是 Objective-C 自己知道)。

這一步的操作就是為了讓 JavaScript 擷取所有子產品的名字

- (NSString *)moduleConfig {
  NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    if (self.executorClass == [RCTJSCExecutor class]) {
      [config addObject:@[moduleData.name]];
    } else {
      [config addObject:RCTNullIfNil(moduleData.config)];
    }
  }

  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
}
複制代碼
           

5.執行JavaScript代碼

這一步也沒什麼技術難度可以,代碼已經加載進了記憶體,該做的配置也已經完成,隻要把 JavaScript 代碼運作一遍即可。

運作代碼時,第三步中所說的那些 Block 就會被執行,進而向 JavaScript 端寫入配置資訊。

至此,JavaScript 和 Objective-C 都具備了向對方互動的能力,準備工作也就全部完成了。

方法調用

如前文所述,在 React Native 中,Objective-C 和 JavaScript 的互動都是通過傳遞 ModuleId、MethodId 和 Arguments 進行的。以下是分情況讨論

OC 調用 JavaScript

也許你在其他文章中曾經多次聽說 JavaScript 代碼總是在一個單獨的線程上面調用,它的實際含義是 Objective-C 會在單獨的線程上運作 JavaScript 代碼

- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block {
  if ([NSThread currentThread] != _javaScriptThread) {
    [self performSelector:@selector(executeBlockOnJavaScriptQueue:)
                 onThread:_javaScriptThread withObject:block waitUntilDone:NO];
  } else {
    block();
  }
}
複制代碼
           

調用JavaScript的核心代碼如下

- (void)_executeJSCall:(NSString *)method
             arguments:(NSArray *)arguments
              callback:(RCTJavaScriptCallback)onComplete{
    [self executeBlockOnJavaScriptQueue:^{
        // 擷取 contextJSRef、methodJSRef、moduleJSRef
        resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, arguments.count, jsArgs, &errorJSRef);
        objcValue = /*resultJSRef 轉換成 Objective-C 類型*/
        onComplete(objcValue, nil);
    }];
}
複制代碼
           

需要注意的是,這個函數名是我們要調用 JavaScript 的中轉函數名,比如 callFunctionReturnFlushedQueue。也就是說它的作用其實是處理參數,而非真正要調用的 JavaScript 函數。

在實際使用的時候,我們可以這樣發起對 JavaScript 的調用:

[_bridge.eventDispatcher sendAppEventWithName:@"greeted"
                                         body:@{ @"name": @"nmae"}];
複制代碼
           

這裡的 Name 和 Body 參數分别表示要調用的 JavaScript 的函數名和參數。

JavaScript調用OC

在調用 Objective-C 代碼時,如前文所述,JavaScript 會解析出方法的 ModuleId、MethodId 和 Arguments 并放入到 MessageQueue 中,等待 Objective-C 主動拿走,或者逾時後主動發送給 Objective-C。

Objective-C 負責處理調用的方法是 handleBuffer,它的參數是一個含有四個元素的數組,每個元素也都是一個數組,分别存放了 ModuleId、MethodId、Params,第四個元素目測用處不大。

函數内部在每一次方調用中調用 _handleRequestNumber:moduleID:methodID:params 方法,通過查找子產品配置表找出要調用的方法,并通過 runtime 動态的調用:

示範JavaScript調用OC方法:

//.h檔案
#import <Foundation/Foundation.h>
#import "RCTBridge.h"
#import "RCTLog.h"
#import "EncryptUtil.h"
#import "RSA.h"
@interface CryptoExport : NSObject<RCTBridgeModule>
@end

//.m檔案
#import "CryptoExport.h"
@implementation CryptoExport
RCT_EXPORT_MODULE()//必須定義的宏
RCT_EXPORT_METHOD(rsaEncryptValue:(NSString *)src withKey:(NSString *)rsaKey successCallback:(RCTResponseSenderBlock)successCallback){
  NSString *rsaValue = [RSA encryptString:src publicKey:rsaKey];
  successCallback(@[rsaValue]);
}
@end
複制代碼
           

每個oc的方法前必須加上 RCT_EXPORT_METHOD 宏,用來注冊子產品表。

在JavaScript中的調動如下

NativeModules.CryptoExport.rsaEncryptValue(value, rsaKey,function (rsaValue) {
		console.log(rsaValue)
});
複制代碼
           

React Native更新機制

之前我們說過,React是狀态機,就是不停的去檢查目前的狀态,判斷是否需要重新整理。

調用this.setState

ReactClass.prototype.setState = function(newState) {
    this._reactInternalInstance.receiveComponent(null, newState);
}
複制代碼
           

調用内部receiveComponent方法,這裡在接受元素的時候主要分三種情況:

  • 文本元素
  • 基本元素
  • 自定義元素

文本元素

ReactDOMTextComponent.prototype.receiveComponent(nextText, transaction) {
     //跟以前儲存的字元串比較
    if (nextText !== this._currentElement) {
      this._currentElement = nextText;
      var nextStringText = '' + nextText;
      if (nextStringText !== this._stringText) {
        this._stringText = nextStringText;
        var commentNodes = this.getHostNode();
        // 替換文本元素
        DOMChildrenOperations.replaceDelimitedText(
          commentNodes[],
          commentNodes[],
          nextStringText
        );
      }
    }
  }
複制代碼
           

基本元素

ReactDOMComponent.prototype.receiveComponent = function(nextElement, transaction, context) {
    var prevElement = this._currentElement;
    this._currentElement = nextElement;
    this.updateComponent(transaction, prevElement, nextElement, context);
}
複制代碼
           

updateComponent 方法

ReactDOMComponent.prototype.updateComponent = function(transaction, prevElement, nextElement, context) {
    // 略.....
    //需要單獨的更新屬性
    this._updateDOMProperties(lastProps, nextProps, transaction, isCustomComponentTag);
    //再更新子節點
    this._updateDOMChildren(
      lastProps,
      nextProps,
      transaction,
      context
    );
    // ......
}
複制代碼
           

自定義元素

ReactCompositeComponent.prototype.receiveComponent = function(nextElement, transaction, nextContext) {
    var prevElement = this._currentElement;
    var prevContext = this._context;
    this._pendingElement = null;
    this.updateComponent(
      transaction,
      prevElement,
      nextElement,
      prevContext,
      nextContext
    );
}
複制代碼
           

updateComponent 方法

ReactCompositeComponent.prototype.updateComponent = function(
    transaction,
    prevParentElement,
    nextParentElement,
    prevUnmaskedContext,
    nextUnmaskedContext
){
//省略
}
複制代碼
           

調用内部 _performComponentUpdate 方法

ReactCompositeComponent.prototype._updateRenderedComponentWithNextElement = function() {

    // 判定兩個element需不需要更新
    if (shouldUpdateReactComponent(prevRenderedElement, nextRenderedElement)) {
      // 如果需要更新,就繼續調用子節點的receiveComponent的方法,傳入新的element更新子節點。
      ReactReconciler.receiveComponent(
        prevComponentInstance,
        nextRenderedElement,
        transaction,
        this._processChildContext(context)
      );
    } else {
      // 解除安裝之前的子節點,安裝新的子節點
      var oldHostNode = ReactReconciler.getHostNode(prevComponentInstance);
      ReactReconciler.unmountComponent(
        prevComponentInstance,
        safely,
        false /* skipLifecycle */
      );
      var nodeType = ReactNodeTypes.getType(nextRenderedElement);
      this._renderedNodeType = nodeType;
      var child = this._instantiateReactComponent(
        nextRenderedElement,
        nodeType !== ReactNodeTypes.EMPTY /* shouldHaveDebugID */
      );
      this._renderedComponent = child;

      var nextMarkup = ReactReconciler.mountComponent(
        child,
        transaction,
        this._hostParent,
        this._hostContainerInfo,
        this._processChildContext(context),
        debugID
      );
    }
複制代碼
           

this._updateDOMChildren 方法内部調用diff算法

實作過程

_updateChildren: function(nextNestedChildrenElements, transaction, context) {
    var prevChildren = this._renderedChildren;
    var removedNodes = {};
    var mountImages = [];

    // 擷取新的子元素數組
    var nextChildren = this._reconcilerUpdateChildren(
      prevChildren,
      nextNestedChildrenElements,
      mountImages,
      removedNodes,
      transaction,
      context
    );

    if (!nextChildren && !prevChildren) {
      return;
    }

    var updates = null;
    var name;
    var nextIndex = ;
    var lastIndex = ;
    var nextMountIndex = ;
    var lastPlacedNode = null;

    for (name in nextChildren) {
      if (!nextChildren.hasOwnProperty(name)) {
        continue;
      }
      var prevChild = prevChildren && prevChildren[name];
      var nextChild = nextChildren[name];
      if (prevChild === nextChild) {
          // 同一個引用,說明是使用的同一個component,是以我們需要做移動的操作
          // 移動已有的子節點
          // NOTICE:這裡根據nextIndex, lastIndex決定是否移動
        updates = enqueue(
          updates,
          this.moveChild(prevChild, lastPlacedNode, nextIndex, lastIndex)
        );

        // 更新lastIndex
        lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        // 更新component的.mountIndex屬性
        prevChild._mountIndex = nextIndex;

      } else {
        if (prevChild) {
          // 更新lastIndex
          lastIndex = Math.max(prevChild._mountIndex, lastIndex);
        }

        // 添加新的子節點在指定的位置上
        updates = enqueue(
          updates,
          this._mountChildAtIndex(
            nextChild,
            mountImages[nextMountIndex],
            lastPlacedNode,
            nextIndex,
            transaction,
            context
          )
        );

        nextMountIndex++;
      }

      // 更新nextIndex
      nextIndex++;
      lastPlacedNode = ReactReconciler.getHostNode(nextChild);
    }

    // 移除掉不存在的舊子節點,和舊子節點和新子節點不同的舊子節點
    for (name in removedNodes) {
      if (removedNodes.hasOwnProperty(name)) {
        updates = enqueue(
          updates,
          this._unmountChild(prevChildren[name], removedNodes[name])
        );
      }
    }
  }
複制代碼
           

以下是針對 Demo 項目的通信原了解釋

通信基本原理

首先,我們來看一下在iOS中Native如何調用JS。從iOS7開始,系統進一步開放了WebCore SDK,提供JavaScript引擎庫,使得我們能夠直接與引擎互動擁有更多的控制權。其中,有兩個最基礎的概念:

JSContext // JS代碼的環境,一個JSContext是一個全局環境的執行個體
JSValue // 包裝了每一個可能的JS值:字元串、數字、數組、對象、方法等
複制代碼
           

通過這兩個類,我們能夠非常友善的實作Javascript與Native代碼之間的互動,首先我們通過一個簡單示例來觀察Native如何調用Javascript代碼:

?:Native -> JavaScript

// 頭檔案
#import <JavaScriptCore/JSContext.h>
#import <JavaScriptCore/JSValue.h>
- (void)createJSContext {
    JSContext *context = [[JSContext alloc] init];
    [context evaluateScript:@"var num = 5 + 5"];
    [context evaluateScript:@"var names = ['Grace', 'Ada', 'Margaret']"];
    [context evaluateScript:@"var triple = function(value) { return value * 3 }"];
    JSValue *tripleNum = [context evaluateScript:@"triple(num)"];
    JSValue *tripleFunction = context[@"triple"];
    JSValue *result = [tripleFunction callWithArguments:@[@]];
    // 列印結果
    NSLog(@"JSContext function \ntripleNum:%@ \nresult:%@", tripleNum, result);
}
複制代碼
           

那麼,JSContext如何通路我們本地用戶端OC代碼呢?答案是通過Blocks和JSExports協定兩種方式。 我們來看一個通過Blocks來實作JS通路本地代碼的示例:

?:JavaScript -> Native

context[@"testSay"] = ^(NSString *input) {
    NSMutableString *mutableString = [input mutableCopy];
    CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformToLatin, NO);
    CFStringTransform((__bridge CFMutableStringRef)mutableString, NULL, kCFStringTransformStripCombiningMarks, NO);
    return mutableString;
};
NSLog(@"%@", [context evaluateScript:@"testSay('hello world')"]);
複制代碼
           

關于JSCore庫的更多學習介紹,請看JavaScriptCore。

Java​Script​Core 相關介紹 http://nshipster.cn/javascriptcore/

React Native 初始化過程解析

在了解React-Native中JS->Native的具體調用之前,我們先做一些準備工作,看看架構中Native app的啟動過程。打開FB提供的AwesomeProject定位到appDelegate的didFinishLaunchingWithOptions方法中:

// 指定JS頁面檔案位置
jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios&dev=false"];
// 建立React Native視圖對象
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
moduleName:@"ReactExperiment"
initialProperties:nil
launchOptions:launchOptions];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
// 建立VC,并且把React Native Root View指派給VC
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
複制代碼
           

可以看到使用內建非常簡單,那麼RCTRootView到底做了哪些事情最後渲染将視圖呈現在使用者面前呢? 我們繼續跟着代碼往下分析就會看到我們今天的主角RCTBridge。

?:RCTBridge

- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions {
    RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:bundleURL
    moduleProvider:nil
    launchOptions:launchOptions];
    return [self initWithBridge:bridge moduleName:moduleName initialProperties:initialProperties];
}
複制代碼
           

RCTBridge是Naitive端的bridge,起着橋接兩端的作用 。事實上具體的實作放置在RCTBatchedBridge中,在它的start方法中執行了一系列重要的初始化工作。這部分也是ReactNative SDK的精髓所在,基于GCD實作一套異步初始化元件架構。大緻的工作流程如下圖所示:

1.Load JS Source Code(并行)

加載頁面源碼階段。該階段主要負責從指定的位置(網絡或者本地)加載React Native頁面代碼。與initModules各子產品初始化過程并行執行,通過GCD分組隊列保證兩個階段完成後才會加載解析頁面源碼。

2.Init Module(同步)

初始化加載React Native子產品。該階段會将所有注冊的Native子產品類整理儲存到一個以Module Id為下标的數組對象中(同時還會儲存一個以Module Name為Key的Dictionary,用于做索引友善後續的子產品查找)。

整個子產品的基礎初始化和注冊過程在系統Load Class階段就會完成。React Native對子產品注冊的實作還是比較巧妙、友善,隻需要對目标類添加相應的宏即可。

  • 1.注冊子產品。實作RCTBridgeModule協定,并且在響應的Implemention檔案中添加RCT_EXPORT_MODULE宏,該宏會為所在類自動添加一個+load方法,調用RCTBridge的RCTRegisterModule實作在Load Class階段就完成子產品注冊工作。
  • 2.注冊函數。待注冊函數所在的類必須是已注冊子產品,在需要注冊的函數前添加RCT_EXPORT_MODULE宏即可。

當然這裡需要注意的問題是子產品初始化是一個同步任務,它必須被同步加載,是以當子產品較多時勢必會帶來高延遲的問題,也是在新的版本中SDK将Module Method改為Lazy Load的原因之一。

3.Setup JS Executor(并行)

初始化JS引擎。React Native在0.18中已經很好的抽象了原來了JSExecutor,目前實作了RCTWebSocketExecutor和RCTJSCExecutor兩個腳本引擎的封裝,前者用于通過WebSocket連結到Chrome調試,後者則是内置預設引擎直接通過IOS SDK JSContext來實作相關的邏輯。

另外,在本階段還會通過block hook的方式注冊部分核心API

  • 1.nativeRequireModuleConfig:用于在JS端擷取對應的Native Module,在0.14後的版本React Native已經對初始化子產品做了部分優化,把關于Native Module Method部分的加載工作放置在requireModuleConfig時才做
  • 2.nativeLoggingHook:調用Native寫入日志
  • 3.nativeFlushQueueImmediate:手動觸發執行目前Native Call隊列中所有的Native處理請求
  • 4.nativePerformanceNow:用于性能統計,擷取目前Native的絕對時間(毫秒)

對于子產品類中想要聲明的方法,需要添加RCT_EXPORT_METHOD宏。它會給方法名添加” rct_export “字首。

?:React 調用 Native 的 SVProgressHUD 提示窗

在 Native 中聲明方法

RCT_EXPORT_METHOD(calliOSActionWithOneParams:(NSString *)name) {
    [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeBlack];
    [SVProgressHUD showSuccessWithStatus:[NSString stringWithFormat:@"參數:%@",name]];
}
複制代碼
           

在 React 中調用 calliOSActionWithOneParams 方法

<TouchableOpacity style={styles.calltonative}
    onPress={()=>{
        RNCalliOSAction.calliOSActionWithOneParams('hello');
    }}>
    <Text>點選調用 Native 方法, 并傳遞一個參數</Text>
</TouchableOpacity>
複制代碼
           

4.Module Config(并行)

這步将第2步中的Native子產品類轉換成Json,儲存為remoteModuleConfig。注意在這裡擷取到的清單并非含有完整子產品資訊,而僅僅是一個Module List而已。

{
"remoteModuleConfig":[
[
"HTSimpleAPI", // module
],
[
"RCTViewManager",
],
[
"HTTestView",
],
[
"RCTAccessibilityManager",
],
...
],
}
複制代碼
           

JS Source Code代碼分析

JS的主入口index.ios.js在我們看來隻有短短數十行,然而這不是最終執行的代碼。React-Native頁面源碼需要通過Transform Server轉換處理,并把轉化後的子產品一起合并為一個bundle.js,這個過程稱為buildBundle。轉換後的index.ios.bundle才是最終可被Javascript引擎直接解釋運作的代碼。下面我們按照主程式的邏輯來分析源碼幾個核心子產品實作原理。

在React Server中需要檢視Bundle的子產品映射關系可以直接通路:http://localhost:8081/index.ios.bundle.map,檢視相關依賴和Bundle的緩存則可以通路: http://localhost:8081/debug

1.BatchedBridge

在上一部分我們知道,Native完成子產品初始化後會通過Inject Json Config将配置資訊同步至JS裡中的全局變量__fbBatchedBridgeConfig,打開BatchedBridge.js我們可以看到如下代碼。

__d('BatchedBridge',function(global, require, module, exports) { 'use strict';
var MessageQueue=require('MessageQueue');
var BatchedBridge=new MessageQueue(
__fbBatchedBridgeConfig.remoteModuleConfig,
__fbBatchedBridgeConfig.localModulesConfig);
//......
Object.defineProperty(global,'__fbBatchedBridge',{value:BatchedBridge});
module.exports = BatchedBridge;
});
複制代碼
           

對于這段代碼,我們可以得出以下幾個結論:

  • 1.在JS端也存在一個bridge子產品BatchedBridge,也是與Native建立雙向通信的關鍵所在
  • 2.BatchedBridge是一個MessageQueue執行個體,它在建立時傳入了__fbBatchedBridgeConfig值儲存Native端支援的子產品清單配置

BatchedBridge在建立時将自己寫入全局變量__fbBatchedBridge上,這樣Native可以通過JSContext[@”__fbBatchedBridge”]通路到JS bridge對象。

2.MessageQueue

接着我們繼續看MessageQueue,它在整個通訊鍊路的機制上面有着重要作用,首先我們來觀察一下它的構造函數。

constructor(remoteModules, localModules) {
this.RemoteModules = {};
this._callableModules = {};
this._queue = [[], [], [], ];
this._moduleTable = {};
this._methodTable = {};
this._callbacks = [];
this._callbackID = ;
this._callID = ;
//......
let modulesConfig = this._genModulesConfig(remoteModules);
this._genModules(modulesConfig);
//......
}
複制代碼
           

從構造函數,我們大緻能了解MessageQueue的幾個資訊:

  • 1.RemoteModules屬性,用于儲存Native端子產品配置
  • 2.Callbacks屬性緩存js的回調方法
  • 3.Queue事件隊列用于處理各類事件等

在構造函數中,解析Native傳入的remoteModules JSON,轉換成JS對象

3.Config Modules

根據上一步MessageQueue的邏輯,繼續往下跟蹤_genModules函數,可以看到在MessageQueue已經對Native注入的Module Config做了一次預處理,如果debug模式可以看到大緻的資料結構會轉換成如下表中所示結構(其中HTSimepleAPI是一個自定義子產品)。

config = ["HTSimpleAPI", Array[]], moduleID = 
config = null, moduleID = 
config = null, moduleID = 
config = ["RCTAccessibilityManager", Array[]], moduleID = 
複制代碼
           

至于這樣的預處理有什麼作用,我們繼續往下分析,後面再來總結。

4.Lazily Config Methods

對于NativeModule,它們在上一步之後隻有一個包含Module Name等簡單資訊的Module List的對象,隻有在實際調用了該子產品之後才會加載該子產品的具體資訊(比如暴露的API等)。

const NativeModules = {};
Object.keys(RemoteModules).forEach((moduleName) => {
Object.defineProperty(NativeModules, moduleName, {
enumerable: true,
get: () => {
let module = RemoteModules[moduleName];
if (module && typeof module.moduleID === 'number' && global.nativeRequireModuleConfig) {
const json = global.nativeRequireModuleConfig(moduleName);
const config = json && JSON.parse(json);
module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
RemoteModules[moduleName] = module;
}
return module;
},
});
});
複制代碼
           

這段代碼定義了一個全局子產品NativeModules,周遊之前取到的remoteModules,将每一個module在NativeModules對象上擴充了一個getter方法,該方法中通過nativeRequireModuleConfig進一步加載子產品的詳細資訊,通過processModuleConfig對子產品資訊進行預處理。進一步分析代碼就可以發現這個方法其實是Native中定義的全局JS Block(nativeRequireModuleConfig)。

接下來我們繼續看processModuleConfig中具體的代碼邏輯,如下表所示:

processModuleConfig(config, moduleID) {
    const module = this._genModule(config, moduleID);
    return module;
}
_genMethod(module, method, type) {
//......
    fn = function(...args) {
    return self.__nativeCall(module, method, args, onFail, onSucc);
};
//......
return fn;
}
複制代碼
           

processModuleConfig方法的主要工作是生成methods配置,并對每一個method封裝了一個閉包fn,當調用method時,會轉換成成調用self.__nativeCall(moduleID, methodID, args, onFail, onSucc)方法

預處理完成後,在JavaScript環境中的Moudle Config資訊才算完整,包含Module Name、Native Method等資訊,具體資訊如下所示。

config = ["HTSimpleAPI", Array[]], moduleID = 
methodName = "test", methodID = 
config = null, moduleID = 
config = null, moduleID = 
config = ["RCTAccessibilityManager", Array[]], moduleID = 
methodName = "setAccessibilityContentSizeMultipliers", methodID = 
methodName = "getMultiplier", methodID = 
methodName = "getCurrentVoiceOverState", methodID = 
複制代碼
           

還記得第二部分第5步中Native端生成的子產品配置表嗎?結合它的結構,我們可以得知:對于Module&Method,在Native和JS端都以數組的形式存放,數組下标即為它們的ModuleID和MethodID。

5.__nativeCall

分析完Bridge部分的映射關系以及子產品加載,那麼我們再來看看最終調用Native代碼是如何實作的。當JS調用module.method時,其實調用了self.__nativeCall(module, method, args, onFail, onSucc),對于__nativeCall方法:

__nativeCall(module, method, params, onFail, onSucc) {
    if (onFail || onSucc) {
    ......
    onFail && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onFail;
    onSucc && params.push(this._callbackID);
    this._callbacks[this._callbackID++] = onSucc;
    }
this._queue[MODULE_IDS].push(module);
this._queue[METHOD_IDS].push(method);
this._queue[PARAMS].push(params);
global.nativeFlushQueueImmediate(this._queue);
......
}
複制代碼
           

這段代碼為每個method建立了一個閉包fn,在__nativeCall方法中,并且在這裡做了兩件重要的工作:

  • 1.把onFail和onSucc緩存到_callbacks中,同時把callbackID添加到params
  • 2.把moduleID, methodID, params放入隊列中,回調Native代碼.

__nativeCall如何做到回調Native代碼呢?看第二部分第3步,在初始化JS引擎JSExecutor Setup時,Native端注冊一個全局block回調nativeFlushedQueueImmediate,nativeCall在處理完畢後,通過該回調把隊列作為傳回值傳給Native。nativeFlushedQueueImmediate的實作如下所示。

[self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray *calls){
RCTJSCExecutor *strongSelf = weakSelf;
    if (!strongSelf.valid || !calls) {
        return;
    }
[strongSelf->_bridge handleBuffer:calls batchEnded:NO];
}];
複制代碼
           

這裡的handleBuffer就是Native端解析JS的子產品調用最後通過NSInvocation機制調用Native代碼對應的邏輯。有興趣的朋友繼續跟蹤handleBuffer代碼會發現,他的實作和React在JS端定義的MessageQueue有驚人的相似之處。

6.Call JS function & Callbacks

最後,我們回過頭來看看Native端是如何調用JS端的相關邏輯的,這部分我們需要回到MessageQueue.js代碼中來,可以看到MessageQueue暴露了3個核心方法:’invokeCallbackAndReturnFlushedQueue’、’callFunctionReturnFlushedQueue’、’flushedQueue’。

// 将API暴露到全局作用域中
[
'invokeCallbackAndReturnFlushedQueue',
'callFunctionReturnFlushedQueue',
'flushedQueue',
].forEach((fn) => this[fn] = this[fn].bind(this));
…
// 聲明帶有傳回值的函數
callFunctionReturnFlushedQueue(module, method, args) {
guard(() => {C
this.__callFunction(module, method, args);
this.__callImmediates();
});
return this.flushedQueue();
}
// 聲明帶有Callback的函數
invokeCallbackAndReturnFlushedQueue(cbID, args) {
guard(() => {
this.__invokeCallback(cbID, args);
this.__callImmediates();
});
return this.flushedQueue();
}
複制代碼
           

callFunctionReturnFlushedQueue用于實作Native調用帶有傳回值的JS端函數(這裡的傳回值也是通過Queue來模拟); invokeCallbackAndReturnFlushedQueue用于實作Native調用帶有Call的JS端函數(可以将Native的Callback作為JS端函數的入參,JS端執行完後調用Native的Callback)。

對于callFunctionReturnFlushedQueue方法,它最終調用的是__callFunction:

__callFunction(module, method, args) {
......
var moduleMethods = this._callableModules[module];
......
moduleMethods[method].apply(moduleMethods, args);
}
複制代碼
           

可以看到,此處會根據Native傳入的module, method,調用JS端相應的子產品并傳入參數清單args. 同時我們又可以獲得對于MessageQueue的另一條推測,_callableModules用來存放JS端暴露給Native的子產品,進一步分析我們可以發現SDK中正是通過registerCallableModules方法注冊JS端暴露API子產品。

對于JS bridge提供的調用回調方法invokeCallbackAndReturnFlushedQueue,原理上和callFunction差不多,不再細說。

JS <-> Native 通信原理

1.Native->JS

綜上所述,在JS端提供callFunctionReturnFlushedQueue,Native bridge調用JS端方法時,應該使用這個方法。檢視Native代碼實作可知,RCTBridge封裝了enqueueJSCall方法調用JS,梳理Native->JS的整體互動流程如下圖所示。

之前已經論述過,如果在NATIVE端需要自定義子產品提供給JS端使用那麼該類需要實作RCTBridgeModule協定 。

此外,React-Native提供了另一種基于通知的方式,通過RCTEventDispatcher發送消息通知 。eventDispatcher作為Native Bridge的屬性,封裝了sendEventWithName:body:方法。使用時,Native中類同樣需要實作RCTBridgeModule協定,通過self.bridge發送通知,JS端對應事件的EventEmitter添加監聽處理調用。

檢視sendEvent方法的代碼可以發現,這種方式本質上還是調用enqueueJSCall方法。官方推薦我們使用通知的方式來實作 Native->JS,這樣可以減少子產品初始化加載解析的時間。

2.JS->Native

最後,我們來看一下JS如何調用Native。答案是JS不會主動傳遞資料給Native,也不能直接調用Native(一種情況除外,在入口直接通過NativeModules調用API),隻有在Native調用JS時才會通過傳回值觸發調用。因為Native是基于事件響應機制的,比如觸摸事件、啟動事件、定時器事件、回調事件等。

當事件發生時,Native會調用JS相應子產品處理,完畢後再通過傳回值把隊列傳遞給Native執行對應的代碼。

如上圖所示,整個調用過程可以歸納為:

  • 1.JS把需要Module, Method, args(CallbackID)儲存在隊列中, 作為傳回值通過blocks回調Native
  • 2.Native調用相應子產品方法,完成
  • 3.Native通過CallbackID調用JS回調

總結

React Native的通訊基礎建立在傳統的JS Bridge之上,不過對于Bridge處理的MessageQueue機制、子產品定義、加載機制上的巧妙處理指的借鑒。對于上述的整個原了解析可以概括為以下四個部分:

  • 1.在啟動階段,初始化JS引擎,生成Native端子產品配置表存于兩端,其中子產品配置是同步取得,而各子產品的方法配置在該方法被真正調用時懶加載。
  • 2.Native和JS端分别有一個bridge,發生調用時,調用端bridge查找子產品配置表将調用轉換成{moduleID, methodID, args(callbackID)},處理端通過同一份子產品配置表轉換為實際的方法實作。
  • 3.Native->JS,原理上使用JSCore從Native執行JS代碼,React-Native在此基礎上給我們提供了通知發送的執行方式。
  • 4.JS->Native,原理上JS并不主動調用Native,而是把方法和參數(回調)緩存到隊列中,在Native事件觸發并通路JS後,通過blocks回調Native。
以上原了解析文章來源:http://i.dotidea.cn/2016/05/react-native-communication-principle-for-ios/、https://blog.csdn.net/passionhe/article/details/52498061、https://blog.csdn.net/xiangzhihong8/article/details/54425807

繼續閱讀