淺析ReactNative之通信機制(一)


自從Facebook提出了react之後,這個框架的關注度一直居高不下,它所引入的一些東西還是值得學習,比如組件化的開發方式,virtual dom的性能提升方式等,最近爲了改進現有的跨平臺方案也在研究react,在這邊也做下相關的記錄。

pre

在開始使用react之前我們需要搭建相應的環境,這個就不在探討了,具體可以查看官方文檔,由於react需要使用javascript語言,所以可能需要去簡單瞭解一下相關的語法(比如:ES6 標準入門),另外iOS在7.0之後引入了javascriptcore框架,極大的方便了js跟oc之間de通信,之前有一篇博客簡單的介紹了javascriptcore,有興趣的也可以去了解一下。

整體框架

react的整體示意圖可以用下面圖表示,我們所編寫的js代碼可以在各個平臺上運行,這讓我們有web的開發效率的同時又有了原生應用的體驗。

Learn once, write anywhere

不過這裏麪包含的東西對於有點多,尤其很多web相關的東西對於沒接觸過的人還是有些難度的,要想快速的研究透徹可能不太現實,至少對於我是這樣的,因此我們首先研究一下react中js跟native之間的通信方法,其它的有待後面在分析。
假設你已經搭建好相關環境,通過下面的命令創建一個新工程:

react-native init TestDemo

運行xcodeproj工程,Xcode在編譯完後會執行打包腳本,將js文件都打包到一個main.jsbundle文件裏,我們可以選擇將該文件放到服務器上或者應用內部,如果放到服務器上需要先下載該文件,接着加載執行該文件可以看到demo頁面了。

packager

demo

通信過程

所謂的通信其實就是js和oc兩者如何相互調用傳參等,爲了更方便的揭示兩者的通信過程,我們可以設置messagequeue.js文件中的SPY_MODE標誌爲true:

//MessageQueue.js,需要處於dev模式
//http://localhost:8081/index.ios.bundle?platform=ios&dev=true爲true
let SPY_MODE = true; //

現在重新reload js你就可以看到如下的日誌輸出,下面的日誌可以比較直觀的揭示兩者的調用方式,'JS->N'即JS調用native代碼,反之亦然。可以看到程序一開始native會調用js的RCTDeviceEventEmitter.emit方法,分別發送'appStateDidChange' 和'networkStatusDidChange'兩個事件,接着調用js的AppRegistry.runApplication方法啓動js應用,然後js層就可以通過native提供的方法來 RCTUIManager.createView來創建視圖了。

N->JS : RCTDeviceEventEmitter.emit(["appStateDidChange",{"app_state":"active"}])
N->JS : RCTDeviceEventEmitter.emit(["networkStatusDidChange",{"network_info":"wifi"}])
N->JS : AppRegistry.runApplication(["TestDemo",{"rootTag":1,"initialProps":{}}])
Running application "TestDemo" with appParams: {"rootTag":1,"initialProps":{}}. __DEV__ === true, development-level warning are ON, performance optimizations are OFF
JS->N : RCTUIManager.createView([2,"RCTView",1,{"flex":1}])
JS->N : RCTUIManager.createView([3,"RCTView",1,{"flex":1}])
JS->N : RCTUIManager.createView([4,"RCTView",1,{"flex":1,"justifyContent":"center","alignItems":"center","backgroundColor":4294311167}])
S->N : RCTUIManager.createView([5,"RCTText",1,{"fontSize":20,"textAlign":"center","margin":10,"accessible":true,"allowFontScaling":true}])
JS->N : RCTUIManager.createView([6,"RCTRawText",1,{"text":"Welcome to React Native!"}])
JS->N : RCTUIManager.setChildren([5,[6]])
JS->Native,JS調用Native

讓我們先來看看native如何創建一個模塊然後暴露給js層調用的,具體的可以參考官方文檔,我們這裏舉個簡單的🌰,創建一個MyModule模塊:

@interface MyModule : NSObject <RCTBridgeModule>
@end

@implementation MyModule

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(addEvent:(NSString *)name location:(NSString *)location)
{
  RCTLogInfo(@"add an event %@ at %@", name, location);
}

我們先來看看這上面的兩個宏定義:

  • RCT_EXPORT_MODULE()
    在native層創建的模塊需要通過這個宏定義將該模塊暴露給js,該宏定義的具體實現也很簡單,如下:
