React Native通訊機制詳解

原文出處: Bang的博客(@bang)   歡迎分享原創到伯樂頭條javascript

React Native是facebook剛開源的框架,能夠用javascript直接開發原生APP,先不說這個框架後續是否能獲得大衆承認,單從源碼來講,這個框架源碼裏有很是多的設計思想和實現方式值得學習,本篇先來看看它最基礎的JavaScript-ObjectC通訊機制(如下簡稱JS/OC)。java

概覽

React Native用iOS自帶的JavaScriptCore做爲JS的解析引擎,但並無用到JavaScriptCore提供的一些可讓JS與OC互調的特性,而是本身實現了一套機制,這套機制能夠通用於全部JS引擎上,在沒有JavaScriptCore的狀況下也能夠用webview代替,實際上項目裏就已經有了用webview做爲解析引擎的實現,應該是用於兼容iOS7如下沒有JavascriptCore的版本。react

普通的JS-OC通訊實際上很簡單,OC向JS傳信息有現成的接口,像webview提供的-stringByEvaluatingJavaScriptFromString方法能夠直接在當前context上執行一段JS腳本,而且能夠獲取執行後的返回值,這個返回值就至關於JS向OC傳遞信息。React Native也是以此爲基礎,經過各類手段,實現了在OC定義一個模塊方法,JS能夠直接調用這個模塊方法並還能夠無縫銜接回調。git

舉個例子,OC定義了一個模塊RCTSQLManager,裏面有個方法-query:successCallback:,JS能夠直接調用RCTSQLManager.query並經過回調獲取執行結果。:github

1
2
3
4
5
6
7
8
9
//OC:
@implement RCTSQLManager
- (void)query:(NSString *)queryData successCallback:(RCTResponseSenderBlOCk)responseSender
{
      RCT_EXPORT();
      NSString *ret = @"ret"
      responseSender(ret);
}
@end
 
 
1
2
3
4
//JS:
RCTSQLManager.query( "SELECT * FROM table" , function (result) {
      //result == "ret";
});

接下來看看它是怎樣實現的。web

模塊配置表

首先OC要告訴JS它有什麼模塊,模塊裏有什麼方法,JS才知道有這些方法後纔有可能去調用這些方法。這裏的實現是OC生成一份模塊配置表傳給JS,配置表裏包括了全部模塊和模塊裏方法的信息。例:react-native

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
     "remoteModuleConfig": {
         "RCTSQLManager": {
             "methods": {
                 "query": {
                     "type": "remote",
                     "methodID": 0
                 }
             },
             "moduleID": 4
         },
         ...
      },
}

OC端和JS端分別各有一個bridge,兩個bridge都保存了一樣一份模塊配置表,JS調用OC模塊方法時,經過bridge裏的配置表把模塊方法轉爲模塊ID和方法ID傳給OC,OC經過bridge的模塊配置表找到對應的方法執行之,以上述代碼爲例,流程大概是這樣(先不考慮callback):數組

在瞭解這個調用流程以前,咱們先來看看OC的模塊配置表式怎麼來的。咱們在新建一個OC模塊時,JS和OC都不須要爲新的模塊手動去某個地方添加一些配置,模塊配置表是自動生成的,只要項目裏有一個模塊,就會把這個模塊加到配置表上,那這個模塊配置表是怎樣自動生成的呢?分兩個步驟:緩存

1.取全部模塊類

每一個模塊類都實現了RCTBridgeModule接口,能夠經過runtime接口objc_getClassList或objc_copyClassList取出項目裏全部類,而後逐個判斷是否實現了RCTBridgeModule接口,就能夠找到全部模塊類,實如今RCTBridgeModuleClassesByModuleID()方法裏。框架

2.取模塊裏暴露給JS的方法

一個模塊裏能夠有不少方法,一些是能夠暴露給JS直接調用的,一些是私有的不想暴露給JS,怎樣作到提取這些暴露的方法呢?我能想到的方法是對要暴露的方法名制定一些規則,好比用RCTExport_做爲前綴,而後用runtime方法class_getInstanceMethod取出全部方法名字,提取以RCTExport_爲前綴的方法,但這樣作惡心的地方是每一個方法必須加前綴。React Native用了另外一種黑魔法似的方法解決這個問題:編譯屬性__attribute__。

在上述例子中咱們看到模塊方法裏有句代碼:RCT_EXPORT(),模塊裏的方法加上這個宏就能夠實現暴露給JS,無需其餘規則,那這個宏作了什麼呢?來看看它的定義:

1
2
#define RCT_EXPORT(JS_name) __attribute__((used, section("__DATA,RCTExport" \
))) static const char *__rct_export_entry__[] = { __func__, #JS_name }
 

這個宏的做用是用編譯屬性__attribute__給二進制文件新建一個section,屬於__DATA數據段,名字爲RCTExport,並在這個段里加入當前方法名。編譯器在編譯時會找到__attribute__進行處理,爲生成的可執行文件加入相應的內容。效果能夠從linkmap看出來:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Sections:
# Address Size Segment Section
0x100001670 0x000C0180 __TEXT __text
...
0x10011EFA0 0x00000330 __DATA RCTExport
0x10011F2D0 0x00000010 __DATA __common
0x10011F2E0 0x000003B8 __DATA __bss
...
  
0x10011EFA0 0x00000010 [ 4] -[RCTStatusBarManager setStyle:animated:].__rct_export_entry__
0x10011EFB0 0x00000010 [ 4] -[RCTStatusBarManager setHidden:withAnimation:].__rct_export_entry__
0x10011EFC0 0x00000010 [ 5] -[RCTSourceCode getScriptText:failureCallback:].__rct_export_entry__
0x10011EFD0 0x00000010 [ 7] -[RCTAlertManager alertWithArgs:callback:].__rct_export_entry__
...
 

能夠看到可執行文件數據段多了個RCTExport段,內容就是各個要暴露給JS的方法。這些內容是能夠在運行時獲取到的,在RCTBridge.m的RCTExportedMethodsByModuleID()方法裏獲取這些內容,提取每一個方法的類名和方法名,就完成了提取模塊裏暴露給JS方法的工做。

總體的模塊類/方法提取實如今RCTRemoteModulesConfig()方法裏。

調用流程

接下來看看JS調用OC模塊方法的詳細流程,包括callback回調。這時須要細化一下上述的調用流程圖:

看起來有點複雜,不過一步步說明,應該很容易弄清楚整個流程,圖中每一個流程都標了序號,從發起調用到執行回調總共有11個步驟,詳細說明下這些步驟:

1.JS端調用某個OC模塊暴露出來的方法。

2.把上一步的調用分解爲ModuleName,MethodName,arguments,再扔給MessageQueue處理。

在初始化時模塊配置表上的每個模塊都生成了對應的remoteModule對象,對象裏也生成了跟模塊配置表裏一一對應的方法,這些方法裏能夠拿到自身的模塊名,方法名,並對callback進行一些處理,再移交給MessageQueue。具體實如今BatchedBridgeFactory.js的_createBridgedModule裏,整個實現區區24行代碼,感覺下JS的魔力吧。

3.在這一步把JS的callback函數緩存在MessageQueue的一個成員變量裏,用CallbackID表明callback。在經過保存在MessageQueue的模塊配置表把上一步傳進來的ModuleName和MethodName轉爲ModuleID和MethodID。

4.把上述步驟獲得的ModuleID,MethodId,CallbackID和其餘參數argus傳給OC。至於具體是怎麼傳的,後面再說。

5.OC接收到消息,經過模塊配置表拿到對應的模塊和方法。

實際上模塊配置表已經通過處理了,跟JS同樣,在初始化時OC也對模塊配置表上的每個模塊生成了對應的實例並緩存起來,模塊上的每個方法也都生成了對應的RCTModuleMethod對象,這裏經過ModuleID和MethodID取到對應的Module實例和RCTModuleMethod實例進行調用。具體實如今_handleRequestNumber:moduleID:methodID:params:。

6.RCTModuleMethod對JS傳過來的每個參數進行處理。

RCTModuleMethod能夠拿到OC要調用的目標方法的每一個參數類型,處理JS類型到目標類型的轉換,全部JS傳過來的數字都是NSNumber,這裏會轉成對應的int/long/double等類型,更重要的是會爲block類型參數的生成一個block。

例如-(void)select:(int)index response:(RCTResponseSenderBlock)callback 這個方法,拿到兩個參數的類型爲int,block,JS傳過來的兩個參數類型是NSNumber,NSString(CallbackID),這時會把NSNumber轉爲int,NSString(CallbackID)轉爲一個block,block的內容是把回調的值和CallbackID傳回給JS。

這些參數組裝完畢後,經過NSInvocation動態調用相應的OC模塊方法。

7.OC模塊方法調用完,執行block回調。

8.調用到第6步說明的RCTModuleMethod生成的block。

9.block裏帶着CallbackID和block傳過來的參數去調JS裏MessageQueue的方法invokeCallbackAndReturnFlushedQueue。

10.MessageQueue經過CallbackID找到相應的JS callback方法。

11.調用callback方法,並把OC帶過來的參數一塊兒傳過去,完成回調。

整個流程就是這樣,簡單歸納下,差很少就是:JS函數調用轉ModuleID/MethodID -> callback轉CallbackID -> OC根據ID拿到方法 -> 處理參數 -> 調用OC方法 -> 回調CallbackID -> JS經過CallbackID拿到callback執行

事件響應

上述第4步留下一個問題,JS是怎樣把數據傳給OC,讓OC去調相應方法的?

答案是經過返回值。JS不會主動傳遞數據給OC,在調OC方法時,會在上述第4步把ModuleID,MethodID等數據加到一個隊列裏,等OC過來調JS的任意方法時,再把這個隊列返回給OC,此時OC再執行這個隊列裏要調用的方法。

一開始不明白,設計成JS沒法直接調用OC,須要在OC去調JS時才經過返回值觸發調用,整個程序還能跑得通嗎。後來想一想純native開發裏的事件響應機制,就有點理解了。native開發裏,何時會執行代碼?只在有事件觸發的時候,這個事件能夠是啓動事件,觸摸事件,timer事件,系統事件,回調事件。而在React Native裏,這些事件發生時OC都會調用JS相應的模塊方法去處理,處理完這些事件後再執行JS想讓OC執行的方法,而沒有事件發生的時候,是不會執行任何代碼的,這跟native開發裏事件響應機制是一致的。

說到OC調用JS,再補充一下,實際上模塊配置表除了有上述OC的模塊remoteModules外,還保存了JS模塊localModules,OC調JS某些模塊的方法時,也是經過傳遞ModuleID和MethodID去調用的,都會走到-enqueueJSCall:args:方法把兩個ID和參數傳給JS的BatchedBridge.callFunctionReturnFlushedQueue,跟JS調OC原理差很少,就再也不贅述了。

總結

整個React Native的JS-OC通訊機制大體就是這樣了,關鍵點在於:模塊化,模塊配置表,傳遞ID,封裝調用,事件響應,其設計思想和實現方法很值得學習借鑑。

相關文章
相關標籤/搜索