#define RCT_EXPORT_MODULE(js_name) \
RCT_EXTERN void RCTRegisterModule(Class); \
+ (NSString *)moduleName { return @#js_name; } \
+ (void)load { RCTRegisterModule(self); }

首先它將RCTRegisterModule這個函數定義爲extern,這樣該函數的實現對編譯器不可見,但會在鏈接的時候可以獲取到;同時聲明一個moduleName函數,該函數返回該模塊的js名稱,如果你沒有指定,默認使用類名;最後聲明一個load函數(當應用載入後會加載所有的類,load函數在類初始化加載的時候就調用),然後調用RCTRegisterModule函數註冊該模塊,該模塊會被註冊添加到一個全局的數組RCTModuleClasses中。

  • RCT_EXPORT_METHOD()
    要暴露給js調用的API接口需要通過該宏定義聲明,該宏定義會額外創建一個函數,形式如下:
+ (NSArray *)__rct_export__230
{
  return @[ @"", @"addEvent:(NSString *)name location:(NSString *)location" ];
}

該函數名稱以 rct_export 開頭,同時加上該函數所在的代碼行數,該函數返回一個包含可選的js名稱以及一個函數簽名的數組,他們的作用後面會說到。

  • RCTBatchedBridge
    爲了橋接js跟native,native層引入了RCTBridge這個類負責雙方的通信,不過真正起作用的是RCTBatchedBridge這個類,這個類應該算是比較重要的一個類了,讓我們來看看這個類主要做啥事情:
//RCTBatchedBridge.m
- (void)start
{
  dispatch_queue_t bridgeQueue = dispatch_queue_create("com.facebook.react.RCTBridgeQueue", DISPATCH_QUEUE_CONCURRENT);

  // 異步的加載打包完成的js文件,也就是main.jsbundle,如果包文件在本地則直接加載,否則根據URL通過NSURLSession方式去下載
  [self loadSource:^(NSError *error, NSData *source) {}];

  // 同步初始化需要暴露給給js層的native模塊
  [self initModules];

  //異步初始化JS Executor,也就是js引擎
  dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
    [weakSelf setUpExecutor];
  });

  //異步獲取各個模塊的配置信息
  dispatch_group_async(setupJSExecutorAndModuleConfig, bridgeQueue, ^{
    config = [weakSelf moduleConfig];
  });

  //獲取各模塊的配置信息後,將這些信息注入到JS環境中
  [self injectJSONConfiguration:config onComplete:^(NSError *error) {}];

  //開始執行main.jsbundle
  [self executeSourceCode:sourceCode];
}

簡單解釋一下其中幾個步驟的具體內容:

initModules
- (void)initModules
{
  NSMutableArray<RCTModuleData *> *moduleDataByID = [NSMutableArray new];
  NSMutableDictionary<NSString *, RCTModuleData *> *moduleDataByName = [NSMutableDictionary new];
  SEL setBridgeSelector = NSSelectorFromString(@"setBridge:");
  IMP objectInitMethod = [NSObject instanceMethodForSelector:@selector(init)];

  //RCTGetModuleClasses()返回之前提到的全局RCTModuleClasses數組,也就是模塊類load時候會註冊添加的數組
  for (Class moduleClass in RCTGetModuleClasses()) {
    NSString *moduleName = RCTBridgeModuleNameForClass(moduleClass);
    //如果該類或者父類沒有重寫了init方法或實現了setBridge方法,則,創建一個類的實例
    //React認爲開發者期望這個模塊在bridge第一次初始化時會實例化,確保該模塊只有一個實例對象
    if ([moduleClass instanceMethodForSelector:@selector(init)] != objectInitMethod ||
        [moduleClass instancesRespondToSelector:setBridgeSelector]) {
      module = [moduleClass new];
    }

    //創建RCTModuleData模塊信息,並保存到數組中
    RCTModuleData *moduleData;
    if (module) {
      if (module != (id)kCFNull) {
        moduleData = [[RCTModuleData alloc] initWithModuleInstance:module
                                                            bridge:self];
      }
    } 
    moduleDataByName[moduleName] = moduleData;
    [moduleDataByID addObject:moduleData];
  }
}

當創建完模塊的實例對象之後,會將該實例保存到一個RCTModuleData對象中,RCTModuleData裏包含模塊的類名,名稱,方法列表,實例對象、該模塊代碼執行的隊列以及配置信息等,js層就是根據這個對象來查詢該模塊的相關信息。

setUpExecutor

reactnative的js引擎在初始化的時候會創建一個新的線程,該線程的優先級跟主線層的優先級一樣,同時創建一個runloop,這樣線程才能循環執行不會退出。所以執行js代碼不會影響到主線程,而且RCTJSCExecutor使用的是JavaScriptCore框架,所以react只支持iOS7及以上的版本。

//RCTJSCExecutor.m
- (instancetype)init
{
  NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
                                                       selector:@selector(runRunLoopThread)
                                                         object:nil];
  javaScriptThread.name = @"com.facebook.React.JavaScript";
  //設置該線程的優先級處於高優先級
  if ([javaScriptThread respondsToSelector:@selector(setQualityOfService:)]) {
    [javaScriptThread setQualityOfService:NSOperationQualityOfServiceUserInteractive];
  } else {
    javaScriptThread.threadPriority = [NSThread mainThread].threadPriority;
  }
  [javaScriptThread start];
  return [self initWithJavaScriptThread:javaScriptThread context:nil];
}
- (void)addSynchronousHookWithName:(NSString *)name usingBlock:(id)block
{
  __weak RCTJSCExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:^{
    //將該block函數添加到js的context中,javascriptcore會將block函數轉成js function
    weakSelf.context.context[name] = block;
  }];
}

- (void)setUp
{
  [self addSynchronousHookWithName:@"noop" usingBlock:^{}];
  [self addSynchronousHookWithName:@"nativeRequireModuleConfig" usingBlock:^NSString *(NSString *moduleName) {
    //獲取該模塊的具體配置信息,包含方法以及導出的常量等信息
    NSArray *config = [strongSelf->_bridge configForModuleName:moduleName];
    NSString *result = config ? RCTJSONStringify(config, NULL) : nil;
    return result;
  }];
  [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
  }];
}

可以看到setup的時候會註冊幾個方法到js的上下文中供後面js調用,比如'nativeFlushQueueImmediate' 和 'nativeRequireModuleConfig'方法等,當js調用相應方法時會執行對應的block,javascriptcore框架會負責js function和block的轉換。

moduleConfig
- (NSString *)moduleConfig
{
  NSMutableArray<NSArray *> *config = [NSMutableArray new];
  for (RCTModuleData *moduleData in _moduleDataByID) {
    [config addObject:@[moduleData.name]]; 
  }
  return RCTJSONStringify(@{
    @"remoteModuleConfig": config,
  }, NULL);
}

從實現可以看出僅僅該過程是將模塊的名稱保存到一個數組中,然後生成一個json字符串的配置信息,包含所有的模塊名稱,類似如下:

{"remoteModuleConfig":[["MyModule"],["RCTStatusBarManager"],["RCTSourceCode"],["RCTAlertManager"],["RCTExceptionsManager"],...]}
injectJSONConfiguration
- (void)injectJSONConfiguration:(NSString *)configJSON
                     onComplete:(void (^)(NSError *))onComplete
{
  [_javaScriptExecutor injectJSONText:configJSON
                  asGlobalObjectNamed:@"__fbBatchedBridgeConfig"
                             callback:onComplete];
}

當我們生成配置信息之後,通過上面的函數將該json信息保存到js的全局對象__fbBatchedBridgeConfig中,這樣js層就可以知道我們提供了哪些模塊,不過細心的話你可能會發現給js的信息只有這些模塊的名稱,那js怎麼調用native的方法的,其實這是react爲了懶加載而採用的方式,具體我們下面說明。

當我們配置好native模塊後,js層要想調用該native模塊的方法如下示例:

var myModule = require('react-native').NativeModules.MyModule;
myModule.addEvent('Birthday Party', '4 Privet Drive, Surrey');

可以看出native模塊是保存在NativeModules中,所以讓我們到NativeModules.js文件中看看:

const BatchedBridge = require('BatchedBridge');
const RemoteModules = BatchedBridge.RemoteModules;
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) {

        //從native層獲取該模塊的具體配置信息,nativeRequireModuleConfig是之前註冊到js global對象的方法,然後把config信息交給BatchedBridge處理
        const json = global.nativeRequireModuleConfig(moduleName);
        const config = json && JSON.parse(json);
        module = config && BatchedBridge.processModuleConfig(config, module.moduleID);
        RemoteModules[moduleName] = module;
      }
      return module;
    },
  });
});

從上面的代碼可以看出,假如你在js層沒有使用到native模塊,那麼這些模塊是不會加載到js層的,只有使用到了該模塊,react纔會去獲取該模塊的具體配置信息然後加載到js,這是react懶加載的一個方式,讓我們能夠節約內存,讓我們看看如何獲取模塊的配置信息:

//只會導出有__rct_export__前綴的方法,也就是之前RCT_EXPORT_METHOD這個宏定義提到的
- (NSArray<id<RCTBridgeMethod>> *)methods
{
  //拷貝該類的所有方法,然後過濾以__rct_export__開頭的方法
  Method *methods = class_copyMethodList(object_getClass(_moduleClass), &methodCount);
  for (unsigned int i = 0; i < methodCount; i++) {
    Method method = methods[i];
    SEL selector = method_getName(method);
    if ([NSStringFromSelector(selector) hasPrefix:@"__rct_export__"]) {
      IMP imp = method_getImplementation(method);
      NSArray<NSString *> *entries =
        ((NSArray<NSString *> *(*)(id, SEL))imp)(_moduleClass, selector);

      [moduleMethods addObject:/*代表該方法的對象*/];
    }
}

//獲取模塊的具體配置信息,以數組形式返回,第一個爲模塊名稱,第二個爲需要導出的常量(如果有),第三個爲導出的方法(如果有)
//以MyModule爲例,導出的config爲:["MyModule",{"FirstDay":"Monday"},["addEvent","findEvents"]]
- (NSArray *)config
{
  //過濾獲取以__rct_export__開頭的方法
  for (id<RCTBridgeMethod> method in self.methods) {
    [methods addObject:method.JSMethodName];
  }
  NSMutableArray *config = [NSMutableArray new];
  [config addObject:self.name];
  if (constants.count) {
    [config addObject:constants];
  }
  if (methods) {
    [config addObject:methods];
  }
  return config;
}

導出的配置信息如下所示,可以看到config裏包含的模塊名稱,導出的常量以及導出的函數等,推薦通過調試工具React Developer Tools打斷點來查看:

__fbBatchedBridgeConfig

  • BatchedBridge

上面我們說過獲取到模塊的具體配置信息之後會交給BatchedBridge處理,之前我們說的是native的bridge,不過js爲了橋接native層也引入了BatchedBridge:

//BatchedBridge.js
const MessageQueue = require('MessageQueue');
const BatchedBridge = new MessageQueue(
  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,
);
//將BatchedBridge添加到js的全局global對象中,
Object.defineProperty(global, '__fbBatchedBridge', { value: BatchedBridge });
module.exports = BatchedBridge;

我們看到BatchedBridge是MessageQueue的一個實例,而且是全局唯一的一個實例,作爲橋接native的一個關鍵點,我們來具體深入看一下它的內部實現“。

看一下傳遞給messageQueue的兩個參數

  __fbBatchedBridgeConfig.remoteModuleConfig,
  __fbBatchedBridgeConfig.localModulesConfig,

__fbBatchedBridgeConfig我們之前提到過,是一個全局的js變量,__fbBatchedBridgeConfig.remoteModuleConfig就是之前我們在native層導出的模塊配置表.

messageQueue

首先看一下messageQueue裏的一些實例變量以及API

//存儲native提供的各個模塊信息,
this.RemoteModules = {};
//存儲js提供的各個模塊信息
this._callableModules = {};
//用於存放調用信息隊列,有三個數組,分別對應調用的模塊,調用的函數和參數信息,也就是一個函數調用由這三個數組拼接而成
this._queue = [[], [], [], 0];
//以moduleID爲key,value爲moduleName,針對js提供的module
this._moduleTable = {};
//以moduleId爲key,value爲模塊導出的方法,針對js提供的module
this._methodTable = {};
//回調函數數組
this._callbacks = [];
//回調函數對應的索引id
this._callbackID = 0;

let modulesConfig = this._genModulesConfig(remoteModules);
this._genModules(modulesConfig);
localModules && this._genLookupTables(
  this._genModulesConfig(localModules),this._moduleTable, this._methodTable
);

//以moduleId爲key,value爲moduleName,針對native提供的module
this._remoteModuleTable = {};
//以moduleId爲key,value爲模塊導出的方法,針對native提供module
this._remoteMethodTable = {};

可以看到這個隊列裏保存着js跟native的模塊交互的所有信息。先看一下_genModules方法,該方法會根據config解析每個模塊的信息並保存到this.RemoteModules中:

_genModules(remoteModules) {
  remoteModules.forEach((config, moduleID) => {
    this._genModule(config, moduleID);
  });
}

_genModules會歷遍所有的remoteModules,根據每個模塊的配置信息(如何生成配置信息下面會提到)和module索引ID來創建每個模塊

_genModule(config, moduleID) {
  let moduleName, constants, methods, asyncMethods;
  //通過解構賦值的方式提取配置信息中的模塊名稱,常量(如果有),方法名等
  [moduleName, constants, methods, asyncMethods] = config;
  let module = {};
  methods && methods.forEach((methodName, methodID) => {
    //歷遍該config中的方法列表,根據配置信息爲每個模塊生成js function方法並添加到module對象,
    module[methodName] = this._genMethod(moduleID, methodID, methodType);
  });
  //常量信息assign到該module對象,並將module保存到this.RemoteModules中
  Object.assign(module, constants);
  this.RemoteModules[moduleName] = module;
  return module;
}

_genMethod方法如下,假如方法的type爲remoteAsync,也就是異步方法,其實就是用一個promise對象(promise是js中的一種異步編程方式)來包裝普通的方法,這裏我們只看下普通方法的處理過程:

  _genMethod(module, method, type) {
    let fn = null;
    let self = this;
    fn = function(...args) {
    let lastArg = args.length > 0 ? args[args.length - 1] : null;
    let secondLastArg = args.length > 1 ? args[args.length - 2] : null;
    let hasSuccCB = typeof lastArg === 'function';
    let hasErrorCB = typeof secondLastArg === 'function';
    hasErrorCB && invariant(
      hasSuccCB,
      'Cannot have a non-function arg after a function arg.'
    );
    let numCBs = hasSuccCB + hasErrorCB;
    let onSucc = hasSuccCB ? lastArg : null;
    let onFail = hasErrorCB ? secondLastArg : null;
    args = args.slice(0, args.length - numCBs);
    return self.__nativeCall(module, method, args, onFail, onSucc);
      };
    }
    fn.type = type;
    return fn;
  }

可以看到該方法也比較簡單,只是在參數列表中提取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);

    var now = new Date().getTime();
    //當兩次調用間隔過小的時候只是先緩存調用信息
    if (global.nativeFlushQueueImmediate &&
        now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS) {
      global.nativeFlushQueueImmediate(this._queue);
      this._queue = [[], [], [], this._callID];
      this._lastFlush = now;
    }
  }
//RCTJSCExecutor.m
  [self addSynchronousHookWithName:@"nativeFlushQueueImmediate" usingBlock:^(NSArray<NSArray *> *calls){
    [strongSelf->_bridge handleBuffer:calls batchEnded:NO];
  }];

__nativeCall方法中,假如有回調參數onFail或onSucc,會將對應的callbackID保存到參數中,並將它們壓入到_callbacks棧中;接着將模塊,名稱以及參數分別保存到_queue的三個數組中;接下來的關鍵就是調用nativeFlushQueueImmediate方法,該方法是之前RCTJSCExecutor setup的時候註冊到js global的方法,因此它會執行相應的native block方法(javascriptcore框架會負責js function和block的轉換),可以看出_queue中的模塊、方法以及參數信息最終會傳遞給native層,由native解析並執行相應的native方法。
我們也可以注意到這裏react爲了性能的優化,當js兩次調用方法的間隔小於MIN_TIME_BETWEEN_FLUSHES_MS(5ms)時間,會將調用信息先緩存到_queue中,等待下次在一併提交給native層執行,可能這也就是這些參數設置成數組形式保存的原因。

讓我們在接下去看看handleBuffer,handleBuffer會將調用信息先按模塊的隊列分好,

//RCTBatchedBridge.m
- (void)handleBuffer:(id)buffer batchEnded:(BOOL)batchEnded
{
  NSArray *requestsArray = [RCTConvert NSArray:buffer];
  //先將messageueue傳遞的參數提取出來分別放到moduleIDs、methodIDs和paramsArrays數組中,
  NSArray<NSNumber *> *moduleIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldRequestModuleIDs]];
  NSArray<NSNumber *> *methodIDs = [RCTConvert NSNumberArray:requestsArray[RCTBridgeFieldMethodIDs]];
  NSArray<NSArray *> *paramsArrays = [RCTConvert NSArrayArray:requestsArray[RCTBridgeFieldParamss]];

  //將調用的信息先安模塊各自指定的隊列分好
  NSMapTable *buckets = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory
                                                  valueOptions:NSPointerFunctionsStrongMemory
                                                      capacity:_moduleDataByName.count];
  [moduleIDs enumerateObjectsUsingBlock:^(NSNumber *moduleID, NSUInteger i, __unused BOOL *stop) {
    RCTModuleData *moduleData = _moduleDataByID[moduleID.integerValue];
    dispatch_queue_t queue = moduleData.methodQueue;
    if (!set) {
      set = [NSMutableOrderedSet new];
      [buckets setObject:set forKey:moduleData.methodQueue];
    }
    [set addObject:@(i)];
  }];
  //按隊列來批量執行相應的調用
  for (dispatch_queue_t queue in buckets) {
    dispatch_block_t block = ^{
      RCTProfileEndFlowEvent();
      NSOrderedSet *calls = [buckets objectForKey:queue];
      @autoreleasepool {
        for (NSNumber *indexObj in calls) {
          NSUInteger index = indexObj.unsignedIntegerValue;
          //在各自模塊上根據參數執行指定的方法
          [self _handleRequestNumber:index
                            moduleID:[moduleIDs[index] integerValue]
                            methodID:[methodIDs[index] integerValue]
                              params:paramsArrays[index]];
        }
      }
    };
    if (queue == RCTJSThread) {
      [_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
    } else if (queue) {
      dispatch_async(queue, block);
    }
  }
}

_handleRequestNumber根據模塊的ID、方法ID以及參數來調用具體的函數:

- (BOOL)_handleRequestNumber:(NSUInteger)i
                    moduleID:(NSUInteger)moduleID
                    methodID:(NSUInteger)methodID
                      params:(NSArray *)params
{
  RCTModuleData *moduleData = _moduleDataByID[moduleID];
  id<RCTBridgeMethod> method = moduleData.methods[methodID];
  [method invokeWithBridge:self module:moduleData.instance arguments:params];
}

其中如何根據函數簽名來調用函數可以具體查閱-(void)processMethodSignature函數,這裏就不去細談了。

  • 回調函數
    當有回調函數的時候,之前看到__nativeCall會將callbackID放置在參數中,對應的回調函數插入到_callbacks中保存,js將該ID傳遞給native,native就是通過該ID來找到對應的回調函數的。
//MyModule.m
RCT_EXPORT_METHOD(findEvents:(RCTResponseSenderBlock)callback)
{
  NSArray *events = @[@"test1",@"test2",@"test3"];
  callback(@[[NSNull null], events]);
}
//生成的函數簽名
findEvents:(RCTResponseSenderBlock)callback

比如MyModule定義的回調函數,當通過函數簽名如果發現參數的類型是RCTResponseSenderBlock,則js傳遞過來的參數就是回調函數的ID,native層就會根據該ID以及RCTResponseSenderBlock提供的參數來回調相應的js回調函數,整個調用過程可以簡單的用下圖表示。

調用過程

Native->JS,Native調用JS

假如你有自己創建的js模塊想要被native層調用,也需要將該js模塊註冊添加到messagequeue的_callableModules中,比如reactjs的事件發送模塊:

//RCTEventEmitter.js
BatchedBridge.registerCallableModule(
  'RCTEventEmitter',
  ReactNativeEventEmitter
);
註冊js模塊

native層調用的js方法類似如下:

  [_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
                    args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];

不過讓我們來看看真正執行js代碼的地方,裏面其實就是用到javascriptcore框架,爲了方便斷點調試我把宏去掉了,不過不影響,簡單示例如下:

- (void)_executeJSCall:(NSString *)method
             arguments:(NSArray *)arguments
              callback:(RCTJavaScriptCallback)onComplete
{
  dispatch_block_t myBlock = ^{
    JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
    JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);
    
    //從js的全局對象中獲取BatchedBridge對象
    JSStringRef moduleNameJSStringRef = JSStringCreateWithUTF8CString("__fbBatchedBridge");
    JSValueRef moduleJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, moduleNameJSStringRef, &errorJSRef);
    JSStringRelease(moduleNameJSStringRef)
    if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) {
      //獲取js對象相應的方法
      JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
      JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
      JSStringRelease(methodNameJSStringRef);
      if (methodJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, methodJSRef)) {
        // 調用相應的js函數
        if (arguments.count == 0) {
          resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
        }
  };
  [self executeBlockOnJavaScriptQueue:myBlock];
}

可以看出native調用js的代碼藉助JavaScriptCore框架變的非常簡單。

總結

上面簡單的說明了react之間的通信過程,至於跟view相關的內容下次在討論,這個其實才是react比較重要的內容,如果有興趣歡迎一起交流~

參考

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